From 98c930b8972803f5c46a2e2d22430fd3205e84e3 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Tue, 17 Nov 2015 11:52:07 +0100 Subject: [PATCH 0001/1161] Updated Symfony2 example to use correct dependency injection --- docs/integrations/symfony2.rst | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index 843fab70e..e1aafc507 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -39,26 +39,23 @@ Capturing context can be done via a monolog processor: namespace Acme\Bundle\AcmeBundle\Monolog; - use Symfony\Component\DependencyInjection\ContainerInterface; + use Symfony\Component\Security\Core\SecurityContext; use Acme\Bundle\AcmeBundle\Entity\User; - class SentryContextProcessor { + class SentryContextProcessor + { + protected $securityContext; - protected $container; - - public function __construct(ContainerInterface $container) + public function __construct(SecurityContext $securityContext) { - $this->container = $container; + $this->securityContext = $securityContext; } public function processRecord($record) { - $securityContext = $this->container->get('security.context'); - $user = $securityContext->getToken()->getUser(); - - if($user instanceof User) - { + $user = $this->securityContext->getToken()->getUser(); + if ($user instanceof User) { $record['context']['user'] = array( 'name' => $user->getName(), 'username' => $user->getUsername(), @@ -74,7 +71,6 @@ Capturing context can be done via a monolog processor: return $record; } - } You'll then register the processor in your config: @@ -84,6 +80,6 @@ You'll then register the processor in your config: services: monolog.processor.sentry_context: class: Applestump\Bundle\ShowsBundle\Monolog\SentryContextProcessor - arguments: ["@service_container"] + arguments: ["@security.context"] tags: - { name: monolog.processor, method: processRecord, handler: sentry } From a12d3116b3ebbe0054decab95cb8ff7f1f7b1de5 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Tue, 17 Nov 2015 12:49:59 +0100 Subject: [PATCH 0002/1161] Fixed namespace in service definition --- docs/integrations/symfony2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index e1aafc507..db4095576 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -79,7 +79,7 @@ You'll then register the processor in your config: services: monolog.processor.sentry_context: - class: Applestump\Bundle\ShowsBundle\Monolog\SentryContextProcessor + class: Acme\Bundle\AcmeBundle\Monolog\SentryContextProcessor arguments: ["@security.context"] tags: - { name: monolog.processor, method: processRecord, handler: sentry } From a3a9424ba4be18e227042fb59423d1177b170063 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Wed, 18 Nov 2015 14:47:43 +0100 Subject: [PATCH 0003/1161] Rename to AppBundle namespace --- docs/integrations/symfony2.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index db4095576..6ce433eee 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -37,10 +37,10 @@ Capturing context can be done via a monolog processor: .. sourcecode:: php - namespace Acme\Bundle\AcmeBundle\Monolog; + namespace AppBundle\Monolog; use Symfony\Component\Security\Core\SecurityContext; - use Acme\Bundle\AcmeBundle\Entity\User; + use AppBundlee\Entity\User; class SentryContextProcessor { @@ -79,7 +79,7 @@ You'll then register the processor in your config: services: monolog.processor.sentry_context: - class: Acme\Bundle\AcmeBundle\Monolog\SentryContextProcessor + class: AppBundle\Monolog\SentryContextProcessor arguments: ["@security.context"] tags: - { name: monolog.processor, method: processRecord, handler: sentry } From a0abc72d62896a2452e0aee51396343955d2579a Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Wed, 18 Nov 2015 14:50:47 +0100 Subject: [PATCH 0004/1161] Use TokenStorage instead of SecurityContext SecurityContext is deprecated since Symfony 2.6 --- docs/integrations/symfony2.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index 6ce433eee..e9ee92b1b 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -39,21 +39,21 @@ Capturing context can be done via a monolog processor: namespace AppBundle\Monolog; - use Symfony\Component\Security\Core\SecurityContext; use AppBundlee\Entity\User; + use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; class SentryContextProcessor { - protected $securityContext; + protected $tokenStorage; - public function __construct(SecurityContext $securityContext) + public function __construct(TokenStorage $tokenStorage) { - $this->securityContext = $securityContext; + $this->tokenStorage = $tokenStorage; } public function processRecord($record) { - $user = $this->securityContext->getToken()->getUser(); + $user = $this->tokenStorage->getToken()->getUser(); if ($user instanceof User) { $record['context']['user'] = array( @@ -80,6 +80,6 @@ You'll then register the processor in your config: services: monolog.processor.sentry_context: class: AppBundle\Monolog\SentryContextProcessor - arguments: ["@security.context"] + arguments: ["@security.token_storage"] tags: - { name: monolog.processor, method: processRecord, handler: sentry } From b929b979a1a9442bdc569cb02311cdf4e080c5f5 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Wed, 18 Nov 2015 14:53:39 +0100 Subject: [PATCH 0005/1161] Added hint about Symfony < 2.6 --- docs/integrations/symfony2.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index e9ee92b1b..3aa5ceb06 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -83,3 +83,6 @@ You'll then register the processor in your config: arguments: ["@security.token_storage"] tags: - { name: monolog.processor, method: processRecord, handler: sentry } + + +If you're using Symfony < 2.6 then you need to use ``security.context`` instead of ``security.token_storage``. From 552f520bb976ee154c96f3d1e678b6e623612ad5 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Wed, 18 Nov 2015 15:08:09 +0100 Subject: [PATCH 0006/1161] Fixed typo and use interface --- docs/integrations/symfony2.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index 3aa5ceb06..a80ec2d6f 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -39,14 +39,14 @@ Capturing context can be done via a monolog processor: namespace AppBundle\Monolog; - use AppBundlee\Entity\User; - use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; + use AppBundle\Entity\User; + use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; class SentryContextProcessor { protected $tokenStorage; - public function __construct(TokenStorage $tokenStorage) + public function __construct(TokenStorageInterface $tokenStorage) { $this->tokenStorage = $tokenStorage; } From 5e5c0f251253fd6875f96076ce507efe1e31a1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Doursenaud?= Date: Wed, 18 Nov 2015 16:50:23 +0100 Subject: [PATCH 0007/1161] Clarify license in Composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cfe82017c..d6d66e896 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "description": "A PHP client for Sentry (http://getsentry.com)", "keywords": ["log", "logging"], "homepage": "http://getsentry.com", - "license": "BSD", + "license": "BSD-3-Clause", "authors": [ { "name": "David Cramer", From 1a4313b2b26d7726cf89153c089b37169c3d6ce6 Mon Sep 17 00:00:00 2001 From: apollopy Date: Tue, 1 Dec 2015 14:15:35 +0800 Subject: [PATCH 0008/1161] if not isset email, don't set to user_context --- lib/Raven/Client.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index efbffbaac..0efcb8b28 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -869,10 +869,11 @@ public function registerSeverityMap($map) */ public function set_user_data($id, $email=null, $data=array()) { - $this->user_context(array_merge(array( - 'id' => $id, - 'email' => $email, - ), $data)); + $user = array('id' => $id); + if (isset($email)) { + $user['email'] = $email; + } + $this->user_context(array_merge($user, $data)); } /** From 70c0ab56d755ce2e13b31ec678a0cf84f6afc919 Mon Sep 17 00:00:00 2001 From: Andy Dawson Date: Tue, 1 Dec 2015 11:06:46 +0000 Subject: [PATCH 0009/1161] Add support for X-Forwarded-Proto --- lib/Raven/Client.php | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 0efcb8b28..6de036bb0 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -788,15 +788,35 @@ private function get_current_url() return null; } - $schema = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' - || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; - // HTTP_HOST is a client-supplied header that is optional in HTTP 1.0 $host = (!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR'] : (!empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''))); - return $schema . $host . $_SERVER['REQUEST_URI']; + $httpS = $this->isHttps() ? 's' : ''; + return "http{$httpS}://{$host}{$_SERVER['REQUEST_URI']}"; + } + + /** + * Was the current request made over https? + * + * @return bool + */ + private function isHttps() + { + if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { + return true; + } + + if ($_SERVER['SERVER_PORT'] == 443) { + return true; + } + + if (!empty($_SERVER['X-FORWARDED-PROTO']) && $_SERVER['X-FORWARDED-PROTO'] === 'https') { + return true; + } + + return false; } /** From 071a36a8b0af7df4b3a40633d67ca55b7ce89dd4 Mon Sep 17 00:00:00 2001 From: Andy Dawson Date: Tue, 1 Dec 2015 11:22:51 +0000 Subject: [PATCH 0010/1161] Add a test for get current url --- test/Raven/Tests/ClientTest.php | 82 +++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index bade08615..b53a028f9 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -41,6 +41,16 @@ public function get_user_data() { return parent::get_user_data(); } + + /** + * Expose the current url method to test it + * + * @return string + */ + public function test_get_current_url() + { + return $this->get_current_url(); + } } class Raven_Tests_ClientTest extends PHPUnit_Framework_TestCase @@ -632,4 +642,76 @@ public function testSendCallback() $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); } + + /** + * Set the server array to the test values, check the current url + * + * @dataProvider currentUrlProvider + * @param array $serverData + * @param string $expected - the url expected + * @param string $message - fail message + */ + public function testCurrentUrl($serverVars, $expected, $message) + { + $_SERVER = $serverVars; + + $client = new Dummy_Raven_Client(); + $result = $client->test_get_current_url(); + + $this->assertSame($expected, $result, $message); + } + + /** + * Arrays of: + * $_SERVER data + * expected url + * Fail message + * + * @return array + */ + public function currentUrlProvider() + { + return [ + [ + [], + null, + 'If request uri is not set, expect nothing' + ], + [ + [ + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + ], + 'http://example.com/', + 'Simple http case' + ], + [ + [ + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + 'HTTPS' => 'on' + ], + 'https://example.com/', + 'Simple https case' + ], + [ + [ + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + 'SERVER_PORT' => '443' + ], + 'https://example.com/', + 'Https based on the server port' + ], + [ + [ + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + 'X-FORWARDED-PROTO' => 'https' + ], + 'https://example.com/', + 'Https based on the forwarded protocol' + ] + ]; + } } From c5e8de410dab64c7f24e2167177d29321aba7ca1 Mon Sep 17 00:00:00 2001 From: Andy Dawson Date: Tue, 1 Dec 2015 11:23:09 +0000 Subject: [PATCH 0011/1161] Make testable --- lib/Raven/Client.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 6de036bb0..999c82c59 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -781,7 +781,7 @@ private function uuid4() * * @return string|null */ - private function get_current_url() + protected function get_current_url() { // When running from commandline the REQUEST_URI is missing. if (!isset($_SERVER['REQUEST_URI'])) { @@ -802,13 +802,13 @@ private function get_current_url() * * @return bool */ - private function isHttps() + protected function isHttps() { if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { return true; } - if ($_SERVER['SERVER_PORT'] == 443) { + if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) { return true; } From e5a8d119c6407fb80ad88fb17553744b3b6e6a21 Mon Sep 17 00:00:00 2001 From: Andy Dawson Date: Tue, 1 Dec 2015 11:23:19 +0000 Subject: [PATCH 0012/1161] Yes backup globals Otherwise changes in one test to global variables bleed into other tests --- phpunit.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index ad58dc61c..9024d987c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,6 @@ - Date: Tue, 1 Dec 2015 12:21:38 +0000 Subject: [PATCH 0013/1161] PHP 5.2 compatibility --- test/Raven/Tests/ClientTest.php | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index b53a028f9..3af404ee0 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -671,47 +671,47 @@ public function testCurrentUrl($serverVars, $expected, $message) */ public function currentUrlProvider() { - return [ - [ - [], + return array( + array( + array(), null, 'If request uri is not set, expect nothing' - ], - [ - [ + ), + array( + array( 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', - ], + ), 'http://example.com/', 'Simple http case' - ], - [ - [ + ), + array( + array( 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' - ], + ), 'https://example.com/', 'Simple https case' - ], - [ - [ + ), + array( + array( 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', 'SERVER_PORT' => '443' - ], + ), 'https://example.com/', 'Https based on the server port' - ], - [ - [ + ), + array( + array( 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', 'X-FORWARDED-PROTO' => 'https' - ], + ), 'https://example.com/', 'Https based on the forwarded protocol' - ] - ]; + ) + ); } } From 50a224dc87512675fc975d315c8c86b37acf38dd Mon Sep 17 00:00:00 2001 From: Andy Dawson Date: Tue, 1 Dec 2015 17:38:24 +0000 Subject: [PATCH 0014/1161] Make that optional --- lib/Raven/Client.php | 5 ++++- test/Raven/Tests/ClientTest.php | 31 ++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 999c82c59..511a2dc48 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -74,6 +74,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->ca_cert = Raven_util::get($options, 'ca_cert', $this->get_default_ca_cert()); $this->verify_ssl = Raven_util::get($options, 'verify_ssl', true); $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); + $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); $this->processors = $this->setProcessorsFromOptions($options); @@ -812,7 +813,9 @@ protected function isHttps() return true; } - if (!empty($_SERVER['X-FORWARDED-PROTO']) && $_SERVER['X-FORWARDED-PROTO'] === 'https') { + if (!empty($this->trust_x_forwarded_proto) && + !empty($_SERVER['X-FORWARDED-PROTO']) && + $_SERVER['X-FORWARDED-PROTO'] === 'https') { return true; } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 3af404ee0..4310e689d 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -648,14 +648,15 @@ public function testSendCallback() * * @dataProvider currentUrlProvider * @param array $serverData + * @param array $options * @param string $expected - the url expected * @param string $message - fail message */ - public function testCurrentUrl($serverVars, $expected, $message) + public function testCurrentUrl($serverVars, $options, $expected, $message) { $_SERVER = $serverVars; - $client = new Dummy_Raven_Client(); + $client = new Dummy_Raven_Client($options); $result = $client->test_get_current_url(); $this->assertSame($expected, $result, $message); @@ -664,6 +665,7 @@ public function testCurrentUrl($serverVars, $expected, $message) /** * Arrays of: * $_SERVER data + * config * expected url * Fail message * @@ -673,17 +675,19 @@ public function currentUrlProvider() { return array( array( + array(), array(), null, - 'If request uri is not set, expect nothing' + 'No url expected for empty REQUEST_URI' ), array( array( 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', ), + array(), 'http://example.com/', - 'Simple http case' + 'The url is expected to be http with the request uri' ), array( array( @@ -691,8 +695,9 @@ public function currentUrlProvider() 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' ), + array(), 'https://example.com/', - 'Simple https case' + 'The url is expected to be https because of HTTPS on' ), array( array( @@ -700,8 +705,19 @@ public function currentUrlProvider() 'HTTP_HOST' => 'example.com', 'SERVER_PORT' => '443' ), + array(), 'https://example.com/', - 'Https based on the server port' + 'The url is expected to be https because of the server port' + ), + array( + array( + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + 'X-FORWARDED-PROTO' => 'https' + ), + array(), + 'http://example.com/', + 'The url is expected to be http because the X-Forwarded header is ignored' ), array( array( @@ -709,8 +725,9 @@ public function currentUrlProvider() 'HTTP_HOST' => 'example.com', 'X-FORWARDED-PROTO' => 'https' ), + array('trust_x_forwarded_proto' => true), 'https://example.com/', - 'Https based on the forwarded protocol' + 'The url is expected to be https because the X-Forwarded header is trusted' ) ); } From 86d44d22847627843191a70bba65512227b5ae9a Mon Sep 17 00:00:00 2001 From: Andy Dawson Date: Wed, 2 Dec 2015 10:30:44 +0000 Subject: [PATCH 0015/1161] Don't mess with content type or length If the request did have content type or content lenght headers - accurately represent that in the submitted data. Don't blindly take the value of CONTENT_TYPE and CONTENT_LENGTH as the keys are always present but an empty string if there is no post body. --- lib/Raven/Client.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 511a2dc48..03acca0a1 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -333,11 +333,8 @@ protected function get_http_data() foreach ($_SERVER as $key => $value) { if (0 === strpos($key, 'HTTP_')) { - if (in_array($key, array('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'))) { - continue; - } $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value; - } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { + } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))))] = $value; } else { $env[$key] = $value; From cb33d014d13c30e327ba48fa693fa4da874d60ec Mon Sep 17 00:00:00 2001 From: Andy Dawson Date: Wed, 2 Dec 2015 10:44:58 +0000 Subject: [PATCH 0016/1161] This is not a realistic test The keys HTTP_CONTENT_TYPE and CONTENT_TYPE and HTTP_CONTENT_LENGTH and CONTENT_LENGTH Don't act like that (or, shouldn't) For a get request the HTTP_ prefixed keys are absent, and the none-prefixed keys are empty. For a post request they should be the same content. --- test/Raven/Tests/ClientTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 4310e689d..7782a5bea 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -466,8 +466,8 @@ public function testGetHttpData() 'HTTP_ACCEPT' => 'text/html', 'HTTP_ACCEPT_CHARSET' => 'utf-8', 'HTTP_COOKIE' => 'cupcake: strawberry', - 'HTTP_CONTENT_TYPE' => 'text/html', - 'HTTP_CONTENT_LENGTH' => '1000', + 'HTTP_CONTENT_TYPE' => 'text/xml', + 'HTTP_CONTENT_LENGTH' => '99', 'SERVER_PORT' => '443', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'REQUEST_METHOD' => 'PATCH', From 264c0dfb6ae19d8d90c456ab3ed95a27d511de8a Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Thu, 3 Dec 2015 10:38:36 -0800 Subject: [PATCH 0017/1161] Fix indenting in docs --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index bfa659557..b52180706 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -82,7 +82,7 @@ Optional Attributes With calls to ``captureException`` or ``captureMessage`` additional data can be supplied:: -.. code-block:: php + .. code-block:: php $client->captureException($ex, array('attr' => 'value')) From 97190c7f4f3b3d4e9d516f829171a2298b72a3ea Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Thu, 3 Dec 2015 13:29:56 -0800 Subject: [PATCH 0018/1161] Remove invalid HTTP_* vars from test --- test/Raven/Tests/ClientTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 7782a5bea..9ce0b8dce 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -466,8 +466,6 @@ public function testGetHttpData() 'HTTP_ACCEPT' => 'text/html', 'HTTP_ACCEPT_CHARSET' => 'utf-8', 'HTTP_COOKIE' => 'cupcake: strawberry', - 'HTTP_CONTENT_TYPE' => 'text/xml', - 'HTTP_CONTENT_LENGTH' => '99', 'SERVER_PORT' => '443', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'REQUEST_METHOD' => 'PATCH', From e99e0414ce057a2e795fbc38f0a4b1436e8b50e5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 10 Dec 2015 23:01:26 -0800 Subject: [PATCH 0019/1161] Add support for "environment" param --- lib/Raven/Client.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 03acca0a1..f4d31c2a4 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -59,6 +59,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->site = Raven_Util::get($options, 'site', $this->_server_variable('SERVER_NAME')); $this->tags = Raven_Util::get($options, 'tags', array()); $this->release = Raven_util::get($options, 'release', null); + $this->environment = Raven_util::get($options, 'environment', null); $this->trace = (bool) Raven_Util::get($options, 'trace', true); $this->timeout = Raven_Util::get($options, 'timeout', 2); $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT); @@ -436,6 +437,9 @@ public function capture($data, $stack, $vars = null) if ($this->release) { $data['release'] = $this->release; } + if ($this->environment) { + $data['environment'] = $this->environment; + } $data['tags'] = array_merge( $this->tags, From 01f97bb64b82316927788dd0f51ebcf11551d4dc Mon Sep 17 00:00:00 2001 From: Pascal Borreli Date: Wed, 30 Dec 2015 19:48:50 +0000 Subject: [PATCH 0020/1161] Fixed typo --- docs/integrations/symfony2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index a80ec2d6f..47096c3a4 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -3,7 +3,7 @@ Symfony2 Symfony2 supports Monolog out of the box, which also provides a native Sentry handler. -Simply add a new handler for Sentry to your config (i.e. in ``config_prod.yaml``), and you're good to go: +Simply add a new handler for Sentry to your config (i.e. in ``config_prod.yml``), and you're good to go: .. sourcecode:: yaml From 1a9f66e3afb6b6d5edba3e2595381b2072a59cdc Mon Sep 17 00:00:00 2001 From: Garanzha Dmitriy Date: Wed, 20 Jan 2016 11:06:57 +0200 Subject: [PATCH 0021/1161] Fix indentation Content not visible in markdown --- docs/usage.rst | 82 +++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index b52180706..dc1baaf59 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -80,82 +80,82 @@ Optional Attributes ------------------- With calls to ``captureException`` or ``captureMessage`` additional data -can be supplied:: +can be supplied: - .. code-block:: php +.. code-block:: php - $client->captureException($ex, array('attr' => 'value')) + $client->captureException($ex, array('attr' => 'value')) .. describe:: extra - Additional context for this event. Must be a mapping. Children can be any native JSON type. +Additional context for this event. Must be a mapping. Children can be any native JSON type. - .. code-block:: php +.. code-block:: php - array( - 'extra' => array('key' => 'value') - ) + array( + 'extra' => array('key' => 'value') + ) .. describe:: fingerprint - The fingerprint for grouping this event. +The fingerprint for grouping this event. - .. code-block:: php +.. code-block:: php - array( - 'fingerprint' => ['{{ default }}', 'other value'] - ) + array( + 'fingerprint' => ['{{ default }}', 'other value'] + ) .. describe:: level - The level of the event. Defaults to ``error``. +The level of the event. Defaults to ``error``. - .. code-block:: php +.. code-block:: php - array( - 'level' => 'warning' - ) + array( + 'level' => 'warning' + ) - Sentry is aware of the following levels: +Sentry is aware of the following levels: - * debug (the least serious) - * info - * warning - * error - * fatal (the most serious) +* debug (the least serious) +* info +* warning +* error +* fatal (the most serious) .. describe:: logger - The logger name for the event. +The logger name for the event. - .. code-block:: php +.. code-block:: php - array( - 'logger' => 'default' - ) + array( + 'logger' => 'default' + ) .. describe:: tags - Tags to index with this event. Must be a mapping of strings. +Tags to index with this event. Must be a mapping of strings. - .. code-block:: php +.. code-block:: php - array( - 'tags' => array('key' => 'value') - ) + array( + 'tags' => array('key' => 'value') + ) .. describe:: user - The acting user. +The acting user. - .. code-block:: php +.. code-block:: php - array( - 'user' => array( - 'id' => 42, - 'email' => 'clever-girl' - ) + array( + 'user' => array( + 'id' => 42, + 'email' => 'clever-girl' ) + ) Testing Your Connection ----------------------- From a0b967cde963d7ce80cc291d79a3f1d84884251b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Jan 2016 12:16:04 -0800 Subject: [PATCH 0022/1161] Bind error handling to explicit types --- lib/Raven/ErrorHandler.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 66d3da297..0d121d4fe 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -29,11 +29,12 @@ class Raven_ErrorHandler private $call_existing_error_handler = false; private $reservedMemory; private $send_errors_last = false; - private $error_types = -1; + private $error_types; /** * @var array - * Error types that can be processed by the handler + * Error types that can be processed by the handler. Only used + * by the shutdown fatal handler. */ private $validErrorTypes = array( E_ERROR, @@ -55,7 +56,8 @@ class Raven_ErrorHandler /** * @var array - * The default Error types that are always processed by the handler. Can be set during construction. + * The default Error types that are always processed. Only used + * by the shutdown fatal handler. Can be set during construction. */ private $defaultErrorTypes = array( E_ERROR, @@ -92,7 +94,7 @@ public function handleException($e, $isError = false, $vars = null) public function handleError($code, $message, $file = '', $line = 0, $context=array()) { - if ($this->error_types & $code & error_reporting()) { + if ($this->error_types & $code) { $e = new ErrorException($message, 0, $code, $file, $line); $this->handleException($e, true, $context); } @@ -155,10 +157,13 @@ public function registerExceptionHandler($call_existing_exception_handler = true $this->call_existing_exception_handler = $call_existing_exception_handler; } - public function registerErrorHandler($call_existing_error_handler = true, $error_types = -1) + public function registerErrorHandler($call_existing_error_handler = true, $error_types = null) { + if ($error_types === null) { + $error_type = error_reporting(); + } $this->error_types = $error_types; - $this->old_error_handler = set_error_handler(array($this, 'handleError'), error_reporting()); + $this->old_error_handler = set_error_handler(array($this, 'handleError'), $error_types); $this->call_existing_error_handler = $call_existing_error_handler; } From 514257a332852a9d72bc03053602eac4513da26c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Jan 2016 12:36:58 -0800 Subject: [PATCH 0023/1161] Ensure error_types are handled correctly This corrects behavior that would allow you to specify which error_types are handled by the SDK, but would simply ignore it if they were also not included in error_reporting. --- lib/Raven/ErrorHandler.php | 7 ++++--- test/Raven/Tests/ErrorHandlerTest.php | 30 ++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 0d121d4fe..614b44099 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -81,6 +81,7 @@ public function __construct($client, $send_errors_last = false, $default_error_t $this->client->store_errors_for_bulk_send = true; register_shutdown_function(array($this->client, 'sendUnsentErrors')); } + $this->error_types = error_reporting(); } public function handleException($e, $isError = false, $vars = null) @@ -99,7 +100,7 @@ public function handleError($code, $message, $file = '', $line = 0, $context=arr $this->handleException($e, true, $context); } - if ($this->call_existing_error_handler) { + if (error_reporting() & $code && $this->call_existing_error_handler) { if ($this->old_error_handler) { return call_user_func($this->old_error_handler, $code, $message, $file, $line, $context); } else { @@ -159,11 +160,11 @@ public function registerExceptionHandler($call_existing_exception_handler = true public function registerErrorHandler($call_existing_error_handler = true, $error_types = null) { - if ($error_types === null) { + if ($error_types == null) { $error_type = error_reporting(); } $this->error_types = $error_types; - $this->old_error_handler = set_error_handler(array($this, 'handleError'), $error_types); + $this->old_error_handler = set_error_handler(array($this, 'handleError'), -1); $this->call_existing_error_handler = $call_existing_error_handler; } diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 656b75935..c943d06eb 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -16,21 +16,30 @@ class Raven_Tests_ErrorHandlerTest extends PHPUnit_Framework_TestCase public function setUp() { $this->errorLevel = error_reporting(); + $this->errorHandlerCalled = false; + $this->existingErrorHandler = set_error_handler(array($this, 'errorHandler'), -1); + } + + public function errorHandler() + { + $this->errorHandlerCalled = true; } public function tearDown() { + // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level + set_error_handler(array($this, 'errorHandler'), error_reporting()); error_reporting($this->errorLevel); } public function testErrorsAreLoggedAsExceptions() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException', 'getIdent', 'sendUnsentErrors')); $client->expects($this->once()) ->method('captureException') ->with($this->isInstanceOf('ErrorException')); - $handler = new Raven_ErrorHandler($client); + $handler = new Raven_ErrorHandler($client, E_ALL); $handler->handleError(E_WARNING, 'message'); } @@ -79,9 +88,24 @@ public function testErrorHandlerPassErrorReportingPass() ->method('captureException'); $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(false); + $handler->registerErrorHandler(false, -1); + + error_reporting(E_USER_WARNING); + trigger_error('Warning', E_USER_WARNING); + } + + public function testErrorHandlerPropagatesUsingErrorReporting() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client->expects($this->never()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(true, E_NONE); error_reporting(E_USER_WARNING); trigger_error('Warning', E_USER_WARNING); + + $this->assertEquals($this->errorHandlerCalled, 1); } } From a01e51543f06999608f138f1b1159d5aff2c3d6d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Jan 2016 13:55:14 -0800 Subject: [PATCH 0024/1161] Revert "Merge pull request #258 from getsentry/better-error-types" This reverts commit 56eb2da2b35a1bb9421db7f1620a7e142fb7bca7, reversing changes made to 51d1647007c37f5d1cdc222b74aa09f395be3dac. --- lib/Raven/ErrorHandler.php | 20 +++++++----------- test/Raven/Tests/ErrorHandlerTest.php | 30 +++------------------------ 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 614b44099..66d3da297 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -29,12 +29,11 @@ class Raven_ErrorHandler private $call_existing_error_handler = false; private $reservedMemory; private $send_errors_last = false; - private $error_types; + private $error_types = -1; /** * @var array - * Error types that can be processed by the handler. Only used - * by the shutdown fatal handler. + * Error types that can be processed by the handler */ private $validErrorTypes = array( E_ERROR, @@ -56,8 +55,7 @@ class Raven_ErrorHandler /** * @var array - * The default Error types that are always processed. Only used - * by the shutdown fatal handler. Can be set during construction. + * The default Error types that are always processed by the handler. Can be set during construction. */ private $defaultErrorTypes = array( E_ERROR, @@ -81,7 +79,6 @@ public function __construct($client, $send_errors_last = false, $default_error_t $this->client->store_errors_for_bulk_send = true; register_shutdown_function(array($this->client, 'sendUnsentErrors')); } - $this->error_types = error_reporting(); } public function handleException($e, $isError = false, $vars = null) @@ -95,12 +92,12 @@ public function handleException($e, $isError = false, $vars = null) public function handleError($code, $message, $file = '', $line = 0, $context=array()) { - if ($this->error_types & $code) { + if ($this->error_types & $code & error_reporting()) { $e = new ErrorException($message, 0, $code, $file, $line); $this->handleException($e, true, $context); } - if (error_reporting() & $code && $this->call_existing_error_handler) { + if ($this->call_existing_error_handler) { if ($this->old_error_handler) { return call_user_func($this->old_error_handler, $code, $message, $file, $line, $context); } else { @@ -158,13 +155,10 @@ public function registerExceptionHandler($call_existing_exception_handler = true $this->call_existing_exception_handler = $call_existing_exception_handler; } - public function registerErrorHandler($call_existing_error_handler = true, $error_types = null) + public function registerErrorHandler($call_existing_error_handler = true, $error_types = -1) { - if ($error_types == null) { - $error_type = error_reporting(); - } $this->error_types = $error_types; - $this->old_error_handler = set_error_handler(array($this, 'handleError'), -1); + $this->old_error_handler = set_error_handler(array($this, 'handleError'), error_reporting()); $this->call_existing_error_handler = $call_existing_error_handler; } diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index c943d06eb..656b75935 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -16,30 +16,21 @@ class Raven_Tests_ErrorHandlerTest extends PHPUnit_Framework_TestCase public function setUp() { $this->errorLevel = error_reporting(); - $this->errorHandlerCalled = false; - $this->existingErrorHandler = set_error_handler(array($this, 'errorHandler'), -1); - } - - public function errorHandler() - { - $this->errorHandlerCalled = true; } public function tearDown() { - // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level - set_error_handler(array($this, 'errorHandler'), error_reporting()); error_reporting($this->errorLevel); } public function testErrorsAreLoggedAsExceptions() { - $client = $this->getMock('Client', array('captureException', 'getIdent', 'sendUnsentErrors')); + $client = $this->getMock('Client', array('captureException', 'getIdent')); $client->expects($this->once()) ->method('captureException') ->with($this->isInstanceOf('ErrorException')); - $handler = new Raven_ErrorHandler($client, E_ALL); + $handler = new Raven_ErrorHandler($client); $handler->handleError(E_WARNING, 'message'); } @@ -88,24 +79,9 @@ public function testErrorHandlerPassErrorReportingPass() ->method('captureException'); $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(false, -1); - - error_reporting(E_USER_WARNING); - trigger_error('Warning', E_USER_WARNING); - } - - public function testErrorHandlerPropagatesUsingErrorReporting() - { - $client = $this->getMock('Client', array('captureException', 'getIdent')); - $client->expects($this->never()) - ->method('captureException'); - - $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(true, E_NONE); + $handler->registerErrorHandler(false); error_reporting(E_USER_WARNING); trigger_error('Warning', E_USER_WARNING); - - $this->assertEquals($this->errorHandlerCalled, 1); } } From a51f071cd911d7f9a730420edba766efa6ddd932 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Jan 2016 12:36:58 -0800 Subject: [PATCH 0025/1161] Ensure error_types are handled correctly This corrects behavior that would allow you to specify which error_types are handled by the SDK, but would simply ignore it if they were also not included in error_reporting. --- test/Raven/Tests/ErrorHandlerTest.php | 51 +++++++++++++++++++++------ 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 656b75935..114e60de6 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -16,21 +16,30 @@ class Raven_Tests_ErrorHandlerTest extends PHPUnit_Framework_TestCase public function setUp() { $this->errorLevel = error_reporting(); + $this->errorHandlerCalled = false; + $this->existingErrorHandler = set_error_handler(array($this, 'errorHandler'), -1); + } + + public function errorHandler() + { + $this->errorHandlerCalled = true; } public function tearDown() { + // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level + set_error_handler(array($this, 'errorHandler'), error_reporting()); error_reporting($this->errorLevel); } public function testErrorsAreLoggedAsExceptions() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException', 'getIdent', 'sendUnsentErrors')); $client->expects($this->once()) ->method('captureException') ->with($this->isInstanceOf('ErrorException')); - $handler = new Raven_ErrorHandler($client); + $handler = new Raven_ErrorHandler($client, E_ALL); $handler->handleError(E_WARNING, 'message'); } @@ -47,41 +56,61 @@ public function testExceptionsAreLogged() $handler->handleException($e); } - public function testErrorHandlerCheckSilentReporting() + public function testErrorHandlerPassErrorReportingPass() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client->expects($this->once()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(false, -1); + + error_reporting(E_USER_WARNING); + trigger_error('Warning', E_USER_WARNING); + } + + public function testErrorHandlerPropagatesUsingErrorReporting() { $client = $this->getMock('Client', array('captureException', 'getIdent')); $client->expects($this->never()) ->method('captureException'); $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(false); + $handler->registerErrorHandler(true, E_NONE); - @trigger_error('Silent', E_USER_WARNING); + error_reporting(E_USER_WARNING); + trigger_error('Warning', E_USER_WARNING); + + $this->assertEquals($this->errorHandlerCalled, 1); } - public function testErrorHandlerBlockErrorReporting() + // Because we cannot **know** that a user silenced an error, we always + // defer to respecting the error reporting settings. + public function testSilentErrorsAreReported() { $client = $this->getMock('Client', array('captureException', 'getIdent')); $client->expects($this->never()) ->method('captureException'); + error_reporting(E_USER_WARNING); + $handler = new Raven_ErrorHandler($client); $handler->registerErrorHandler(false); - error_reporting(E_USER_ERROR); - trigger_error('Warning', E_USER_WARNING); + @trigger_error('Silent', E_USER_WARNING); } - public function testErrorHandlerPassErrorReportingPass() + public function testErrorHandlerDefaultsErrorReporting() { $client = $this->getMock('Client', array('captureException', 'getIdent')); - $client->expects($this->once()) + $client->expects($this->never()) ->method('captureException'); + error_reporting(E_USER_ERROR); + $handler = new Raven_ErrorHandler($client); $handler->registerErrorHandler(false); - error_reporting(E_USER_WARNING); trigger_error('Warning', E_USER_WARNING); } } From 85860726b420c58d6fd963e918ccde735e797be4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Feb 2016 14:03:43 -0800 Subject: [PATCH 0026/1161] Further improve error_reporting usage - When no error types are defined, default to error_reporting (at time of error) - Deprecates the default error type methods as they are no implemented entirely. --- lib/Raven/ErrorHandler.php | 50 +++++++++++++++++++++------ test/Raven/Tests/ErrorHandlerTest.php | 21 +++++++++-- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 66d3da297..57aca9812 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -21,6 +21,9 @@ * @package raven */ +// TODO(dcramer): deprecate default error types in favor of runtime configuration +// unless a reason can be determined that making them dynamic is better. They +// currently are not used outside of the fatal handler. class Raven_ErrorHandler { private $old_exception_handler; @@ -29,9 +32,16 @@ class Raven_ErrorHandler private $call_existing_error_handler = false; private $reservedMemory; private $send_errors_last = false; - private $error_types = -1; /** + * @var array + * Error types which should be processed by the handler. + * A 'null' value implies "whatever error_reporting is at time of error". + */ + private $error_types = null; + + /** + * @deprecated * @var array * Error types that can be processed by the handler */ @@ -54,6 +64,7 @@ class Raven_ErrorHandler ); /** + * @deprecated * @var array * The default Error types that are always processed by the handler. Can be set during construction. */ @@ -67,12 +78,14 @@ class Raven_ErrorHandler E_STRICT, ); - public function __construct($client, $send_errors_last = false, $default_error_types = null) + public function __construct($client, $send_errors_last = false, $default_error_types = null, + $error_types = null) { $this->client = $client; if ($default_error_types !== null) { $this->defaultErrorTypes = $default_error_types; } + $this->error_types = $error_types; register_shutdown_function(array($this, 'detectShutdown')); if ($send_errors_last) { $this->send_errors_last = true; @@ -92,13 +105,18 @@ public function handleException($e, $isError = false, $vars = null) public function handleError($code, $message, $file = '', $line = 0, $context=array()) { - if ($this->error_types & $code & error_reporting()) { - $e = new ErrorException($message, 0, $code, $file, $line); - $this->handleException($e, true, $context); + if (error_reporting() !== 0) { + $error_types = $this->error_types; + if ($error_types === null) { + $error_types = error_reporting(); + } + if ($error_types & $code) { + $e = new ErrorException($message, 0, $code, $file, $line); + $this->handleException($e, true, $context); + } } - if ($this->call_existing_error_handler) { - if ($this->old_error_handler) { + if ($this->old_error_handler !== null) { return call_user_func($this->old_error_handler, $code, $message, $file, $line, $context); } else { return false; @@ -110,14 +128,17 @@ public function handleError($code, $message, $file = '', $line = 0, $context=arr * Nothing by default, use it in child classes for catching other types of errors * Only constants from $this->validErrorTypes can be used * + * @deprecated * @return array */ + protected function getAdditionalErrorTypesToProcess() { return array(); } /** + * @deprecated * @return array */ private function getErrorTypesToProcess() @@ -155,10 +176,19 @@ public function registerExceptionHandler($call_existing_exception_handler = true $this->call_existing_exception_handler = $call_existing_exception_handler; } - public function registerErrorHandler($call_existing_error_handler = true, $error_types = -1) + /** + * Register a handler which will intercept standard PHP errors and report them to the + * associated Sentry client. + * + * @return array + */ + // + public function registerErrorHandler($call_existing_error_handler = true, $error_types = null) { - $this->error_types = $error_types; - $this->old_error_handler = set_error_handler(array($this, 'handleError'), error_reporting()); + if ($error_types !== null) { + $this->error_types = $error_types; + } + $this->old_error_handler = set_error_handler(array($this, 'handleError'), E_ALL); $this->call_existing_error_handler = $call_existing_error_handler; } diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 114e60de6..04e10e856 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -69,14 +69,14 @@ public function testErrorHandlerPassErrorReportingPass() trigger_error('Warning', E_USER_WARNING); } - public function testErrorHandlerPropagatesUsingErrorReporting() + public function testErrorHandlerPropagates() { $client = $this->getMock('Client', array('captureException', 'getIdent')); $client->expects($this->never()) ->method('captureException'); $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(true, E_NONE); + $handler->registerErrorHandler(true, E_DEPRECATED); error_reporting(E_USER_WARNING); trigger_error('Warning', E_USER_WARNING); @@ -84,6 +84,23 @@ public function testErrorHandlerPropagatesUsingErrorReporting() $this->assertEquals($this->errorHandlerCalled, 1); } + public function testErrorHandlerRespectsErrorReportingDefault() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client->expects($this->once()) + ->method('captureException'); + + error_reporting(E_DEPRECATED); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(true); + + error_reporting(E_ALL); + trigger_error('Warning', E_USER_WARNING); + + $this->assertEquals($this->errorHandlerCalled, 1); + } + // Because we cannot **know** that a user silenced an error, we always // defer to respecting the error reporting settings. public function testSilentErrorsAreReported() From 2378eed8f78b905d327f6ecd232e71fae6d1f349 Mon Sep 17 00:00:00 2001 From: Dieter Wyns Date: Mon, 22 Feb 2016 15:57:53 +0100 Subject: [PATCH 0027/1161] Docs: Lumen framework added --- docs/integrations/laravel.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 4b9e27973..e8f2a184e 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -34,6 +34,24 @@ To configure logging, pop open your ``app/start/global.php`` file, and insert th $monolog = Log::getMonolog(); $monolog->pushHandler($handler); +Lumen 5.x +----------- + +To configure logging, pop open your ``bootstrap/app.php`` file, and insert the following: + +.. sourcecode:: php + + $app->configureMonologUsing(function($monolog) { + $client = new Raven_Client('___DSN___'); + + $handler = new Monolog\Handler\RavenHandler($client); + $handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n")); + + $monolog->pushHandler($handler); + + return $monolog; + }); + Adding Context -------------- From 2170713348d2eb676e18e8c9deb7d2e6505d5851 Mon Sep 17 00:00:00 2001 From: dasmfm <2@borisklimenko.ru> Date: Fri, 11 Mar 2016 11:30:57 +0300 Subject: [PATCH 0028/1161] Fix curl_exec method to work with self-signed certificates Bug fixed: send_http_asynchronous_curl_exec method ignores verify_ssl flag and doesn't work with self-signed certificates. Curl flag added. --- lib/Raven/Client.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index f4d31c2a4..025d6d231 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -662,6 +662,8 @@ private function send_http_asynchronous_curl_exec($url, $data, $headers) $cmd .= '-d \''. $data .'\' '; $cmd .= '\''. $url .'\' '; $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send) + if (!$this->verify_ssl) + $cmd .= '-k '; $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background exec($cmd); From c8659f4c2499114a317a3a23529bdb686ced4587 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 23 Mar 2016 19:58:52 -0700 Subject: [PATCH 0029/1161] Basic docs for release attribute (refs GH-268) --- docs/config.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index 746032586..18f7b43d2 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -32,6 +32,13 @@ The following settings are available for the client: 'php_version' => phpversion(), ) +.. describe:: release + + The version of your application (e.g. git SHA) + + .. code-block:: php + + 'release' => MyApp::getReleaseVersion(), .. describe:: curl_method From 6cc582ef84fef0fac5af992a488ff35ea1dfe4d1 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 23 Mar 2016 19:59:26 -0700 Subject: [PATCH 0030/1161] Basic docs for environment attribute --- docs/config.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index 18f7b43d2..4be2fece9 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -40,6 +40,14 @@ The following settings are available for the client: 'release' => MyApp::getReleaseVersion(), +.. describe:: environment + + The environment your application is running in. + + .. code-block:: php + + 'environment' => 'production', + .. describe:: curl_method Defaults to 'sync'. From 08e653614df08835feea1ef025809307740f4b1b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 24 Mar 2016 18:26:21 -0700 Subject: [PATCH 0031/1161] Improve user context docs to be more correct --- docs/integrations/symfony2.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index 47096c3a4..2be206985 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -53,14 +53,17 @@ Capturing context can be done via a monolog processor: public function processRecord($record) { - $user = $this->tokenStorage->getToken()->getUser(); - - if ($user instanceof User) { - $record['context']['user'] = array( - 'name' => $user->getName(), - 'username' => $user->getUsername(), - 'email' => $user->getEmail(), - ); + $token = $this->tokenStorage->getToken(); + + if ($token !== null){ + $user = $token->getUser(); + if ($user instanceof UserInterface) { + $record['context']['user'] = array( + 'name' => $user->getName(), + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + ); + } } // Add various tags From cf700e952a7542bf63055a169d2ee5d7220c6da7 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 24 Mar 2016 23:01:29 -0700 Subject: [PATCH 0032/1161] Helper for updating submodules --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5e574ece1..1a4d8b73b 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,13 @@ .PHONY: test -develop: +develop: update-submodules composer install --dev make setup-git +update-submodules: + git submodule init + git submodule update + cs: vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff From c355b76561c4aaabf59d93ac3ff5273d4208943a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 24 Mar 2016 23:01:53 -0700 Subject: [PATCH 0033/1161] Lint --- lib/Raven/Client.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 025d6d231..a3b84c111 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -662,8 +662,9 @@ private function send_http_asynchronous_curl_exec($url, $data, $headers) $cmd .= '-d \''. $data .'\' '; $cmd .= '\''. $url .'\' '; $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send) - if (!$this->verify_ssl) + if (!$this->verify_ssl) { $cmd .= '-k '; + } $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background exec($cmd); From 8c3e39ff8cb923195ca4aaca6f56751d99b0d8d3 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 24 Mar 2016 10:12:46 -0700 Subject: [PATCH 0034/1161] Improve serialization (fixes GH-269) - Explicitly use repr-like serialization for reflection args - Use standard JSON approach for other args --- lib/Raven/Client.php | 10 +++- lib/Raven/Serializer.php | 33 ++++++++++- lib/Raven/Stacktrace.php | 2 +- test/Raven/Tests/SerializerTest.php | 91 +++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index a3b84c111..1fe9820f9 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -454,11 +454,19 @@ public function capture($data, $stack, $vars = null) // avoid empty arrays (which dont convert to dicts) if (empty($data['extra'])) { unset($data['extra']); + } else { + $data['extra'] = Raven_Serializer::serialize($data['extra']); } if (empty($data['tags'])) { unset($data['tags']); + } else { + $data['tags'] = Raven_Serializer::serialize($data['tags']); + } + if (!empty($data['user'])) { + $data['user'] = Raven_Serializer::serialize($data['user']); } + if ((!$stack && $this->auto_log_stacks) || $stack === true) { $stack = debug_backtrace(); @@ -502,8 +510,6 @@ public function sanitize(&$data) if (!class_exists('Raven_Serializer')) { spl_autoload_call('Raven_Serializer'); } - - $data = Raven_Serializer::serialize($data); } /** diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index df04d7826..53cef525f 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -30,7 +30,7 @@ class Raven_Serializer * Serialize an object (recursively) into something safe for data * sanitization and encoding. */ - public static function serialize($value, $max_depth=9, $_depth=0) + public static function serialize($value, $max_depth=3, $_depth=0) { if (is_object($value) || is_resource($value)) { return self::serializeValue($value); @@ -46,6 +46,35 @@ public static function serialize($value, $max_depth=9, $_depth=0) } } + public static function serializeValue($value) + { + if (is_null($value) || is_bool($value) || is_float($value) || is_integer($value)) { + return $value; + } elseif (is_object($value) || gettype($value) == 'object') { + return 'Object '.get_class($value); + } elseif (is_resource($value)) { + return 'Resource '.get_resource_type($value); + } elseif (is_array($value)) { + return 'Array of length ' . count($value); + } else { + $value = (string) $value; + + if (function_exists('mb_convert_encoding')) { + $value = mb_convert_encoding($value, 'UTF-8', 'auto'); + } + + return $value; + } + } +} + +class Raven_ReprSerializer extends Raven_Serializer +{ + /** + * Serialize a value recursively into a string-based representation + * of the typed PHP value. + */ + public static function serializeValue($value) { if ($value === null) { @@ -62,8 +91,6 @@ public static function serializeValue($value) return 'Resource '.get_resource_type($value); } elseif (is_array($value)) { return 'Array of length ' . count($value); - } elseif (is_integer($value)) { - return (integer) $value; } else { $value = (string) $value; diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index c006b7a42..d353b0a3d 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -180,7 +180,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client } } } - $args[$params[$i]->name] = $arg; + $args[$params[$i]->name] = Raven_ReprSerializer::serialize($arg); } else { // TODO: Sentry thinks of these as context locals, so they must be named // Assign the argument by number diff --git a/test/Raven/Tests/SerializerTest.php b/test/Raven/Tests/SerializerTest.php index af157130b..03c93290a 100644 --- a/test/Raven/Tests/SerializerTest.php +++ b/test/Raven/Tests/SerializerTest.php @@ -38,6 +38,97 @@ public function testIntsAreInts() $this->assertEquals(1, $result); } + public function testFloats() + { + $input = 1.5; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_float($result)); + $this->assertEquals(1.5, $result); + } + + public function testBooleans() + { + $input = true; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_bool($result)); + $this->assertEquals(true, $result); + + $input = false; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_bool($result)); + $this->assertEquals(false, $result); + } + + public function testNull() + { + $input = null; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_null($result)); + $this->assertEquals(null, $result); + } + + public function testRecursionMaxDepth() + { + $input = array(); + $input[] = &$input; + $result = Raven_Serializer::serialize($input, 3); + $this->assertEquals(array(array(array('Array of length 1'))), $result); + } +} + +class Raven_Tests_ReprSerializerTest extends PHPUnit_Framework_TestCase +{ + public function testArraysAreArrays() + { + $input = array(1, 2, 3); + $result = Raven_Serializer::serialize($input); + $this->assertEquals(array('1', '2', '3'), $result); + } + + public function testObjectsAreStrings() + { + $input = new Raven_StacktraceTestObject(); + $result = Raven_Serializer::serialize($input); + $this->assertEquals('Object Raven_StacktraceTestObject', $result); + } + + public function testIntsAreInts() + { + $input = 1; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_integer($result)); + $this->assertEquals(1, $result); + } + + public function testFloats() + { + $input = 1.5; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals('1.5', $result); + } + + public function testBooleans() + { + $input = true; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals('true', $result); + + $input = false; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals('false', $result); + } + + public function testNull() + { + $input = null; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals('null', $result); + } + public function testRecursionMaxDepth() { $input = array(); From 4cb3b11f941a14bba796e767d4a23a3d439a3b20 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 27 Mar 2016 23:01:13 -0700 Subject: [PATCH 0035/1161] Revert "Improve serialization (fixes GH-269)" This reverts commit 8c3e39ff8cb923195ca4aaca6f56751d99b0d8d3. --- lib/Raven/Client.php | 10 +--- lib/Raven/Serializer.php | 33 +---------- lib/Raven/Stacktrace.php | 2 +- test/Raven/Tests/SerializerTest.php | 91 ----------------------------- 4 files changed, 6 insertions(+), 130 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 1fe9820f9..a3b84c111 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -454,19 +454,11 @@ public function capture($data, $stack, $vars = null) // avoid empty arrays (which dont convert to dicts) if (empty($data['extra'])) { unset($data['extra']); - } else { - $data['extra'] = Raven_Serializer::serialize($data['extra']); } if (empty($data['tags'])) { unset($data['tags']); - } else { - $data['tags'] = Raven_Serializer::serialize($data['tags']); - } - if (!empty($data['user'])) { - $data['user'] = Raven_Serializer::serialize($data['user']); } - if ((!$stack && $this->auto_log_stacks) || $stack === true) { $stack = debug_backtrace(); @@ -510,6 +502,8 @@ public function sanitize(&$data) if (!class_exists('Raven_Serializer')) { spl_autoload_call('Raven_Serializer'); } + + $data = Raven_Serializer::serialize($data); } /** diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 53cef525f..df04d7826 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -30,7 +30,7 @@ class Raven_Serializer * Serialize an object (recursively) into something safe for data * sanitization and encoding. */ - public static function serialize($value, $max_depth=3, $_depth=0) + public static function serialize($value, $max_depth=9, $_depth=0) { if (is_object($value) || is_resource($value)) { return self::serializeValue($value); @@ -46,35 +46,6 @@ public static function serialize($value, $max_depth=3, $_depth=0) } } - public static function serializeValue($value) - { - if (is_null($value) || is_bool($value) || is_float($value) || is_integer($value)) { - return $value; - } elseif (is_object($value) || gettype($value) == 'object') { - return 'Object '.get_class($value); - } elseif (is_resource($value)) { - return 'Resource '.get_resource_type($value); - } elseif (is_array($value)) { - return 'Array of length ' . count($value); - } else { - $value = (string) $value; - - if (function_exists('mb_convert_encoding')) { - $value = mb_convert_encoding($value, 'UTF-8', 'auto'); - } - - return $value; - } - } -} - -class Raven_ReprSerializer extends Raven_Serializer -{ - /** - * Serialize a value recursively into a string-based representation - * of the typed PHP value. - */ - public static function serializeValue($value) { if ($value === null) { @@ -91,6 +62,8 @@ public static function serializeValue($value) return 'Resource '.get_resource_type($value); } elseif (is_array($value)) { return 'Array of length ' . count($value); + } elseif (is_integer($value)) { + return (integer) $value; } else { $value = (string) $value; diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index d353b0a3d..c006b7a42 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -180,7 +180,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client } } } - $args[$params[$i]->name] = Raven_ReprSerializer::serialize($arg); + $args[$params[$i]->name] = $arg; } else { // TODO: Sentry thinks of these as context locals, so they must be named // Assign the argument by number diff --git a/test/Raven/Tests/SerializerTest.php b/test/Raven/Tests/SerializerTest.php index 03c93290a..af157130b 100644 --- a/test/Raven/Tests/SerializerTest.php +++ b/test/Raven/Tests/SerializerTest.php @@ -38,97 +38,6 @@ public function testIntsAreInts() $this->assertEquals(1, $result); } - public function testFloats() - { - $input = 1.5; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_float($result)); - $this->assertEquals(1.5, $result); - } - - public function testBooleans() - { - $input = true; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_bool($result)); - $this->assertEquals(true, $result); - - $input = false; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_bool($result)); - $this->assertEquals(false, $result); - } - - public function testNull() - { - $input = null; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_null($result)); - $this->assertEquals(null, $result); - } - - public function testRecursionMaxDepth() - { - $input = array(); - $input[] = &$input; - $result = Raven_Serializer::serialize($input, 3); - $this->assertEquals(array(array(array('Array of length 1'))), $result); - } -} - -class Raven_Tests_ReprSerializerTest extends PHPUnit_Framework_TestCase -{ - public function testArraysAreArrays() - { - $input = array(1, 2, 3); - $result = Raven_Serializer::serialize($input); - $this->assertEquals(array('1', '2', '3'), $result); - } - - public function testObjectsAreStrings() - { - $input = new Raven_StacktraceTestObject(); - $result = Raven_Serializer::serialize($input); - $this->assertEquals('Object Raven_StacktraceTestObject', $result); - } - - public function testIntsAreInts() - { - $input = 1; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_integer($result)); - $this->assertEquals(1, $result); - } - - public function testFloats() - { - $input = 1.5; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals('1.5', $result); - } - - public function testBooleans() - { - $input = true; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals('true', $result); - - $input = false; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals('false', $result); - } - - public function testNull() - { - $input = null; - $result = Raven_Serializer::serialize($input); - $this->assertTrue(is_string($result)); - $this->assertEquals('null', $result); - } - public function testRecursionMaxDepth() { $input = array(); From 90cbb75d3c0aefa1ed5adf207a35627a2cdcd012 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 27 Mar 2016 23:01:24 -0700 Subject: [PATCH 0036/1161] Improve serialization (fixes GH-269) This reverts commit 4cb3b11f941a14bba796e767d4a23a3d439a3b20. --- lib/Raven/Client.php | 10 +++- lib/Raven/ReprSerializer.php | 47 +++++++++++++++ lib/Raven/Serializer.php | 22 +++---- lib/Raven/Stacktrace.php | 3 +- test/Raven/Tests/ReprSerializerTest.php | 79 +++++++++++++++++++++++++ test/Raven/Tests/SerializerTest.php | 44 ++++++++++++-- 6 files changed, 183 insertions(+), 22 deletions(-) create mode 100644 lib/Raven/ReprSerializer.php create mode 100644 test/Raven/Tests/ReprSerializerTest.php diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index a3b84c111..fdcadeaf6 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -451,12 +451,20 @@ public function capture($data, $stack, $vars = null) $this->context->extra, $data['extra']); + $serializer = new Raven_Serializer(); // avoid empty arrays (which dont convert to dicts) if (empty($data['extra'])) { unset($data['extra']); + } else { + $data['extra'] = $serializer->serialize($data['extra']); } if (empty($data['tags'])) { unset($data['tags']); + } else { + $data['tags'] = $serializer->serialize($data['tags']); + } + if (!empty($data['user'])) { + $data['user'] = $serializer->serialize($data['user']); } if ((!$stack && $this->auto_log_stacks) || $stack === true) { @@ -502,8 +510,6 @@ public function sanitize(&$data) if (!class_exists('Raven_Serializer')) { spl_autoload_call('Raven_Serializer'); } - - $data = Raven_Serializer::serialize($data); } /** diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php new file mode 100644 index 000000000..579b53bb5 --- /dev/null +++ b/lib/Raven/ReprSerializer.php @@ -0,0 +1,47 @@ +serializeValue($value); } elseif ($_depth < $max_depth && is_array($value)) { $new = array(); foreach ($value as $k => $v) { - $new[self::serializeValue($k)] = self::serialize($v, $max_depth, $_depth + 1); + $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); } return $new; } else { - return self::serializeValue($value); + return $this->serializeValue($value); } } - public static function serializeValue($value) + protected function serializeValue($value) { - if ($value === null) { - return 'null'; - } elseif ($value === false) { - return 'false'; - } elseif ($value === true) { - return 'true'; - } elseif (is_float($value) && (int) $value == $value) { - return $value.'.0'; + if (is_null($value) || is_bool($value) || is_float($value) || is_integer($value)) { + return $value; } elseif (is_object($value) || gettype($value) == 'object') { return 'Object '.get_class($value); } elseif (is_resource($value)) { return 'Resource '.get_resource_type($value); } elseif (is_array($value)) { return 'Array of length ' . count($value); - } elseif (is_integer($value)) { - return (integer) $value; } else { $value = (string) $value; diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index c006b7a42..9f4ff969c 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -169,6 +169,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client $params = $reflection->getParameters(); + $serializer = new Raven_ReprSerializer(); $args = array(); foreach ($frame['args'] as $i => $arg) { if (isset($params[$i])) { @@ -180,7 +181,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client } } } - $args[$params[$i]->name] = $arg; + $args[$params[$i]->name] = $serializer->serialize($arg); } else { // TODO: Sentry thinks of these as context locals, so they must be named // Assign the argument by number diff --git a/test/Raven/Tests/ReprSerializerTest.php b/test/Raven/Tests/ReprSerializerTest.php new file mode 100644 index 000000000..dc8ed525e --- /dev/null +++ b/test/Raven/Tests/ReprSerializerTest.php @@ -0,0 +1,79 @@ +serialize($input); + $this->assertEquals(array('1', '2', '3'), $result); + } + + public function testObjectsAreStrings() + { + $serializer = new Raven_ReprSerializer(); + $input = new Raven_StacktraceTestObject(); + $result = $serializer->serialize($input); + $this->assertEquals('Object Raven_StacktraceTestObject', $result); + } + + public function testIntsAreInts() + { + $serializer = new Raven_ReprSerializer(); + $input = 1; + $result = $serializer->serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals(1, $result); + } + + public function testFloats() + { + $serializer = new Raven_ReprSerializer(); + $input = 1.5; + $result = $serializer->serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals('1.5', $result); + } + + public function testBooleans() + { + $serializer = new Raven_ReprSerializer(); + $input = true; + $result = $serializer->serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals('true', $result); + + $input = false; + $result = $serializer->serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals('false', $result); + } + + public function testNull() + { + $serializer = new Raven_ReprSerializer(); + $input = null; + $result = $serializer->serialize($input); + $this->assertTrue(is_string($result)); + $this->assertEquals('null', $result); + } + + public function testRecursionMaxDepth() + { + $serializer = new Raven_ReprSerializer(); + $input = array(); + $input[] = &$input; + $result = $serializer->serialize($input, 3); + $this->assertEquals(array(array(array('Array of length 1'))), $result); + } +} diff --git a/test/Raven/Tests/SerializerTest.php b/test/Raven/Tests/SerializerTest.php index af157130b..0bc43a276 100644 --- a/test/Raven/Tests/SerializerTest.php +++ b/test/Raven/Tests/SerializerTest.php @@ -18,31 +18,67 @@ class Raven_Tests_SerializerTest extends PHPUnit_Framework_TestCase { public function testArraysAreArrays() { + $serializer = new Raven_Serializer(); $input = array(1, 2, 3); - $result = Raven_Serializer::serialize($input); + $result = $serializer->serialize($input); $this->assertEquals(array('1', '2', '3'), $result); } public function testObjectsAreStrings() { + $serializer = new Raven_Serializer(); $input = new Raven_StacktraceTestObject(); - $result = Raven_Serializer::serialize($input); + $result = $serializer->serialize($input); $this->assertEquals('Object Raven_StacktraceTestObject', $result); } public function testIntsAreInts() { + $serializer = new Raven_Serializer(); $input = 1; - $result = Raven_Serializer::serialize($input); + $result = $serializer->serialize($input); $this->assertTrue(is_integer($result)); $this->assertEquals(1, $result); } + public function testFloats() + { + $serializer = new Raven_Serializer(); + $input = 1.5; + $result = $serializer->serialize($input); + $this->assertTrue(is_float($result)); + $this->assertEquals(1.5, $result); + } + + public function testBooleans() + { + $serializer = new Raven_Serializer(); + $input = true; + $result = $serializer->serialize($input); + $this->assertTrue(is_bool($result)); + $this->assertEquals(true, $result); + + $input = false; + $result = $serializer->serialize($input); + $this->assertTrue(is_bool($result)); + $this->assertEquals(false, $result); + } + + public function testNull() + { + $serializer = new Raven_Serializer(); + $input = null; + $result = $serializer->serialize($input); + $this->assertTrue(is_null($result)); + $this->assertEquals(null, $result); + } + public function testRecursionMaxDepth() { + $serializer = new Raven_Serializer(); $input = array(); $input[] = &$input; - $result = Raven_Serializer::serialize($input, 3); + $result = $serializer->serialize($input, 3); $this->assertEquals(array(array(array('Array of length 1'))), $result); } } From cc6c3122595d1381320d0e463e76eab42a33a208 Mon Sep 17 00:00:00 2001 From: Israel Shirk Date: Fri, 8 Apr 2016 09:29:07 -0600 Subject: [PATCH 0037/1161] Exit after timeout, not before --- lib/Raven/CurlHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/CurlHandler.php b/lib/Raven/CurlHandler.php index cce4f8a98..10449af71 100644 --- a/lib/Raven/CurlHandler.php +++ b/lib/Raven/CurlHandler.php @@ -81,7 +81,7 @@ public function join($timeout=null) break; } usleep(10000); - } while ($timeout !== 0 && time() - $start > $timeout); + } while ($timeout !== 0 && time() - $start < $timeout); } // http://se2.php.net/manual/en/function.curl-multi-exec.php From a4b3e746937f4eb2c9c859460ebde1092b938031 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 26 Apr 2016 00:06:49 -0700 Subject: [PATCH 0038/1161] Kill old domain --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8ee944d40..eb65bb0c1 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ raven-php :target: http://travis-ci.org/getsentry/raven-php -raven-php is a PHP client for `Sentry `_. +raven-php is a PHP client for `Sentry `_. .. code-block:: php From cd3ed417c85dc488ad33b7a2ff846a7d056101bb Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 26 Apr 2016 13:20:44 +0200 Subject: [PATCH 0039/1161] Support dsn as parameter in options --- lib/Raven/Client.php | 28 ++++++++++++++++------------ test/Raven/Tests/ClientTest.php | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index fdcadeaf6..853542b98 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -35,19 +35,23 @@ class Raven_Client public function __construct($options_or_dsn=null, $options=array()) { - if (is_null($options_or_dsn) && !empty($_SERVER['SENTRY_DSN'])) { - // Read from environment - $options_or_dsn = $_SERVER['SENTRY_DSN']; - } - if (!is_array($options_or_dsn)) { - if (!empty($options_or_dsn)) { - // Must be a valid DSN - $options_or_dsn = self::parseDSN($options_or_dsn); - } else { - $options_or_dsn = array(); - } + if (is_array($options_or_dsn)) { + $options = array_merge($options_or_dsn, $options); + } + + if (!is_array($options_or_dsn) && !empty($options_or_dsn)) { + $dsn = $options_or_dsn; + } else if (!empty($_SERVER['SENTRY_DSN'])) { + $dsn = @$_SERVER['SENTRY_DSN']; + } else if (!empty($options['dsn'])) { + $dsn = $options['dsn']; + } else { + $dsn = null; + } + + if (!empty($dsn)) { + $options = array_merge($options, self::parseDSN($dsn)); } - $options = array_merge($options_or_dsn, $options); $this->logger = Raven_Util::get($options, 'logger', 'php'); $this->server = Raven_Util::get($options, 'server'); diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 9ce0b8dce..fc04f850d 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -195,6 +195,31 @@ public function testOptionsFirstArgument() $this->assertEquals($client->server, 'http://example.com/api/1/store/'); } + + public function testDsnInOptionsFirstArg() + { + $client = new Raven_Client(array( + 'dsn' => 'http://public:secret@example.com/1', + )); + + $this->assertEquals($client->project, 1); + $this->assertEquals($client->server, 'http://example.com/api/1/store/'); + $this->assertEquals($client->public_key, 'public'); + $this->assertEquals($client->secret_key, 'secret'); + } + + public function testDsnInOptionsSecondArg() + { + $client = new Raven_Client(null, array( + 'dsn' => 'http://public:secret@example.com/1', + )); + + $this->assertEquals($client->project, 1); + $this->assertEquals($client->server, 'http://example.com/api/1/store/'); + $this->assertEquals($client->public_key, 'public'); + $this->assertEquals($client->secret_key, 'secret'); + } + public function testOptionsFirstArgumentWithOptions() { $client = new Raven_Client(array( From f9281aa9f69cf5b37a72c1e52e73ddf20c8ad481 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 26 Apr 2016 13:38:53 +0200 Subject: [PATCH 0040/1161] Add base_path and app_path options --- lib/Raven/Client.php | 15 +++++++---- lib/Raven/Stacktrace.php | 14 +++++++++- test/Raven/Tests/StacktraceTest.php | 42 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 853542b98..3a1ea696c 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -62,8 +62,8 @@ public function __construct($options_or_dsn=null, $options=array()) $this->name = Raven_Util::get($options, 'name', Raven_Compat::gethostname()); $this->site = Raven_Util::get($options, 'site', $this->_server_variable('SERVER_NAME')); $this->tags = Raven_Util::get($options, 'tags', array()); - $this->release = Raven_util::get($options, 'release', null); - $this->environment = Raven_util::get($options, 'environment', null); + $this->release = Raven_Util::get($options, 'release', null); + $this->environment = Raven_Util::get($options, 'environment', null); $this->trace = (bool) Raven_Util::get($options, 'trace', true); $this->timeout = Raven_Util::get($options, 'timeout', 2); $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT); @@ -76,10 +76,14 @@ public function __construct($options_or_dsn=null, $options=array()) $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync'); $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl'); $this->curl_ipv4 = Raven_util::get($options, 'curl_ipv4', true); - $this->ca_cert = Raven_util::get($options, 'ca_cert', $this->get_default_ca_cert()); - $this->verify_ssl = Raven_util::get($options, 'verify_ssl', true); + $this->ca_cert = Raven_Util::get($options, 'ca_cert', $this->get_default_ca_cert()); + $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true); $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); + // the base path is used to strip prefixes on filenames in the stacktrace + $this->base_path = Raven_Util::get($options, 'base_path', null); + // app path is used to determine if code is part of your application + $this->app_path = Raven_Util::get($options, 'app_path', null); $this->processors = $this->setProcessorsFromOptions($options); @@ -282,7 +286,8 @@ public function captureException($exception, $culprit_or_options=null, $logger=n $exc_data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit + $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->base_path, + $this->app_path ), ); diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 9f4ff969c..1c05fe6c5 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -14,7 +14,8 @@ class Raven_Stacktrace ); public static function get_stack_info($frames, $trace = false, $shiftvars = true, $errcontext = null, - $frame_var_limit = Raven_Client::MESSAGE_LIMIT) + $frame_var_limit = Raven_Client::MESSAGE_LIMIT, $base_path = null, + $app_path = null) { /** * PHP's way of storing backstacks seems bass-ackwards to me @@ -86,6 +87,17 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true 'context_line' => $context['line'], 'post_context' => $context['suffix'], ); + + // strip base path if present + if ($base_path && substr($abs_path, 0, strlen($base_path)) === $base_path) { + $data['filename'] = substr($abs_path, strlen($base_path) + 1); + } + + // detect in_app based on app path + if ($app_path) { + $data['in_app'] = (bool)(substr($abs_path, 0, strlen($app_path)) === $app_path); + } + // dont set this as an empty array as PHP will treat it as a numeric array // instead of a mapping which goes against the defined Sentry spec if (!empty($vars)) { diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 939a959c7..c354ce022 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -259,4 +259,46 @@ public function testDoesFixFrameInfo() $this->assertEquals('StacktraceTest.php', $frame['module']); $this->assertEquals('raven_test_recurse', $frame['function']); } + + public function testInApp() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + ), + array( + "file" => dirname(__FILE__) . "/resources/b.php", + "line" => 3, + "function" => "include_once", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, dirname(__FILE__), dirname(__FILE__)); + + $this->assertEquals($frames[0]['in_app'], true); + $this->assertEquals($frames[1]['in_app'], true); + } + + public function testBasePath() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + ), + array( + "file" => dirname(__FILE__) . "/resources/b.php", + "line" => 3, + "function" => "include_once", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, dirname(__FILE__)); + + $this->assertEquals($frames[0]['filename'], 'resources/b.php'); + $this->assertEquals($frames[1]['filename'], 'resources/a.php'); + } } From d43637794b17f34c7bde627abb4bb8278eccd060 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 26 Apr 2016 13:41:12 +0200 Subject: [PATCH 0041/1161] lint --- lib/Raven/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 3a1ea696c..b63ac3bbf 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -41,9 +41,9 @@ public function __construct($options_or_dsn=null, $options=array()) if (!is_array($options_or_dsn) && !empty($options_or_dsn)) { $dsn = $options_or_dsn; - } else if (!empty($_SERVER['SENTRY_DSN'])) { + } elseif (!empty($_SERVER['SENTRY_DSN'])) { $dsn = @$_SERVER['SENTRY_DSN']; - } else if (!empty($options['dsn'])) { + } elseif (!empty($options['dsn'])) { $dsn = $options['dsn']; } else { $dsn = null; From 57d63d7204c7d9643fc2112875533e1a666ec338 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 10:21:11 +0200 Subject: [PATCH 0042/1161] Kill abs_path --- lib/Raven/Stacktrace.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 1c05fe6c5..dfd767dd2 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -78,7 +78,9 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true } $data = array( - 'abs_path' => $abs_path, + // abs_path isn't overly useful, wastes space, and exposes + // filesystem internals + // 'abs_path' => $abs_path, 'filename' => $context['filename'], 'lineno' => (int) $context['lineno'], 'module' => $module, From 09b04ff83c358b0ff7a3333228c209a99e843856 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 10:27:34 +0200 Subject: [PATCH 0043/1161] Simplify filename --- lib/Raven/Stacktrace.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index dfd767dd2..dd1a1c9c4 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -54,10 +54,14 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true } else { $context = self::read_source_file($frame['file'], $frame['line']); $abs_path = $frame['file']; - $filename = basename($frame['file']); } - $module = $filename; + // strip base path if present + if ($base_path && substr($abs_path, 0, strlen($base_path)) === $base_path) { + $context['filename'] = substr($abs_path, strlen($base_path) + 1); + } + + $module = basename($context['filename']); if (isset($nextframe['class'])) { $module .= ':' . $nextframe['class']; } @@ -90,11 +94,6 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true 'post_context' => $context['suffix'], ); - // strip base path if present - if ($base_path && substr($abs_path, 0, strlen($base_path)) === $base_path) { - $data['filename'] = substr($abs_path, strlen($base_path) + 1); - } - // detect in_app based on app path if ($app_path) { $data['in_app'] = (bool)(substr($abs_path, 0, strlen($app_path)) === $app_path); From 1f3447bfcca7b5f8f47f4a8ee3ca0de41d7a8cc6 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 10:33:15 +0200 Subject: [PATCH 0044/1161] Add app/base path to test command --- bin/raven | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/raven b/bin/raven index eb5fd9187..fddc0a81b 100755 --- a/bin/raven +++ b/bin/raven @@ -35,6 +35,8 @@ function cmd_test($dsn) $client = new Raven_Client($dsn, array( 'trace' => true, 'curl_method' => 'sync', + 'app_path' => realpath(__DIR__ . '/..'), + 'base_path' => realpath(__DIR__ . '/..'), )); $config = get_object_vars($client); From 7905acb8f7a0a2ca25461eeefa4511adbdbc04af Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 10:33:25 +0200 Subject: [PATCH 0045/1161] Fix auto stack management - Dont add stack when exception is present - Pass base/app paths --- lib/Raven/Client.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index b63ac3bbf..13d0b9045 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -489,10 +489,11 @@ public function capture($data, $stack, $vars = null) spl_autoload_call('Raven_Stacktrace'); } - if (!isset($data['stacktrace'])) { + if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit + $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit, + $this->base_path, $this->app_path ), ); } From b13485dc0d2aec6f1c82820c1a24cb63024f16c2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 10:41:21 +0200 Subject: [PATCH 0046/1161] Support multiple prefixes for stripping --- lib/Raven/Client.php | 8 ++++---- lib/Raven/Stacktrace.php | 20 +++++++++++++++----- test/Raven/Tests/StacktraceTest.php | 4 ++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 13d0b9045..ae2a01fea 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -80,8 +80,8 @@ public function __construct($options_or_dsn=null, $options=array()) $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true); $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); - // the base path is used to strip prefixes on filenames in the stacktrace - $this->base_path = Raven_Util::get($options, 'base_path', null); + // a list of prefixes used to coerce absolute paths into relative + $this->prefixes = Raven_Util::get($options, 'prefixes', null); // app path is used to determine if code is part of your application $this->app_path = Raven_Util::get($options, 'app_path', null); @@ -286,7 +286,7 @@ public function captureException($exception, $culprit_or_options=null, $logger=n $exc_data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->base_path, + $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, $this->app_path ), ); @@ -493,7 +493,7 @@ public function capture($data, $stack, $vars = null) $data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit, - $this->base_path, $this->app_path + $this->prefixes, $this->app_path ), ); } diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index dd1a1c9c4..60c9ccddf 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -14,7 +14,7 @@ class Raven_Stacktrace ); public static function get_stack_info($frames, $trace = false, $shiftvars = true, $errcontext = null, - $frame_var_limit = Raven_Client::MESSAGE_LIMIT, $base_path = null, + $frame_var_limit = Raven_Client::MESSAGE_LIMIT, $strip_prefixes = null, $app_path = null) { /** @@ -57,11 +57,9 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true } // strip base path if present - if ($base_path && substr($abs_path, 0, strlen($base_path)) === $base_path) { - $context['filename'] = substr($abs_path, strlen($base_path) + 1); - } + $context['filename'] = self::strip_prefixes($context['filename'], $strip_prefixes); - $module = basename($context['filename']); + $module = basename($abs_path); if (isset($nextframe['class'])) { $module .= ':' . $nextframe['class']; } @@ -205,6 +203,18 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client return $args; } + private static function strip_prefixes($filename, $prefixes) + { + if ($prefixes === null) return; + foreach ($prefixes as $prefix) { + if (substr($filename, 0, strlen($prefix)) === $prefix) + { + return substr($filename, strlen($prefix) + 1); + } + } + return $filename; + } + private static function read_source_file($filename, $lineno, $context_lines = 5) { $frame = array( diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index c354ce022..b9269c756 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -275,7 +275,7 @@ public function testInApp() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, dirname(__FILE__), dirname(__FILE__)); + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, null, dirname(__FILE__)); $this->assertEquals($frames[0]['in_app'], true); $this->assertEquals($frames[1]['in_app'], true); @@ -296,7 +296,7 @@ public function testBasePath() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, dirname(__FILE__)); + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, array(dirname(__FILE__))); $this->assertEquals($frames[0]['filename'], 'resources/b.php'); $this->assertEquals($frames[1]['filename'], 'resources/a.php'); From a24d5e43656328fdb54ce903288ee3316e0cf221 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 15:38:54 +0200 Subject: [PATCH 0047/1161] 0.15.0.dev0 --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ae2a01fea..1972422d3 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '0.14.0.dev0'; + const VERSION = '0.15.0.dev0'; const PROTOCOL = '6'; const DEBUG = 'debug'; From 767858c741bddf61b61fd693d0f7144b09673bd8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 15:41:03 +0200 Subject: [PATCH 0048/1161] Add Changes for 0.14.0 --- CHANGES | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES b/CHANGES index fb16cd416..d9ef0950c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,15 @@ +0.14.0 +------ + +- Added ``prefixes`` option for stripping absolute paths. +- Removed ``abs_path`` from stacktraces. +- Added ``app_path`` to specify application root for resolving ``in_app` on frames. +- Moved Laravel support to ``sentry-laravel`` project. +- Fixed duplicate stack computation. +- Added ``dsn`` option to ease configuration. +- Fixed an issue with the curl async transport. +- Improved serialization of values. + 0.13.0 ------ From fdd9a9e7a972e5694ea56dd38c89dd29a155be92 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 15:43:56 +0200 Subject: [PATCH 0049/1161] rename package to sentry/sentry --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d6d66e896..0dfecc09b 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "raven/raven", + "name": "sentry/sentry", "type": "library", "description": "A PHP client for Sentry (http://getsentry.com)", "keywords": ["log", "logging"], From 4641412e861c86b010939b1e0ec3881ca65d7529 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 15:44:06 +0200 Subject: [PATCH 0050/1161] update composer version to 0.15.x-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0dfecc09b..26e97119a 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.14.x-dev" + "dev-master": "0.15.x-dev" } } } From 7ae3d86f97212f9b4f3adc57ddb7cca3b87fe279 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Apr 2016 15:47:58 +0200 Subject: [PATCH 0051/1161] Rename references from raven-php to sentry-php --- AUTHORS | 2 +- README.rst | 16 ++++++++-------- docs/advanced.rst | 4 ++-- docs/conf.py | 14 +++++++------- docs/config.rst | 2 +- docs/index.rst | 12 ++++++------ lib/Raven/Client.php | 4 ++-- test/Raven/Tests/ClientTest.php | 4 ++-- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/AUTHORS b/AUTHORS index b9769880b..4aa5ecf4a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,4 @@ The Raven PHP client was originally written by Michael van Tellingen and is maintained by the Sentry Team. -http://github.com/getsentry/raven-php/contributors \ No newline at end of file +http://github.com/getsentry/sentry-php/contributors diff --git a/README.rst b/README.rst index eb65bb0c1..3de579bef 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,11 @@ -raven-php -========= +sentry-php +========== -.. image:: https://secure.travis-ci.org/getsentry/raven-php.png?branch=master - :target: http://travis-ci.org/getsentry/raven-php +.. image:: https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master + :target: http://travis-ci.org/getsentry/sentry-php -raven-php is a PHP client for `Sentry `_. +The official PHP SDK for `Sentry `_. .. code-block:: php @@ -78,7 +78,7 @@ To install the source code: :: - $ git clone git://github.com/getsentry/raven-php.git + $ git clone git://github.com/getsentry/sentry-php.git And including it using the autoloader: @@ -267,7 +267,7 @@ You may now use phpunit : Resources --------- -* `Bug Tracker `_ -* `Code `_ +* `Bug Tracker `_ +* `Code `_ * `Mailing List `_ * `IRC `_ (irc.freenode.net, #sentry) diff --git a/docs/advanced.rst b/docs/advanced.rst index 4ecdc9b53..e3bf81be2 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -3,7 +3,7 @@ Advanced Topics This covers various advanced usage topics for the PHP client. -.. _raven-php-advanced-installation: +.. _sentry-php-advanced-installation: Advanced Installation --------------------- @@ -48,7 +48,7 @@ Install Source from GitHub To install the source code:: - $ git clone git://github.com/getsentry/raven-php.git + $ git clone git://github.com/getsentry/sentry-php.git And including it using the autoloader: diff --git a/docs/conf.py b/docs/conf.py index 5f9336487..04ee267bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# raven-php documentation build configuration file, created by +# sentry-php documentation build configuration file, created by # sphinx-quickstart on Mon Jan 21 21:04:27 2013. # # This file is execfile()d with the current directory set to its containing dir. @@ -40,7 +40,7 @@ master_doc = 'index' # General information about the project. -project = u'raven-php' +project = u'sentry-php' copyright = u'%s, Functional Software Inc.' % datetime.date.today().year # The version info for the project you're documenting, acts as replacement for @@ -164,7 +164,7 @@ #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'raven-phpdoc' +htmlhelp_basename = 'sentry-phpdoc' # -- Options for LaTeX output -------------------------------------------------- @@ -183,7 +183,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'raven-php.tex', u'raven-php Documentation', + ('index', 'sentry-php.tex', u'sentry-php Documentation', u'Functional Software Inc.', 'manual'), ] @@ -213,7 +213,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'raven-php', u'raven-php Documentation', + ('index', 'sentry-php', u'sentry-php Documentation', [u'Functional Software Inc.'], 1) ] @@ -227,8 +227,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'raven-php', u'raven-php Documentation', - u'Functional Software Inc.', 'raven-php', 'One line description of project.', + ('index', 'sentry-php', u'sentry-php Documentation', + u'Functional Software Inc.', 'sentry-php', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/config.rst b/docs/config.rst index 4be2fece9..48dfa2890 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -129,7 +129,7 @@ The following settings are available for the client: ) ) -.. _raven-php-request-context: +.. _sentry-php-request-context: Providing Request Context ------------------------- diff --git a/docs/index.rst b/docs/index.rst index b1ce2d5c0..e6df18e53 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,8 +23,8 @@ recommended way is to use `Composer `__:: Alternatively you can manually install it: -1. Download and extract the latest `raven-php - `__ archive +1. Download and extract the latest `sentry-php + `__ archive to your PHP project. 2. Require the autoloader in your application: @@ -33,7 +33,7 @@ Alternatively you can manually install it: require_once '/path/to/Raven/library/Raven/Autoloader.php'; Raven_Autoloader::register(); -For more methods have a look at :ref:`raven-php-advanced-installation`. +For more methods have a look at :ref:`sentry-php-advanced-installation`. Configuration ------------- @@ -70,7 +70,7 @@ automatically with all events. For instance you can use the 'email' => $USER->getEmail() )); -For more information see :ref:`raven-php-request-context`. +For more information see :ref:`sentry-php-request-context`. Deep Dive --------- @@ -88,5 +88,5 @@ Want more? Have a look at the full documentation for more information. Resources: -* `Bug Tracker `_ -* `Github Project `_ +* `Bug Tracker `_ +* `Github Project `_ diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 1972422d3..8f651db85 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -571,7 +571,7 @@ public function send($data) } $message = base64_encode($message); // PHP's builtin curl_* function are happy without this, but the exec method requires it - $client_string = 'raven-php/' . self::VERSION; + $client_string = 'sentry-php/' . self::VERSION; $timestamp = microtime(true); $headers = array( 'User-Agent' => $client_string, @@ -610,7 +610,7 @@ protected function get_curl_options() CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_SSL_VERIFYPEER => $this->verify_ssl, CURLOPT_CAINFO => $this->ca_cert, - CURLOPT_USERAGENT => 'raven-php/' . self::VERSION, + CURLOPT_USERAGENT => 'sentry-php/' . self::VERSION, ); if ($this->http_proxy) { $options[CURLOPT_PROXY] = $this->http_proxy; diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index fc04f850d..0573800fc 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -580,14 +580,14 @@ public function testGetAuthHeader() { $client = new Dummy_Raven_Client(); - $clientstring = 'raven-php/test'; + $clientstring = 'sentry-php/test'; $timestamp = '1234341324.340000'; $expected = "Sentry sentry_timestamp={$timestamp}, sentry_client={$clientstring}, " . "sentry_version=" . Dummy_Raven_Client::PROTOCOL . ", " . "sentry_key=publickey, sentry_secret=secretkey"; - $this->assertEquals($expected, $client->get_auth_header($timestamp, 'raven-php/test', 'publickey', 'secretkey')); + $this->assertEquals($expected, $client->get_auth_header($timestamp, 'sentry-php/test', 'publickey', 'secretkey')); } public function testCaptureMessageWithUserContext() From 2fe7c0176265dec36f5b1589be5692c9a0d39051 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 28 Apr 2016 11:33:17 +0200 Subject: [PATCH 0052/1161] Update package name --- README.rst | 8 ++++---- docs/advanced.rst | 6 +++--- docs/index.rst | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 3de579bef..3ce7fc18e 100644 --- a/README.rst +++ b/README.rst @@ -49,14 +49,14 @@ dependencies, you can add Raven with it. :: - $ composer require raven/raven:$VERSION + $ composer require sentry/sentry:$VERSION -(replace ``$VERSION`` with one of the available versions on `Packagist `_) +(replace ``$VERSION`` with one of the available versions on `Packagist `_) or to get the latest version off the master branch: :: - $ composer require raven/raven:dev-master + $ composer require sentry/sentry:dev-master Note that using unstable versions is not recommended and should be avoided. Also you should define a maximum version, e.g. by doing ``>=0.6,<1.0`` or ``~0.6``. @@ -65,7 +65,7 @@ Alternatively, use the ``^`` operator for specifying a version, e.g., :: - $ composer require raven/raven:^0.11.0 + $ composer require sentry/sentry:^0.11.0 Composer will take care of the autoloading for you, so if you require the ``vendor/autoload.php``, you're good to go. diff --git a/docs/advanced.rst b/docs/advanced.rst index e3bf81be2..51a5b0570 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -20,19 +20,19 @@ dependencies, you can add Raven with it. { "require": { - "raven/raven": "$VERSION" + "sentry/sentry": "$VERSION" } } (replace ``$VERSION`` with one of the available versions on `Packagist -`_) or to get the latest +`_) or to get the latest version off the master branch: .. code-block:: json { "require": { - "raven/raven": "dev-master" + "sentry/sentry": "dev-master" } } diff --git a/docs/index.rst b/docs/index.rst index e6df18e53..3d05cd20c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,7 +19,7 @@ Installation There are various ways to install the PHP integration for Sentry. The recommended way is to use `Composer `__:: - $ composer require "raven/raven" + $ composer require "sentry/sentry" Alternatively you can manually install it: From 47cadf7a6a7ef9b750d79ce69523c1029cd563b0 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 12:06:02 +0200 Subject: [PATCH 0053/1161] Move serializer autoloader call --- lib/Raven/Client.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 8f651db85..b36b79449 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -460,6 +460,11 @@ public function capture($data, $stack, $vars = null) $this->context->extra, $data['extra']); + // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) + if (!class_exists('Raven_Serializer')) { + spl_autoload_call('Raven_Serializer'); + } + $serializer = new Raven_Serializer(); // avoid empty arrays (which dont convert to dicts) if (empty($data['extra'])) { @@ -516,10 +521,6 @@ public function capture($data, $stack, $vars = null) public function sanitize(&$data) { - // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('Raven_Serializer')) { - spl_autoload_call('Raven_Serializer'); - } } /** From 25c4ba00a685cda20638b57b7a4236fff8e3bd4f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 12:07:20 +0200 Subject: [PATCH 0054/1161] Force serialization on HTTP data (refs GH-275) --- lib/Raven/Client.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index b36b79449..c1e5fa1a0 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -480,6 +480,9 @@ public function capture($data, $stack, $vars = null) if (!empty($data['user'])) { $data['user'] = $serializer->serialize($data['user']); } + if (!empty($data['request'])) { + $data['request'] = $serializer->serialize($data['request']); + } if ((!$stack && $this->auto_log_stacks) || $stack === true) { $stack = debug_backtrace(); From 1f64a5f706128330b23d534d4e89fa80710138c8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 12:10:16 +0200 Subject: [PATCH 0055/1161] Add sdk attribute --- lib/Raven/Client.php | 4 ++++ test/Raven/Tests/ClientTest.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c1e5fa1a0..6b6daab6a 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -410,6 +410,10 @@ public function get_default_data() 'logger' => $this->logger, 'tags' => $this->tags, 'platform' => 'php', + 'sdk' => array( + 'name' => 'sentry-php', + 'version' => self::VERSION, + ), ); } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 0573800fc..7834d7260 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -474,6 +474,10 @@ public function testGetDefaultData() 'site' => $client->site, 'logger' => $client->logger, 'tags' => $client->tags, + 'sdk' => array( + 'name' => 'sentry-php', + 'version' => $client::VERSION, + ), ); $this->assertEquals($expected, $client->get_default_data()); } From fcf903d90d57f748db128ebc7ab9026d28e23a28 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 12:13:03 +0200 Subject: [PATCH 0056/1161] lint --- lib/Raven/Stacktrace.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 60c9ccddf..c30608356 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -205,10 +205,11 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client private static function strip_prefixes($filename, $prefixes) { - if ($prefixes === null) return; + if ($prefixes === null) { + return; + } foreach ($prefixes as $prefix) { - if (substr($filename, 0, strlen($prefix)) === $prefix) - { + if (substr($filename, 0, strlen($prefix)) === $prefix) { return substr($filename, strlen($prefix) + 1); } } From f7790ef7a1625ca77c3f609a5684b1817f64d555 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 12:13:31 +0200 Subject: [PATCH 0057/1161] Run cs-dry-run as part of test --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1a4d8b73b..9e2840e47 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ cs: cs-dry-run: vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run -test: +test: cs-dry-run vendor/bin/phpunit setup-git: From ce8b38157c4b0e187aef3148cd247fa7573c5d81 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 12:18:41 +0200 Subject: [PATCH 0058/1161] Ensure all reflection vars run through serializer (refs GH-275) --- lib/Raven/Stacktrace.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index c30608356..df9ecfa4e 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -100,8 +100,10 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true // dont set this as an empty array as PHP will treat it as a numeric array // instead of a mapping which goes against the defined Sentry spec if (!empty($vars)) { + $serializer = new Raven_ReprSerializer(); $cleanVars = array(); foreach ($vars as $key => $value) { + $value = $serializer->serialize($value); if (is_string($value) || is_numeric($value)) { $cleanVars[$key] = substr($value, 0, $frame_var_limit); } else { @@ -180,7 +182,6 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client $params = $reflection->getParameters(); - $serializer = new Raven_ReprSerializer(); $args = array(); foreach ($frame['args'] as $i => $arg) { if (isset($params[$i])) { @@ -192,7 +193,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client } } } - $args[$params[$i]->name] = $serializer->serialize($arg); + $args[$params[$i]->name] = $arg; } else { // TODO: Sentry thinks of these as context locals, so they must be named // Assign the argument by number From 13d341fee53bc33a88713d7c2036221dc0dc9a2d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 12:47:28 +0200 Subject: [PATCH 0059/1161] 0.16.0.dev0 --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 26e97119a..7468f4cd0 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.15.x-dev" + "dev-master": "0.16.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 6b6daab6a..20a205d70 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '0.15.0.dev0'; + const VERSION = '0.16.0.dev0'; const PROTOCOL = '6'; const DEBUG = 'debug'; From deb184b57b10a9c1595ba94d7dabe1bc302f6a02 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 12:48:11 +0200 Subject: [PATCH 0060/1161] Changes for 0.15.0. --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index d9ef0950c..da768e5df 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +0.15.0 +------ + +- Fixed some cases where serialization wouldn't happen. +- Added sdk attribute. + 0.14.0 ------ From 8e96a49470112359211f29670e965690d47677d6 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 13:39:54 +0200 Subject: [PATCH 0061/1161] Update laravel docs to use sentry-laravel package --- docs/integrations/laravel.rst | 152 +++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 49 deletions(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index e8f2a184e..fc6060bcd 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -1,84 +1,138 @@ Laravel ======= -Laravel supports Monolog out of the box, which also provides a native Sentry handler. +Laravel is supported via a native extension, [sentry-laravel](https://github.com/getsentry/sentry-laravel). Laravel 5.x ----------- -To configure logging, pop open your ``bootstrap/app.php`` file, and insert the following: +Install the ``sentry/sentry-laravel`` package: -.. sourcecode:: php +.. code-block:: bash - $app->configureMonologUsing(function($monolog) { - $client = new Raven_Client('___DSN___'); + $ composer require sentry/sentry-laravel - $handler = new Monolog\Handler\RavenHandler($client); - $handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n")); +Add the Sentry service provider and facade in ``config/app.php``: - $monolog->pushHandler($handler); - }); +.. code-block:: php + + 'providers' => array( + // ... + Sentry\SentryLaravel\SentryLaravelServiceProvider::class, + ) + + 'aliases' => array( + // ... + 'Sentry' => Sentry\SentryLaravel\SentryFacade::class, + ) + + +Add Sentry reporting to ``App/Exceptions/Handler.php``: + +.. code-block:: php + + public function report(Exception $e) + { + if ($this->shouldReport($e)) { + app('sentry')->captureException($e); + } + parent::report($e); + } + +Create the Sentry configuration file (``config/sentry.php``): + +.. code-block:: bash + + $ php artisan vendor:publish --provider="Sentry\SentryLaravel\SentryLaravelServiceProvider" + + +Add your DSN to ``.env``: + +.. code-block:: + + SENTRY_DSN=___DSN___ Laravel 4.x ----------- -To configure logging, pop open your ``app/start/global.php`` file, and insert the following: +Install the ``sentry/sentry-laravel`` package: -.. sourcecode:: php +.. code-block:: bash - $client = new Raven_Client('___DSN___'); + $ composer require sentry/sentry-laravel - $handler = new Monolog\Handler\RavenHandler($client); - $handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n")); +Add the Sentry service provider and facade in ``config/app.php``: - $monolog = Log::getMonolog(); - $monolog->pushHandler($handler); +.. code-block:: php -Lumen 5.x ------------ + 'providers' => array( + // ... + 'Sentry\SentryLaravel\SentryLaravelServiceProvider', + ) + + 'aliases' => array( + // ... + 'Sentry' => 'Sentry\SentryLaravel\SentryFacade', + ) + + +Create the Sentry configuration file (``config/sentry.php``): + +.. code-block:: php + + $ php artisan config:publish sentry/sentry-laravel + +Add your DSN to ``config/sentry.php``: -To configure logging, pop open your ``bootstrap/app.php`` file, and insert the following: +.. code-block:: php -.. sourcecode:: php + configureMonologUsing(function($monolog) { - $client = new Raven_Client('___DSN___'); + return array( + 'dsn' => '___DSN___', - $handler = new Monolog\Handler\RavenHandler($client); - $handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n")); + // ... + ); - $monolog->pushHandler($handler); +Lumen 5.x +--------- + +Install the ``sentry/sentry-laravel`` package: + +.. code-block:: bash + + $ composer require sentry/sentry-laravel - return $monolog; - }); +Register Sentry in ``bootstrap/app.php``: -Adding Context --------------- +.. code-block:: php -Context can be added via a Monolog processor: + $app->register('Sentry\SentryLaravel\SentryLumenServiceProvider'); -.. sourcecode:: php + # Sentry must be registered before routes are included + require __DIR__ . '/../app/Http/routes.php'; - $monolog->pushProcessor(function ($record) { - $user = Auth::user(); +Add Sentry reporting to ``app/Exceptions/Handler.php``: - // Add the authenticated user - if ($user) { - $record['context']['user'] = array( - 'username' => Auth::user()->username, - 'ip_address' => Request::getClientIp(), - ); - } else { - $record['context']['user'] = array( - 'ip_address' => Request::getClientIp(), - ); +.. code-block:: php + + public function report(Exception $e) + { + if ($this->shouldReport($e)) { + app('sentry')->captureException($e); } + parent::report($e); + } + +Create the Sentry configuration file (``config/sentry.php``): + +.. code-block:: php - // Add various tags - $record['context']['tags'] = array('key' => 'value'); + '___DSN___', - return $record; - }); + // capture release as git sha + // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), + ); From 562300d67ca4544056546e1a0d874b76b7f8aecc Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 13:42:41 +0200 Subject: [PATCH 0062/1161] Bad syntax --- docs/integrations/laravel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index fc6060bcd..169aa3586 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -48,7 +48,7 @@ Create the Sentry configuration file (``config/sentry.php``): Add your DSN to ``.env``: -.. code-block:: +.. code-block:: bash SENTRY_DSN=___DSN___ From 70d4810d2611b5f80b6a42a281cab28e0a252f1a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Apr 2016 16:59:16 +0200 Subject: [PATCH 0063/1161] Rename bin/raven to bin/sentry (fixes GH-277) --- README.rst | 2 +- bin/{raven => sentry} | 0 composer.json | 2 +- docs/usage.rst | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename bin/{raven => sentry} (100%) diff --git a/README.rst b/README.rst index 3ce7fc18e..9da1bb3f1 100644 --- a/README.rst +++ b/README.rst @@ -95,7 +95,7 @@ the Sentry master server: .. code-block:: bash - $ bin/raven test https://public:secret@app.getsentry.com/1 + $ bin/sentry test https://public:secret@app.getsentry.com/1 Client configuration: -> server: [https://sentry.example.com/api/store/] -> project: 1 diff --git a/bin/raven b/bin/sentry similarity index 100% rename from bin/raven rename to bin/sentry diff --git a/composer.json b/composer.json index 7468f4cd0..7ab410907 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "ext-curl": "*" }, "bin": [ - "bin/raven" + "bin/sentry" ], "autoload": { "psr-0" : { diff --git a/docs/usage.rst b/docs/usage.rst index dc1baaf59..0f5fe1d5c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -163,7 +163,7 @@ Testing Your Connection The PHP client includes a simple helper script to test your connection and credentials with the Sentry master server:: - $ bin/raven test ___DSN___ + $ bin/sentry test ___DSN___ Client configuration: -> server: [___API_URL___] -> project: ___PROJECT_ID___ From 924424f6ecf6942798c3af4d00beffb49fdadabe Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 09:01:51 -0700 Subject: [PATCH 0064/1161] Add getLastEventID helper Fixes getsentry/sentry-laravel#4 --- lib/Raven/Client.php | 10 +++++++++- test/Raven/Tests/ClientTest.php | 7 +++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 20a205d70..e7660b8d1 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -88,6 +88,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->processors = $this->setProcessorsFromOptions($options); $this->_lasterror = null; + $this->_last_event_id = null; $this->_user = null; $this->context = new Raven_Context(); @@ -332,6 +333,11 @@ public function captureQuery($query, $level=self::INFO, $engine = '') return $this->capture($data, false); } + public function getLastEventID() + { + return $this->_last_event_id; + } + protected function is_http_request() { return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; @@ -417,7 +423,7 @@ public function get_default_data() ); } - public function capture($data, $stack, $vars = null) + public function capture($data, $stack = null, $vars = null) { if (!isset($data['timestamp'])) { $data['timestamp'] = gmdate('Y-m-d\TH:i:s\Z'); @@ -523,6 +529,8 @@ public function capture($data, $stack, $vars = null) $this->error_data[] = $data; } + $this->_last_event_id = $data['event_id']; + return $data['event_id']; } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 7834d7260..6a8dde853 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -645,6 +645,13 @@ public function testCaptureMessageWithExtraContext() ), $event['extra']); } + public function testGetLastEventID() + { + $client = new Dummy_Raven_Client(); + $client->capture(array('message' => 'test', 'event_id' => 'abc')); + $this->assertEquals($client->getLastEventID(), 'abc'); + } + public function cb1($data) { $this->assertEquals('test', $data['message']); From f348bb4496d0d928688463be0304d648386591e9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 09:09:06 -0700 Subject: [PATCH 0065/1161] Add docstring for getLastEventID --- lib/Raven/Client.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index e7660b8d1..5dfabf144 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -333,6 +333,9 @@ public function captureQuery($query, $level=self::INFO, $engine = '') return $this->capture($data, false); } + /** + * Return the last captured event's ID or null if none available. + */ public function getLastEventID() { return $this->_last_event_id; From f90ca6551d1040cf1b811b87463d98b9ab9c4a58 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 09:16:46 -0700 Subject: [PATCH 0066/1161] Basic docs for user feedback --- docs/usage.rst | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/usage.rst b/docs/usage.rst index 0f5fe1d5c..cf4d0123b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -157,6 +157,65 @@ The acting user. ) ) +Getting Back an Event ID +------------------------ + +An event id is a globally unique id for the event that was just sent. This +event id can be used to find the exact event from within Sentry. + +This is often used to display for the user and report an error to customer +service. + +.. code-block:: php + + $client->getLastEventID(); + +.. _php-user-feedback: + +User Feedback +------------- + +To enable user feedback for crash reports you will need to create an error handler +which is aware of the last event ID. + +.. sourcecode:: php + + captureException($exc); + + return $this->render('500.html', array( + 'sentry_event_id' => $event_id, + ), 500); + } + } + +Then in your template you can load up the feedback widget: + +.. sourcecode:: html+django + + + + + {% if sentry_event_id %} + + {% endif %} + +That's it! + +For more details on this feature, see the :doc:`User Feedback guide <../../../learn/user-feedback>`. + Testing Your Connection ----------------------- From b2e17b553f6b4c085e7d6b186517ece616287de3 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 09:29:03 -0700 Subject: [PATCH 0067/1161] Language around failure reporting --- docs/usage.rst | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index cf4d0123b..1d15f15f2 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -58,24 +58,6 @@ takes a message and reports it to sentry. // Capture a message $event_id = $client->getIdent($client->captureMessage('my log message')); -Give User Feedback ------------------- - -The `event_id` returned can be shown to the user to help track down the -particular exception in Sentry later. In case reporting to Sentry failed -you can also detect that: - -.. code-block:: php - - if ($client->getLastError() !== null) { - echo "Something went very, very wrong"; - // $client->getLastError() contains the error that occurred - } else { - // Give the user feedback - echo "Sorry, there was an error!"; - echo "Your reference ID is " . $event_id; - } - Optional Attributes ------------------- @@ -216,6 +198,24 @@ That's it! For more details on this feature, see the :doc:`User Feedback guide <../../../learn/user-feedback>`. +Handling Failures +----------------- + +The SDK attempts to minimize failures, and when they happen will always try to avoid bubbling them up +to your application. If you do want to know when an event fails to record, you can use the ``getLastError`` +helper: + +.. code-block:: php + + if ($client->getLastError() !== null) { + echo "Something went very, very wrong"; + // $client->getLastError() contains the error that occurred + } else { + // Give the user feedback + echo "Sorry, there was an error!"; + echo "Your reference ID is " . $event_id; + } + Testing Your Connection ----------------------- From a5bdf56a12bce9b497ad96fc1fedaf4e077c2436 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 10:47:02 -0700 Subject: [PATCH 0068/1161] Switch to sentry-symfony bundle --- docs/integrations/symfony2.rst | 97 +++++++++------------------------- 1 file changed, 24 insertions(+), 73 deletions(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index 2be206985..63c9ec404 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -1,91 +1,42 @@ -Symfony2 -======== +Symfony +======= -Symfony2 supports Monolog out of the box, which also provides a native Sentry handler. +Symfony is supported via the `sentry-symfony `_ package as a native bundle. -Simply add a new handler for Sentry to your config (i.e. in ``config_prod.yml``), and you're good to go: +Symfony 2+ +---------- -.. sourcecode:: yaml +Install the ``sentry/sentry-laravel`` package: - monolog: - handlers: - main: - type: fingers_crossed - action_level: error - handler: grouped_main +.. code-block:: bash - sentry: - type: raven - dsn: '___DSN___' - level: error + $ composer require sentry/sentry-symfony - # Groups - grouped_main: - type: group - members: [sentry, streamed_main] - # Streams - streamed_main: - type: stream - path: %kernel.logs_dir%/%kernel.environment%.log - level: error +Enable the bundle in ``app/AppKernel.php``: -Adding Context --------------- +.. code-block:: php -Capturing context can be done via a monolog processor: - -.. sourcecode:: php - - namespace AppBundle\Monolog; - - use AppBundle\Entity\User; - use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; - - class SentryContextProcessor + tokenStorage = $tokenStorage; - } + $bundles = array( + // ... - public function processRecord($record) - { - $token = $this->tokenStorage->getToken(); + new Sentry/SentryBundle/SentryBundle(), + ); - if ($token !== null){ - $user = $token->getUser(); - if ($user instanceof UserInterface) { - $record['context']['user'] = array( - 'name' => $user->getName(), - 'username' => $user->getUsername(), - 'email' => $user->getEmail(), - ); - } - } - - // Add various tags - $record['context']['tags'] = array('key' => 'value'); - - // Add various generic context - $record['extra']['key'] = 'value'; - - return $record; + // ... } - } - -You'll then register the processor in your config: -.. sourcecode:: php + // ... + } - services: - monolog.processor.sentry_context: - class: AppBundle\Monolog\SentryContextProcessor - arguments: ["@security.token_storage"] - tags: - - { name: monolog.processor, method: processRecord, handler: sentry } +Add your DSN to ``app/config/config.yml``: +.. code-block:: yaml -If you're using Symfony < 2.6 then you need to use ``security.context`` instead of ``security.token_storage``. + sentry: + dsn: "___DSN___" From 1e9459788f840b5e7a08ba33f9b097a2ce3baa28 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 20:22:19 -0700 Subject: [PATCH 0069/1161] Initial support for breadcrumbs --- lib/Raven/Breadcrumbs.php | 58 ++++++++++++++++++++++++++++ lib/Raven/Client.php | 5 +++ test/Raven/Tests/BreadcrumbsTest.php | 28 ++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 lib/Raven/Breadcrumbs.php create mode 100644 test/Raven/Tests/BreadcrumbsTest.php diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php new file mode 100644 index 000000000..9c16cef92 --- /dev/null +++ b/lib/Raven/Breadcrumbs.php @@ -0,0 +1,58 @@ +count = 0; + $this->pos = 0; + $this->size = $size; + $this->buffer = array(); + } + + public function record($crumb) + { + $this->buffer[$this->pos] = $crumb; + $this->pos = ($this->pos + 1) % $this->size; + $this->count++; + } + + public function fetch() + { + $results = array(); + for ($i = 0; $i <= ($this->size - 1); $i++) { + $node = @$this->buffer[($this->pos + $i) % $this->size]; + + if (isset($node)) { + $results[] = $node; + } + } + return $results; + } + + public function is_empty() + { + return $this->count === 0; + } + + public function to_json() + { + return array( + 'values' => $this->fetch, + ); + } +} diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 5dfabf144..a1ff6c930 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -91,6 +91,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->_last_event_id = null; $this->_user = null; $this->context = new Raven_Context(); + $this->breadcrumbs = new Raven_Breadcrumbs(); if ($this->curl_method == 'async') { $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); @@ -497,6 +498,10 @@ public function capture($data, $stack = null, $vars = null) $data['request'] = $serializer->serialize($data['request']); } + if (!$this->breadcrumbs->is_empty()) { + $data['breadcrumbs'] = $this->breadcrumbs->fetch(); + } + if ((!$stack && $this->auto_log_stacks) || $stack === true) { $stack = debug_backtrace(); diff --git a/test/Raven/Tests/BreadcrumbsTest.php b/test/Raven/Tests/BreadcrumbsTest.php new file mode 100644 index 000000000..883f52a74 --- /dev/null +++ b/test/Raven/Tests/BreadcrumbsTest.php @@ -0,0 +1,28 @@ +record(array('message' => $i)); + } + + $results = $breadcrumbs->fetch(); + + $this->assertEquals(count($results), 10); + for ($i = 1; $i <= 10; $i++) { + $this->assertEquals($results[$i - 1]['message'], $i); + } + } +} From 83ad8678ce656936ec267291f8fae15ab433c940 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 20:53:01 -0700 Subject: [PATCH 0070/1161] Add monolog handler for breadcrumbs --- lib/Raven/Breadcrumbs/Monolog.php | 149 ++++++++++++++++++++++++++++++ lib/Raven/Breadcrumbs/monolog.py | 0 2 files changed, 149 insertions(+) create mode 100644 lib/Raven/Breadcrumbs/Monolog.php create mode 100644 lib/Raven/Breadcrumbs/monolog.py diff --git a/lib/Raven/Breadcrumbs/Monolog.php b/lib/Raven/Breadcrumbs/Monolog.php new file mode 100644 index 000000000..dc538635c --- /dev/null +++ b/lib/Raven/Breadcrumbs/Monolog.php @@ -0,0 +1,149 @@ + Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @var LineFormatter The formatter to use for the logs generated via handleBatch() + */ + protected $batchFormatter; + + /** + * @param Raven_Client $ravenClient + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $level = $this->level; + + // filter records based on their level + $records = array_filter($records, function ($record) use ($level) { + return $record['level'] >= $level; + }); + + if (!$records) { + return; + } + + // the record with the highest severity is the "main" one + $record = array_reduce($records, function ($highest, $record) { + if ($record['level'] >= $highest['level']) { + return $record; + } + + return $highest; + }); + + // the other ones are added as a context item + $logs = array(); + foreach ($records as $r) { + $logs[] = $this->processRecord($r); + } + + if ($logs) { + $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); + } + + $this->handle($record); + } + + /** + * Sets the formatter for the logs generated by handleBatch(). + * + * @param FormatterInterface $formatter + */ + public function setBatchFormatter(FormatterInterface $formatter) + { + $this->batchFormatter = $formatter; + } + + /** + * Gets the formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + public function getBatchFormatter() + { + if (!$this->batchFormatter) { + $this->batchFormatter = $this->getDefaultBatchFormatter(); + } + + return $this->batchFormatter; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // TODO(dcramer): support $record['context']['exception'] instanceof \Exception + $crumb = array( + 'level' => $this->logLevels[$record['level']], + 'category' => $record['channel'], + 'message' => $record['formatted'], + ); + + $this->ravenClient->breadcrumbs->record($crumb); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%message%'); + } + + /** + * Gets the default formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + protected function getDefaultBatchFormatter() + { + return new LineFormatter(); + } + + /** + * Gets extra parameters supported by Raven that can be found in "extra" and "context" + * + * @return array + */ + protected function getExtraParameters() + { + return array(); + } +} diff --git a/lib/Raven/Breadcrumbs/monolog.py b/lib/Raven/Breadcrumbs/monolog.py new file mode 100644 index 000000000..e69de29bb From 018584cbed2623c003ef171d55152b7b30d31d7a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 20:56:04 -0700 Subject: [PATCH 0071/1161] Add test for Raven_Breadcrumbs.to_json --- lib/Raven/Breadcrumbs.php | 2 +- test/Raven/Tests/BreadcrumbsTest.php | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php index 9c16cef92..4df2475c6 100644 --- a/lib/Raven/Breadcrumbs.php +++ b/lib/Raven/Breadcrumbs.php @@ -52,7 +52,7 @@ public function is_empty() public function to_json() { return array( - 'values' => $this->fetch, + 'values' => $this->fetch(), ); } } diff --git a/test/Raven/Tests/BreadcrumbsTest.php b/test/Raven/Tests/BreadcrumbsTest.php index 883f52a74..aa94581db 100644 --- a/test/Raven/Tests/BreadcrumbsTest.php +++ b/test/Raven/Tests/BreadcrumbsTest.php @@ -11,7 +11,7 @@ class Raven_Tests_BreadcrumbsTest extends PHPUnit_Framework_TestCase { - public function testSimple() + public function testBuffer() { $breadcrumbs = new Raven_Breadcrumbs(10); for ($i = 0; $i <= 10; $i++) { @@ -25,4 +25,17 @@ public function testSimple() $this->assertEquals($results[$i - 1]['message'], $i); } } + + public function testJson() + { + $breadcrumbs = new Raven_Breadcrumbs(1); + $breadcrumbs->record(array('message' => 'test')); + $json = $breadcrumbs->to_json(); + + $this->assertEquals($json, array( + 'values' => array( + 0 => array('message' => 'test'), + ), + )); + } } From aa2ead747ca39643169a8d37bad8a6008b4afce8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 21:00:02 -0700 Subject: [PATCH 0072/1161] Simplify monolog handler --- lib/Raven/Breadcrumbs/Monolog.php | 98 +------------------------------ 1 file changed, 1 insertion(+), 97 deletions(-) diff --git a/lib/Raven/Breadcrumbs/Monolog.php b/lib/Raven/Breadcrumbs/Monolog.php index dc538635c..3bf51f3b9 100644 --- a/lib/Raven/Breadcrumbs/Monolog.php +++ b/lib/Raven/Breadcrumbs/Monolog.php @@ -1,6 +1,5 @@ ravenClient = $ravenClient; } - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - $level = $this->level; - - // filter records based on their level - $records = array_filter($records, function ($record) use ($level) { - return $record['level'] >= $level; - }); - - if (!$records) { - return; - } - - // the record with the highest severity is the "main" one - $record = array_reduce($records, function ($highest, $record) { - if ($record['level'] >= $highest['level']) { - return $record; - } - - return $highest; - }); - - // the other ones are added as a context item - $logs = array(); - foreach ($records as $r) { - $logs[] = $this->processRecord($r); - } - - if ($logs) { - $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); - } - - $this->handle($record); - } - - /** - * Sets the formatter for the logs generated by handleBatch(). - * - * @param FormatterInterface $formatter - */ - public function setBatchFormatter(FormatterInterface $formatter) - { - $this->batchFormatter = $formatter; - } - - /** - * Gets the formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - public function getBatchFormatter() - { - if (!$this->batchFormatter) { - $this->batchFormatter = $this->getDefaultBatchFormatter(); - } - - return $this->batchFormatter; - } - /** * {@inheritdoc} */ @@ -118,32 +50,4 @@ protected function write(array $record) $this->ravenClient->breadcrumbs->record($crumb); } - - /** - * {@inheritDoc} - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter('%message%'); - } - - /** - * Gets the default formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - protected function getDefaultBatchFormatter() - { - return new LineFormatter(); - } - - /** - * Gets extra parameters supported by Raven that can be found in "extra" and "context" - * - * @return array - */ - protected function getExtraParameters() - { - return array(); - } } From 7f46ede091f7751a81d3948b30b391e73e70592f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 May 2016 21:10:40 -0700 Subject: [PATCH 0073/1161] Fixes/tests for Monolog --- composer.json | 3 +- lib/Raven/Autoloader.php | 1 + .../{Monolog.php => MonologHandler.php} | 6 ++-- lib/Raven/Breadcrumbs/monolog.py | 0 test/Raven/Tests/Breadcrumbs/MonologTest.php | 29 +++++++++++++++++++ 5 files changed, 35 insertions(+), 4 deletions(-) rename lib/Raven/Breadcrumbs/{Monolog.php => MonologHandler.php} (90%) delete mode 100644 lib/Raven/Breadcrumbs/monolog.py create mode 100644 test/Raven/Tests/Breadcrumbs/MonologTest.php diff --git a/composer.json b/composer.json index 7ab410907..bbc47ad1a 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ }, "require": { "php": ">=5.2.4", - "ext-curl": "*" + "ext-curl": "*", + "monolog/monolog": "*" }, "bin": [ "bin/sentry" diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php index d5adf1a58..682585dba 100644 --- a/lib/Raven/Autoloader.php +++ b/lib/Raven/Autoloader.php @@ -36,6 +36,7 @@ public static function autoload($class) return; } + var_dump(dirname(__FILE__) . '/../' . str_replace(array('_', "\0"), array('/', ''), $class) . '.php'); if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) { require $file; } diff --git a/lib/Raven/Breadcrumbs/Monolog.php b/lib/Raven/Breadcrumbs/MonologHandler.php similarity index 90% rename from lib/Raven/Breadcrumbs/Monolog.php rename to lib/Raven/Breadcrumbs/MonologHandler.php index 3bf51f3b9..4dc295575 100644 --- a/lib/Raven/Breadcrumbs/Monolog.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -1,9 +1,9 @@ $this->logLevels[$record['level']], 'category' => $record['channel'], - 'message' => $record['formatted'], + 'message' => $record['message'], ); $this->ravenClient->breadcrumbs->record($crumb); diff --git a/lib/Raven/Breadcrumbs/monolog.py b/lib/Raven/Breadcrumbs/monolog.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/Raven/Tests/Breadcrumbs/MonologTest.php b/test/Raven/Tests/Breadcrumbs/MonologTest.php new file mode 100644 index 000000000..386590b2f --- /dev/null +++ b/test/Raven/Tests/Breadcrumbs/MonologTest.php @@ -0,0 +1,29 @@ +pushHandler($handler); + $logger->addWarning('Foo'); + + $crumbs = $client->breadcrumbs->fetch(); + $this->assertEquals(count($crumbs), 1); + $this->assertEquals($crumbs[0]['message'], 'Foo'); + $this->assertEquals($crumbs[0]['category'], 'sentry'); + $this->assertEquals($crumbs[0]['level'], 'warning'); + } +} From d2865c96b6c7ca5e6fa205f703f0b226dde7c356 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 12:47:43 -0700 Subject: [PATCH 0074/1161] Add "nobreadcrumb" attribute for skipping logs --- lib/Raven/Breadcrumbs/MonologHandler.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 4dc295575..79c6eea0a 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -41,6 +41,11 @@ public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $ */ protected function write(array $record) { + // sentry uses the 'nobreadcrumb' attribute to skip reporting + if (!empty($record['context']['nobreadcrumb'])) { + return; + } + // TODO(dcramer): support $record['context']['exception'] instanceof \Exception $crumb = array( 'level' => $this->logLevels[$record['level']], From f53a5586ec80884802fd471f8dd7648a187dfd68 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 12:56:08 -0700 Subject: [PATCH 0075/1161] Add timestamp when none present --- lib/Raven/Breadcrumbs.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php index 4df2475c6..a07e90081 100644 --- a/lib/Raven/Breadcrumbs.php +++ b/lib/Raven/Breadcrumbs.php @@ -26,6 +26,9 @@ public function __construct($size = 100) public function record($crumb) { + if (empty($crumb['timestamp'])) { + $crumb['timestamp'] = microtime(true); + } $this->buffer[$this->pos] = $crumb; $this->pos = ($this->pos + 1) % $this->size; $this->count++; From b17196c86e9248511bb9723be7b17cc329f13897 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 13:00:39 -0700 Subject: [PATCH 0076/1161] Kill bad output --- lib/Raven/Autoloader.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php index 682585dba..d5adf1a58 100644 --- a/lib/Raven/Autoloader.php +++ b/lib/Raven/Autoloader.php @@ -36,7 +36,6 @@ public static function autoload($class) return; } - var_dump(dirname(__FILE__) . '/../' . str_replace(array('_', "\0"), array('/', ''), $class) . '.php'); if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) { require $file; } From 65adc90811f37e9fd746fc394c578f32e9c42a55 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 13:01:18 -0700 Subject: [PATCH 0077/1161] Add Vanilla example --- examples/vanilla/README.md | 6 ++++++ examples/vanilla/index.php | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 examples/vanilla/README.md create mode 100644 examples/vanilla/index.php diff --git a/examples/vanilla/README.md b/examples/vanilla/README.md new file mode 100644 index 000000000..5a32cd67d --- /dev/null +++ b/examples/vanilla/README.md @@ -0,0 +1,6 @@ +Running this example: + +``` +# Run webserver +php -S localhost:8000 +``` diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php new file mode 100644 index 000000000..1ad0a8598 --- /dev/null +++ b/examples/vanilla/index.php @@ -0,0 +1,22 @@ +registerExceptionHandler(); + $error_handler->registerErrorHandler(); + $error_handler->registerShutdownFunction(); +} + +function createError() { + 1 / 0; +} + +setupSentry(); +createError(); From b3a438b3e187a114697fd5b57f818bb9d27cee90 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 13:11:08 -0700 Subject: [PATCH 0078/1161] 0.17.x --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bbc47ad1a..1f6137dd3 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.16.x-dev" + "dev-master": "0.17.x-dev" } } } From 4208bfa03f9a35a22bcdc71d043ae9d4bbff560a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 13:16:14 -0700 Subject: [PATCH 0079/1161] Lint example --- examples/vanilla/index.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php index 1ad0a8598..38a5edf75 100644 --- a/examples/vanilla/index.php +++ b/examples/vanilla/index.php @@ -6,7 +6,8 @@ Raven_Autoloader::register(); -function setupSentry() { +function setupSentry() +{ $client = new \Raven_Client('https://e9ebbd88548a441288393c457ec90441:399aaee02d454e2ca91351f29bdc3a07@app.getsentry.com/3235'); $error_handler = new Raven_ErrorHandler($client); $error_handler->registerExceptionHandler(); @@ -14,7 +15,8 @@ function setupSentry() { $error_handler->registerShutdownFunction(); } -function createError() { +function createError() +{ 1 / 0; } From ac8ec1561b5b805f713b2fd60d3d91e5d97e60aa Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 13:17:01 -0700 Subject: [PATCH 0080/1161] Update breadcrumb test --- test/Raven/Tests/BreadcrumbsTest.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/Raven/Tests/BreadcrumbsTest.php b/test/Raven/Tests/BreadcrumbsTest.php index aa94581db..5046459da 100644 --- a/test/Raven/Tests/BreadcrumbsTest.php +++ b/test/Raven/Tests/BreadcrumbsTest.php @@ -32,10 +32,7 @@ public function testJson() $breadcrumbs->record(array('message' => 'test')); $json = $breadcrumbs->to_json(); - $this->assertEquals($json, array( - 'values' => array( - 0 => array('message' => 'test'), - ), - )); + $this->assertEquals(count($json['values']), 1); + $this->assertEquals($json['values'][0]['message'], 'test'); } } From 547b30050b29db6b0775c8cfd9cd8ca0f82b9b2b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 14:07:52 -0700 Subject: [PATCH 0081/1161] Support exception attribute in context --- lib/Raven/Breadcrumbs/MonologHandler.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 79c6eea0a..7b3c5474e 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -46,12 +46,24 @@ protected function write(array $record) return; } - // TODO(dcramer): support $record['context']['exception'] instanceof \Exception - $crumb = array( - 'level' => $this->logLevels[$record['level']], - 'category' => $record['channel'], - 'message' => $record['message'], - ); + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + $exc = $record['context']['exception']; + $crumb = array( + 'level' => $this->logLevels[$record['level']], + 'category' => $record['channel'], + 'data' => array( + 'type' => get_class($exc), + 'value' => $exc->getMessage(), + ), + ); + } else { + // TODO(dcramer): parse exceptions out of messages and format as above + $crumb = array( + 'level' => $this->logLevels[$record['level']], + 'category' => $record['channel'], + 'message' => $record['message'], + ); + } $this->ravenClient->breadcrumbs->record($crumb); } From b5390573a5d7942a43c42e148079779dc8644874 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 3 May 2016 14:19:16 -0700 Subject: [PATCH 0082/1161] Parse errors out of logs --- lib/Raven/Breadcrumbs/MonologHandler.php | 34 ++++++++++++++--- test/Raven/Tests/Breadcrumbs/MonologTest.php | 39 ++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 7b3c5474e..2f5811680 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -19,6 +19,8 @@ class Raven_Breadcrumbs_MonologHandler extends AbstractProcessingHandler Logger::EMERGENCY => Raven_Client::FATAL, ); + private $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; + /** * @var Raven_Client the client object that sends the message to the server */ @@ -36,6 +38,15 @@ public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $ $this->ravenClient = $ravenClient; } + protected function parseException($message) + { + if (!preg_match($this->excMatch, $message, $matches)) { + return; + } + + return array($matches[1], $matches[2]); + } + /** * {@inheritdoc} */ @@ -49,6 +60,7 @@ protected function write(array $record) if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { $exc = $record['context']['exception']; $crumb = array( + 'type' => 'error', 'level' => $this->logLevels[$record['level']], 'category' => $record['channel'], 'data' => array( @@ -58,11 +70,23 @@ protected function write(array $record) ); } else { // TODO(dcramer): parse exceptions out of messages and format as above - $crumb = array( - 'level' => $this->logLevels[$record['level']], - 'category' => $record['channel'], - 'message' => $record['message'], - ); + if ($error = $this->parseException($record['message'])) { + $crumb = array( + 'type' => 'error', + 'level' => $this->logLevels[$record['level']], + 'category' => $record['channel'], + 'data' => array( + 'type' => $error[0], + 'value' => $error[1], + ), + ); + } else { + $crumb = array( + 'level' => $this->logLevels[$record['level']], + 'category' => $record['channel'], + 'message' => $record['message'], + ); + } } $this->ravenClient->breadcrumbs->record($crumb); diff --git a/test/Raven/Tests/Breadcrumbs/MonologTest.php b/test/Raven/Tests/Breadcrumbs/MonologTest.php index 386590b2f..f536985ff 100644 --- a/test/Raven/Tests/Breadcrumbs/MonologTest.php +++ b/test/Raven/Tests/Breadcrumbs/MonologTest.php @@ -11,6 +11,28 @@ class Raven_Tests_MonologBreadcrumbHandlerTest extends PHPUnit_Framework_TestCase { + protected function getSampleErrorMessage() + { + return <<run(Object(Illuminate\Http\Request)) +#3 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(5053): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request)) +#4 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(715): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request)) +#5 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(696): Illuminate\Foundation\Application->dispatch(Object(Illuminate\Http\Request)) +#6 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(7825): Illuminate\Foundation\Application->handle(Object(Illuminate\Http\Request), 1, true) +#7 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(8432): Illuminate\Session\Middleware->handle(Object(Illuminate\Http\Request), 1, true) +#8 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(8379): Illuminate\Cookie\Queue->handle(Object(Illuminate\Http\Request), 1, true) +#9 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(11123): Illuminate\Cookie\Guard->handle(Object(Illuminate\Http\Request), 1, true) +#10 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(657): Stack\StackedHttpKernel->handle(Object(Illuminate\Http\Request)) +#11 /sentry-laravel/examples/laravel-4.2/public/index.php(49): Illuminate\Foundation\Application->run() +#12 /sentry-laravel/examples/laravel-4.2/server.php(19): require_once('/Users/dcramer/...') +#13 {main} +EOF; + } + public function testSimple() { $client = new \Raven_Client(); @@ -26,4 +48,21 @@ public function testSimple() $this->assertEquals($crumbs[0]['category'], 'sentry'); $this->assertEquals($crumbs[0]['level'], 'warning'); } + + public function testErrorInMessage() + { + $client = new \Raven_Client(); + $handler = new \Raven_Breadcrumbs_MonologHandler($client); + + $logger = new Monolog\Logger('sentry'); + $logger->pushHandler($handler); + $logger->addError($this->getSampleErrorMessage()); + + $crumbs = $client->breadcrumbs->fetch(); + $this->assertEquals(count($crumbs), 1); + $this->assertEquals($crumbs[0]['data']['type'], 'Exception'); + $this->assertEquals($crumbs[0]['data']['value'], 'An unhandled exception'); + $this->assertEquals($crumbs[0]['category'], 'sentry'); + $this->assertEquals($crumbs[0]['level'], 'error'); + } } From 1398f16a987905eb1314717e7cf9bd6767450ad5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 6 May 2016 09:42:59 -0500 Subject: [PATCH 0083/1161] Allow overwriting of sdk --- lib/Raven/Client.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index a1ff6c930..1e40b3bbe 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -92,6 +92,10 @@ public function __construct($options_or_dsn=null, $options=array()) $this->_user = null; $this->context = new Raven_Context(); $this->breadcrumbs = new Raven_Breadcrumbs(); + $this->sdk = Raven_Util::get($options, 'sdk', array( + 'name' => 'sentry-php', + 'version' => self::VERSION, + )); if ($this->curl_method == 'async') { $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); @@ -420,10 +424,7 @@ public function get_default_data() 'logger' => $this->logger, 'tags' => $this->tags, 'platform' => 'php', - 'sdk' => array( - 'name' => 'sentry-php', - 'version' => self::VERSION, - ), + 'sdk' => $this->sdk, ); } From b92eef80eb157235f0049890023f8013ad68cf96 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 6 May 2016 09:53:22 -0500 Subject: [PATCH 0084/1161] Brief note on breadcrumbs --- docs/usage.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/usage.rst b/docs/usage.rst index 1d15f15f2..5aeb20e07 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -216,6 +216,19 @@ helper: echo "Your reference ID is " . $event_id; } +Breadcrumbs +----------- + +Sentry supports capturing breadcrumbs -- events that happened prior to an issue. + +.. code-block:: php + + $client->breadcrumbs->record(array( + 'message' => 'Authenticating user as ' . $username, + 'category' => 'auth', + 'level' => 'info', + )); + Testing Your Connection ----------------------- From 987c943a30196f906d66261a30417375852d49f9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 6 May 2016 10:55:08 -0500 Subject: [PATCH 0085/1161] Remove serialization of user-provided values --- lib/Raven/Client.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 1e40b3bbe..f91622144 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -475,28 +475,22 @@ public function capture($data, $stack = null, $vars = null) $this->context->extra, $data['extra']); - // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('Raven_Serializer')) { - spl_autoload_call('Raven_Serializer'); - } - - $serializer = new Raven_Serializer(); - // avoid empty arrays (which dont convert to dicts) if (empty($data['extra'])) { unset($data['extra']); } else { - $data['extra'] = $serializer->serialize($data['extra']); + $data['extra'] = $data['extra']; } + if (empty($data['tags'])) { unset($data['tags']); } else { - $data['tags'] = $serializer->serialize($data['tags']); + $data['tags'] = $data['tags']; } if (!empty($data['user'])) { - $data['user'] = $serializer->serialize($data['user']); + $data['user'] = $data['user']; } if (!empty($data['request'])) { - $data['request'] = $serializer->serialize($data['request']); + $data['request'] = $data['request']; } if (!$this->breadcrumbs->is_empty()) { From e0c33760e5618946730c8390cc0a1c4823852abf Mon Sep 17 00:00:00 2001 From: Eric Feng Date: Fri, 6 May 2016 12:17:16 -0500 Subject: [PATCH 0086/1161] fixing laravel's github link --- docs/integrations/laravel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 169aa3586..4d87f8d02 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -1,7 +1,7 @@ Laravel ======= -Laravel is supported via a native extension, [sentry-laravel](https://github.com/getsentry/sentry-laravel). +Laravel is supported via a native extension, `sentry-laravel `_. Laravel 5.x ----------- From bb55078c8fcd8298cbcfe2e72be554a0c3ce23bc Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 8 May 2016 08:50:51 -0700 Subject: [PATCH 0087/1161] Fluid interfaces for handler registration (fixes GH-283) --- lib/Raven/ErrorHandler.php | 3 +++ test/Raven/Tests/ErrorHandlerTest.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 57aca9812..79bb5f2a8 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -174,6 +174,7 @@ public function registerExceptionHandler($call_existing_exception_handler = true { $this->old_exception_handler = set_exception_handler(array($this, 'handleException')); $this->call_existing_exception_handler = $call_existing_exception_handler; + return $this; } /** @@ -190,6 +191,7 @@ public function registerErrorHandler($call_existing_error_handler = true, $error } $this->old_error_handler = set_error_handler(array($this, 'handleError'), E_ALL); $this->call_existing_error_handler = $call_existing_error_handler; + return $this; } public function registerShutdownFunction($reservedMemorySize = 10) @@ -197,6 +199,7 @@ public function registerShutdownFunction($reservedMemorySize = 10) register_shutdown_function(array($this, 'handleFatalError')); $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); + return $this; } public function detectShutdown() diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 04e10e856..fb6b224f0 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -130,4 +130,18 @@ public function testErrorHandlerDefaultsErrorReporting() trigger_error('Warning', E_USER_WARNING); } + + public function testFluidInterface() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $handler = new Raven_ErrorHandler($client); + $result = $handler->registerErrorHandler(); + $this->assertEquals($result, $handler); + $result = $handler->registerExceptionHandler(); + $this->assertEquals($result, $handler); + // TODO(dcramer): cant find a great way to test resetting the shutdown + // handler + // $result = $handler->registerShutdownHandler(); + // $this->assertEquals($result, $handler); + } } From 07654f63d646f928bee5c8b860508561c93200ee Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 9 May 2016 09:20:29 -0700 Subject: [PATCH 0088/1161] Expand configuration API - Add get/setRelease - Add get/setEnvironment - Add get/setAppPath - Add get/setPrefixes - Expose $breadcrumbs - Expose $context --- lib/Raven/Client.php | 49 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index f91622144..db88d9dfd 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -28,9 +28,10 @@ class Raven_Client const MESSAGE_LIMIT = 1024; - public $severity_map; + public $breadcrumbs; + public $context; public $extra_data; - + public $severity_map; public $store_errors_for_bulk_send = false; public function __construct($options_or_dsn=null, $options=array()) @@ -102,6 +103,50 @@ public function __construct($options_or_dsn=null, $options=array()) } } + public function getRelease($value) + { + return $this->release; + } + + public function setRelease($value) + { + $this->release = $value; + return $this; + } + + public function getEnvironment($value) + { + return $this->environment; + } + + public function setEnvironment($value) + { + $this->environment = $value; + return $this; + } + + public function getAppPath($value) + { + return $this->app_path; + } + + public function setAppPath($value) + { + $this->app_path = $value; + return $this; + } + + public function getPrefixes($value) + { + return $this->prefixes; + } + + public function setPrefixes($value) + { + $this->prefixes = $value; + return $this; + } + public static function getDefaultProcessors() { return array( From 96382e02f52578005164792c16a0cbab17a36c7c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 10 May 2016 15:40:15 -0700 Subject: [PATCH 0089/1161] Remove some Raven references --- AUTHORS | 2 +- LICENSE | 4 ++-- docs/advanced.rst | 2 +- docs/index.rst | 6 +++--- docs/usage.rst | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4aa5ecf4a..675a1e94c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,4 @@ -The Raven PHP client was originally written by Michael van Tellingen +The Sentry PHP SDK was originally written by Michael van Tellingen and is maintained by the Sentry Team. http://github.com/getsentry/sentry-php/contributors diff --git a/LICENSE b/LICENSE index c53c66caf..9c9f585d4 100644 --- a/LICENSE +++ b/LICENSE @@ -7,6 +7,6 @@ Redistribution and use in source and binary forms, with or without modification, 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of the Raven nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + 3. Neither the name of the Raven, Sentry, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/advanced.rst b/docs/advanced.rst index 51a5b0570..5d5f80cf9 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -14,7 +14,7 @@ Install with Composer ````````````````````` If you're using `Composer `_ to manage -dependencies, you can add Raven with it. +dependencies, you can add the Sentry SDK with it. .. code-block:: json diff --git a/docs/index.rst b/docs/index.rst index 3d05cd20c..2eab7478e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ .. sentry:edition:: self - Raven-PHP - ========= + Sentry-PHP + ========== .. sentry:edition:: on-premise, hosted @@ -10,7 +10,7 @@ PHP === -Raven-PHP is a PHP client for Sentry supporting PHP 5.3 or higher. It'a +The PHP SDK for Sentry supports PHP 5.3 and higher. It'a available as a BSD licensed Open Source library. Installation diff --git a/docs/usage.rst b/docs/usage.rst index 5aeb20e07..fdeffc9b9 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,7 +1,7 @@ Usage ===== -Using Raven for PHP is straightforward. After installation of the library +Using Sentry with PHP is straightforward. After installation of the library you can directly interface with the client and start submitting data. Basics @@ -17,8 +17,8 @@ once and reference it from anywhere you want to interface with Sentry: Capturing Errors ---------------- -The most basic functionality is to use Raven for reporting any uncaught -exceptions or PHP errors. As this functionality is common enough, Raven +The most basic functionality is to use Sentry for reporting any uncaught +exceptions or PHP errors. As this functionality is common enough, Sentry provides support for this out of the box: .. code-block:: php From f66f56c5c3662f0e6efc565975cf7b994f150e46 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 10 May 2016 15:52:34 -0700 Subject: [PATCH 0090/1161] 0.18.0.dev0 --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1f6137dd3..6e427e1bd 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.17.x-dev" + "dev-master": "0.18.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index f91622144..16b3006c3 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '0.16.0.dev0'; + const VERSION = '0.18.0.dev0'; const PROTOCOL = '6'; const DEBUG = 'debug'; From 3075ebee1f2a6e5f58135752e5c0d1c714d123a4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 10 May 2016 16:21:56 -0700 Subject: [PATCH 0091/1161] Add getter/setter for send_callback --- lib/Raven/Client.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index d9ddf467a..16f28900d 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -147,6 +147,17 @@ public function setPrefixes($value) return $this; } + public function getSendCallback($value) + { + return $this->send_callback; + } + + public function setSendCallback($value) + { + $this->send_callback = $value; + return $this; + } + public static function getDefaultProcessors() { return array( From 0c435777ceacc43167faa28b59d34d51a427151d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 10 May 2016 16:25:03 -0700 Subject: [PATCH 0092/1161] BREAKING: dont use falsey, require false for send_callback rejections --- lib/Raven/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 16f28900d..74296fe8d 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -630,8 +630,8 @@ public function sendUnsentErrors() */ public function send($data) { - if (is_callable($this->send_callback) && !call_user_func($this->send_callback, $data)) { - // if send_callback returns falsely, end native send + if (is_callable($this->send_callback) && call_user_func($this->send_callback, $data) === false) { + // if send_callback returns false, end native send return; } From 8785e1f57be87a22e5823a81fd6d29612d63686c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 10 May 2016 16:26:31 -0700 Subject: [PATCH 0093/1161] Document send_callback --- docs/usage.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/usage.rst b/docs/usage.rst index fdeffc9b9..a7e7205a2 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -229,6 +229,22 @@ Sentry supports capturing breadcrumbs -- events that happened prior to an issue. 'level' => 'info', )); +Filtering Out Errors +-------------------- + +Its common that you might want to prevent automatic capture of certain areas. Ideally you simply would avoid calling out to Sentry in that case, but that's often easier said than done. Instead, you can provide a function which the SDK will call before it sends any data, allowing you both to mutate that data, as well as prevent it from being sent to the server. + +.. code-block:: php + + $client->setSendCallback(function($data) { + $ignore_types = array('Symfony\Component\HttpKernel\Exception\NotFoundHttpException') + + if (isset($data['exception'] && in_array($data['exception']['values'][0]['type'], $ignore_types) + { + return false; + } + }); + Testing Your Connection ----------------------- From c3b79e398ce3bd3483230ff18266d1796b59fc90 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 10 May 2016 16:29:42 -0700 Subject: [PATCH 0094/1161] Simple note about accessing sentry sdk --- docs/integrations/laravel.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 4d87f8d02..9a0a24607 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -75,7 +75,6 @@ Add the Sentry service provider and facade in ``config/app.php``: 'Sentry' => 'Sentry\SentryLaravel\SentryFacade', ) - Create the Sentry configuration file (``config/sentry.php``): .. code-block:: php @@ -94,6 +93,14 @@ Add your DSN to ``config/sentry.php``: // ... ); +If you wish to wire up Sentry anywhere outside of the standard error handlers, or +if you need to configure additional settings, you can access the Sentry instance +through ``$app``: + +.. code-block:: php + + $app['sentry']->setRelease(Git::sha()); + Lumen 5.x --------- From c7a42c6b79d5da7f3f4687bc90718733d97eb81c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 10 May 2016 16:40:38 -0700 Subject: [PATCH 0095/1161] Expand config docs --- docs/config.rst | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index 48dfa2890..e6ff283a8 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -32,6 +32,12 @@ The following settings are available for the client: 'php_version' => phpversion(), ) + .. code-block:: php + + $client->tags_context(array( + 'php_version' => phpversion(), + )); + .. describe:: release The version of your application (e.g. git SHA) @@ -40,6 +46,10 @@ The following settings are available for the client: 'release' => MyApp::getReleaseVersion(), + .. code-block:: php + + $client->setRelease(MyApp::getReleaseVersion()); + .. describe:: environment The environment your application is running in. @@ -48,6 +58,62 @@ The following settings are available for the client: 'environment' => 'production', + .. code-block:: php + + $client->setEnvironment('production'); + +.. describe:: app_path + + The root path to your application code. + + .. code-block:: php + + 'app_path' => app_root(), + + .. code-block:: php + + $client->setAppPath(app_root()); + +.. describe:: prefixes + + Prefixes which should be stripped from filenames to create relative + paths. + + .. code-block:: php + + 'prefixes' => array( + '/www/php/lib', + ), + + .. code-block:: php + + $client->setPrefixes(array( + '/www/php/lib', + )); + +.. describe:: send_callback + + A function which will be called whenever data is ready to be sent. Within + the function you can mutate the data, or alternatively return ``false`` to + instruct the SDK to not send the event. + + .. code-block:: php + + 'send_callback' => function($data) { + // strip HTTP data + @unset($data['request']); + }, + + .. code-block:: php + + $client->setSendCallback(unction($data) { + // dont send events if POST + if ($_SERVER['REQUEST_METHOD'] === 'POST') + { + return false; + } + }); + .. describe:: curl_method Defaults to 'sync'. From 8bdf688d7986326c3f3ed39482ddd6c31c6ac7f4 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 13 May 2016 09:50:59 +0200 Subject: [PATCH 0096/1161] add conflict rule for raven/raven Both packages cannot be installed at the same time as they use the same class names. --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 6e427e1bd..04d3130e5 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,9 @@ "ext-curl": "*", "monolog/monolog": "*" }, + "conflict": { + "raven/raven": "*" + }, "bin": [ "bin/sentry" ], From 2594e5c7fafc9f169e37c86fae3f865ce9180f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Mon, 16 May 2016 13:44:33 +0200 Subject: [PATCH 0097/1161] Fix: Case-insensitive method calls --- test/Raven/Tests/ClientTest.php | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 6a8dde853..db82934fe 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -77,9 +77,9 @@ private function create_chained_exception() } } - public function testParseDsnHttp() + public function testParseDSNHttp() { - $result = Raven_Client::parseDsn('http://public:secret@example.com/1'); + $result = Raven_Client::ParseDSN('http://public:secret@example.com/1'); $this->assertEquals($result['project'], 1); $this->assertEquals($result['server'], 'http://example.com/api/1/store/'); @@ -87,9 +87,9 @@ public function testParseDsnHttp() $this->assertEquals($result['secret_key'], 'secret'); } - public function testParseDsnHttps() + public function testParseDSNHttps() { - $result = Raven_Client::parseDsn('https://public:secret@example.com/1'); + $result = Raven_Client::ParseDSN('https://public:secret@example.com/1'); $this->assertEquals($result['project'], 1); $this->assertEquals($result['server'], 'https://example.com/api/1/store/'); @@ -97,9 +97,9 @@ public function testParseDsnHttps() $this->assertEquals($result['secret_key'], 'secret'); } - public function testParseDsnPath() + public function testParseDSNPath() { - $result = Raven_Client::parseDsn('http://public:secret@example.com/app/1'); + $result = Raven_Client::ParseDSN('http://public:secret@example.com/app/1'); $this->assertEquals($result['project'], 1); $this->assertEquals($result['server'], 'http://example.com/app/api/1/store/'); @@ -107,9 +107,9 @@ public function testParseDsnPath() $this->assertEquals($result['secret_key'], 'secret'); } - public function testParseDsnPort() + public function testParseDSNPort() { - $result = Raven_Client::parseDsn('http://public:secret@example.com:9000/app/1'); + $result = Raven_Client::ParseDSN('http://public:secret@example.com:9000/app/1'); $this->assertEquals($result['project'], 1); $this->assertEquals($result['server'], 'http://example.com:9000/app/api/1/store/'); @@ -117,30 +117,30 @@ public function testParseDsnPort() $this->assertEquals($result['secret_key'], 'secret'); } - public function testParseDsnInvalidScheme() + public function testParseDSNInvalidScheme() { try { - Raven_Client::parseDsn('gopher://public:secret@/1'); + Raven_Client::ParseDSN('gopher://public:secret@/1'); $this->fail(); } catch (Exception $e) { return; } } - public function testParseDsnMissingNetloc() + public function testParseDSNMissingNetloc() { try { - Raven_Client::parseDsn('http://public:secret@/1'); + Raven_Client::ParseDSN('http://public:secret@/1'); $this->fail(); } catch (Exception $e) { return; } } - public function testParseDsnMissingProject() + public function testParseDSNMissingProject() { try { - Raven_Client::parseDsn('http://public:secret@example.com'); + Raven_Client::ParseDSN('http://public:secret@example.com'); $this->fail(); } catch (Exception $e) { return; @@ -150,16 +150,16 @@ public function testParseDsnMissingProject() /** * @expectedException InvalidArgumentException */ - public function testParseDsnMissingPublicKey() + public function testParseDSNMissingPublicKey() { - Raven_Client::parseDsn('http://:secret@example.com/1'); + Raven_Client::ParseDSN('http://:secret@example.com/1'); } /** * @expectedException InvalidArgumentException */ - public function testParseDsnMissingSecretKey() + public function testParseDSNMissingSecretKey() { - Raven_Client::parseDsn('http://public@example.com/1'); + Raven_Client::ParseDSN('http://public@example.com/1'); } public function testDsnFirstArgument() From 85a1ede732cebf80ba386e12de632ad90ef901b5 Mon Sep 17 00:00:00 2001 From: Sam Tape Date: Mon, 16 May 2016 14:43:20 -0500 Subject: [PATCH 0098/1161] Fix array key for adding user context --- docs/integrations/monolog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/monolog.rst b/docs/integrations/monolog.rst index 775ef0967..651662265 100644 --- a/docs/integrations/monolog.rst +++ b/docs/integrations/monolog.rst @@ -22,7 +22,7 @@ Capturing context can be done via a monolog processor: $monolog->pushProcessor(function ($record) { // record the current user $user = Acme::getCurrentUser(); - $record['user'] = array( + $record['context']['user'] = array( 'name' => $user->getName(), 'username' => $user->getUsername(), 'email' => $user->getEmail(), From 4789a85929602b1ab3dc264e787bdbbe29deafc5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 17 May 2016 11:46:32 -0500 Subject: [PATCH 0099/1161] 0.19.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 04d3130e5..0c5e7c2b7 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.18.x-dev" + "dev-master": "0.19.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 74296fe8d..96f3ef2b6 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '0.18.0.dev0'; + const VERSION = '0.19.0.dev0'; const PROTOCOL = '6'; const DEBUG = 'debug'; From 275eb82ef49cdae7edc483a60026c387e104b92e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 17 May 2016 15:50:25 -0500 Subject: [PATCH 0100/1161] Bad instructions --- docs/integrations/symfony2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index 63c9ec404..afbd9eb25 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -25,7 +25,7 @@ Enable the bundle in ``app/AppKernel.php``: $bundles = array( // ... - new Sentry/SentryBundle/SentryBundle(), + new Sentry\SentryBundle\SentryBundle(), ); // ... From dd7529b2b1d1a48d4b0c1c5dfff8c087cd5c4cea Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 18 May 2016 11:09:38 -0500 Subject: [PATCH 0101/1161] Add export-ignore for example/docs/tests --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..33a59aed3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +/examples export-ignore +/docs export-ignore +/test export-ignore From a1fe7fbd8f6c02539aba55b3b89faaa697c9c00b Mon Sep 17 00:00:00 2001 From: Stephan Wentz Date: Fri, 20 May 2016 15:35:49 +0200 Subject: [PATCH 0102/1161] fix cli name and test for command argument --- bin/sentry | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/sentry b/bin/sentry index fddc0a81b..4694cfcb4 100755 --- a/bin/sentry +++ b/bin/sentry @@ -76,6 +76,10 @@ function cmd_test($dsn) function main() { global $argv; + if (!isset($argv[1])) { + exit('Usage: sentry test '); + } + $cmd = $argv[1]; switch ($cmd) { @@ -83,7 +87,7 @@ function main() { cmd_test(@$argv[2]); break; default: - exit('Usage: raven test '); + exit('Usage: sentry test '); } } From 49888eba869d14116c1022231165830d98c3d96c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 23 May 2016 12:50:25 -0700 Subject: [PATCH 0103/1161] Correct handling of prefix-less stacks --- lib/Raven/Stacktrace.php | 2 +- test/Raven/Tests/StacktraceTest.php | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index df9ecfa4e..a3eda386e 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -207,7 +207,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client private static function strip_prefixes($filename, $prefixes) { if ($prefixes === null) { - return; + return $filename; } foreach ($prefixes as $prefix) { if (substr($filename, 0, strlen($prefix)) === $prefix) { diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index b9269c756..8917f5ce1 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -301,4 +301,29 @@ public function testBasePath() $this->assertEquals($frames[0]['filename'], 'resources/b.php'); $this->assertEquals($frames[1]['filename'], 'resources/a.php'); } + + public function testNoBasePath() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack); + $this->assertEquals($frames[0]['filename'], dirname(__FILE__) . '/resources/a.php'); + } + + public function testWithEvaldCode() + { + try { + eval("throw new Exception('foobar');"); + } catch (Exception $ex) { + $trace = $ex->getTrace(); + $frames = Raven_Stacktrace::get_stack_info($trace); + } + $this->assertEquals($frames[count($frames) -1]['filename'], __FILE__); + } } From e98871240dab3008c144549418552b21e4cce580 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 26 May 2016 17:05:43 -0700 Subject: [PATCH 0104/1161] Add error_reporting support to breadcrumbs --- lib/Raven/Breadcrumbs/ErrorHandler.php | 107 ++++++++++++++++++ lib/Raven/Client.php | 10 ++ .../Tests/Breadcrumbs/ErrorHandlerTest.php | 29 +++++ test/Raven/Tests/Breadcrumbs/MonologTest.php | 8 +- 4 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 lib/Raven/Breadcrumbs/ErrorHandler.php create mode 100644 test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php new file mode 100644 index 000000000..17eac456f --- /dev/null +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -0,0 +1,107 @@ +ravenClient = $ravenClient; + } + + public function codeToName($code) + { + switch ($code) { + case 1: + return 'E_ERROR'; + case 2: + return 'E_WARNING'; + case 4: + return 'E_PARSE'; + case 8: + return 'E_NOTICE'; + case 16: + return 'E_CORE_ERROR'; + case 32: + return 'E_CORE_WARNING'; + case 64: + return 'E_COMPILE_ERROR'; + case 128: + return 'E_COMPILE_WARNING'; + case 256: + return 'E_USER_ERROR'; + case 512: + return 'E_USER_WARNING'; + case 1024: + return 'E_USER_NOTICE'; + case 2048: + return 'E_STRICT'; + case 4096: + return 'E_RECOVERABLE_ERROR'; + case 8192: + return 'E_DEPRECATED'; + case 16384: + return 'E_USER_DEPRECATED'; + case 30719: + return 'E_ALL'; + default: + return 'E_UNKNOWN'; + } + } + + public function codeToLevel($code) + { + switch ($code) { + case 1: + case 4: + case 16: + case 64: + case 256: + case 4096: + return 'error'; + case 2: + case 32: + case 128: + case 512: + return 'warning'; + default: + return 'info'; + } + } + + public function handleError($code, $message, $file = '', $line = 0, $context=array()) + { + $this->ravenClient->breadcrumbs->record(array( + 'category' => 'error_reporting', + 'message' => $message, + 'level' => $this->codeToLevel($code), + 'data' => array( + 'code' => $code, + 'line' => $line, + 'file' => $file, + ), + )); + + if ($this->existingHandler !== null) { + return call_user_func($this->existingHandler, $code, $message, $file, $line, $context); + } else { + return false; + } + } + + public function install() + { + $this->existingHandler = set_error_handler(array($this, 'handleError'), E_ALL); + return $this; + } +} diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 96f3ef2b6..771168bf2 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -101,6 +101,10 @@ public function __construct($options_or_dsn=null, $options=array()) if ($this->curl_method == 'async') { $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); } + + if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { + $this->registerDefaultBreadcrumbHandlers(); + } } public function getRelease($value) @@ -402,6 +406,12 @@ public function getLastEventID() return $this->_last_event_id; } + protected function registerDefaultBreadcrumbHandlers() + { + $handler = new Raven_Breadcrumbs_ErrorHandler($this); + $handler->install(); + } + protected function is_http_request() { return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; diff --git a/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php b/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php new file mode 100644 index 000000000..b5c993783 --- /dev/null +++ b/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php @@ -0,0 +1,29 @@ + false, + )); + + $handler = new \Raven_Breadcrumbs_ErrorHandler($client); + $handler->handleError(E_WARNING, 'message'); + + $crumbs = $client->breadcrumbs->fetch(); + $this->assertEquals(count($crumbs), 1); + $this->assertEquals($crumbs[0]['message'], 'message'); + $this->assertEquals($crumbs[0]['category'], 'error_reporting'); + $this->assertEquals($crumbs[0]['level'], 'warning'); + } +} diff --git a/test/Raven/Tests/Breadcrumbs/MonologTest.php b/test/Raven/Tests/Breadcrumbs/MonologTest.php index f536985ff..1c26f1107 100644 --- a/test/Raven/Tests/Breadcrumbs/MonologTest.php +++ b/test/Raven/Tests/Breadcrumbs/MonologTest.php @@ -35,7 +35,9 @@ protected function getSampleErrorMessage() public function testSimple() { - $client = new \Raven_Client(); + $client = new \Raven_Client(array( + 'install_default_breadcrumb_handlers' => false, + )); $handler = new \Raven_Breadcrumbs_MonologHandler($client); $logger = new Monolog\Logger('sentry'); @@ -51,7 +53,9 @@ public function testSimple() public function testErrorInMessage() { - $client = new \Raven_Client(); + $client = new \Raven_Client(array( + 'install_default_breadcrumb_handlers' => false, + )); $handler = new \Raven_Breadcrumbs_MonologHandler($client); $logger = new Monolog\Logger('sentry'); From 2bccb380828a0028ff0ab4e668cd9edb8eb453a2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 26 May 2016 17:10:55 -0700 Subject: [PATCH 0105/1161] Document breadcrumb handler for monolog --- docs/integrations/monolog.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/integrations/monolog.rst b/docs/integrations/monolog.rst index 651662265..b2d99f84d 100644 --- a/docs/integrations/monolog.rst +++ b/docs/integrations/monolog.rst @@ -1,6 +1,9 @@ Monolog ======= +Capturing Errors +---------------- + Monolog supports Sentry out of the box, so you'll just need to configure a handler: .. sourcecode:: php @@ -36,3 +39,16 @@ Capturing context can be done via a monolog processor: return $record; }); + + +Breadcrumbs +----------- + +Sentry provides a breadcrumb handler to automatically send logs along as crumbs: + +.. sourcecode:: php + + $client = new Raven_Client('___DSN___'); + + $handler = new \Raven_Breadcrumbs_MonologHandler($client); + $monolog->pushHandler($handler); From 81e79d3a1bf0bf20b9ba12e2f6448e6d421aeb1e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 26 May 2016 19:11:11 -0700 Subject: [PATCH 0106/1161] Changes up to 0.17.0 --- CHANGES | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES b/CHANGES index da768e5df..e09461aeb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,14 @@ +0.17.0 +------ + +- Don't attempt to serialize fixed SDK inputs. +- Improvements to breadcrumbs support in Monolog. + +0.16.0 +------ + +- Initial breadcrumbs support with Monolog handler. + 0.15.0 ------ From b64e3b6939a7f29a9e6867ab3c66e42bf1318d05 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 26 May 2016 19:11:37 -0700 Subject: [PATCH 0107/1161] Changes for 0.19.0 and below --- CHANGES | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES b/CHANGES index e09461aeb..a5fe9876a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,15 @@ +0.19.0 +------ + +- Add error_reporting breadcrumb handler. + +0.18.0 +------ + +- Remove session from serialized data. +- send_callback return value must now be false to prevent capture. +- Add various getter/setter methods for configuration. + 0.17.0 ------ From 435f29c76df8c0aef102980be7fcce574de4ed0f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 26 May 2016 19:13:41 -0700 Subject: [PATCH 0108/1161] 0.20.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 0c5e7c2b7..8b8d1bb41 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.19.x-dev" + "dev-master": "0.20.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 771168bf2..634dbbe14 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '0.19.0.dev0'; + const VERSION = '0.20.x-dev'; const PROTOCOL = '6'; const DEBUG = 'debug'; From 9c1aa42911e16aacf775b813269db31ee520eb44 Mon Sep 17 00:00:00 2001 From: Wellming Li Date: Sun, 29 May 2016 22:48:15 +0800 Subject: [PATCH 0109/1161] Update symfony2.rst --- docs/integrations/symfony2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index afbd9eb25..b11916445 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -6,7 +6,7 @@ Symfony is supported via the `sentry-symfony Date: Tue, 31 May 2016 11:09:31 -0700 Subject: [PATCH 0110/1161] Re-use existing code for severity --- lib/Raven/Breadcrumbs/ErrorHandler.php | 62 +------------------------- 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index 17eac456f..d48ab1dc5 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -19,72 +19,12 @@ public function __construct(Raven_Client $ravenClient) $this->ravenClient = $ravenClient; } - public function codeToName($code) - { - switch ($code) { - case 1: - return 'E_ERROR'; - case 2: - return 'E_WARNING'; - case 4: - return 'E_PARSE'; - case 8: - return 'E_NOTICE'; - case 16: - return 'E_CORE_ERROR'; - case 32: - return 'E_CORE_WARNING'; - case 64: - return 'E_COMPILE_ERROR'; - case 128: - return 'E_COMPILE_WARNING'; - case 256: - return 'E_USER_ERROR'; - case 512: - return 'E_USER_WARNING'; - case 1024: - return 'E_USER_NOTICE'; - case 2048: - return 'E_STRICT'; - case 4096: - return 'E_RECOVERABLE_ERROR'; - case 8192: - return 'E_DEPRECATED'; - case 16384: - return 'E_USER_DEPRECATED'; - case 30719: - return 'E_ALL'; - default: - return 'E_UNKNOWN'; - } - } - - public function codeToLevel($code) - { - switch ($code) { - case 1: - case 4: - case 16: - case 64: - case 256: - case 4096: - return 'error'; - case 2: - case 32: - case 128: - case 512: - return 'warning'; - default: - return 'info'; - } - } - public function handleError($code, $message, $file = '', $line = 0, $context=array()) { $this->ravenClient->breadcrumbs->record(array( 'category' => 'error_reporting', 'message' => $message, - 'level' => $this->codeToLevel($code), + 'level' => $this->ravenClient->translateSeverity($code), 'data' => array( 'code' => $code, 'line' => $line, From b7c1621a3c68ca7004e95b147cc6cbbd3f6ed08e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 2 Jun 2016 01:01:25 -0700 Subject: [PATCH 0111/1161] Dont use suppression operator in ring buffer Fixes getsentry/sentry-laravel#19 --- lib/Raven/Breadcrumbs.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php index a07e90081..d1a994654 100644 --- a/lib/Raven/Breadcrumbs.php +++ b/lib/Raven/Breadcrumbs.php @@ -38,10 +38,9 @@ public function fetch() { $results = array(); for ($i = 0; $i <= ($this->size - 1); $i++) { - $node = @$this->buffer[($this->pos + $i) % $this->size]; - - if (isset($node)) { - $results[] = $node; + $idx = ($this->pos + $i) % $this->size; + if (isset($this->buffer[$idx])) { + $results[] = $this->buffer[$idx]; } } return $results; From 8c3abaf85c2689561e4edca52e7cf2ab8d417791 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 2 Jun 2016 12:11:32 -0700 Subject: [PATCH 0112/1161] Handle missing function in frames (fixes GH-303) --- lib/Raven/Stacktrace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index a3eda386e..73cdf85c9 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -86,7 +86,7 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true 'filename' => $context['filename'], 'lineno' => (int) $context['lineno'], 'module' => $module, - 'function' => $nextframe['function'], + 'function' => isset($nextframe['function']) ? $nextframe['function'] : null, 'pre_context' => $context['prefix'], 'context_line' => $context['line'], 'post_context' => $context['suffix'], From b98b38134053794bfafcc3beda359cc6f84f66f2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 2 Jun 2016 12:31:22 -0700 Subject: [PATCH 0113/1161] Serialize contextual data (fixes GH-300) --- lib/Raven/Client.php | 14 ++++++++++++++ test/Raven/Tests/ClientTest.php | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 634dbbe14..b43a1a73e 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -605,6 +605,20 @@ public function capture($data, $stack = null, $vars = null) public function sanitize(&$data) { + // attempt to sanitize any user provided data + $serializer = new Raven_Serializer(); + if (!empty($data['request'])) { + $data['request'] = $serializer->serialize($data['request']); + } + if (!empty($data['user'])) { + $data['user'] = $serializer->serialize($data['user']); + } + if (!empty($data['extra'])) { + $data['extra'] = $serializer->serialize($data['extra']); + } + if (!empty($data['tags'])) { + $data['tags'] = $serializer->serialize($data['tags']); + } } /** diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index db82934fe..fd0da164b 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -609,6 +609,24 @@ public function testCaptureMessageWithUserContext() ), $event['user']); } + public function testCaptureMessageWithUnserializableUserData() + { + $client = new Dummy_Raven_Client(); + + $client->user_context(array( + 'email' => 'foo@example.com', + 'data' => array( + 'error' => new Exception('test'), + ) + )); + + $client->captureMessage('test'); + $events = $client->getSentEvents(); + // we're just asserting that this goes off without a hitch + $this->assertEquals(1, count($events)); + $event = array_pop($events); + } + public function testCaptureMessageWithTagsContext() { $client = new Dummy_Raven_Client(); From 2723537e561c0c86644b635eeef6620411b67320 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 2 Jun 2016 12:34:01 -0700 Subject: [PATCH 0114/1161] Changes for 0.20.0 --- CHANGES | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index a5fe9876a..8f5a32f4b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +0.20.0 +------ + +- Handle missing function names on frames. +- Remove suppression operator usage in breadcrumbs buffer. +- Force serialization of context values. + 0.19.0 ------ From 84106550fe1e38c9657db8f9e33b390a8ba3d023 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 2 Jun 2016 12:34:42 -0700 Subject: [PATCH 0115/1161] 0.21.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8b8d1bb41..86288fe25 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.20.x-dev" + "dev-master": "0.21.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index b43a1a73e..47e3f092e 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '0.20.x-dev'; + const VERSION = '0.21.x-dev'; const PROTOCOL = '6'; const DEBUG = 'debug'; From bfaf4041a6493e7221e4bf57e990b7798577cf87 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 6 Jun 2016 10:43:27 -0700 Subject: [PATCH 0116/1161] Add basic test for object serialization --- test/Raven/Tests/SerializerTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/Raven/Tests/SerializerTest.php b/test/Raven/Tests/SerializerTest.php index 0bc43a276..04e3cda01 100644 --- a/test/Raven/Tests/SerializerTest.php +++ b/test/Raven/Tests/SerializerTest.php @@ -81,4 +81,12 @@ public function testRecursionMaxDepth() $result = $serializer->serialize($input, 3); $this->assertEquals(array(array(array('Array of length 1'))), $result); } + + public function testObjectInArray() + { + $serializer = new Raven_Serializer(); + $input = array('foo' => new Raven_Serializer()); + $result = $serializer->serialize($input); + $this->assertEquals(array('foo' => 'Object Raven_Serializer'), $result); + } } From 2001e1aae120737b786b7015ff9189e3ac62bc39 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 6 Jun 2016 10:51:21 -0700 Subject: [PATCH 0117/1161] Clarify API on captureMessage (refs GH-300) --- docs/usage.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/usage.rst b/docs/usage.rst index a7e7205a2..12be3a262 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -58,6 +58,17 @@ takes a message and reports it to sentry. // Capture a message $event_id = $client->getIdent($client->captureMessage('my log message')); +Note, ``captureMessage`` has a slightly different API than ``captureException`` to support +parameterized formatting: + +.. code-block:: php + + $client->captureMessage('my %s message', array('log'), array( + 'extra' => array( + 'foo' => 'bar', + ), + )); + Optional Attributes ------------------- From 43111b9cb04e1b1f9cad51f30c88984e122446a9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 6 Jun 2016 10:51:54 -0700 Subject: [PATCH 0118/1161] Cleanup empty data logic --- lib/Raven/Client.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 47e3f092e..b76968b93 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -543,20 +543,15 @@ public function capture($data, $stack = null, $vars = null) if (empty($data['extra'])) { unset($data['extra']); - } else { - $data['extra'] = $data['extra']; } - if (empty($data['tags'])) { unset($data['tags']); - } else { - $data['tags'] = $data['tags']; } - if (!empty($data['user'])) { - $data['user'] = $data['user']; + if (empty($data['user'])) { + unset($data['user']); } - if (!empty($data['request'])) { - $data['request'] = $data['request']; + if (empty($data['request'])) { + unset($data['request']); } if (!$this->breadcrumbs->is_empty()) { From eca7063c9c519413fff1ecaf33f79078573a27a3 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 6 Jun 2016 11:31:24 -0700 Subject: [PATCH 0119/1161] Improve docstrings for capture methods (refs GH-300) --- lib/Raven/Client.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index b76968b93..ff2610a59 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -268,8 +268,13 @@ public function exception($exception) /** * Log a message to sentry + * + * @param string $message The message (primary description) for the event. + * @param array $params params to use when formatting the message. + * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param boolean $stack Capture a stacktrace for this event. */ - public function captureMessage($message, $params=array(), $level_or_options=array(), + public function captureMessage($message, $params=array(), $data=array(), $stack=false, $vars = null) { // Gracefully handle messages which contain formatting characters, but were not @@ -280,14 +285,13 @@ public function captureMessage($message, $params=array(), $level_or_options=arra $formatted_message = $message; } - if ($level_or_options === null) { + if ($data === null) { $data = array(); - } elseif (!is_array($level_or_options)) { + // support legacy method of passing in a level name as the third arg + } elseif (!is_array($data)) { $data = array( - 'level' => $level_or_options, + 'level' => $data, ); - } else { - $data = $level_or_options; } $data['message'] = $formatted_message; @@ -301,8 +305,11 @@ public function captureMessage($message, $params=array(), $level_or_options=arra /** * Log an exception to sentry + * + * @param string $exception The Exception object. + * @param array $data Additional attributes to pass with this event (see Sentry docs). */ - public function captureException($exception, $culprit_or_options=null, $logger=null, $vars=null) + public function captureException($exception, $data=null, $logger=null, $vars=null) { $has_chained_exceptions = version_compare(PHP_VERSION, '5.3.0', '>='); @@ -310,13 +317,12 @@ public function captureException($exception, $culprit_or_options=null, $logger=n return null; } - if (!is_array($culprit_or_options)) { + if ($data === null) { $data = array(); - if ($culprit_or_options !== null) { - $data['culprit'] = $culprit_or_options; - } - } else { - $data = $culprit_or_options; + } elseif (!is_array($data)) { + $data = array( + 'culprit' => (string)$data, + ); } // TODO(dcramer): DRY this up From 3deac8be75b6bb9f4062355f2fba5bed3db52f29 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 6 Jun 2016 11:32:18 -0700 Subject: [PATCH 0120/1161] Remove stack from docs as its mostly internal --- lib/Raven/Client.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ff2610a59..09df7b68a 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -272,7 +272,6 @@ public function exception($exception) * @param string $message The message (primary description) for the event. * @param array $params params to use when formatting the message. * @param array $data Additional attributes to pass with this event (see Sentry docs). - * @param boolean $stack Capture a stacktrace for this event. */ public function captureMessage($message, $params=array(), $data=array(), $stack=false, $vars = null) From 97ec7ac1892f35dd6c5921cb1eb319001372fd0f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 26 May 2016 17:08:11 -0700 Subject: [PATCH 0121/1161] Add basic install() hook for easy setup --- README.rst | 11 +++-------- lib/Raven/Client.php | 12 ++++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 9da1bb3f1..aff86c453 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,9 @@ The official PHP SDK for `Sentry `_. .. code-block:: php - // Instantiate a new client with a compatible DSN - $client = new Raven_Client('http://public:secret@example.com/1'); + // Instantiate a new client with a compatible DSN and install built-in + // handlers + $client = (new Raven_Client('http://public:secret@example.com/1'))->install(); // Capture a message $event_id = $client->getIdent($client->captureMessage('my log message')); @@ -32,12 +33,6 @@ The official PHP SDK for `Sentry `_. echo "Sorry, there was an error!"; echo "Your reference ID is " . $event_id; - // Install error handlers and shutdown function to catch fatal errors - $error_handler = new Raven_ErrorHandler($client); - $error_handler->registerExceptionHandler(); - $error_handler->registerErrorHandler(); - $error_handler->registerShutdownFunction(); - Installation ------------ diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 09df7b68a..20895e288 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -107,6 +107,18 @@ public function __construct($options_or_dsn=null, $options=array()) } } + /** + * Installs any available automated hooks (such as error_reporting). + */ + public function install() + { + $error_handler = new Raven_ErrorHandler($this); + $error_handler->registerExceptionHandler(); + $error_handler->registerErrorHandler(); + $error_handler->registerShutdownFunction(); + return $this; + } + public function getRelease($value) { return $this->release; From e803aaba99655eb7ab4f6ac9fabf82a0ffef126c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 9 Jun 2016 14:28:42 -0700 Subject: [PATCH 0122/1161] Add transport capability Fixes GH-301 --- docs/config.rst | 24 +++++++++++++++ lib/Raven/Client.php | 52 +++++++++++++++++++++++++++++---- test/Raven/Tests/ClientTest.php | 12 ++++++++ 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index e6ff283a8..b83e04a66 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -133,6 +133,30 @@ The following settings are available for the client: Specify the path to the curl binary to be used with the 'exec' curl method. +.. describe:: transport + + Set a custom transport to override how Sentry events are sent upstream. + + .. code-block:: php + + 'transport' => function($client, $data) { + $myHttpClient->send(array( + 'url' => $client->getServerEndpoint(), + 'method' => 'POST', + 'headers' => array( + 'Content-Encoding' => 'gzip', + 'Content-Type' => 'application/octet-stream', + 'User-Agent' => $client->getUserAgent(), + 'X-Sentry-Auth' => $client->getAuthHeader(), + ), + 'body' => gzipCompress(jsonEncode($data)), + )) + }, + + .. code-block:: php + + $client->setTransport(...); + .. describe:: trace Set this to ``false`` to disable reflection tracing (function calling diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 20895e288..8160e06e8 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -17,6 +17,7 @@ class Raven_Client { const VERSION = '0.21.x-dev'; + const PROTOCOL = '6'; const DEBUG = 'debug'; @@ -85,6 +86,8 @@ public function __construct($options_or_dsn=null, $options=array()) $this->prefixes = Raven_Util::get($options, 'prefixes', null); // app path is used to determine if code is part of your application $this->app_path = Raven_Util::get($options, 'app_path', null); + $this->transport = Raven_Util::get($options, 'transport', null); + ; $this->processors = $this->setProcessorsFromOptions($options); @@ -174,6 +177,36 @@ public function setSendCallback($value) return $this; } + public function getTransport($value) + { + return $this->transport; + } + + public function getServerEndpoint($value) + { + return $this->server; + } + + public function getUserAgent() + { + return 'sentry-php/' . self::VERSION; + } + + /** + * Set a custom transport to override how Sentry events are sent upstream. + * + * The bound function will be called with ``$client`` and ``$data`` arguments + * and is responsible for encoding the data, authenticating, and sending + * the data to the upstream Sentry server. + * + * @param function $value Function to be called + */ + public function setTransport($value) + { + $this->transport = $value; + return $this; + } + public static function getDefaultProcessors() { return array( @@ -675,6 +708,10 @@ public function send($data) return; } + if ($this->transport) { + return call_user_func($this->transport, $this, $data); + } + $message = Raven_Compat::json_encode($data); if (function_exists("gzcompress")) { @@ -682,13 +719,10 @@ public function send($data) } $message = base64_encode($message); // PHP's builtin curl_* function are happy without this, but the exec method requires it - $client_string = 'sentry-php/' . self::VERSION; - $timestamp = microtime(true); $headers = array( - 'User-Agent' => $client_string, - 'X-Sentry-Auth' => $this->get_auth_header( - $timestamp, $client_string, $this->public_key, - $this->secret_key), + 'User-Agent' => $this->getUserAgent(), + 'X-Sentry-Auth' => $this->getAuthHeader(), + 'Content-Encoding' => 'gzip', 'Content-Type' => 'application/octet-stream' ); @@ -878,6 +912,12 @@ protected function get_auth_header($timestamp, $client, $api_key, $secret_key) return sprintf('Sentry %s', implode(', ', $header)); } + public function getAuthHeader($client) + { + $timestamp = microtime(true); + return $this->get_auth_header($timestamp, $this->getUserAgent(), $this->public_key, $this->secret_key); + } + /** * Generate an uuid4 value * diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index fd0da164b..d92f1b340 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -670,6 +670,18 @@ public function testGetLastEventID() $this->assertEquals($client->getLastEventID(), 'abc'); } + public function testCustomTransport() + { + $events = array(); + + $client = new Raven_Client('https://public:secret@sentry.example.com/1'); + $client->setTransport(function ($client, $data) use (&$events) { + $events[] = $data; + }); + $client->capture(array('message' => 'test', 'event_id' => 'abc')); + $this->assertEquals(count($events), 1); + } + public function cb1($data) { $this->assertEquals('test', $data['message']); From 24ab03373aeec7bcdedc996e1389ac869cd349c4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 9 Jun 2016 16:08:00 -0700 Subject: [PATCH 0123/1161] Changes for 0.21.0 --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 8f5a32f4b..0a061e74d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +0.21.0 +------ + +- Added ``transport`` option. +- Added ``install()`` shortcut. + 0.20.0 ------ From 1f343a3a90b216f07f7f3a523226378d0bdeacdd Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 9 Jun 2016 16:08:31 -0700 Subject: [PATCH 0124/1161] 0.22.x-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 86288fe25..c240affd0 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.21.x-dev" + "dev-master": "0.22.x-dev" } } } From ad9fc45b3ab05c8fdd1b353bbcc642b3a03592d7 Mon Sep 17 00:00:00 2001 From: Wico Chandra Date: Fri, 10 Jun 2016 14:30:56 +0700 Subject: [PATCH 0125/1161] Remove unused argument in getAuthHeader() --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 8160e06e8..ae33e730e 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -912,7 +912,7 @@ protected function get_auth_header($timestamp, $client, $api_key, $secret_key) return sprintf('Sentry %s', implode(', ', $header)); } - public function getAuthHeader($client) + public function getAuthHeader() { $timestamp = microtime(true); return $this->get_auth_header($timestamp, $this->getUserAgent(), $this->public_key, $this->secret_key); From 5da8f0ce1ac8ab2a54171862d567cd28b926e16d Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Fri, 10 Jun 2016 15:05:34 +0200 Subject: [PATCH 0126/1161] Make serializer transcoding more resilient mb_convert_encoding throws a warning if it can't detect the encoding. this cases could be prevented by using mb_detect_encoding. --- lib/Raven/ReprSerializer.php | 7 +++++-- lib/Raven/Serializer.php | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php index 579b53bb5..88231ed83 100644 --- a/lib/Raven/ReprSerializer.php +++ b/lib/Raven/ReprSerializer.php @@ -37,8 +37,11 @@ protected function serializeValue($value) } else { $value = (string) $value; - if (function_exists('mb_convert_encoding')) { - $value = mb_convert_encoding($value, 'UTF-8', 'auto'); + if (function_exists('mb_detect_encoding') + && function_exists('mb_convert_encoding') + && $currentEncoding = mb_detect_encoding($value, 'auto') + ) { + $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); } return $value; diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index c60ee8565..321d93ca8 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -59,8 +59,11 @@ protected function serializeValue($value) } else { $value = (string) $value; - if (function_exists('mb_convert_encoding')) { - $value = mb_convert_encoding($value, 'UTF-8', 'auto'); + if (function_exists('mb_detect_encoding') + && function_exists('mb_convert_encoding') + && $currentEncoding = mb_detect_encoding($value, 'auto') + ) { + $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); } return $value; From 34efdb1670ad15c9be190b690bdd67bbf1d32549 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 10 Jun 2016 10:34:49 -0700 Subject: [PATCH 0127/1161] Remove invalid encoding (fixes GH-312) --- lib/Raven/Client.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ae33e730e..098973b8c 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -722,7 +722,6 @@ public function send($data) $headers = array( 'User-Agent' => $this->getUserAgent(), 'X-Sentry-Auth' => $this->getAuthHeader(), - 'Content-Encoding' => 'gzip', 'Content-Type' => 'application/octet-stream' ); From 3827d5a2e7077a728099e7d1ae50f95d01a41207 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 10 Jun 2016 11:08:32 -0700 Subject: [PATCH 0128/1161] Add formatted message to interface --- lib/Raven/Client.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 098973b8c..ad1fd2387 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -342,6 +342,7 @@ public function captureMessage($message, $params=array(), $data=array(), $data['sentry.interfaces.Message'] = array( 'message' => $message, 'params' => $params, + 'formatted' => $formatted_message, ); return $this->capture($data, $stack, $vars); From d205b8930704972f3b596f66869dcabbe4e2d11e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 10 Jun 2016 11:15:46 -0700 Subject: [PATCH 0129/1161] Correct formatted test --- test/Raven/Tests/ClientTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index d92f1b340..236eb4a65 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -288,6 +288,7 @@ public function testCaptureMessageSetsInterface() $this->assertEquals($event['sentry.interfaces.Message'], array( 'message' => 'Test Message %s', 'params' => array('foo'), + 'formatted' => 'Test Message foo', )); } From 3e192e3adb4c88fec8dbd69d6db71fb1191a4b01 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Wed, 15 Jun 2016 10:46:18 +0200 Subject: [PATCH 0130/1161] fabpot/php-cs-fixer is abandoned > The author suggests using the friendsofphp/php-cs-fixer package instead. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c240affd0..2fde6239d 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require-dev": { - "fabpot/php-cs-fixer": "^1.8.0", + "friendsofphp/php-cs-fixer": "^1.8.0", "phpunit/phpunit": "^4.6.6" }, "require": { From d424d23a29f585d17a5ced532d9a55ad648b746b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 23 Jun 2016 12:40:37 -0700 Subject: [PATCH 0131/1161] Changes for 0.22.0 --- CHANGES | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 0a061e74d..9023592c8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +0.22.0 +------ + +- Improve handling of encodings. +- Improve resiliency of variable serialization. +- Add 'formatted' attribute to Message interface. + 0.21.0 ------ From dc003d8688750de78751fbbfd26c9e34d1dc476f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 23 Jun 2016 12:46:55 -0700 Subject: [PATCH 0132/1161] 0.23.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2fde6239d..84b3d0b67 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.22.x-dev" + "dev-master": "0.23.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ad1fd2387..b26b1bd33 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '0.21.x-dev'; + const VERSION = '0.23.x-dev'; const PROTOCOL = '6'; From 01f53a76f875e26396f9f9a2a55c71a046cd30e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Slobodan=20Mi=C5=A1kovi=C4=87?= Date: Fri, 1 Jul 2016 16:01:58 -0700 Subject: [PATCH 0133/1161] Fix php doc for captureException --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index b26b1bd33..6e2b7774d 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -351,7 +351,7 @@ public function captureMessage($message, $params=array(), $data=array(), /** * Log an exception to sentry * - * @param string $exception The Exception object. + * @param Exception $exception The Exception object. * @param array $data Additional attributes to pass with this event (see Sentry docs). */ public function captureException($exception, $data=null, $logger=null, $vars=null) From b62928b546443ab34f29add1c2ab0ea52f945754 Mon Sep 17 00:00:00 2001 From: Tobias Nitsche Date: Wed, 13 Jul 2016 11:01:03 +0200 Subject: [PATCH 0134/1161] Encodes the stacktrace, if available. Also provides the possibily to provide an own mb_detect_order for a more reliable detection of encoding. --- lib/Raven/Client.php | 9 +++++-- lib/Raven/ReprSerializer.php | 2 +- lib/Raven/Serializer.php | 52 ++++++++++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 6e2b7774d..b460d65c3 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -87,7 +87,7 @@ public function __construct($options_or_dsn=null, $options=array()) // app path is used to determine if code is part of your application $this->app_path = Raven_Util::get($options, 'app_path', null); $this->transport = Raven_Util::get($options, 'transport', null); - ; + $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null); $this->processors = $this->setProcessorsFromOptions($options); @@ -652,7 +652,7 @@ public function capture($data, $stack = null, $vars = null) public function sanitize(&$data) { // attempt to sanitize any user provided data - $serializer = new Raven_Serializer(); + $serializer = new Raven_Serializer($this->mb_detect_order); if (!empty($data['request'])) { $data['request'] = $serializer->serialize($data['request']); } @@ -665,6 +665,11 @@ public function sanitize(&$data) if (!empty($data['tags'])) { $data['tags'] = $serializer->serialize($data['tags']); } + if (!empty($data['stacktrace']) && !empty($data['stacktrace']['frames'])) { + $data['stacktrace']['frames'] = $serializer->serialize($data['stacktrace']['frames']); + } + + $serializer->serialize($data); } /** diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php index 88231ed83..6610fc612 100644 --- a/lib/Raven/ReprSerializer.php +++ b/lib/Raven/ReprSerializer.php @@ -39,7 +39,7 @@ protected function serializeValue($value) if (function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding') - && $currentEncoding = mb_detect_encoding($value, 'auto') + && $currentEncoding = mb_detect_encoding($value, $this->getMbDetectOrder()) ) { $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); } diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 321d93ca8..a5f4bd6b4 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -26,6 +26,34 @@ */ class Raven_Serializer { + /* + * The default mb detect order + * + * @see http://php.net/manual/en/function.mb-detect-encoding.php + */ + const DEFAULT_MB_DETECT_ORDER = 'auto'; + + /* + * Suggested detect order for western countries + */ + const WESTERN_MB_DETECT_ORDER = 'UTF-8, ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-8859-10, ISO-8859-13, ISO-8859-14, ISO-8859-15, ISO-8859-16, Windows-1251, Windows-1252, Windows-1254'; + + /** + * This is the default mb detect order for the detection of encoding + * + * @var string + */ + private $mb_detect_order= self::DEFAULT_MB_DETECT_ORDER; + + /** + * @param null|string $mb_detect_order + */ + public function __construct($mb_detect_order = null) + { + if ($mb_detect_order != null) { + $this->mb_detect_order = $mb_detect_order; + } + } /** * Serialize an object (recursively) into something safe for data * sanitization and encoding. @@ -58,10 +86,9 @@ protected function serializeValue($value) return 'Array of length ' . count($value); } else { $value = (string) $value; - if (function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding') - && $currentEncoding = mb_detect_encoding($value, 'auto') + && $currentEncoding = mb_detect_encoding($value, $this->mb_detect_order) ) { $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); } @@ -69,4 +96,25 @@ protected function serializeValue($value) return $value; } } + + + /** + * @return string + */ + public function getMbDetectOrder() + { + return $this->mb_detect_order; + } + + /** + * @param string $mb_detect_order + * + * @return Raven_Serializer + */ + public function setMbDetectOrder($mb_detect_order) + { + $this->mb_detect_order = $mb_detect_order; + + return $this; + } } From b2e3d65e5b510114c194386dcc0056585ca651fe Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 19 Jul 2016 09:41:17 -0700 Subject: [PATCH 0135/1161] Support data mutation in send_callback Fixes GH-323 @getsentry/php --- lib/Raven/Client.php | 2 +- test/Raven/Tests/ClientTest.php | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 6e2b7774d..25a68d911 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -698,7 +698,7 @@ public function sendUnsentErrors() * * @param array $data Associative array of data to log */ - public function send($data) + public function send(&$data) { if (is_callable($this->send_callback) && call_user_func($this->send_callback, $data) === false) { // if send_callback returns false, end native send diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 236eb4a65..b1be7d154 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -17,7 +17,7 @@ public function getSentEvents() { return $this->__sent_events; } - public function send($data) + public function send(&$data) { if (is_callable($this->send_callback) && !call_user_func($this->send_callback, $data)) { // if send_callback returns falsely, end native send @@ -695,6 +695,12 @@ public function cb2($data) return true; } + public function cb3(&$data) + { + unset($data['message']); + return true; + } + public function testSendCallback() { $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb1'))); @@ -706,6 +712,12 @@ public function testSendCallback() $client->captureMessage('test'); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); + + $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb3'))); + $client->captureMessage('test'); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $this->assertEquals(empty($events[0]['message']), true); } /** From dadb966da0b8036e55ac9d1e2bcb5da125020eba Mon Sep 17 00:00:00 2001 From: Phil Cross Date: Wed, 20 Jul 2016 10:33:04 +0100 Subject: [PATCH 0136/1161] Allow array to be passed by reference --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 25a68d911..2b74cf898 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -700,7 +700,7 @@ public function sendUnsentErrors() */ public function send(&$data) { - if (is_callable($this->send_callback) && call_user_func($this->send_callback, $data) === false) { + if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { // if send_callback returns false, end native send return; } From 7bb92fb164651f71fc462eb2a44eb26c8ea2207c Mon Sep 17 00:00:00 2001 From: Phil Cross Date: Wed, 20 Jul 2016 10:33:12 +0100 Subject: [PATCH 0137/1161] Update test --- test/Raven/Tests/ClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index b1be7d154..a001299be 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -19,7 +19,7 @@ public function getSentEvents() } public function send(&$data) { - if (is_callable($this->send_callback) && !call_user_func($this->send_callback, $data)) { + if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { // if send_callback returns falsely, end native send return; } From 8ca704a9e3836bb9c822daa9fd677456abaf2248 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Jul 2016 12:32:08 -0700 Subject: [PATCH 0138/1161] Correct invalid args on getters --- lib/Raven/Client.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index a8b0006f3..731089756 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -122,7 +122,7 @@ public function install() return $this; } - public function getRelease($value) + public function getRelease() { return $this->release; } @@ -133,7 +133,7 @@ public function setRelease($value) return $this; } - public function getEnvironment($value) + public function getEnvironment() { return $this->environment; } @@ -144,7 +144,7 @@ public function setEnvironment($value) return $this; } - public function getAppPath($value) + public function getAppPath() { return $this->app_path; } @@ -155,7 +155,7 @@ public function setAppPath($value) return $this; } - public function getPrefixes($value) + public function getPrefixes() { return $this->prefixes; } @@ -166,7 +166,7 @@ public function setPrefixes($value) return $this; } - public function getSendCallback($value) + public function getSendCallback() { return $this->send_callback; } @@ -177,7 +177,7 @@ public function setSendCallback($value) return $this; } - public function getTransport($value) + public function getTransport() { return $this->transport; } From 6061e3850dea2d77b8f5a684db6eaf5950e6cc7b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Jul 2016 12:41:19 -0700 Subject: [PATCH 0139/1161] Evaluate appPath when binding --- lib/Raven/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 731089756..c33ccae19 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -85,10 +85,10 @@ public function __construct($options_or_dsn=null, $options=array()) // a list of prefixes used to coerce absolute paths into relative $this->prefixes = Raven_Util::get($options, 'prefixes', null); // app path is used to determine if code is part of your application - $this->app_path = Raven_Util::get($options, 'app_path', null); $this->transport = Raven_Util::get($options, 'transport', null); $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null); + $this->setAppPath(Raven_Util::get($options, 'app_path', null)); $this->processors = $this->setProcessorsFromOptions($options); $this->_lasterror = null; @@ -151,7 +151,7 @@ public function getAppPath() public function setAppPath($value) { - $this->app_path = $value; + $this->app_path = $value ? realpath($value) : null; return $this; } From a984843106b1bc33a2c281125dea1fa32c00f8d6 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 21 Jul 2016 12:46:03 -0700 Subject: [PATCH 0140/1161] Normalize prefixes --- lib/Raven/Client.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c33ccae19..8417544fb 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -82,13 +82,13 @@ public function __construct($options_or_dsn=null, $options=array()) $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true); $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); - // a list of prefixes used to coerce absolute paths into relative - $this->prefixes = Raven_Util::get($options, 'prefixes', null); - // app path is used to determine if code is part of your application $this->transport = Raven_Util::get($options, 'transport', null); $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null); + // app path is used to determine if code is part of your application $this->setAppPath(Raven_Util::get($options, 'app_path', null)); + // a list of prefixes used to coerce absolute paths into relative + $this->setPrefixes(Raven_Util::get($options, 'prefixes', null)); $this->processors = $this->setProcessorsFromOptions($options); $this->_lasterror = null; @@ -162,7 +162,7 @@ public function getPrefixes() public function setPrefixes($value) { - $this->prefixes = $value; + $this->prefixes = $value ? array_map('realpath', $value) : $value; return $this; } From 35245bca16fa6a9be4ab2efc977ddfcdf422b957 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 22 Jul 2016 09:48:13 -0700 Subject: [PATCH 0141/1161] Remove env data from HTTP This contains potentially sensitive information, and as we move forward with Sentry it no longer logically makes sense for it to exist as part of this. If users want it they can use ``extra_context``. @getsentry/php --- lib/Raven/Client.php | 7 +------ test/Raven/Tests/ClientTest.php | 9 --------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 8417544fb..14d07d72c 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -470,15 +470,13 @@ protected function is_http_request() protected function get_http_data() { - $env = $headers = array(); + $headers = array(); foreach ($_SERVER as $key => $value) { if (0 === strpos($key, 'HTTP_')) { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value; } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))))] = $value; - } else { - $env[$key] = $value; } } @@ -499,9 +497,6 @@ protected function get_http_data() if (!empty($headers)) { $result['headers'] = $headers; } - if (!empty($env)) { - $result['env'] = $env; - } return array( 'request' => $result, diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index a001299be..5ee3a403d 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -529,15 +529,6 @@ public function testGetHttpData() 'Content-Type' => 'text/xml', 'Content-Length' => '99', ), - 'env' => array( - 'REDIRECT_STATUS' => '200', - 'SERVER_PORT' => '443', - 'SERVER_PROTOCOL' => 'HTTP/1.1', - 'REQUEST_METHOD' => 'PATCH', - 'QUERY_STRING' => 'q=bitch&l=en', - 'REQUEST_URI' => '/welcome/', - 'SCRIPT_NAME' => '/index.php', - ), ) ); From 673b8775e4f72230cb55be5e416768daff7f9035 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 25 Jul 2016 13:44:21 -0700 Subject: [PATCH 0142/1161] Expand example app --- examples/vanilla/index.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php index 38a5edf75..ad7b342f2 100644 --- a/examples/vanilla/index.php +++ b/examples/vanilla/index.php @@ -2,21 +2,24 @@ error_reporting(E_ALL); +define('SENTRY_DSN', 'https://e9ebbd88548a441288393c457ec90441:399aaee02d454e2ca91351f29bdc3a07@app.getsentry.com/3235'); + require_once '../../lib/Raven/Autoloader.php'; Raven_Autoloader::register(); - function setupSentry() { - $client = new \Raven_Client('https://e9ebbd88548a441288393c457ec90441:399aaee02d454e2ca91351f29bdc3a07@app.getsentry.com/3235'); - $error_handler = new Raven_ErrorHandler($client); - $error_handler->registerExceptionHandler(); - $error_handler->registerErrorHandler(); - $error_handler->registerShutdownFunction(); + (new \Raven_Client(SENTRY_DSN)) + ->setAppPath(__DIR__) + ->setRelease(Raven_Client::VERSION) + ->setPrefixes(array(__DIR__)) + ->install(); } function createError() { + echo($undefined['foobar']); + 1 / 0; } From 88a239c8f39dd6358597662f01b41c5c220851be Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 25 Jul 2016 13:49:21 -0700 Subject: [PATCH 0143/1161] Expand example to include exception --- examples/vanilla/index.php | 15 ++++++++++++++- lib/Raven/ErrorHandler.php | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php index ad7b342f2..849f45062 100644 --- a/examples/vanilla/index.php +++ b/examples/vanilla/index.php @@ -16,12 +16,25 @@ function setupSentry() ->install(); } -function createError() +function createCrumbs() { echo($undefined['foobar']); + echo($undefined['bizbaz']); + +} +function createError() +{ 1 / 0; } + +function createException() +{ + throw new Exception('example exception'); +} + setupSentry(); +createCrumbs(); createError(); +createException(); diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 79bb5f2a8..d8f877044 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -96,7 +96,7 @@ public function __construct($client, $send_errors_last = false, $default_error_t public function handleException($e, $isError = false, $vars = null) { - $e->event_id = $this->client->getIdent($this->client->captureException($e, null, null, $vars)); + $e->event_id = $this->client->captureException($e, null, null, $vars); if (!$isError && $this->call_existing_exception_handler && $this->old_exception_handler) { call_user_func($this->old_exception_handler, $e); From c4e0a4165a00c3a1186e8554058a29bc197f1d5e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 25 Jul 2016 14:23:05 -0700 Subject: [PATCH 0144/1161] Lint --- examples/vanilla/index.php | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php index 849f45062..c96c0af97 100644 --- a/examples/vanilla/index.php +++ b/examples/vanilla/index.php @@ -20,7 +20,6 @@ function createCrumbs() { echo($undefined['foobar']); echo($undefined['bizbaz']); - } function createError() From e2f077d72b7492b921752f3f4a7a27a3973469f3 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 27 Jul 2016 14:40:37 -0400 Subject: [PATCH 0145/1161] Remove deprecated ErrorHandler configuration This removes the deprecated error types confgurations for fatal errors. They will now use the standard error types configured handler-wide. Fixes GH-168 @getsentry/php --- lib/Raven/ErrorHandler.php | 92 ++++++++------------------------------ 1 file changed, 19 insertions(+), 73 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index d8f877044..fa8a1df82 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -40,51 +40,15 @@ class Raven_ErrorHandler */ private $error_types = null; - /** - * @deprecated - * @var array - * Error types that can be processed by the handler - */ - private $validErrorTypes = array( - E_ERROR, - E_WARNING, - E_PARSE, - E_NOTICE, - E_CORE_ERROR, - E_CORE_WARNING, - E_COMPILE_ERROR, - E_COMPILE_WARNING, - E_USER_ERROR, - E_USER_WARNING, - E_USER_NOTICE, - E_STRICT, - E_RECOVERABLE_ERROR, - E_DEPRECATED, - E_USER_DEPRECATED, - ); - - /** - * @deprecated - * @var array - * The default Error types that are always processed by the handler. Can be set during construction. - */ - private $defaultErrorTypes = array( - E_ERROR, - E_PARSE, - E_CORE_ERROR, - E_CORE_WARNING, - E_COMPILE_ERROR, - E_COMPILE_WARNING, - E_STRICT, - ); - - public function __construct($client, $send_errors_last = false, $default_error_types = null, - $error_types = null) + public function __construct($client, $send_errors_last = false, $error_types = null, + $__error_types = null) { - $this->client = $client; - if ($default_error_types !== null) { - $this->defaultErrorTypes = $default_error_types; + // support legacy fourth argument for error types + if ($error_types === null) { + $error_types = $__error_types; } + + $this->client = $client; $this->error_types = $error_types; register_shutdown_function(array($this, 'detectShutdown')); if ($send_errors_last) { @@ -124,30 +88,6 @@ public function handleError($code, $message, $file = '', $line = 0, $context=arr } } - /** - * Nothing by default, use it in child classes for catching other types of errors - * Only constants from $this->validErrorTypes can be used - * - * @deprecated - * @return array - */ - - protected function getAdditionalErrorTypesToProcess() - { - return array(); - } - - /** - * @deprecated - * @return array - */ - private function getErrorTypesToProcess() - { - $additionalErrorTypes = array_intersect($this->getAdditionalErrorTypesToProcess(), $this->validErrorTypes); - // array_unique so bitwise "or" operation wouldn't fail if some error type gets repeated - return array_unique(array_merge($this->defaultErrorTypes, $additionalErrorTypes)); - } - public function handleFatalError() { if (null === $lastError = error_get_last()) { @@ -161,12 +101,18 @@ public function handleFatalError() $errors |= $errorType; } - if ($lastError['type'] & $errors) { - $e = new ErrorException( - @$lastError['message'], @$lastError['type'], @$lastError['type'], - @$lastError['file'], @$lastError['line'] - ); - $this->handleException($e, true); + if (error_reporting() !== 0) { + $error_types = $this->error_types; + if ($error_types === null) { + $error_types = error_reporting(); + } + if ($error_types & $lastError['type']) { + $e = new ErrorException( + @$lastError['message'], @$lastError['type'], @$lastError['type'], + @$lastError['file'], @$lastError['line'] + ); + $this->handleException($e, true); + } } } From 882cb7ba12a4a0b32fee12fb00f26722e45e6541 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 28 Jul 2016 09:24:59 -0400 Subject: [PATCH 0146/1161] Dont pass message with exceptions --- lib/Raven/Client.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 14d07d72c..738022e97 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -370,12 +370,6 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul ); } - // TODO(dcramer): DRY this up - $message = $exception->getMessage(); - if (empty($message)) { - $message = get_class($exception); - } - $exc = $exception; do { $exc_data = array( @@ -411,7 +405,6 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul $exceptions[] = $exc_data; } while ($has_chained_exceptions && $exc = $exc->getPrevious()); - $data['message'] = $message; $data['exception'] = array( 'values' => array_reverse($exceptions), ); From 8e425a81cea8ac95f6da67b2f62f6414175f378c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 28 Jul 2016 09:29:24 -0400 Subject: [PATCH 0147/1161] Changes for 1.0 --- CHANGES | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index 9023592c8..394427c12 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,13 @@ +1.0.0 +----- + +- Removed deprecated error codes configuration from ErrorHandler. +- Removed env data from HTTP interface. +- Removed 'message' attribute from exceptions'. +- appPath and prefixes are now resolved fully. +- Fixed various getter methods requiring invalid args. +- Fixed data mutation with 'send_callback'. + 0.22.0 ------ From a771a2962fa0e5b172ec050a7fd25ebbc835fcde Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 28 Jul 2016 09:31:01 -0400 Subject: [PATCH 0148/1161] 1.1.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 84b3d0b67..7df7ce661 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.23.x-dev" + "dev-master": "1.1.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 738022e97..3030d939f 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '0.23.x-dev'; + const VERSION = '1.1.x-dev'; const PROTOCOL = '6'; From df28ae18e5bedc1bb78c2146329a3ad068e6d2cb Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Jul 2016 11:33:03 -0400 Subject: [PATCH 0149/1161] Remove bad call to deprecated function --- lib/Raven/ErrorHandler.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index fa8a1df82..9a01f502e 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -96,11 +96,6 @@ public function handleFatalError() unset($this->reservedMemory); - $errors = 0; - foreach ($this->getErrorTypesToProcess() as $errorType) { - $errors |= $errorType; - } - if (error_reporting() !== 0) { $error_types = $this->error_types; if ($error_types === null) { From f8ddaac229b77b2a3a10d2e3caf6d6749d60075f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Jul 2016 11:40:43 -0400 Subject: [PATCH 0150/1161] Basic test for handleFatalError --- test/Raven/Tests/ErrorHandlerTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index fb6b224f0..0d066a6cb 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -130,6 +130,21 @@ public function testErrorHandlerDefaultsErrorReporting() trigger_error('Warning', E_USER_WARNING); } + public function testHandleFatalError() + { + $client = $this->getMock('Client', array('captureException')); + $client->expects($this->once()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + + # http://php.net/manual/en/function.error-get-last.php#113518 + set_error_handler('var_dump', 0); + @$undef_var; + restore_error_handler(); + + $handler->handleFatalError(); + } public function testFluidInterface() { From d3f805ff4073e92ccd6f67bf6aa22e856bda158d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Jul 2016 11:51:24 -0400 Subject: [PATCH 0151/1161] Document feedback in Laravel --- docs/integrations/laravel.rst | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 9a0a24607..a4bce4afd 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -52,6 +52,48 @@ Add your DSN to ``.env``: SENTRY_DSN=___DSN___ +Finally, if you wish to wire up User Feedback, you can do so by creating a custom +error response. To do this, open up ``App/Exceptions/Handler.php`` and except the +``render`` method: + +.. code-block:: php + + view('errors.500', [ + 'sentryID' => app('sentry')->getLastEventID(), + ], 500); + } + } + +Next, create ``resources/views/errors/500.blade.php``, and embed the feedback code:: + +.. code-block:: blade + +
+
Something went wrong.
+ @unless(empty($sentryID)) + + + + + @endunless +
+ +That's it! + Laravel 4.x ----------- From 3208569bf9a70bd434c285cc2b0f68902272e361 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Jul 2016 11:58:28 -0400 Subject: [PATCH 0152/1161] Better implementation for feedback --- docs/integrations/laravel.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index a4bce4afd..4ecc56440 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -62,11 +62,22 @@ error response. To do this, open up ``App/Exceptions/Handler.php`` and except th class Handler extends ExceptionHandler { + private $sentryID; + + public function report(Exception $e) + { + if ($this->shouldReport($e)) { + // bind the event ID for Feedback + $this->sentryID = app('sentry')->captureException($e); + } + parent::report($e); + } + // ... public function render($request, Exception $e) { return response()->view('errors.500', [ - 'sentryID' => app('sentry')->getLastEventID(), + 'sentryID' => $this->sentryID, ], 500); } } From fff9562a21c955775d744ceac6d7bc4f6ec11a85 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Jul 2016 12:08:40 -0400 Subject: [PATCH 0153/1161] Bad syntax --- docs/integrations/laravel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 4ecc56440..3ea4da8be 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -82,7 +82,7 @@ error response. To do this, open up ``App/Exceptions/Handler.php`` and except th } } -Next, create ``resources/views/errors/500.blade.php``, and embed the feedback code:: +Next, create ``resources/views/errors/500.blade.php``, and embed the feedback code: .. code-block:: blade From 365ba7c1e7f6ed755eedb8a58558122c4b65011a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 29 Jul 2016 12:12:47 -0400 Subject: [PATCH 0154/1161] No blade lexer :( --- docs/integrations/laravel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 3ea4da8be..2af620f77 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -84,7 +84,7 @@ error response. To do this, open up ``App/Exceptions/Handler.php`` and except th Next, create ``resources/views/errors/500.blade.php``, and embed the feedback code: -.. code-block:: blade +.. code-block:: html
Something went wrong.
From d34ab2569aa4841230f6125585c3fe426652137f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 25 Jul 2016 14:17:07 -0700 Subject: [PATCH 0155/1161] Handle invalid encoding errors gracefully Fixes GH-322 @getsentry/php --- lib/Raven/Client.php | 29 +++++++++++++++++++++++------ lib/Raven/ReprSerializer.php | 11 +---------- lib/Raven/Serializer.php | 27 ++++++++++++++++++--------- test/Raven/Tests/ClientTest.php | 29 +++++++++++++++++++++++++++++ test/data/binary | Bin 0 -> 128 bytes 5 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 test/data/binary diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 3030d939f..1b057dad3 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -686,6 +686,28 @@ public function sendUnsentErrors() } } + public function encode(&$data) + { + $message = Raven_Compat::json_encode($data); + if ($message === false) { + if (function_exists('json_last_error_msg')) { + $this->_lasterror = json_last_error_msg(); + } else { + $this->_lasterror = json_last_error(); + } + return false; + } + + if (function_exists("gzcompress")) { + $message = gzcompress($message); + } + + // PHP's builtin curl_* function are happy without this, but the exec method requires it + $message = base64_encode($message); + + return $message; + } + /** * Wrapper to handle encoding and sending data to the Sentry API server. * @@ -706,12 +728,7 @@ public function send(&$data) return call_user_func($this->transport, $this, $data); } - $message = Raven_Compat::json_encode($data); - - if (function_exists("gzcompress")) { - $message = gzcompress($message); - } - $message = base64_encode($message); // PHP's builtin curl_* function are happy without this, but the exec method requires it + $message = $this->encode($data); $headers = array( 'User-Agent' => $this->getUserAgent(), diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php index 6610fc612..2b2f42658 100644 --- a/lib/Raven/ReprSerializer.php +++ b/lib/Raven/ReprSerializer.php @@ -35,16 +35,7 @@ protected function serializeValue($value) } elseif (is_array($value)) { return 'Array of length ' . count($value); } else { - $value = (string) $value; - - if (function_exists('mb_detect_encoding') - && function_exists('mb_convert_encoding') - && $currentEncoding = mb_detect_encoding($value, $this->getMbDetectOrder()) - ) { - $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); - } - - return $value; + return $this->serializeString($value); } } } diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index a5f4bd6b4..c3355d68e 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -74,6 +74,23 @@ public function serialize($value, $max_depth=3, $_depth=0) } } + protected function serializeString($value) + { + $value = (string) $value; + if (function_exists('mb_detect_encoding') + && function_exists('mb_convert_encoding') + ) { + // we always gurantee this is coerced, even if we can't detect encoding + if ($currentEncoding = mb_detect_encoding($value, $this->mb_detect_order)) { + $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); + } else { + $value = mb_convert_encoding($value, 'UTF-8'); + } + } + + return $value; + } + protected function serializeValue($value) { if (is_null($value) || is_bool($value) || is_float($value) || is_integer($value)) { @@ -85,15 +102,7 @@ protected function serializeValue($value) } elseif (is_array($value)) { return 'Array of length ' . count($value); } else { - $value = (string) $value; - if (function_exists('mb_detect_encoding') - && function_exists('mb_convert_encoding') - && $currentEncoding = mb_detect_encoding($value, $this->mb_detect_order) - ) { - $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); - } - - return $value; + return $this->serializeString($value); } } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 5ee3a403d..546f7d3b0 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -8,6 +8,19 @@ * file that was distributed with this source code. */ +function simple_function($a=null, $b=null, $c=null) +{ + assert(0); +} + +function invalid_encoding() +{ + $fp = fopen(__DIR__ . '/../../data/binary', 'r'); + simple_function(fread($fp, 64)); + fclose($fp); +} + + // XXX: Is there a better way to stub the client? class Dummy_Raven_Client extends Raven_Client { @@ -427,6 +440,22 @@ public function testCaptureExceptionHandlesExcludeOption() $this->assertEquals(count($events), 0); } + public function testCaptureExceptionInvalidUTF8() + { + $client = new Dummy_Raven_Client(); + try { + invalid_encoding(); + } catch (Exception $ex) { + $client->captureException($ex); + } + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + + // if this fails to encode it returns false + $message = $client->encode($events[0]); + $this->assertNotEquals($message, false, $client->getLastError()); + } + public function testDoesRegisterProcessors() { $client = new Dummy_Raven_Client(array( diff --git a/test/data/binary b/test/data/binary new file mode 100644 index 0000000000000000000000000000000000000000..a10c242e0ddc267623eaddba058ab8554d687ea3 GIT binary patch literal 128 zcmV-`0Du2IPfVTpcWcyB=dagn7|>m@0z2ew>--#YD^o_57e9z literal 0 HcmV?d00001 From 4d749a590ab67995c23dcd241a649fc3f7616355 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sat, 30 Jul 2016 14:25:05 -0400 Subject: [PATCH 0156/1161] Prevent install() from being called multiple twices --- lib/Raven/Client.php | 13 +++++++++---- lib/Raven/ErrorHandler.php | 1 + lib/Raven/Exception.php | 4 ++++ test/Raven/Tests/ClientTest.php | 11 +++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 lib/Raven/Exception.php diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 1b057dad3..82ad5cfaf 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -35,6 +35,8 @@ class Raven_Client public $severity_map; public $store_errors_for_bulk_send = false; + private $error_handler; + public function __construct($options_or_dsn=null, $options=array()) { if (is_array($options_or_dsn)) { @@ -115,10 +117,13 @@ public function __construct($options_or_dsn=null, $options=array()) */ public function install() { - $error_handler = new Raven_ErrorHandler($this); - $error_handler->registerExceptionHandler(); - $error_handler->registerErrorHandler(); - $error_handler->registerShutdownFunction(); + if ($this->error_handler) { + throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this))); + } + $this->error_handler = new Raven_ErrorHandler($this); + $this->error_handler->registerExceptionHandler(); + $this->error_handler->registerErrorHandler(); + $this->error_handler->registerShutdownFunction(); return $this; } diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 9a01f502e..364e4b73e 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -143,6 +143,7 @@ public function registerShutdownFunction($reservedMemorySize = 10) return $this; } + // TODO(dcramer): this should be in Client public function detectShutdown() { if (!defined('RAVEN_CLIENT_END_REACHED')) { diff --git a/lib/Raven/Exception.php b/lib/Raven/Exception.php new file mode 100644 index 000000000..a6199f4c1 --- /dev/null +++ b/lib/Raven/Exception.php @@ -0,0 +1,4 @@ +assertEquals(count($events), 1); } + /** + * @expectedException Raven_Exception + * @expectedExceptionMessage Raven_Client->install() must only be called once + */ + public function testCannotInstallTwice() + { + $client = new Raven_Client('https://public:secret@sentry.example.com/1'); + $client->install(); + $client->install(); + } + public function cb1($data) { $this->assertEquals('test', $data['message']); From 72f58d468984c82a72ee3e70c7b301e890c52428 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sat, 30 Jul 2016 14:28:14 -0400 Subject: [PATCH 0157/1161] Changes for 1.1.0 --- CHANGES | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 394427c12..41b440359 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +1.1.0 +----- + +- Uncoercable values should no longer prevent exceptions from sending + to the Sentry server. +- ``install()`` can no longer be called multiple times. + 1.0.0 ----- From 869876e225d91519436eb97566c228b015b774af Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sat, 30 Jul 2016 14:28:56 -0400 Subject: [PATCH 0158/1161] 1.2.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7df7ce661..7fe213fe9 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 82ad5cfaf..c04013103 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '1.1.x-dev'; + const VERSION = '1.2.x-dev'; const PROTOCOL = '6'; From 804ebc109398935c3394c39c4d4b95ec0a1154df Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 8 Aug 2016 16:17:57 -0700 Subject: [PATCH 0159/1161] Set a maximum length for serialization of strings (#329) * Set a maximum length for serialization of strings Refs GH-284 --- lib/Raven/Serializer.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index c3355d68e..50650f7f3 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -88,6 +88,10 @@ protected function serializeString($value) } } + if (strlen($value) > 1024) { + $value = substr($value, 0, 1014) . ' {clipped}'; + } + return $value; } From 3fa2f143aa01f157ba6a6e6547a0f03ac439b56c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 9 Aug 2016 15:15:53 -0700 Subject: [PATCH 0160/1161] Remove invalid mention of async --- docs/usage.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 12be3a262..83cabb6a6 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -273,6 +273,3 @@ credentials with the Sentry master server:: -> event ID: f1765c9aed4f4ceebe5a93df9eb2d34f Done! - -.. note:: The CLI enforces the synchronous option on HTTP requests whereas - the default configuration is asynchronous. From d37e243fb53d0616b0cd0b679655ed24ec552995 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 11 Aug 2016 12:04:52 -0700 Subject: [PATCH 0161/1161] Improve error handling - Dont report duplicate errors during fatal - Add ``captureLastError`` helper Refs GH-332 --- docs/usage.rst | 74 ++++++++++++++++++++------- lib/Raven/Client.php | 18 +++++++ lib/Raven/ErrorHandler.php | 66 +++++++++++++++++++----- test/Raven/Tests/ErrorHandlerTest.php | 19 +++++++ 4 files changed, 145 insertions(+), 32 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 83cabb6a6..2aa756745 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -12,22 +12,27 @@ once and reference it from anywhere you want to interface with Sentry: .. code-block:: php - $client = new Raven_Client('___DSN___'); + $sentryClient = new Raven_Client('___DSN___'); + Capturing Errors ---------------- -The most basic functionality is to use Sentry for reporting any uncaught -exceptions or PHP errors. As this functionality is common enough, Sentry -provides support for this out of the box: +Sentry includes basic functionality for reporting any uncaught +exceptions or PHP errors. This is done via the error handler, +and appropriate hooks for each of PHP's built-in reporting: .. code-block:: php - $error_handler = new Raven_ErrorHandler($client); + $error_handler = new Raven_ErrorHandler($sentryClient); $error_handler->registerExceptionHandler(); $error_handler->registerErrorHandler(); $error_handler->registerShutdownFunction(); +.. note:: Calling ``install()`` on a Raven_Client instance will automatically + register these handlers. + + Reporting Exceptions -------------------- @@ -37,38 +42,40 @@ If you want to report exceptions manually you can use the .. code-block:: php // Basic Reporting - $event_id = $client->getIdent($client->captureException($ex)); + $sentryClient->captureException($ex); // Provide some additional data with an exception - $event_id = $client->getIdent($client->captureException($ex, array( + $sentryClient->captureException($ex, array( 'extra' => array( 'php_version' => phpversion() ), - ))); + )); + -Reporting Messages ------------------- +Reporting Other Errors +---------------------- -Sometimes you don't have a PHP error but something bad happened and you +Sometimes you don't have an actual exception object, but something bad happened and you want to report it anyways. This is where `captureMessage` comes in. It takes a message and reports it to sentry. .. code-block:: php // Capture a message - $event_id = $client->getIdent($client->captureMessage('my log message')); + $sentryClient->captureMessage('my log message'); Note, ``captureMessage`` has a slightly different API than ``captureException`` to support parameterized formatting: .. code-block:: php - $client->captureMessage('my %s message', array('log'), array( + $sentryClient->captureMessage('my %s message', array('log'), array( 'extra' => array( 'foo' => 'bar', ), )); + Optional Attributes ------------------- @@ -77,7 +84,10 @@ can be supplied: .. code-block:: php - $client->captureException($ex, array('attr' => 'value')) + $sentryClient->captureException($ex, array( + 'attr' => 'value', + )); + .. describe:: extra @@ -161,7 +171,7 @@ service. .. code-block:: php - $client->getLastEventID(); + $sentryClient->getLastEventID(); .. _php-user-feedback: @@ -209,6 +219,7 @@ That's it! For more details on this feature, see the :doc:`User Feedback guide <../../../learn/user-feedback>`. + Handling Failures ----------------- @@ -218,15 +229,16 @@ helper: .. code-block:: php - if ($client->getLastError() !== null) { + if ($sentryClient->getLastError() !== null) { echo "Something went very, very wrong"; - // $client->getLastError() contains the error that occurred + // $sentryClient->getLastError() contains the error that occurred } else { // Give the user feedback echo "Sorry, there was an error!"; echo "Your reference ID is " . $event_id; } + Breadcrumbs ----------- @@ -234,12 +246,13 @@ Sentry supports capturing breadcrumbs -- events that happened prior to an issue. .. code-block:: php - $client->breadcrumbs->record(array( + $sentryClient->breadcrumbs->record(array( 'message' => 'Authenticating user as ' . $username, 'category' => 'auth', 'level' => 'info', )); + Filtering Out Errors -------------------- @@ -247,7 +260,7 @@ Its common that you might want to prevent automatic capture of certain areas. Id .. code-block:: php - $client->setSendCallback(function($data) { + $sentryClient->setSendCallback(function($data) { $ignore_types = array('Symfony\Component\HttpKernel\Exception\NotFoundHttpException') if (isset($data['exception'] && in_array($data['exception']['values'][0]['type'], $ignore_types) @@ -256,6 +269,29 @@ Its common that you might want to prevent automatic capture of certain areas. Id } }); + +Error Control Operators +----------------------- + +In PHP its fairly common to use the `suppression operator `_ +to avoid bubbling up handled errors: + +.. code-block:: php + + $my_file = @file('non_existent_file'); + +In these situations, Sentry will never capture the error. If you wish to capture it at that stage +you'd need to manually call out to the PHP client: + +.. code-block:: php + + $my_file = @file('non_existent_file'); + if (!$my_file) { + // ... + $sentryClient->captureLastError(); + } + + Testing Your Connection ----------------------- diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c04013103..7c3099bc2 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -428,6 +428,24 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul return $this->capture($data, $trace, $vars); } + + /** + * Capture the most recent error (obtained with ``error_get_last``). + */ + public function captureLastError() + { + if (null === $error = error_get_last()) { + return; + } + + $e = new ErrorException( + @$error['message'], 0, @$error['type'], + @$error['file'], @$error['line'] + ); + + return $this->captureException($e); + } + /** * Log an query to sentry */ diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 364e4b73e..e34f2be88 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -39,6 +39,7 @@ class Raven_ErrorHandler * A 'null' value implies "whatever error_reporting is at time of error". */ private $error_types = null; + private $_last_handled_error = null; public function __construct($client, $send_errors_last = false, $error_types = null, $__error_types = null) @@ -67,21 +68,29 @@ public function handleException($e, $isError = false, $vars = null) } } - public function handleError($code, $message, $file = '', $line = 0, $context=array()) + public function handleError($type, $message, $file = '', $line = 0, $context = array()) { if (error_reporting() !== 0) { $error_types = $this->error_types; if ($error_types === null) { $error_types = error_reporting(); } - if ($error_types & $code) { - $e = new ErrorException($message, 0, $code, $file, $line); + if ($error_types & $type) { + $e = new ErrorException($message, 0, $type, $file, $line); $this->handleException($e, true, $context); + $this->_last_handled_error = $e; } } if ($this->call_existing_error_handler) { if ($this->old_error_handler !== null) { - return call_user_func($this->old_error_handler, $code, $message, $file, $line, $context); + return call_user_func( + $this->old_error_handler, + $type, + $message, + $file, + $line, + $context + ); } else { return false; } @@ -90,7 +99,7 @@ public function handleError($code, $message, $file = '', $line = 0, $context=arr public function handleFatalError() { - if (null === $lastError = error_get_last()) { + if (null === $error = error_get_last()) { return; } @@ -101,20 +110,42 @@ public function handleFatalError() if ($error_types === null) { $error_types = error_reporting(); } - if ($error_types & $lastError['type']) { + if ($error_types & $error['type']) { $e = new ErrorException( - @$lastError['message'], @$lastError['type'], @$lastError['type'], - @$lastError['file'], @$lastError['line'] + @$error['message'], 0, @$error['type'], + @$error['file'], @$error['line'] ); + + // ensure that if this error was reported via handleError that + // we don't duplicate it here + if ($this->_last_handled_error) { + $le = $this->_last_handled_error; + if ($e->getMessage() === $le->getMessage() && + $e->getSeverity() === $le->getSeverity() && + $e->getLine() === $le->getLine() && + $e->getFile() === $le->getFile() + ) { + return; + } + } + $this->handleException($e, true); } } } - public function registerExceptionHandler($call_existing_exception_handler = true) + /** + * Register a handler which will intercept unhnalded exceptions and report them to the + * associated Sentry client. + * + * @param bool $call_existing Call any existing exception handlers after processing + * this instance. + * @return $this + */ + public function registerExceptionHandler($call_existing = true) { $this->old_exception_handler = set_exception_handler(array($this, 'handleException')); - $this->call_existing_exception_handler = $call_existing_exception_handler; + $this->call_existing_exception_handler = $call_existing; return $this; } @@ -122,19 +153,28 @@ public function registerExceptionHandler($call_existing_exception_handler = true * Register a handler which will intercept standard PHP errors and report them to the * associated Sentry client. * + * @param bool $call_existing Call any existing errors handlers after processing + * this instance. * @return array */ - // - public function registerErrorHandler($call_existing_error_handler = true, $error_types = null) + public function registerErrorHandler($call_existing = true, $error_types = null) { if ($error_types !== null) { $this->error_types = $error_types; } $this->old_error_handler = set_error_handler(array($this, 'handleError'), E_ALL); - $this->call_existing_error_handler = $call_existing_error_handler; + $this->call_existing_error_handler = $call_existing; return $this; } + /** + * Register a fatal error handler, which will attempt to capture errors which + * shutdown the PHP process. These are commonly things like OOM or timeouts. + * + * @param int $reservedMemorySize Number of kilobytes memory space to reserve, + * which is utilized when handling fatal errors. + * @return $this + */ public function registerShutdownFunction($reservedMemorySize = 10) { register_shutdown_function(array($this, 'handleFatalError')); diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 0d066a6cb..7e43207d6 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -146,6 +146,25 @@ public function testHandleFatalError() $handler->handleFatalError(); } + public function testHandleFatalErrorDuplicate() + { + $client = $this->getMock('Client', array('captureException')); + $client->expects($this->once()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + + # http://php.net/manual/en/function.error-get-last.php#113518 + set_error_handler('var_dump', 0); + @$undef_var; + restore_error_handler(); + + $error = error_get_last(); + + $handler->handleError($error['type'], $error['message'], $error['file'], $error['line']); + $handler->handleFatalError(); + } + public function testFluidInterface() { $client = $this->getMock('Client', array('captureException', 'getIdent')); From 48e947a3980598c526ec1fde6efb18d822620c3b Mon Sep 17 00:00:00 2001 From: Dave James Miller Date: Thu, 11 Aug 2016 20:53:05 +0100 Subject: [PATCH 0162/1161] Add missing semicolon in sample code --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index 2aa756745..83cbd43f8 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -261,7 +261,7 @@ Its common that you might want to prevent automatic capture of certain areas. Id .. code-block:: php $sentryClient->setSendCallback(function($data) { - $ignore_types = array('Symfony\Component\HttpKernel\Exception\NotFoundHttpException') + $ignore_types = array('Symfony\Component\HttpKernel\Exception\NotFoundHttpException'); if (isset($data['exception'] && in_array($data['exception']['values'][0]['type'], $ignore_types) { From 629ffac9d37d16de251b97beb24ceacae9866c1e Mon Sep 17 00:00:00 2001 From: Dave James Miller Date: Thu, 11 Aug 2016 20:57:29 +0100 Subject: [PATCH 0163/1161] Add link to full documentation --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index aff86c453..262672c70 100644 --- a/README.rst +++ b/README.rst @@ -262,6 +262,7 @@ You may now use phpunit : Resources --------- +* `Documentation `_ * `Bug Tracker `_ * `Code `_ * `Mailing List `_ From 2367dbcf007dbd7190c42d23518d8a59241f0ad5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 11 Aug 2016 13:06:43 -0700 Subject: [PATCH 0164/1161] Simplify readme to focus on external docs --- README.rst | 229 ++--------------------------------------------------- 1 file changed, 5 insertions(+), 224 deletions(-) diff --git a/README.rst b/README.rst index 262672c70..b24f25f85 100644 --- a/README.rst +++ b/README.rst @@ -13,252 +13,33 @@ The official PHP SDK for `Sentry `_. // handlers $client = (new Raven_Client('http://public:secret@example.com/1'))->install(); - // Capture a message - $event_id = $client->getIdent($client->captureMessage('my log message')); - if ($client->getLastError() !== null) { - printf('There was an error sending the event to Sentry: %s', $client->getLastError()); - } - // Capture an exception - $event_id = $client->getIdent($client->captureException($ex)); - - // Provide some additional data with an exception - $event_id = $client->getIdent($client->captureException($ex, array( - 'extra' => array( - 'php_version' => phpversion() - ), - ))); + $event_id = $client->captureException($ex); // Give the user feedback echo "Sorry, there was an error!"; echo "Your reference ID is " . $event_id; -Installation ------------- - -Install with Composer -~~~~~~~~~~~~~~~~~~~~~ - -If you're using `Composer `_ to manage -dependencies, you can add Raven with it. - -:: - - $ composer require sentry/sentry:$VERSION - -(replace ``$VERSION`` with one of the available versions on `Packagist `_) -or to get the latest version off the master branch: - -:: - - $ composer require sentry/sentry:dev-master - -Note that using unstable versions is not recommended and should be avoided. Also -you should define a maximum version, e.g. by doing ``>=0.6,<1.0`` or ``~0.6``. - -Alternatively, use the ``^`` operator for specifying a version, e.g., - -:: - - $ composer require sentry/sentry:^0.11.0 - -Composer will take care of the autoloading for you, so if you require the -``vendor/autoload.php``, you're good to go. - - -Install source from GitHub -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To install the source code: - -:: - - $ git clone git://github.com/getsentry/sentry-php.git - -And including it using the autoloader: - -.. code-block:: php - - require_once '/path/to/Raven/library/Raven/Autoloader.php'; - Raven_Autoloader::register(); - -Testing Your Connection ------------------------ - -The PHP client includes a simple helper script to test your connection and credentials with -the Sentry master server: - -.. code-block:: bash - - $ bin/sentry test https://public:secret@app.getsentry.com/1 - Client configuration: - -> server: [https://sentry.example.com/api/store/] - -> project: 1 - -> public_key: public - -> secret_key: secret - - Sending a test event: - -> event ID: f1765c9aed4f4ceebe5a93df9eb2d34f - - Done! - -.. note:: The CLI enforces the synchronous option on HTTP requests whereas the default configuration is asyncrhonous. - -Configuration -------------- - -Several options exist that allow you to configure the behavior of the ``Raven_Client``. These are passed as the -second parameter of the constructor, and is expected to be an array of key value pairs: - -.. code-block:: php - - $client = new Raven_Client($dsn, array( - 'option_name' => 'value', - )); - -``name`` -~~~~~~~~ - -A string to override the default value for the server's hostname. - -Defaults to ``Raven_Compat::gethostname()``. - -``tags`` -~~~~~~~~ - -An array of tags to apply to events in this context. - -.. code-block:: php - - 'tags' => array( - 'php_version' => phpversion(), - ) - - -``curl_method`` -~~~~~~~~~~~~~~~ - -Defaults to 'sync'. - -Available methods: - -- sync (default): send requests immediately when they're made -- async: uses a curl_multi handler for best-effort asynchronous submissions -- exec: asynchronously send events by forking a curl process for each item - -``curl_path`` -~~~~~~~~~~~~~ - -Defaults to 'curl'. - -Specify the path to the curl binary to be used with the 'exec' curl method. - - -``trace`` -~~~~~~~~~ - -Set this to ``false`` to disable reflection tracing (function calling arguments) in stacktraces. - - -``logger`` -~~~~~~~~~~ - -Adjust the default logger name for messages. - -Defaults to ``php``. - -``ca_cert`` -~~~~~~~~~~~ - -The path to the CA certificate bundle. - -Defaults to the common bundle which includes getsentry.com: ./data/cacert.pem - -Caveats: - -- The CA bundle is ignored unless curl throws an error suggesting it needs a cert. -- The option is only currently used within the synchronous curl transport. - -``curl_ssl_version`` -~~~~~~~~~~~~~~~~~~~~ - -The SSL version (2 or 3) to use. -By default PHP will try to determine this itself, although in some cases this must be set manually. - -``message_limit`` -~~~~~~~~~~~~~~~~~ - -Defaults to 1024 characters. - -This value is used to truncate message and frame variables. However it is not guarantee that length of whole message will be restricted by this value. - -``processors`` -~~~~~~~~~~~~~~~~~ - -An array of classes to use to process data before it is sent to Sentry. By default, Raven_SanitizeDataProcessor is used - -``processorOptions`` -~~~~~~~~~~~~~~~~~ -Options that will be passed on to a setProcessorOptions() function in a Raven_Processor sub-class before that Processor is added to the list of processors used by Raven_Client - -An example of overriding the regular expressions in Raven_SanitizeDataProcessor is below: - -.. code-block:: php - - 'processorOptions' => array( - 'Raven_SanitizeDataProcessor' => array( - 'fields_re' => '/(user_password|user_token|user_secret)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/' - ) - ) - -Providing Request Context -------------------------- - -Most of the time you're not actually calling out to Raven directly, but you still want to provide some additional context. This lifecycle generally constists of something like the following: - -- Set some context via a middleware (e.g. the logged in user) -- Send all given context with any events during the request lifecycle -- Cleanup context - -There are three primary methods for providing request context: - -.. code-block:: php - - // bind the logged in user - $client->user_context(array('email' => 'foo@example.com')); - - // tag the request with something interesting - $client->tags_context(array('interesting' => 'yes')); - - // provide a bit of additional context - $client->extra_context(array('happiness' => 'very')); - - -If you're performing additional requests during the lifecycle, you'll also need to ensure you cleanup the context (to reset its state): - -.. code-block:: php - - $client->context->clear(); +For more information, see our `documentation `_. Contributing ------------ -First, make sure you can run the test suite. Install development dependencies : +Dependencies are managed through composer: :: $ composer install -You may now use phpunit : + +Tests can then be run via phpunit: :: $ vendor/bin/phpunit - Resources --------- From b8d5571124694b449e72249de364f42fa952d11a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 11 Aug 2016 13:07:50 -0700 Subject: [PATCH 0165/1161] Remove unnessesary advanced docs --- docs/advanced.rst | 58 ----------------------------------------------- docs/index.rst | 3 --- 2 files changed, 61 deletions(-) delete mode 100644 docs/advanced.rst diff --git a/docs/advanced.rst b/docs/advanced.rst deleted file mode 100644 index 5d5f80cf9..000000000 --- a/docs/advanced.rst +++ /dev/null @@ -1,58 +0,0 @@ -Advanced Topics -=============== - -This covers various advanced usage topics for the PHP client. - -.. _sentry-php-advanced-installation: - -Advanced Installation ---------------------- - -This covers all methods of installation and a bit more detail. - -Install with Composer -````````````````````` - -If you're using `Composer `_ to manage -dependencies, you can add the Sentry SDK with it. - -.. code-block:: json - - { - "require": { - "sentry/sentry": "$VERSION" - } - } - -(replace ``$VERSION`` with one of the available versions on `Packagist -`_) or to get the latest -version off the master branch: - -.. code-block:: json - - { - "require": { - "sentry/sentry": "dev-master" - } - } - -Note that using unstable versions is not recommended and should be -avoided. Also you should define a maximum version, e.g. by doing -``>=0.6,<1.0`` or ``~0.6``. - -Composer will take care of the autoloading for you, so if you require the -``vendor/autoload.php``, you're good to go. - -Install Source from GitHub -`````````````````````````` - -To install the source code:: - - $ git clone git://github.com/getsentry/sentry-php.git - -And including it using the autoloader: - -.. code-block:: php - - require_once '/path/to/Raven/library/Raven/Autoloader.php'; - Raven_Autoloader::register(); diff --git a/docs/index.rst b/docs/index.rst index 2eab7478e..a0d15f1d3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,8 +33,6 @@ Alternatively you can manually install it: require_once '/path/to/Raven/library/Raven/Autoloader.php'; Raven_Autoloader::register(); -For more methods have a look at :ref:`sentry-php-advanced-installation`. - Configuration ------------- @@ -81,7 +79,6 @@ Want more? Have a look at the full documentation for more information. :maxdepth: 2 :titlesonly: - advanced usage config integrations/index From daf2acb9c40b0d55b917596f31681260c326cc56 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 15 Aug 2016 11:10:38 -0700 Subject: [PATCH 0166/1161] Ensure pending events are always sent on shutdown - Move shutdown handler into Raven_Client - Always fire sendUnsentErrors - Enforce join() on async curl handler Fixes GH-337 --- lib/Raven/Client.php | 28 +++++++++++++++++++--------- lib/Raven/ErrorHandler.php | 10 ---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 7c3099bc2..1bb6e596f 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -96,6 +96,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->_lasterror = null; $this->_last_event_id = null; $this->_user = null; + $this->_pending_events = null; $this->context = new Raven_Context(); $this->breadcrumbs = new Raven_Breadcrumbs(); $this->sdk = Raven_Util::get($options, 'sdk', array( @@ -110,6 +111,8 @@ public function __construct($options_or_dsn=null, $options=array()) if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { $this->registerDefaultBreadcrumbHandlers(); } + + register_shutdown_function(array($this, 'onShutdown')); } /** @@ -649,10 +652,7 @@ public function capture($data, $stack = null, $vars = null) if (!$this->store_errors_for_bulk_send) { $this->send($data); } else { - if (empty($this->error_data)) { - $this->error_data = array(); - } - $this->error_data[] = $data; + $this->_pending_events[] = $data; } $this->_last_event_id = $data['event_id']; @@ -697,12 +697,10 @@ public function process(&$data) public function sendUnsentErrors() { - if (!empty($this->error_data)) { - foreach ($this->error_data as $data) { - $this->send($data); - } - unset($this->error_data); + foreach ($this->_pending_events as $data) { + $this->send($data); } + $this->_pending_events = array(); if ($this->store_errors_for_bulk_send) { //in case an error occurs after this is called, on shutdown, send any new errors. $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED'); @@ -1090,6 +1088,7 @@ public function registerSeverityMap($map) /** * Convenience function for setting a user's ID and Email * + * @deprecated * @param string $id User's ID * @param string|null $email User's email * @param array $data Additional user data @@ -1103,6 +1102,17 @@ public function set_user_data($id, $email=null, $data=array()) $this->user_context(array_merge($user, $data)); } + public function onShutdown() + { + if (!defined('RAVEN_CLIENT_END_REACHED')) { + define('RAVEN_CLIENT_END_REACHED', true); + } + $this->sendUnsentErrors(); + if ($this->curl_method == 'async') { + $this->_curl_handler->join(); + } + } + /** * Sets user context. * diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index e34f2be88..759d14b8c 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -51,11 +51,9 @@ public function __construct($client, $send_errors_last = false, $error_types = n $this->client = $client; $this->error_types = $error_types; - register_shutdown_function(array($this, 'detectShutdown')); if ($send_errors_last) { $this->send_errors_last = true; $this->client->store_errors_for_bulk_send = true; - register_shutdown_function(array($this->client, 'sendUnsentErrors')); } } @@ -182,12 +180,4 @@ public function registerShutdownFunction($reservedMemorySize = 10) $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); return $this; } - - // TODO(dcramer): this should be in Client - public function detectShutdown() - { - if (!defined('RAVEN_CLIENT_END_REACHED')) { - define('RAVEN_CLIENT_END_REACHED', true); - } - } } From e0bb401bc32822cecd08c12de2c67dce5ba2bdf3 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 16 Aug 2016 10:46:11 -0700 Subject: [PATCH 0167/1161] Correct default value _pending_events (fixes GH-339) --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 1bb6e596f..198994821 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -96,7 +96,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->_lasterror = null; $this->_last_event_id = null; $this->_user = null; - $this->_pending_events = null; + $this->_pending_events = array(); $this->context = new Raven_Context(); $this->breadcrumbs = new Raven_Breadcrumbs(); $this->sdk = Raven_Util::get($options, 'sdk', array( From c1fe9f041e01e88005be7582ed650b3ce554e531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ch=C3=A1bek?= Date: Wed, 24 Aug 2016 09:44:06 +0200 Subject: [PATCH 0168/1161] Fix typo in Raven_Client Changed `u` to `U`. --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 198994821..b8869c913 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -79,7 +79,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->send_callback = Raven_Util::get($options, 'send_callback', null); $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync'); $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl'); - $this->curl_ipv4 = Raven_util::get($options, 'curl_ipv4', true); + $this->curl_ipv4 = Raven_Util::get($options, 'curl_ipv4', true); $this->ca_cert = Raven_Util::get($options, 'ca_cert', $this->get_default_ca_cert()); $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true); $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); From 40a1c2a724d7cfb420e0818c1a744cd3092efcee Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Fri, 2 Sep 2016 15:11:21 +0200 Subject: [PATCH 0169/1161] Fix encoding errors for exceptions $data['exception'] was not encoded to utf8 via sanitized(). This causes problems, if sourcecode (stacktrace) or the exception's message are not in utf8 and contain special characters (e.g. german umlauts) --- lib/Raven/Client.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index b8869c913..d206675ff 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -679,6 +679,9 @@ public function sanitize(&$data) if (!empty($data['stacktrace']) && !empty($data['stacktrace']['frames'])) { $data['stacktrace']['frames'] = $serializer->serialize($data['stacktrace']['frames']); } + if (!empty($data['exception'])) { + $data['exception'] = $serializer->serialize($data['exception'], 7); + } $serializer->serialize($data); } From c26a7696a379e7ed1f0abfd8ac117c783e2788bd Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Tue, 6 Sep 2016 19:32:55 +0200 Subject: [PATCH 0170/1161] Add test case: encoding errors for exceptions --- test/Raven/Tests/ClientTest.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 3c5183e28..d4895f95b 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -684,6 +684,26 @@ public function testCaptureMessageWithExtraContext() ), $event['extra']); } + public function testCaptureExceptionInLatin1File() + { + // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order + $options = ['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']]; + + $client = new Dummy_Raven_Client($options); + + // we need a non-utf8 string here. + // nobody writes non-utf8 in exceptions, but it is the easiest way to test. + // in real live non-utf8 may be somewhere in the exception's stacktrace + $utf8String = 'äöü'; + $latin1String = utf8_decode($utf8String); + $client->captureException(new \Exception($latin1String)); + + $events = $client->getSentEvents(); + $event = array_pop($events); + + $this->assertEquals($event['exception']['values'][0]['value'], $utf8String); + } + public function testGetLastEventID() { $client = new Dummy_Raven_Client(); From b7b013731468a70c4118911ba7293a68f10f639b Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Tue, 6 Sep 2016 21:33:41 +0200 Subject: [PATCH 0171/1161] Fix test for php5.3 --- test/Raven/Tests/ClientTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index d4895f95b..898d8fb72 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -687,8 +687,12 @@ public function testCaptureMessageWithExtraContext() public function testCaptureExceptionInLatin1File() { // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order - $options = ['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']]; - + $options = array( + 'mb_detect_order' => array( + 'ISO-8859-1', 'ASCII', 'UTF-8' + ) + ); + $client = new Dummy_Raven_Client($options); // we need a non-utf8 string here. From 5970a39ea9361cd8931d6120ac0883c512d99a67 Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Tue, 6 Sep 2016 22:25:17 +0200 Subject: [PATCH 0172/1161] Fix improve tests for encoding errors for exceptions --- test/Raven/Tests/ClientTest.php | 35 +++++++++++++++++-- .../captureExceptionInLatin1File.php | 4 +++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/Raven/Tests/resources/captureExceptionInLatin1File.php diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 898d8fb72..073a68a84 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -684,7 +684,7 @@ public function testCaptureMessageWithExtraContext() ), $event['extra']); } - public function testCaptureExceptionInLatin1File() + public function testCaptureExceptionContainingLatin1() { // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order $options = array( @@ -692,7 +692,7 @@ public function testCaptureExceptionInLatin1File() 'ISO-8859-1', 'ASCII', 'UTF-8' ) ); - + $client = new Dummy_Raven_Client($options); // we need a non-utf8 string here. @@ -708,6 +708,37 @@ public function testCaptureExceptionInLatin1File() $this->assertEquals($event['exception']['values'][0]['value'], $utf8String); } + + public function testCaptureExceptionInLatin1File() + { + // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order + $options = array( + 'mb_detect_order' => array( + 'ISO-8859-1', 'ASCII', 'UTF-8' + ) + ); + + $client = new Dummy_Raven_Client($options); + + require_once(__DIR__.'/resources/captureExceptionInLatin1File.php'); + + $events = $client->getSentEvents(); + $event = array_pop($events); + + $stackTrace = array_pop($event['exception']['values'][0]['stacktrace']['frames']); + + $utf8String = "// äöü"; + $found = false; + foreach ($stackTrace['pre_context'] as $line) { + if ($line == $utf8String) { + $found = true; + break; + } + } + + $this->assertEquals($found, true); + } + public function testGetLastEventID() { $client = new Dummy_Raven_Client(); diff --git a/test/Raven/Tests/resources/captureExceptionInLatin1File.php b/test/Raven/Tests/resources/captureExceptionInLatin1File.php new file mode 100644 index 000000000..3fabbd698 --- /dev/null +++ b/test/Raven/Tests/resources/captureExceptionInLatin1File.php @@ -0,0 +1,4 @@ +captureException(new \Exception()); From feed98771ac9d593c0a9f8355d28d2a8474c2353 Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Wed, 7 Sep 2016 10:29:03 +0200 Subject: [PATCH 0173/1161] Fix: Code was just doing nothing --- lib/Raven/Client.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index d206675ff..3e873f579 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -682,8 +682,6 @@ public function sanitize(&$data) if (!empty($data['exception'])) { $data['exception'] = $serializer->serialize($data['exception'], 7); } - - $serializer->serialize($data); } /** From 722e7f905311f281ce56a7c975a5998e351c6b1d Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Wed, 7 Sep 2016 12:06:56 +0200 Subject: [PATCH 0174/1161] Move serialization to get_stack_info and captureException --- lib/Raven/Client.php | 29 +++++++++++++++-------------- lib/Raven/Stacktrace.php | 9 ++++----- test/Raven/Tests/StacktraceTest.php | 20 ++++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 3e873f579..4693dba63 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -37,6 +37,9 @@ class Raven_Client private $error_handler; + private $serializer; + private $reprSerializer; + public function __construct($options_or_dsn=null, $options=array()) { if (is_array($options_or_dsn)) { @@ -103,6 +106,8 @@ public function __construct($options_or_dsn=null, $options=array()) 'name' => 'sentry-php', 'version' => self::VERSION, )); + $this->serializer = new Raven_Serializer($this->mb_detect_order); + $this->reprSerializer = new Raven_ReprSerializer($this->mb_detect_order); if ($this->curl_method == 'async') { $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); @@ -381,7 +386,7 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul $exc = $exception; do { $exc_data = array( - 'value' => $exc->getMessage(), + 'value' => $this->serializer->serialize($exc->getMessage()), 'type' => get_class($exc), 'module' => $exc->getFile() .':'. $exc->getLine(), ); @@ -405,8 +410,8 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul $exc_data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, - $this->app_path + $trace, $this->serializer, $this->reprSerializer, $this->trace, $this->shift_vars, $vars, + $this->message_limit, $this->prefixes, $this->app_path, $this->serializer ), ); @@ -639,8 +644,8 @@ public function capture($data, $stack = null, $vars = null) if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit, - $this->prefixes, $this->app_path + $stack, $this->serializer, $this->reprSerializer, $this->trace, $this->shift_vars, $vars, + $this->message_limit, $this->prefixes, $this->app_path, $this->serializer ), ); } @@ -663,24 +668,20 @@ public function capture($data, $stack = null, $vars = null) public function sanitize(&$data) { // attempt to sanitize any user provided data - $serializer = new Raven_Serializer($this->mb_detect_order); if (!empty($data['request'])) { - $data['request'] = $serializer->serialize($data['request']); + $data['request'] = $this->serializer->serialize($data['request']); } if (!empty($data['user'])) { - $data['user'] = $serializer->serialize($data['user']); + $data['user'] = $this->serializer->serialize($data['user']); } if (!empty($data['extra'])) { - $data['extra'] = $serializer->serialize($data['extra']); + $data['extra'] = $this->serializer->serialize($data['extra']); } if (!empty($data['tags'])) { - $data['tags'] = $serializer->serialize($data['tags']); + $data['tags'] = $this->serializer->serialize($data['tags']); } if (!empty($data['stacktrace']) && !empty($data['stacktrace']['frames'])) { - $data['stacktrace']['frames'] = $serializer->serialize($data['stacktrace']['frames']); - } - if (!empty($data['exception'])) { - $data['exception'] = $serializer->serialize($data['exception'], 7); + $data['stacktrace']['frames'] = $this->serializer->serialize($data['stacktrace']['frames']); } } diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 73cdf85c9..3c114031f 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -13,7 +13,7 @@ class Raven_Stacktrace 'require_once', ); - public static function get_stack_info($frames, $trace = false, $shiftvars = true, $errcontext = null, + public static function get_stack_info($frames, Raven_Serializer $serializer, Raven_ReprSerializer $reprSerializer, $trace = false, $shiftvars = true, $errcontext = null, $frame_var_limit = Raven_Client::MESSAGE_LIMIT, $strip_prefixes = null, $app_path = null) { @@ -87,9 +87,9 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true 'lineno' => (int) $context['lineno'], 'module' => $module, 'function' => isset($nextframe['function']) ? $nextframe['function'] : null, - 'pre_context' => $context['prefix'], + 'pre_context' => $serializer->serialize($context['prefix']), 'context_line' => $context['line'], - 'post_context' => $context['suffix'], + 'post_context' => $serializer->serialize($context['suffix']), ); // detect in_app based on app path @@ -100,10 +100,9 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true // dont set this as an empty array as PHP will treat it as a numeric array // instead of a mapping which goes against the defined Sentry spec if (!empty($vars)) { - $serializer = new Raven_ReprSerializer(); $cleanVars = array(); foreach ($vars as $key => $value) { - $value = $serializer->serialize($value); + $value = $reprSerializer->serialize($value); if (is_string($value) || is_numeric($value)) { $cleanVars[$key] = substr($value, 0, $frame_var_limit); } else { diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 8917f5ce1..38eac0417 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -57,7 +57,7 @@ public function testSimpleTrace() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true); $frame = $frames[0]; $this->assertEquals('b.php', $frame["module"]); @@ -92,7 +92,7 @@ public function testSimpleUnshiftedTrace() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, false); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, false); $frame = $frames[0]; $this->assertEquals('b.php', $frame["module"]); @@ -134,7 +134,7 @@ public function testShiftedCaptureVars() "baz" => "zoom" ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, true, $vars); $frame = $frames[0]; $this->assertEquals('b.php', $frame["module"]); @@ -179,7 +179,7 @@ public function testDoesNotModifyCaptureVars() "foo" => &$iAmFoo ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars, 5); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, true, $vars, 5); // Check we haven't modified our vars. $this->assertEquals($originalFoo, $vars["foo"]); @@ -215,7 +215,7 @@ public function testUnshiftedCaptureVars() "baz" => "zoom" ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, false, $vars); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, false, $vars); $frame = $frames[0]; $this->assertEquals('b.php', $frame["module"]); @@ -240,7 +240,7 @@ public function testDoesFixFrameInfo() */ $stack = raven_test_create_stacktrace(); - $frames = Raven_Stacktrace::get_stack_info($stack, true); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true); // just grab the last few frames $frames = array_slice($frames, -5); $frame = $frames[0]; @@ -275,7 +275,7 @@ public function testInApp() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, null, dirname(__FILE__)); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, null, null, 0, null, dirname(__FILE__)); $this->assertEquals($frames[0]['in_app'], true); $this->assertEquals($frames[1]['in_app'], true); @@ -296,7 +296,7 @@ public function testBasePath() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, array(dirname(__FILE__))); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, null, null, 0, array(dirname(__FILE__))); $this->assertEquals($frames[0]['filename'], 'resources/b.php'); $this->assertEquals($frames[1]['filename'], 'resources/a.php'); @@ -312,7 +312,7 @@ public function testNoBasePath() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack); + $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer()); $this->assertEquals($frames[0]['filename'], dirname(__FILE__) . '/resources/a.php'); } @@ -322,7 +322,7 @@ public function testWithEvaldCode() eval("throw new Exception('foobar');"); } catch (Exception $ex) { $trace = $ex->getTrace(); - $frames = Raven_Stacktrace::get_stack_info($trace); + $frames = Raven_Stacktrace::get_stack_info($trace, new Raven_Serializer(), new Raven_ReprSerializer()); } $this->assertEquals($frames[count($frames) -1]['filename'], __FILE__); } From 0230fdb7f3e678fc2cffca7ea1d32c8b7cd8fd7e Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Wed, 7 Sep 2016 16:15:36 +0200 Subject: [PATCH 0175/1161] Do not break method signature for bc i was running in a problem while having multiple sentry variants (legacy-code) inside the autoloader --- lib/Raven/Client.php | 8 ++++---- lib/Raven/Stacktrace.php | 8 ++++++-- test/Raven/Tests/StacktraceTest.php | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 4693dba63..00bf26c8c 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -410,8 +410,8 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul $exc_data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $trace, $this->serializer, $this->reprSerializer, $this->trace, $this->shift_vars, $vars, - $this->message_limit, $this->prefixes, $this->app_path, $this->serializer + $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, + $this->app_path, $this->serializer, $this->reprSerializer ), ); @@ -644,8 +644,8 @@ public function capture($data, $stack = null, $vars = null) if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $stack, $this->serializer, $this->reprSerializer, $this->trace, $this->shift_vars, $vars, - $this->message_limit, $this->prefixes, $this->app_path, $this->serializer + $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, + $this->app_path, $this->serializer, $this->reprSerializer ), ); } diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 3c114031f..aceba2ca2 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -13,10 +13,14 @@ class Raven_Stacktrace 'require_once', ); - public static function get_stack_info($frames, Raven_Serializer $serializer, Raven_ReprSerializer $reprSerializer, $trace = false, $shiftvars = true, $errcontext = null, + public static function get_stack_info($frames, $trace = false, $shiftvars = true, $errcontext = null, $frame_var_limit = Raven_Client::MESSAGE_LIMIT, $strip_prefixes = null, - $app_path = null) + $app_path = null, Raven_Serializer $serializer = null, + Raven_ReprSerializer $reprSerializer = null) { + $serializer = $serializer ?: new Raven_Serializer(); + $reprSerializer = $reprSerializer ?: new Raven_ReprSerializer(); + /** * PHP's way of storing backstacks seems bass-ackwards to me * 'function' is not the function you're in; it's any function being diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 38eac0417..8917f5ce1 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -57,7 +57,7 @@ public function testSimpleTrace() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true); + $frames = Raven_Stacktrace::get_stack_info($stack, true); $frame = $frames[0]; $this->assertEquals('b.php', $frame["module"]); @@ -92,7 +92,7 @@ public function testSimpleUnshiftedTrace() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, false); + $frames = Raven_Stacktrace::get_stack_info($stack, true, false); $frame = $frames[0]; $this->assertEquals('b.php', $frame["module"]); @@ -134,7 +134,7 @@ public function testShiftedCaptureVars() "baz" => "zoom" ); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, true, $vars); + $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars); $frame = $frames[0]; $this->assertEquals('b.php', $frame["module"]); @@ -179,7 +179,7 @@ public function testDoesNotModifyCaptureVars() "foo" => &$iAmFoo ); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, true, $vars, 5); + $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars, 5); // Check we haven't modified our vars. $this->assertEquals($originalFoo, $vars["foo"]); @@ -215,7 +215,7 @@ public function testUnshiftedCaptureVars() "baz" => "zoom" ); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, false, $vars); + $frames = Raven_Stacktrace::get_stack_info($stack, true, false, $vars); $frame = $frames[0]; $this->assertEquals('b.php', $frame["module"]); @@ -240,7 +240,7 @@ public function testDoesFixFrameInfo() */ $stack = raven_test_create_stacktrace(); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true); + $frames = Raven_Stacktrace::get_stack_info($stack, true); // just grab the last few frames $frames = array_slice($frames, -5); $frame = $frames[0]; @@ -275,7 +275,7 @@ public function testInApp() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, null, null, 0, null, dirname(__FILE__)); + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, null, dirname(__FILE__)); $this->assertEquals($frames[0]['in_app'], true); $this->assertEquals($frames[1]['in_app'], true); @@ -296,7 +296,7 @@ public function testBasePath() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer(), true, null, null, 0, array(dirname(__FILE__))); + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, array(dirname(__FILE__))); $this->assertEquals($frames[0]['filename'], 'resources/b.php'); $this->assertEquals($frames[1]['filename'], 'resources/a.php'); @@ -312,7 +312,7 @@ public function testNoBasePath() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, new Raven_Serializer(), new Raven_ReprSerializer()); + $frames = Raven_Stacktrace::get_stack_info($stack); $this->assertEquals($frames[0]['filename'], dirname(__FILE__) . '/resources/a.php'); } @@ -322,7 +322,7 @@ public function testWithEvaldCode() eval("throw new Exception('foobar');"); } catch (Exception $ex) { $trace = $ex->getTrace(); - $frames = Raven_Stacktrace::get_stack_info($trace, new Raven_Serializer(), new Raven_ReprSerializer()); + $frames = Raven_Stacktrace::get_stack_info($trace); } $this->assertEquals($frames[count($frames) -1]['filename'], __FILE__); } From dde481af60bc7de2deaa3e11cb3778129c283619 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 8 Sep 2016 10:37:55 -0700 Subject: [PATCH 0176/1161] Changes for 1.2.0 --- CHANGES | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index 41b440359..65ca1111c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,12 @@ +1.2.0 +----- + +- Handle non-latin encoding in source code and exception values (#342) +- Ensure pending events are sent on shutdown by default (#338) +- Add ``captureLastError`` helper (#334) +- Dont report duplicate errors with fatal error handler (#334) +- Enforce maximum length for string serialization (#329) + 1.1.0 ----- From de770c4aeca2088fc9115c9880e9c07e660ec525 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 8 Sep 2016 10:38:22 -0700 Subject: [PATCH 0177/1161] 1.3.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7fe213fe9..6d59eaa03 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "1.3.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 00bf26c8c..189081fbd 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '1.2.x-dev'; + const VERSION = '1.3.x-dev'; const PROTOCOL = '6'; From b57a8ec3353a9d3c2d45fd85df170a58f51e09b2 Mon Sep 17 00:00:00 2001 From: Martin Abraham Date: Fri, 9 Sep 2016 12:15:16 +0200 Subject: [PATCH 0178/1161] Fix: Encoding for context_line missing --- lib/Raven/Stacktrace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index aceba2ca2..dcd3e6799 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -92,7 +92,7 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true 'module' => $module, 'function' => isset($nextframe['function']) ? $nextframe['function'] : null, 'pre_context' => $serializer->serialize($context['prefix']), - 'context_line' => $context['line'], + 'context_line' => $serializer->serialize($context['line']), 'post_context' => $serializer->serialize($context['suffix']), ); From 9d8d9b532dfd13ad6e5f2546d32319ba79fd55b1 Mon Sep 17 00:00:00 2001 From: Fredrik Forsmo Date: Sun, 11 Sep 2016 10:41:32 +0200 Subject: [PATCH 0179/1161] Fix release value from being masked --- lib/Raven/SanitizeDataProcessor.php | 4 ++++ test/Raven/Tests/ClientTest.php | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/SanitizeDataProcessor.php index 97c7c1595..011fe6d1a 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/SanitizeDataProcessor.php @@ -47,6 +47,10 @@ public function setProcessorOptions(array $options) */ public function sanitize(&$item, $key) { + if ($key === 'release') { + return; + } + if (empty($item)) { return; } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 073a68a84..167322588 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -290,6 +290,21 @@ public function testCaptureMessageDoesHandleInterpolatedMessage() $this->assertEquals($event['message'], 'Test Message foo'); } + public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() + { + $client = new Dummy_Raven_Client(); + $client->setRelease(20160909144742); + + $this->assertEquals(20160909144742, $client->getRelease()); + + $client->captureMessage('Test Message %s', array('foo')); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['release'], 20160909144742); + $this->assertEquals($event['message'], 'Test Message foo'); + } + public function testCaptureMessageSetsInterface() { $client = new Dummy_Raven_Client(); From cc4c3af89175bb8b6fb43404307135cff4eca95f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 11 Sep 2016 20:57:02 -0700 Subject: [PATCH 0180/1161] Correct test name --- test/Raven/Tests/ErrorHandlerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 7e43207d6..ed61b8b2d 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -103,7 +103,7 @@ public function testErrorHandlerRespectsErrorReportingDefault() // Because we cannot **know** that a user silenced an error, we always // defer to respecting the error reporting settings. - public function testSilentErrorsAreReported() + public function testSilentErrorsAreNotReported() { $client = $this->getMock('Client', array('captureException', 'getIdent')); $client->expects($this->never()) From 33fe628fdac2e5144a4d5137ebfd2e6dd7360d16 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 11 Sep 2016 20:58:32 -0700 Subject: [PATCH 0181/1161] Remove references to getIdent --- test/Raven/Tests/ErrorHandlerTest.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index ed61b8b2d..f6c0f138b 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -34,7 +34,7 @@ public function tearDown() public function testErrorsAreLoggedAsExceptions() { - $client = $this->getMock('Client', array('captureException', 'getIdent', 'sendUnsentErrors')); + $client = $this->getMock('Client', array('captureException', 'sendUnsentErrors')); $client->expects($this->once()) ->method('captureException') ->with($this->isInstanceOf('ErrorException')); @@ -45,7 +45,7 @@ public function testErrorsAreLoggedAsExceptions() public function testExceptionsAreLogged() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException')); $client->expects($this->once()) ->method('captureException') ->with($this->isInstanceOf('ErrorException')); @@ -58,7 +58,7 @@ public function testExceptionsAreLogged() public function testErrorHandlerPassErrorReportingPass() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException')); $client->expects($this->once()) ->method('captureException'); @@ -71,7 +71,7 @@ public function testErrorHandlerPassErrorReportingPass() public function testErrorHandlerPropagates() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException')); $client->expects($this->never()) ->method('captureException'); @@ -86,7 +86,7 @@ public function testErrorHandlerPropagates() public function testErrorHandlerRespectsErrorReportingDefault() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException')); $client->expects($this->once()) ->method('captureException'); @@ -105,7 +105,7 @@ public function testErrorHandlerRespectsErrorReportingDefault() // defer to respecting the error reporting settings. public function testSilentErrorsAreNotReported() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException')); $client->expects($this->never()) ->method('captureException'); @@ -119,7 +119,7 @@ public function testSilentErrorsAreNotReported() public function testErrorHandlerDefaultsErrorReporting() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException')); $client->expects($this->never()) ->method('captureException'); @@ -130,6 +130,7 @@ public function testErrorHandlerDefaultsErrorReporting() trigger_error('Warning', E_USER_WARNING); } + public function testHandleFatalError() { $client = $this->getMock('Client', array('captureException')); @@ -167,7 +168,7 @@ public function testHandleFatalErrorDuplicate() public function testFluidInterface() { - $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client = $this->getMock('Client', array('captureException')); $handler = new Raven_ErrorHandler($client); $result = $handler->registerErrorHandler(); $this->assertEquals($result, $handler); From e1d8ba25b5741987fa919ac821c9dbb8069bbb05 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 11 Sep 2016 21:15:33 -0700 Subject: [PATCH 0182/1161] Ensure fatal errors respect control operator (fixes GH-348) --- lib/Raven/ErrorHandler.php | 7 +++++-- test/Raven/Tests/ErrorHandlerTest.php | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 759d14b8c..4bc5a429d 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -68,15 +68,18 @@ public function handleException($e, $isError = false, $vars = null) public function handleError($type, $message, $file = '', $line = 0, $context = array()) { + // we always need to bind _last_handled_error in the case of a suppressed + // error getting passed to handleFatalError + $e = new ErrorException($message, 0, $type, $file, $line); + $this->_last_handled_error = $e; + if (error_reporting() !== 0) { $error_types = $this->error_types; if ($error_types === null) { $error_types = error_reporting(); } if ($error_types & $type) { - $e = new ErrorException($message, 0, $type, $file, $line); $this->handleException($e, true, $context); - $this->_last_handled_error = $e; } } if ($this->call_existing_error_handler) { diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index f6c0f138b..3d08964c3 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -115,6 +115,9 @@ public function testSilentErrorsAreNotReported() $handler->registerErrorHandler(false); @trigger_error('Silent', E_USER_WARNING); + + // also ensure it doesnt get reported by the fatal handler + $handler->handleFatalError(); } public function testErrorHandlerDefaultsErrorReporting() From ba7ebd992ed595ea2a229553a434680d4ce6dae1 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 11 Sep 2016 21:27:28 -0700 Subject: [PATCH 0183/1161] Ensure serializers only affect relevant attributes --- lib/Raven/SanitizeDataProcessor.php | 49 +++++++++++++------ .../Raven/Tests/SanitizeDataProcessorTest.php | 6 ++- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/SanitizeDataProcessor.php index 011fe6d1a..b0f514387 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/SanitizeDataProcessor.php @@ -47,10 +47,6 @@ public function setProcessorOptions(array $options) */ public function sanitize(&$item, $key) { - if ($key === 'release') { - return; - } - if (empty($item)) { return; } @@ -68,26 +64,51 @@ public function sanitize(&$item, $key) } } - public function sanitizeHttp(&$data) + public function sanitizeException(&$data) { - if (empty($data['request'])) { - return; + foreach ($data['exception']['values'] as &$value) { + return $this->sanitizeStacktrace($value['stacktrace']); } + } + + public function sanitizeHttp(&$data) + { $http = &$data['request']; - if (empty($http['cookies'])) { - return; + if (!empty($http['cookies'])) { + $cookies = &$http['cookies']; + if (!empty($cookies[$this->session_cookie_name])) { + $cookies[$this->session_cookie_name] = self::MASK; + } + } + if (!empty($http['data'])) { + array_walk_recursive($http['data'], array($this, 'sanitize')); } + } - $cookies = &$http['cookies']; - if (!empty($cookies[$this->session_cookie_name])) { - $cookies[$this->session_cookie_name] = self::MASK; + public function sanitizeStacktrace(&$data) + { + foreach ($data['frames'] as &$frame) { + if (empty($frame['vars'])) { + continue; + } + array_walk_recursive($frame['vars'], array($this, 'sanitize')); } } public function process(&$data) { - array_walk_recursive($data, array($this, 'sanitize')); - $this->sanitizeHttp($data); + if (!empty($data['exception'])) { + $this->sanitizeException($data); + } + if (!empty($data['stacktrace'])) { + $this->sanitizeStacktrace($data['stacktrace']); + } + if (!empty($data['request'])) { + $this->sanitizeHttp($data); + } + if (!empty($data['extra'])) { + array_walk_recursive($data['extra'], array($this, 'sanitize')); + } } /** diff --git a/test/Raven/Tests/SanitizeDataProcessorTest.php b/test/Raven/Tests/SanitizeDataProcessorTest.php index 40c6aa606..eced94508 100644 --- a/test/Raven/Tests/SanitizeDataProcessorTest.php +++ b/test/Raven/Tests/SanitizeDataProcessorTest.php @@ -70,14 +70,16 @@ public function testDoesFilterSessionId() public function testDoesFilterCreditCard() { $data = array( - 'ccnumba' => '4242424242424242' + 'extra' => array( + 'ccnumba' => '4242424242424242', + ), ); $client = new Raven_Client(); $processor = new Raven_SanitizeDataProcessor($client); $processor->process($data); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $data['ccnumba']); + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $data['extra']['ccnumba']); } /** From f90eee387547bc3e4adb6e3f9cdba029e9113b04 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 11 Sep 2016 22:12:06 -0700 Subject: [PATCH 0184/1161] Ensure app path has trialing slash (refs GH-343) --- lib/Raven/Client.php | 16 +++++++++++++++- test/Raven/Tests/ClientTest.php | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 189081fbd..75db928ce 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -164,7 +164,21 @@ public function getAppPath() public function setAppPath($value) { - $this->app_path = $value ? realpath($value) : null; + if ($value) { + $path = @realpath($value); + if ($path === false) { + $path = $value; + } + // we need app_path to have a trailing slash otherwise + // base path detection becomes complex if the same + // prefix is matched + if (substr($path, 0, 1) === '/' && substr($path, -1, 1) !== '/') { + $path = $path . '/'; + } + $this->app_path = $path; + } else { + $this->app_path = null; + } return $this; } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 167322588..b9b7d70d3 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -773,6 +773,27 @@ public function testCustomTransport() $this->assertEquals(count($events), 1); } + + public function testAppPathLinux() + { + $client = new Dummy_Raven_Client(); + $client->setAppPath('/foo/bar'); + + $this->assertEquals($client->getAppPath(), '/foo/bar/'); + + $client->setAppPath('/foo/baz/'); + + $this->assertEquals($client->getAppPath(), '/foo/baz/'); + } + + public function testAppPathWindows() + { + $client = new Dummy_Raven_Client(); + $client->setAppPath('C:\\foo\\bar\\'); + + $this->assertEquals($client->getAppPath(), 'C:\\foo\\bar\\'); + } + /** * @expectedException Raven_Exception * @expectedExceptionMessage Raven_Client->install() must only be called once From 52390e73fe51ae7b1964b149964dcfd96de0f88e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 12 Sep 2016 19:16:11 -0700 Subject: [PATCH 0185/1161] English --- lib/Raven/Serializer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 50650f7f3..2184cfe27 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -80,7 +80,7 @@ protected function serializeString($value) if (function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding') ) { - // we always gurantee this is coerced, even if we can't detect encoding + // we always guarantee this is coerced, even if we can't detect encoding if ($currentEncoding = mb_detect_encoding($value, $this->mb_detect_order)) { $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); } else { From 0241c4ab9279d78b5fffd619fa528bf2bdba07a2 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 12 Sep 2016 19:17:13 -0700 Subject: [PATCH 0186/1161] English --- lib/Raven/ReprSerializer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php index 2b2f42658..dc704695b 100644 --- a/lib/Raven/ReprSerializer.php +++ b/lib/Raven/ReprSerializer.php @@ -10,7 +10,7 @@ */ /** - * Serializes a value into a representation that should reasonable suggest + * Serializes a value into a representation that should reasonably suggest * both the type and value, and be serializable into JSON. * @package raven */ From 98856440540a5a9ce294d59402ce434b56135666 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 13 Sep 2016 11:01:32 -0700 Subject: [PATCH 0187/1161] Add test for captureLastError --- test/Raven/Tests/ClientTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index b9b7d70d3..c4d9ffcb9 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -754,6 +754,19 @@ public function testCaptureExceptionInLatin1File() $this->assertEquals($found, true); } + public function testCaptureLastError() + { + $client = new Dummy_Raven_Client(); + + @$undefined; + + $client->captureLastError(); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals($event['exception']['values'][0]['value'], 'Undefined variable: undefined'); + } + public function testGetLastEventID() { $client = new Dummy_Raven_Client(); From 323cf6a68f4f2e3ebadf976ea6521ce04949c7f9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 14 Sep 2016 22:59:52 -0700 Subject: [PATCH 0188/1161] Improve serialization behavior (refs GH-281) --- lib/Raven/Client.php | 10 ++++--- test/Raven/Tests/ClientTest.php | 51 ++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 75db928ce..550a04357 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -686,16 +686,18 @@ public function sanitize(&$data) $data['request'] = $this->serializer->serialize($data['request']); } if (!empty($data['user'])) { - $data['user'] = $this->serializer->serialize($data['user']); + $data['user'] = $this->serializer->serialize($data['user'], 3); } if (!empty($data['extra'])) { $data['extra'] = $this->serializer->serialize($data['extra']); } if (!empty($data['tags'])) { - $data['tags'] = $this->serializer->serialize($data['tags']); + foreach ($data['tags'] as $key => $value) { + $data['tags'][$key] = @(string)$value; + } } - if (!empty($data['stacktrace']) && !empty($data['stacktrace']['frames'])) { - $data['stacktrace']['frames'] = $this->serializer->serialize($data['stacktrace']['frames']); + if (!empty($data['contexts'])) { + $data['contexts'] = $this->serializer->serialize($data['contexts'], 5); } } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index c4d9ffcb9..92bc09b25 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -786,7 +786,6 @@ public function testCustomTransport() $this->assertEquals(count($events), 1); } - public function testAppPathLinux() { $client = new Dummy_Raven_Client(); @@ -855,6 +854,56 @@ public function testSendCallback() $this->assertEquals(empty($events[0]['message']), true); } + public function testSanitizeExtra() + { + $client = new Dummy_Raven_Client(); + $data = array('extra' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, array(2), 3 + ), + ), + )); + $client->sanitize($data); + + $this->assertEquals($data, array('extra' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, 'Array of length 1', 3 + ), + ), + ))); + } + + public function testSanitizeTags() + { + $client = new Dummy_Raven_Client(); + $data = array('tags' => array( + 'foo' => 'bar', + 'baz' => array('biz'), + )); + $client->sanitize($data); + + $this->assertEquals($data, array('tags' => array( + 'foo' => 'bar', + 'baz' => 'Array', + ))); + } + + public function testSanitizeUser() + { + $client = new Dummy_Raven_Client(); + $data = array('user' => array( + 'email' => 'foo@example.com', + )); + $client->sanitize($data); + + $this->assertEquals($data, array('user' => array( + 'email' => 'foo@example.com', + ))); + } /** * Set the server array to the test values, check the current url * From 911315575f157729e5f59fafb3053413947916c4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 09:37:31 -0700 Subject: [PATCH 0189/1161] Correct behavior of error suppression operator This fixes a case on PHP 5.x (confirmed on 5.6) where the error control operator was not being respected due to ``handleFatalError`` and the result of ``error_get_last`` not matching the current error during ``handleError``. Fixes GH-332 --- lib/Raven/ErrorHandler.php | 28 +++++++++------------ test/Raven/Tests/ErrorHandlerTest.php | 35 ++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 4bc5a429d..209a90470 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -69,9 +69,12 @@ public function handleException($e, $isError = false, $vars = null) public function handleError($type, $message, $file = '', $line = 0, $context = array()) { // we always need to bind _last_handled_error in the case of a suppressed - // error getting passed to handleFatalError + // error getting passed to handleFatalError. In PHP 5.x it seems that + // ``error_get_last`` is not always populated, so we instead always bind + // it to the last value (rather than whatever error we're handling now) + $this->_last_handled_error = error_get_last(); + $e = new ErrorException($message, 0, $type, $file, $line); - $this->_last_handled_error = $e; if (error_reporting() !== 0) { $error_types = $this->error_types; @@ -82,6 +85,7 @@ public function handleError($type, $message, $file = '', $line = 0, $context = a $this->handleException($e, true, $context); } } + if ($this->call_existing_error_handler) { if ($this->old_error_handler !== null) { return call_user_func( @@ -112,24 +116,16 @@ public function handleFatalError() $error_types = error_reporting(); } if ($error_types & $error['type']) { - $e = new ErrorException( - @$error['message'], 0, @$error['type'], - @$error['file'], @$error['line'] - ); - // ensure that if this error was reported via handleError that // we don't duplicate it here - if ($this->_last_handled_error) { - $le = $this->_last_handled_error; - if ($e->getMessage() === $le->getMessage() && - $e->getSeverity() === $le->getSeverity() && - $e->getLine() === $le->getLine() && - $e->getFile() === $le->getFile() - ) { - return; - } + if ($this->_last_handled_error === $error) { + return; } + $e = new ErrorException( + @$error['message'], 0, @$error['type'], + @$error['file'], @$error['line'] + ); $this->handleException($e, true); } } diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 3d08964c3..8e0d79ddb 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -18,6 +18,10 @@ public function setUp() $this->errorLevel = error_reporting(); $this->errorHandlerCalled = false; $this->existingErrorHandler = set_error_handler(array($this, 'errorHandler'), -1); + // improves the reliability of tests + if (function_exists('error_clear_last')) { + error_clear_last(); + } } public function errorHandler() @@ -27,8 +31,10 @@ public function errorHandler() public function tearDown() { - // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level - set_error_handler(array($this, 'errorHandler'), error_reporting()); + restore_exception_handler(); + set_error_handler($this->existingErrorHandler); + // // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level + // set_error_handler(array($this, 'errorHandler'), error_reporting()); error_reporting($this->errorLevel); } @@ -103,18 +109,35 @@ public function testErrorHandlerRespectsErrorReportingDefault() // Because we cannot **know** that a user silenced an error, we always // defer to respecting the error reporting settings. - public function testSilentErrorsAreNotReported() + public function testSilentErrorsAreNotReportedWithGlobal() { $client = $this->getMock('Client', array('captureException')); $client->expects($this->never()) ->method('captureException'); - error_reporting(E_USER_WARNING); + error_reporting(E_ALL); $handler = new Raven_ErrorHandler($client); - $handler->registerErrorHandler(false); + $handler->registerErrorHandler(true); + + @$undefined; + + // also ensure it doesnt get reported by the fatal handler + $handler->handleFatalError(); + } + + // Because we cannot **know** that a user silenced an error, we always + // defer to respecting the error reporting settings. + public function testSilentErrorsAreNotReportedWithLocal() + { + $client = $this->getMock('Client', array('captureException')); + $client->expects($this->never()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(true, E_ALL); - @trigger_error('Silent', E_USER_WARNING); + @$my_array[2]; // also ensure it doesnt get reported by the fatal handler $handler->handleFatalError(); From 01fb067ef4cd469fdb92272e0c254980ee943c6c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 10:42:54 -0700 Subject: [PATCH 0190/1161] Add basic integration test --- test/Raven/Tests/IntegrationTest.php | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/Raven/Tests/IntegrationTest.php diff --git a/test/Raven/Tests/IntegrationTest.php b/test/Raven/Tests/IntegrationTest.php new file mode 100644 index 000000000..33953da82 --- /dev/null +++ b/test/Raven/Tests/IntegrationTest.php @@ -0,0 +1,65 @@ +__sent_events; + } + public function send(&$data) + { + if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { + // if send_callback returns falsely, end native send + return; + } + $this->__sent_events[] = $data; + } + public function is_http_request() + { + return true; + } +} + +class Raven_Tests_IntegrationTest extends PHPUnit_Framework_TestCase +{ + private function create_chained_exception() + { + try { + throw new Exception('Foo bar'); + } catch (Exception $ex) { + try { + throw new Exception('Child exc', 0, $ex); + } catch (Exception $ex2) { + return $ex2; + } + } + } + + public function testCaptureSimpleError() + { + $client = new DummyIntegration_Raven_Client('https://public:secret@example.com/1'); + + @mkdir('/no/way'); + + $client->captureLastError(); + + $events = $client->getSentEvents(); + $event = array_pop($events); + + $exc = $event['exception']['values'][0]; + $this->assertEquals($exc['value'], 'mkdir(): No such file or directory'); + $stack = $exc['stacktrace']['frames']; + $lastFrame = $stack[count($stack) - 1]; + $this->assertEquals(@$lastFrame['filename'], __file__); + } +} From 72a1b6c74473156372cda317babf27a4172e0fb4 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 10:44:02 -0700 Subject: [PATCH 0191/1161] Improve scope handling of unnamed params --- lib/Raven/Stacktrace.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index dcd3e6799..3d84ff89d 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -108,9 +108,9 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true foreach ($vars as $key => $value) { $value = $reprSerializer->serialize($value); if (is_string($value) || is_numeric($value)) { - $cleanVars[$key] = substr($value, 0, $frame_var_limit); + $cleanVars[(string)$key] = substr($value, 0, $frame_var_limit); } else { - $cleanVars[$key] = $value; + $cleanVars[(string)$key] = $value; } } $data['vars'] = $cleanVars; @@ -164,7 +164,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client return array(); } else { // Sanitize the file path - return array($frame['args'][0]); + return array('param1' => $frame['args'][0]); } } try { @@ -198,9 +198,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client } $args[$params[$i]->name] = $arg; } else { - // TODO: Sentry thinks of these as context locals, so they must be named - // Assign the argument by number - // $args[$i] = $arg; + $args['param'.$i] = $arg; } } From 22a80c63ff84d2ede40cf79baae79d3931cba67f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 13:46:38 -0700 Subject: [PATCH 0192/1161] Changes for 1.3.0 --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index 65ca1111c..41c572a4c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +1.3.0 +----- + +- Fixed an issue causing the error suppression operator to not be respected (#335) +- Fixed some serialization behavior (#352) +- Fixed an issue with app paths and trailing slashes (#350) +- Handle non-latin encoding with source code context line (#345) + 1.2.0 ----- From a62edbc453608b408925c479b5ce280e58be94a8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 13:47:38 -0700 Subject: [PATCH 0193/1161] 1.4.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 6d59eaa03..366e9805d 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 550a04357..304edf037 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '1.3.x-dev'; + const VERSION = '1.4.x-dev'; const PROTOCOL = '6'; From 52176da0200e141af7ec11cce443edbecfbd48f9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 15:05:45 -0700 Subject: [PATCH 0194/1161] Add excluded_app_paths config (refs GH-343) --- lib/Raven/Client.php | 48 ++++++++++++++++++++--------- lib/Raven/Stacktrace.php | 23 +++++++++++--- test/Raven/Tests/StacktraceTest.php | 24 +++++++++++++++ 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 304edf037..c2c9c5967 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -92,6 +92,7 @@ public function __construct($options_or_dsn=null, $options=array()) // app path is used to determine if code is part of your application $this->setAppPath(Raven_Util::get($options, 'app_path', null)); + $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null)); // a list of prefixes used to coerce absolute paths into relative $this->setPrefixes(Raven_Util::get($options, 'prefixes', null)); $this->processors = $this->setProcessorsFromOptions($options); @@ -157,6 +158,21 @@ public function setEnvironment($value) return $this; } + private function _convertPath($value) + { + $path = @realpath($value); + if ($path === false) { + $path = $value; + } + // we need app_path to have a trailing slash otherwise + // base path detection becomes complex if the same + // prefix is matched + if (substr($path, 0, 1) === '/' && substr($path, -1, 1) !== '/') { + $path = $path . '/'; + } + return $path; + } + public function getAppPath() { return $this->app_path; @@ -165,23 +181,27 @@ public function getAppPath() public function setAppPath($value) { if ($value) { - $path = @realpath($value); - if ($path === false) { - $path = $value; - } - // we need app_path to have a trailing slash otherwise - // base path detection becomes complex if the same - // prefix is matched - if (substr($path, 0, 1) === '/' && substr($path, -1, 1) !== '/') { - $path = $path . '/'; - } - $this->app_path = $path; + $this->app_path = $this->_convertPath($value); } else { $this->app_path = null; } return $this; } + public function getExcludedAppPaths() + { + return $this->excluded_app_paths; + } + + public function setExcludedAppPaths($value) + { + if ($value) { + $this->excluded_app_paths = $value ? array_map(array($this, '_convertPath'), $value) : $value; + } else { + $this->excluded_app_paths = null; + } + return $this; + } public function getPrefixes() { return $this->prefixes; @@ -189,7 +209,7 @@ public function getPrefixes() public function setPrefixes($value) { - $this->prefixes = $value ? array_map('realpath', $value) : $value; + $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value; return $this; } @@ -425,7 +445,7 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul $exc_data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, - $this->app_path, $this->serializer, $this->reprSerializer + $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer ), ); @@ -659,7 +679,7 @@ public function capture($data, $stack = null, $vars = null) $data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, - $this->app_path, $this->serializer, $this->reprSerializer + $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer ), ); } diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 3d84ff89d..b66004446 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -13,9 +13,15 @@ class Raven_Stacktrace 'require_once', ); - public static function get_stack_info($frames, $trace = false, $shiftvars = true, $errcontext = null, - $frame_var_limit = Raven_Client::MESSAGE_LIMIT, $strip_prefixes = null, - $app_path = null, Raven_Serializer $serializer = null, + public static function get_stack_info($frames, + $trace = false, + $shiftvars = true, + $errcontext = null, + $frame_var_limit = Raven_Client::MESSAGE_LIMIT, + $strip_prefixes = null, + $app_path = null, + $excluded_app_paths = null, + Raven_Serializer $serializer = null, Raven_ReprSerializer $reprSerializer = null) { $serializer = $serializer ?: new Raven_Serializer(); @@ -98,7 +104,16 @@ public static function get_stack_info($frames, $trace = false, $shiftvars = true // detect in_app based on app path if ($app_path) { - $data['in_app'] = (bool)(substr($abs_path, 0, strlen($app_path)) === $app_path); + $in_app = (bool)(substr($abs_path, 0, strlen($app_path)) === $app_path); + if ($in_app && $excluded_app_paths) { + foreach ($excluded_app_paths as $path) { + if (substr($abs_path, 0, strlen($path)) === $path) { + $in_app = false; + break; + } + } + } + $data['in_app'] = $in_app; } // dont set this as an empty array as PHP will treat it as a numeric array diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 8917f5ce1..9dd85b5bb 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -281,6 +281,30 @@ public function testInApp() $this->assertEquals($frames[1]['in_app'], true); } + public function testInAppWithExclusion() + { + $stack = array( + array( + "file" => dirname(__FILE__) . '/resources/foo/a.php', + "line" => 11, + "function" => "a_test", + ), + array( + "file" => dirname(__FILE__) . '/resources/bar/b.php', + "line" => 3, + "function" => "include_once", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info( + $stack, true, null, null, 0, null, dirname(__FILE__), + array(dirname(__FILE__) . '/resources/bar/')); + + // stack gets reversed + $this->assertEquals($frames[0]['in_app'], false); + $this->assertEquals($frames[1]['in_app'], true); + } + public function testBasePath() { $stack = array( From 2108830abc646e9f806ae2b7bef7dfc5cb52ede6 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 23:06:16 -0700 Subject: [PATCH 0195/1161] Overhaul stacktrace generation - Bring back anonymous (etc) frames - Remove shift_vars configurability (option kept for deprecation) - Remove module usage - Better handle empty context - Correct alignment of filename (current frame) + function (caller frame) - Add default prefixes using include paths --- lib/Raven/Client.php | 13 +- lib/Raven/Stacktrace.php | 93 +++----- test/Raven/Tests/ClientTest.php | 25 ++- test/Raven/Tests/IntegrationTest.php | 6 +- .../Raven/Tests/SanitizeDataProcessorTest.php | 12 +- test/Raven/Tests/StacktraceTest.php | 200 +++--------------- test/Raven/Tests/resources/a.php | 2 - test/Raven/Tests/resources/b.php | 3 +- 8 files changed, 103 insertions(+), 251 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c2c9c5967..13efe56f6 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -94,7 +94,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->setAppPath(Raven_Util::get($options, 'app_path', null)); $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null)); // a list of prefixes used to coerce absolute paths into relative - $this->setPrefixes(Raven_Util::get($options, 'prefixes', null)); + $this->setPrefixes(Raven_Util::get($options, 'prefixes', $this->getDefaultPrefixes())); $this->processors = $this->setProcessorsFromOptions($options); $this->_lasterror = null; @@ -158,6 +158,12 @@ public function setEnvironment($value) return $this; } + private function getDefaultPrefixes() + { + $value = get_include_path(); + return explode(':', $value); + } + private function _convertPath($value) { $path = @realpath($value); @@ -422,7 +428,6 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul $exc_data = array( 'value' => $this->serializer->serialize($exc->getMessage()), 'type' => get_class($exc), - 'module' => $exc->getFile() .':'. $exc->getLine(), ); /**'exception' @@ -444,7 +449,7 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul $exc_data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, + $trace, $this->trace, $vars, $this->message_limit, $this->prefixes, $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer ), ); @@ -678,7 +683,7 @@ public function capture($data, $stack = null, $vars = null) if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = array( 'frames' => Raven_Stacktrace::get_stack_info( - $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit, $this->prefixes, + $stack, $this->trace, $vars, $this->message_limit, $this->prefixes, $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer ), ); diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index b66004446..7a3857aee 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -15,7 +15,6 @@ class Raven_Stacktrace public static function get_stack_info($frames, $trace = false, - $shiftvars = true, $errcontext = null, $frame_var_limit = Raven_Client::MESSAGE_LIMIT, $strip_prefixes = null, @@ -28,39 +27,29 @@ public static function get_stack_info($frames, $reprSerializer = $reprSerializer ?: new Raven_ReprSerializer(); /** - * PHP's way of storing backstacks seems bass-ackwards to me - * 'function' is not the function you're in; it's any function being - * called, so we have to shift 'function' down by 1. Ugh. + * PHP stores calls in the stacktrace, rather than executing context. Sentry + * wants to know "when Im calling this code, where am I", and PHP says "I'm + * calling this function" not "I'm in this function". Due to that, we shift + * the context for a frame up one, meaning the variables (which are the calling + * args) come from the previous frame. */ $result = array(); for ($i = 0; $i < count($frames); $i++) { - $frame = $frames[$i]; + $frame = isset($frames[$i]) ? $frames[$i] : null; $nextframe = isset($frames[$i + 1]) ? $frames[$i + 1] : null; if (!array_key_exists('file', $frame)) { - // XXX: Disable capturing of anonymous functions until we can implement a better grouping mechanism. - // In our examples these generally didn't help with debugging as the information was found elsewhere - // within the exception or the stacktrace - continue; - // if (isset($frame['args'])) { - // $args = is_string($frame['args']) ? $frame['args'] : @json_encode($frame['args']); - // } - // else { - // $args = array(); - // } - // if (!empty($nextframe['class'])) { - // $context['line'] = sprintf('%s%s%s(%s)', - // $nextframe['class'], $nextframe['type'], $nextframe['function'], - // $args); - // } - // else { - // $context['line'] = sprintf('%s(%s)', $nextframe['function'], $args); - // } - // $abs_path = ''; - // $context['prefix'] = ''; - // $context['suffix'] = ''; - // $context['filename'] = $filename = '[Anonymous function]'; - // $context['lineno'] = 0; + if (!empty($frame['class'])) { + $context['line'] = sprintf('%s%s%s', + $frame['class'], $frame['type'], $frame['function']); + } else { + $context['line'] = sprintf('%s(anonymous)', $frame['function']); + } + $abs_path = ''; + $context['prefix'] = ''; + $context['suffix'] = ''; + $context['filename'] = $filename = '[Anonymous function]'; + $context['lineno'] = 0; } else { $context = self::read_source_file($frame['file'], $frame['line']); $abs_path = $frame['file']; @@ -68,34 +57,20 @@ public static function get_stack_info($frames, // strip base path if present $context['filename'] = self::strip_prefixes($context['filename'], $strip_prefixes); - - $module = basename($abs_path); - if (isset($nextframe['class'])) { - $module .= ':' . $nextframe['class']; - } - - if (empty($result) && isset($errcontext)) { + if ($i === 0 && isset($errcontext)) { // If we've been given an error context that can be used as the vars for the first frame. $vars = $errcontext; } else { if ($trace) { - if ($shiftvars) { - $vars = self::get_frame_context($nextframe, $frame_var_limit); - } else { - $vars = self::get_caller_frame_context($frame); - } + $vars = self::get_frame_context($nextframe, $frame_var_limit); } else { $vars = array(); } } $data = array( - // abs_path isn't overly useful, wastes space, and exposes - // filesystem internals - // 'abs_path' => $abs_path, 'filename' => $context['filename'], 'lineno' => (int) $context['lineno'], - 'module' => $module, 'function' => isset($nextframe['function']) ? $nextframe['function'] : null, 'pre_context' => $serializer->serialize($context['prefix']), 'context_line' => $serializer->serialize($context['line']), @@ -137,15 +112,14 @@ public static function get_stack_info($frames, return array_reverse($result); } - public static function get_caller_frame_context($frame) + public static function get_default_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) { - if (!isset($frame['args'])) { - return array(); - } - $i = 1; $args = array(); foreach ($frame['args'] as $arg) { + if (is_string($arg) || is_numeric($arg)) { + $arg = substr($arg, 0, $frame_arg_limit); + } $args['param'.$i] = $arg; $i++; } @@ -154,24 +128,23 @@ public static function get_caller_frame_context($frame) public static function get_frame_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) { - // The reflection API seems more appropriate if we associate it with the frame - // where the function is actually called (since we're treating them as function context) - if (!isset($frame['function'])) { - return array(); - } - if (!isset($frame['args'])) { return array(); } + // The reflection API seems more appropriate if we associate it with the frame + // where the function is actually called (since we're treating them as function context) + if (!isset($frame['function'])) { + return $this->get_default_context($frame, $frame_arg_limit); + } if (strpos($frame['function'], '__lambda_func') !== false) { - return array(); + return $this->get_default_context($frame, $frame_arg_limit); } if (isset($frame['class']) && $frame['class'] == 'Closure') { - return array(); + return $this->get_default_context($frame, $frame_arg_limit); } if (strpos($frame['function'], '{closure}') !== false) { - return array(); + return $this->get_default_context($frame, $frame_arg_limit); } if (in_array($frame['function'], self::$statements)) { if (empty($frame['args'])) { @@ -195,7 +168,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client $reflection = new ReflectionFunction($frame['function']); } } catch (ReflectionException $e) { - return array(); + return $this->get_default_context($frame, $frame_arg_limit); } $params = $reflection->getParameters(); @@ -227,7 +200,7 @@ private static function strip_prefixes($filename, $prefixes) } foreach ($prefixes as $prefix) { if (substr($filename, 0, strlen($prefix)) === $prefix) { - return substr($filename, strlen($prefix) + 1); + return substr($filename, strlen($prefix)); } } return $filename; diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 92bc09b25..d3c676646 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -54,6 +54,10 @@ public function get_user_data() { return parent::get_user_data(); } + // short circuit breadcrumbs + public function registerDefaultBreadcrumbHandlers() + { + } /** * Expose the current url method to test it @@ -177,7 +181,7 @@ public function testParseDSNMissingSecretKey() public function testDsnFirstArgument() { - $client = new Raven_Client('http://public:secret@example.com/1'); + $client = new Dummy_Raven_Client('http://public:secret@example.com/1'); $this->assertEquals($client->project, 1); $this->assertEquals($client->server, 'http://example.com/api/1/store/'); @@ -187,7 +191,7 @@ public function testDsnFirstArgument() public function testDsnFirstArgumentWithOptions() { - $client = new Raven_Client('http://public:secret@example.com/1', array( + $client = new Dummy_Raven_Client('http://public:secret@example.com/1', array( 'site' => 'foo', )); @@ -200,7 +204,7 @@ public function testDsnFirstArgumentWithOptions() public function testOptionsFirstArgument() { - $client = new Raven_Client(array( + $client = new Dummy_Raven_Client(array( 'server' => 'http://example.com/api/1/store/', 'project' => 1, )); @@ -211,7 +215,7 @@ public function testOptionsFirstArgument() public function testDsnInOptionsFirstArg() { - $client = new Raven_Client(array( + $client = new Dummy_Raven_Client(array( 'dsn' => 'http://public:secret@example.com/1', )); @@ -223,7 +227,7 @@ public function testDsnInOptionsFirstArg() public function testDsnInOptionsSecondArg() { - $client = new Raven_Client(null, array( + $client = new Dummy_Raven_Client(null, array( 'dsn' => 'http://public:secret@example.com/1', )); @@ -235,7 +239,7 @@ public function testDsnInOptionsSecondArg() public function testOptionsFirstArgumentWithOptions() { - $client = new Raven_Client(array( + $client = new Dummy_Raven_Client(array( 'server' => 'http://example.com/api/1/store/', 'project' => 1, ), array( @@ -361,13 +365,11 @@ public function testCaptureExceptionSetsInterfaces() $this->assertEquals(count($exc['values']), 1); $this->assertEquals($exc['values'][0]['value'], 'Foo bar'); $this->assertEquals($exc['values'][0]['type'], 'Exception'); - $this->assertFalse(empty($exc['values'][0]['module'])); $this->assertFalse(empty($exc['values'][0]['stacktrace']['frames'])); $frames = $exc['values'][0]['stacktrace']['frames']; $frame = $frames[count($frames) - 1]; $this->assertTrue($frame['lineno'] > 0); - $this->assertEquals($frame['module'], 'ClientTest.php:Raven_Tests_ClientTest'); $this->assertEquals($frame['function'], 'create_exception'); $this->assertFalse(isset($frame['vars'])); $this->assertEquals($frame['context_line'], ' throw new Exception(\'Foo bar\');'); @@ -778,7 +780,10 @@ public function testCustomTransport() { $events = array(); - $client = new Raven_Client('https://public:secret@sentry.example.com/1'); + // transport test requires default client + $client = new Raven_Client('https://public:secret@sentry.example.com/1', array( + 'install_default_breadcrumb_handlers' => false, + )); $client->setTransport(function ($client, $data) use (&$events) { $events[] = $data; }); @@ -812,7 +817,7 @@ public function testAppPathWindows() */ public function testCannotInstallTwice() { - $client = new Raven_Client('https://public:secret@sentry.example.com/1'); + $client = new Dummy_Raven_Client('https://public:secret@sentry.example.com/1'); $client->install(); $client->install(); } diff --git a/test/Raven/Tests/IntegrationTest.php b/test/Raven/Tests/IntegrationTest.php index 33953da82..9625ad023 100644 --- a/test/Raven/Tests/IntegrationTest.php +++ b/test/Raven/Tests/IntegrationTest.php @@ -28,6 +28,10 @@ public function is_http_request() { return true; } + // short circuit breadcrumbs + public function registerDefaultBreadcrumbHandlers() + { + } } class Raven_Tests_IntegrationTest extends PHPUnit_Framework_TestCase @@ -60,6 +64,6 @@ public function testCaptureSimpleError() $this->assertEquals($exc['value'], 'mkdir(): No such file or directory'); $stack = $exc['stacktrace']['frames']; $lastFrame = $stack[count($stack) - 1]; - $this->assertEquals(@$lastFrame['filename'], __file__); + $this->assertEquals(@$lastFrame['filename'], 'test/Raven/Tests/IntegrationTest.php'); } } diff --git a/test/Raven/Tests/SanitizeDataProcessorTest.php b/test/Raven/Tests/SanitizeDataProcessorTest.php index eced94508..bc22b8431 100644 --- a/test/Raven/Tests/SanitizeDataProcessorTest.php +++ b/test/Raven/Tests/SanitizeDataProcessorTest.php @@ -32,7 +32,7 @@ public function testDoesFilterHttpData() ) ); - $client = new Raven_Client(); + $client = new Dummy_Raven_Client(); $processor = new Raven_SanitizeDataProcessor($client); $processor->process($data); @@ -59,7 +59,7 @@ public function testDoesFilterSessionId() ) ); - $client = new Raven_Client(); + $client = new Dummy_Raven_Client(); $processor = new Raven_SanitizeDataProcessor($client); $processor->process($data); @@ -75,7 +75,7 @@ public function testDoesFilterCreditCard() ), ); - $client = new Raven_Client(); + $client = new Dummy_Raven_Client(); $processor = new Raven_SanitizeDataProcessor($client); $processor->process($data); @@ -88,7 +88,7 @@ public function testDoesFilterCreditCard() */ public function testSettingProcessorOptions() { - $client = new Raven_Client(); + $client = new Dummy_Raven_Client(); $processor = new Raven_SanitizeDataProcessor($client); $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); @@ -114,7 +114,7 @@ public function testSettingProcessorOptions() */ public function testOverrideOptions($processorOptions, $client_options, $dsn) { - $client = new Raven_Client($dsn, $client_options); + $client = new Dummy_Raven_Client($dsn, $client_options); $processor = $client->processors[0]; $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); @@ -150,7 +150,7 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) ) ); - $client = new Raven_Client($dsn, $client_options); + $client = new Dummy_Raven_Client($dsn, $client_options); $processor = $client->processors[0]; $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 9dd85b5bb..ae98203f6 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -39,115 +39,34 @@ public function testCanTraceParamContext() public function testSimpleTrace() { $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), + array( + 'file' => dirname(__FILE__) . '/resources/a.php', + 'line' => 9, + 'function' => 'a_test', + 'args' => array('friend'), + ), + array( + 'file' => dirname(__FILE__) . '/resources/b.php', + 'line' => 2, + 'args' => array( + dirname(__FILE__) . '/resources/a.php', + ), + 'function' => 'include_once', + ) ); $frames = Raven_Stacktrace::get_stack_info($stack, true); $frame = $frames[0]; - $this->assertEquals('b.php', $frame["module"]); - $this->assertEquals(3, $frame["lineno"]); - $this->assertNull($frame["function"]); - $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); - $frame = $frames[1]; - $this->assertEquals('a.php', $frame["module"]); - $this->assertEquals(11, $frame["lineno"]); - $this->assertEquals('include_once', $frame["function"]); - $this->assertEquals('a_test($foo);', $frame["context_line"]); - } - - public function testSimpleUnshiftedTrace() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, false); - - $frame = $frames[0]; - $this->assertEquals('b.php', $frame["module"]); - $this->assertEquals(3, $frame["lineno"]); - $this->assertNull($frame["function"]); - $this->assertEquals('/tmp/a.php', $frame['vars']['param1']); - $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); - $frame = $frames[1]; - $this->assertEquals('a.php', $frame["module"]); - $this->assertEquals(11, $frame["lineno"]); - $this->assertEquals('include_once', $frame["function"]); - $this->assertEquals('friend', $frame['vars']['param1']); - $this->assertEquals('a_test($foo);', $frame["context_line"]); - } - - public function testShiftedCaptureVars() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); - - $vars = array( - "foo" => "bar", - "baz" => "zoom" - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars); - - $frame = $frames[0]; - $this->assertEquals('b.php', $frame["module"]); - $this->assertEquals(3, $frame["lineno"]); - $this->assertNull($frame["function"]); - $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); + $this->assertEquals(2, $frame['lineno']); + $this->assertNull($frame['function']); + $this->assertEquals("include_once 'a.php';", $frame['context_line']); $this->assertFalse(isset($frame['vars'])); $frame = $frames[1]; - $this->assertEquals('a.php', $frame["module"]); - $this->assertEquals(11, $frame["lineno"]); - $this->assertEquals('include_once', $frame["function"]); - $this->assertEquals('a_test($foo);', $frame["context_line"]); - $this->assertEquals($vars, $frame['vars']); + $this->assertEquals(9, $frame['lineno']); + $this->assertEquals('include_once', $frame['function']); + $this->assertEquals('a_test($foo);', $frame['context_line']); + $this->assertEquals(dirname(__FILE__) . '/resources/a.php', $frame['vars']['param1']); } public function testDoesNotModifyCaptureVars() @@ -179,56 +98,14 @@ public function testDoesNotModifyCaptureVars() "foo" => &$iAmFoo ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars, 5); + $frames = Raven_Stacktrace::get_stack_info($stack, true, $vars, 5); // Check we haven't modified our vars. - $this->assertEquals($originalFoo, $vars["foo"]); + $this->assertEquals($originalFoo, $vars['foo']); $frame = $frames[1]; // Check that we did truncate the variable in our output - $this->assertEquals(5, strlen($frame['vars']['foo'])); - } - - public function testUnshiftedCaptureVars() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); - - $vars = array( - "foo" => "bar", - "baz" => "zoom" - ); - - $frames = Raven_Stacktrace::get_stack_info($stack, true, false, $vars); - - $frame = $frames[0]; - $this->assertEquals('b.php', $frame["module"]); - $this->assertEquals(3, $frame["lineno"]); - $this->assertNull($frame["function"]); - $this->assertEquals(array('param1' => '/tmp/a.php'), $frame['vars']); - $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); - $frame = $frames[1]; - $this->assertEquals('a.php', $frame["module"]); - $this->assertEquals(11, $frame["lineno"]); - $this->assertEquals('include_once', $frame["function"]); - $this->assertEquals($vars, $frame['vars']); - $this->assertEquals('a_test($foo);', $frame["context_line"]); + $this->assertEquals(strlen($frame['vars']['foo']), 5); } public function testDoesFixFrameInfo() @@ -242,21 +119,18 @@ public function testDoesFixFrameInfo() $frames = Raven_Stacktrace::get_stack_info($stack, true); // just grab the last few frames - $frames = array_slice($frames, -5); + $frames = array_slice($frames, -6); $frame = $frames[0]; - $this->assertEquals('StacktraceTest.php:Raven_Tests_StacktraceTest', $frame['module']); - $this->assertEquals('testDoesFixFrameInfo', $frame['function']); - $frame = $frames[1]; - $this->assertEquals('StacktraceTest.php', $frame['module']); $this->assertEquals('raven_test_create_stacktrace', $frame['function']); - $frame = $frames[2]; - $this->assertEquals('StacktraceTest.php', $frame['module']); + $frame = $frames[1]; $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[2]; + $this->assertEquals('call_user_func', $frame['function']); $frame = $frames[3]; - $this->assertEquals('StacktraceTest.php', $frame['module']); $this->assertEquals('raven_test_recurse', $frame['function']); $frame = $frames[4]; - $this->assertEquals('StacktraceTest.php', $frame['module']); + $this->assertEquals('call_user_func', $frame['function']); + $frame = $frames[5]; $this->assertEquals('raven_test_recurse', $frame['function']); } @@ -275,7 +149,7 @@ public function testInApp() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, null, dirname(__FILE__)); + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, 0, null, dirname(__FILE__)); $this->assertEquals($frames[0]['in_app'], true); $this->assertEquals($frames[1]['in_app'], true); @@ -297,7 +171,7 @@ public function testInAppWithExclusion() ); $frames = Raven_Stacktrace::get_stack_info( - $stack, true, null, null, 0, null, dirname(__FILE__), + $stack, true, null, 0, null, dirname(__FILE__) . '/', array(dirname(__FILE__) . '/resources/bar/')); // stack gets reversed @@ -313,17 +187,11 @@ public function testBasePath() "line" => 11, "function" => "a_test", ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "function" => "include_once", - ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, null, 0, array(dirname(__FILE__))); + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, 0, array(dirname(__FILE__) . '/')); - $this->assertEquals($frames[0]['filename'], 'resources/b.php'); - $this->assertEquals($frames[1]['filename'], 'resources/a.php'); + $this->assertEquals($frames[0]['filename'], 'resources/a.php'); } public function testNoBasePath() diff --git a/test/Raven/Tests/resources/a.php b/test/Raven/Tests/resources/a.php index 78ae29c80..1d8ed2e3d 100644 --- a/test/Raven/Tests/resources/a.php +++ b/test/Raven/Tests/resources/a.php @@ -1,6 +1,4 @@ Date: Mon, 19 Sep 2016 23:22:21 -0700 Subject: [PATCH 0196/1161] Restore proper fatal error handling --- lib/Raven/ErrorHandler.php | 42 ++++++++++++++------------- test/Raven/Tests/ErrorHandlerTest.php | 35 ---------------------- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 209a90470..7c7d4a2bb 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -32,6 +32,15 @@ class Raven_ErrorHandler private $call_existing_error_handler = false; private $reservedMemory; private $send_errors_last = false; + private $fatal_error_types = array( + E_ERROR, + E_PARSE, + E_CORE_ERROR, + E_CORE_WARNING, + E_COMPILE_ERROR, + E_COMPILE_WARNING, + E_STRICT, + ); /** * @var array @@ -68,6 +77,11 @@ public function handleException($e, $isError = false, $vars = null) public function handleError($type, $message, $file = '', $line = 0, $context = array()) { + // http://php.net/set_error_handler + // The following error types cannot be handled with a user defined function: E_ERROR, + // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and + // most of E_STRICT raised in the file where set_error_handler() is called. + // we always need to bind _last_handled_error in the case of a suppressed // error getting passed to handleFatalError. In PHP 5.x it seems that // ``error_get_last`` is not always populated, so we instead always bind @@ -104,30 +118,18 @@ public function handleError($type, $message, $file = '', $line = 0, $context = a public function handleFatalError() { + unset($this->reservedMemory); + if (null === $error = error_get_last()) { return; } - unset($this->reservedMemory); - - if (error_reporting() !== 0) { - $error_types = $this->error_types; - if ($error_types === null) { - $error_types = error_reporting(); - } - if ($error_types & $error['type']) { - // ensure that if this error was reported via handleError that - // we don't duplicate it here - if ($this->_last_handled_error === $error) { - return; - } - - $e = new ErrorException( - @$error['message'], 0, @$error['type'], - @$error['file'], @$error['line'] - ); - $this->handleException($e, true); - } + if ($error['type'] & $this->fatal_error_types) { + $e = new ErrorException( + @$error['message'], 0, @$error['type'], + @$error['file'], @$error['line'] + ); + $this->handleException($e, true); } } diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 8e0d79ddb..5dfdec2df 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -157,41 +157,6 @@ public function testErrorHandlerDefaultsErrorReporting() trigger_error('Warning', E_USER_WARNING); } - public function testHandleFatalError() - { - $client = $this->getMock('Client', array('captureException')); - $client->expects($this->once()) - ->method('captureException'); - - $handler = new Raven_ErrorHandler($client); - - # http://php.net/manual/en/function.error-get-last.php#113518 - set_error_handler('var_dump', 0); - @$undef_var; - restore_error_handler(); - - $handler->handleFatalError(); - } - - public function testHandleFatalErrorDuplicate() - { - $client = $this->getMock('Client', array('captureException')); - $client->expects($this->once()) - ->method('captureException'); - - $handler = new Raven_ErrorHandler($client); - - # http://php.net/manual/en/function.error-get-last.php#113518 - set_error_handler('var_dump', 0); - @$undef_var; - restore_error_handler(); - - $error = error_get_last(); - - $handler->handleError($error['type'], $error['message'], $error['file'], $error['line']); - $handler->handleFatalError(); - } - public function testFluidInterface() { $client = $this->getMock('Client', array('captureException')); From 5d97526f71b95476fb62553b19bf9e2f595c823c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 23:26:37 -0700 Subject: [PATCH 0197/1161] Remove unneeded _last_handled_error --- lib/Raven/ErrorHandler.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 7c7d4a2bb..5407f8aec 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -48,7 +48,6 @@ class Raven_ErrorHandler * A 'null' value implies "whatever error_reporting is at time of error". */ private $error_types = null; - private $_last_handled_error = null; public function __construct($client, $send_errors_last = false, $error_types = null, $__error_types = null) @@ -82,12 +81,6 @@ public function handleError($type, $message, $file = '', $line = 0, $context = a // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and // most of E_STRICT raised in the file where set_error_handler() is called. - // we always need to bind _last_handled_error in the case of a suppressed - // error getting passed to handleFatalError. In PHP 5.x it seems that - // ``error_get_last`` is not always populated, so we instead always bind - // it to the last value (rather than whatever error we're handling now) - $this->_last_handled_error = error_get_last(); - $e = new ErrorException($message, 0, $type, $file, $line); if (error_reporting() !== 0) { From 9d1f33b3a49410274bc0b4f18b418694446d795f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 19 Sep 2016 23:32:08 -0700 Subject: [PATCH 0198/1161] Kill shift_vars entirely --- lib/Raven/Client.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 13efe56f6..d183de47c 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -76,7 +76,6 @@ public function __construct($options_or_dsn=null, $options=array()) $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT); $this->exclude = Raven_Util::get($options, 'exclude', array()); $this->severity_map = null; - $this->shift_vars = (bool) Raven_Util::get($options, 'shift_vars', true); $this->http_proxy = Raven_Util::get($options, 'http_proxy'); $this->extra_data = Raven_Util::get($options, 'extra', array()); $this->send_callback = Raven_Util::get($options, 'send_callback', null); From 98051f1e94ba0c079ec7af781be08a8998bf6cc7 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 20 Sep 2016 09:35:03 -0700 Subject: [PATCH 0199/1161] Document excluded_app_paths --- docs/config.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index b83e04a66..75ba72fac 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -74,6 +74,18 @@ The following settings are available for the client: $client->setAppPath(app_root()); +.. describe:: excluded_app_paths + + Paths to exclude from app_path detection. + + .. code-block:: php + + 'excluded_app_paths' => array(app_root() . '/cache'), + + .. code-block:: php + + $client->setExcludedAppPaths(array(app_root() . '/cache')); + .. describe:: prefixes Prefixes which should be stripped from filenames to create relative From 046c7b7b2d5ac0fe37ea0d87f4cc490d3e805e60 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 20 Sep 2016 09:37:39 -0700 Subject: [PATCH 0200/1161] Changes for 1.4.0 --- CHANGES | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGES b/CHANGES index 41c572a4c..aba98d5b0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,19 @@ +1.4.0 +----- + +This version primarily overhauls the exception/stacktrace generation to fix +a few bugs and improve the quality of data (#359). + +- Added ``excluded_app_paths`` config. +- Removed ``shift_vars`` config. +- Correct fatal error handling to only operate on expected types. This also + fixes some behavior with the error suppression operator. +- Expose anonymous and similar frames in the stacktrace. +- Default ``prefixes`` to PHP's include paths. +- Remove ``module`` usage. +- Better handle empty argument context. +- Correct alignment of filename (current frame) and function (caller frame) + 1.3.0 ----- From 4bc453db0cba57cb8fb084275227fd7e170a4e64 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 20 Sep 2016 09:38:06 -0700 Subject: [PATCH 0201/1161] 1.5.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 366e9805d..4447ed7e9 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.5.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index d183de47c..461124867 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '1.4.x-dev'; + const VERSION = '1.5.x-dev'; const PROTOCOL = '6'; From 38dad0f5850fb4e47f39cddb8ac3153e01bd5f2a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 20 Sep 2016 14:51:59 -0700 Subject: [PATCH 0202/1161] Correct calls to static functions (fixes GH-360) --- lib/Raven/Stacktrace.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 7a3857aee..0c174d734 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -135,16 +135,16 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client // The reflection API seems more appropriate if we associate it with the frame // where the function is actually called (since we're treating them as function context) if (!isset($frame['function'])) { - return $this->get_default_context($frame, $frame_arg_limit); + return self::get_default_context($frame, $frame_arg_limit); } if (strpos($frame['function'], '__lambda_func') !== false) { - return $this->get_default_context($frame, $frame_arg_limit); + return self::get_default_context($frame, $frame_arg_limit); } if (isset($frame['class']) && $frame['class'] == 'Closure') { - return $this->get_default_context($frame, $frame_arg_limit); + return self::get_default_context($frame, $frame_arg_limit); } if (strpos($frame['function'], '{closure}') !== false) { - return $this->get_default_context($frame, $frame_arg_limit); + return self::get_default_context($frame, $frame_arg_limit); } if (in_array($frame['function'], self::$statements)) { if (empty($frame['args'])) { @@ -168,7 +168,7 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client $reflection = new ReflectionFunction($frame['function']); } } catch (ReflectionException $e) { - return $this->get_default_context($frame, $frame_arg_limit); + return self::get_default_context($frame, $frame_arg_limit); } $params = $reflection->getParameters(); From 2f3f424c780a0e333b69b98a42af5b2c694338f5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 29 Sep 2016 14:17:08 -0700 Subject: [PATCH 0203/1161] Add transaction handling ```php $ctx = $client->transaction->push('/route/name'); routeName(); $client->transaction->pop($ctx); ``` --- lib/Raven/Client.php | 11 +++++--- lib/Raven/TransactionStack.php | 48 +++++++++++++++++++++++++++++++++ test/Raven/Tests/ClientTest.php | 13 ++------- 3 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 lib/Raven/TransactionStack.php diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 461124867..1952c9bd8 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -102,6 +102,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->_pending_events = array(); $this->context = new Raven_Context(); $this->breadcrumbs = new Raven_Breadcrumbs(); + $this->sdk = Raven_Util::get($options, 'sdk', array( 'name' => 'sentry-php', 'version' => self::VERSION, @@ -113,6 +114,11 @@ public function __construct($options_or_dsn=null, $options=array()) $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); } + $this->transaction = new Raven_TransactionStack(); + if ($this->is_http_request() && isset($_SERVER['REQUEST_URI'])) { + $this->transaction->push($_SERVER['REQUEST_URI']); + } + if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { $this->registerDefaultBreadcrumbHandlers(); } @@ -416,10 +422,6 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul if ($data === null) { $data = array(); - } elseif (!is_array($data)) { - $data = array( - 'culprit' => (string)$data, - ); } $exc = $exception; @@ -599,6 +601,7 @@ public function get_default_data() 'tags' => $this->tags, 'platform' => 'php', 'sdk' => $this->sdk, + 'culprit' => $this->transaction->peek(), ); } diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php new file mode 100644 index 000000000..5d428c723 --- /dev/null +++ b/lib/Raven/TransactionStack.php @@ -0,0 +1,48 @@ +stack = array(); + } + + public function clear() + { + $this->stack = array(); + } + + public function peek() + { + $len = count($this->stack); + if ($len === 0) { + return null; + } + return $this->stack[$len - 1]; + } + + public function push($context) + { + $this->stack[] = $context; + } + + public function pop($context=null) + { + if (!$context) { + return array_pop($this->stack); + } + while (!empty($this->stack)) { + if (array_pop($this->stack) === $context) { + return $context; + } + } + } +} diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index d3c676646..8700f4ca6 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -435,17 +435,6 @@ public function testCaptureExceptionHandlesOptionsAsSecondArg() $this->assertEquals($event['culprit'], 'test'); } - public function testCaptureExceptionHandlesCulpritAsSecondArg() - { - $client = new Dummy_Raven_Client(); - $ex = $this->create_exception(); - $client->captureException($ex, 'test'); - $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); - $event = array_pop($events); - $this->assertEquals($event['culprit'], 'test'); - } - public function testCaptureExceptionHandlesExcludeOption() { $client = new Dummy_Raven_Client(array( @@ -514,6 +503,7 @@ public function testDefaultProcessorsContainSanitizeDataProcessor() public function testGetDefaultData() { $client = new Dummy_Raven_Client(); + $client->transaction->push('test'); $expected = array( 'platform' => 'php', 'project' => $client->project, @@ -525,6 +515,7 @@ public function testGetDefaultData() 'name' => 'sentry-php', 'version' => $client::VERSION, ), + 'culprit' => 'test', ); $this->assertEquals($expected, $client->get_default_data()); } From 6c7dd666c5a794bce7f0914e40591bc64651012d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 29 Sep 2016 14:24:48 -0700 Subject: [PATCH 0204/1161] [transaction] add tests --- test/Raven/Tests/TransactionStackTest.php | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/Raven/Tests/TransactionStackTest.php diff --git a/test/Raven/Tests/TransactionStackTest.php b/test/Raven/Tests/TransactionStackTest.php new file mode 100644 index 000000000..838626c29 --- /dev/null +++ b/test/Raven/Tests/TransactionStackTest.php @@ -0,0 +1,29 @@ +push('hello'); + $foo = $stack->push('foo'); + $stack->push('bar'); + $stack->push('world'); + $this->assertEquals($stack->peek(), 'world'); + $this->assertEquals($stack->pop(), 'world'); + $this->assertEquals($stack->pop('foo'), 'foo'); + $this->assertEquals($stack->peek(), 'hello'); + $this->assertEquals($stack->pop(), 'hello'); + $this->assertEquals($stack->peek(), null); + } +} From 381307cbd874c380055f7d1cb79ab540af7d934b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 29 Sep 2016 14:26:36 -0700 Subject: [PATCH 0205/1161] 1.6.x-dev --- composer.json | 2 +- lib/Raven/Client.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 4447ed7e9..2cd8e610c 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "1.6.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 1952c9bd8..0b9acc9a1 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '1.5.x-dev'; + const VERSION = '1.6.x-dev'; const PROTOCOL = '6'; From 0a9c5f01a466bb627974b6d713f346b527686e9b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 29 Sep 2016 14:28:30 -0700 Subject: [PATCH 0206/1161] [transaction] utilize PATH_INFO for default context --- lib/Raven/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 0b9acc9a1..41fb3cf47 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -115,8 +115,8 @@ public function __construct($options_or_dsn=null, $options=array()) } $this->transaction = new Raven_TransactionStack(); - if ($this->is_http_request() && isset($_SERVER['REQUEST_URI'])) { - $this->transaction->push($_SERVER['REQUEST_URI']); + if ($this->is_http_request() && isset($_SERVER['PATH_INFO'])) { + $this->transaction->push($_SERVER['PATH_INFO']); } if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { From 5b654649fd40c3776b4c05f13423b859977911be Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 29 Sep 2016 14:31:33 -0700 Subject: [PATCH 0207/1161] Gracefully handle empty arguments context --- lib/Raven/Stacktrace.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 0c174d734..c3c339830 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -114,6 +114,10 @@ public static function get_stack_info($frames, public static function get_default_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) { + if (!isset($frame['args'])) { + return array(); + } + $i = 1; $args = array(); foreach ($frame['args'] as $arg) { From 8335076bde2d16bb19e54b0f10cdbaf5c2c8deb7 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 29 Sep 2016 14:25:47 -0700 Subject: [PATCH 0208/1161] Changes for 1.5.0 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index aba98d5b0..0b2c1f817 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +1.5.0 +----- + +- Added named transaction support. + 1.4.0 ----- From 8bb2f968263c564308e0b226ac23057ece5e58f9 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 29 Sep 2016 15:55:41 -0700 Subject: [PATCH 0209/1161] Add available configuration settings for Laravel --- docs/integrations/laravel.rst | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 2af620f77..91c62efc5 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -196,3 +196,36 @@ Create the Sentry configuration file (``config/sentry.php``): // capture release as git sha // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), ); + +Available Settings +------------------ + +The following settings are available for the client: + +.. describe:: dsn + + The DSN to authenticate with Sentry. + + .. code-block:: php + + 'dsn' => '___DSN___', + +.. describe:: release + + The version of your application (e.g. git SHA) + + .. code-block:: php + + 'release' => MyApp::getReleaseVersion(), + + +.. describe:: breadcrumbs.sql_bindings + + Capturing bindings on SQL queries. + + Defaults to ``true``. + + .. code-block:: php + + 'breadcrumbs.sql_bindings' => false, + From d58230e33a2233c6f4aa27590505036ddbe197ec Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 4 Oct 2016 17:15:45 -0700 Subject: [PATCH 0210/1161] Fix ref to symfony2 quickstart --- docs/sentry-doc-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 77800e4f7..fe0854355 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -35,7 +35,7 @@ "doc_link": "integrations/symfony2/", "wizard": [ "index#installation", - "integrations/symfony2#symfony2" + "integrations/symfony2#symfony-2" ] } } From 5f0f606cb7f51b99e536e044c69d3e1b8a63af4a Mon Sep 17 00:00:00 2001 From: Philip Hofstetter Date: Tue, 11 Oct 2016 16:51:31 +0200 Subject: [PATCH 0211/1161] work around PHP bug #50688 if any descendant of Exception is instantiated from within a callback passed into the various array sorting functions, then PHP will raise a warning ("Array was modified by the user comparison function"). As Raven always registers with E_ALL and then checks whether it needs to run in the handler itself, even if Raven decides not to do anything, it still instantiates the exception and thus will still cause the warning. For example, if you run with E_NOTICE off and thus register Raven to not run on E_NOTICE, but then the user raises an E_NOTICE in a usort() callback, php bug 50688 will cause a warning to be thrown (which then likely *will* activate Raven on the second pass) --- lib/Raven/ErrorHandler.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 5407f8aec..ed19fb28d 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -81,14 +81,13 @@ public function handleError($type, $message, $file = '', $line = 0, $context = a // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and // most of E_STRICT raised in the file where set_error_handler() is called. - $e = new ErrorException($message, 0, $type, $file, $line); - if (error_reporting() !== 0) { $error_types = $this->error_types; if ($error_types === null) { $error_types = error_reporting(); } if ($error_types & $type) { + $e = new ErrorException($message, 0, $type, $file, $line); $this->handleException($e, true, $context); } } From 5db7ec43cb68ca5468460993621987a4ad52d1e5 Mon Sep 17 00:00:00 2001 From: MeredithAnya Date: Wed, 12 Oct 2016 15:48:37 -0700 Subject: [PATCH 0212/1161] fix typo --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index a0d15f1d3..9f04540bd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ PHP === -The PHP SDK for Sentry supports PHP 5.3 and higher. It'a +The PHP SDK for Sentry supports PHP 5.3 and higher. It's available as a BSD licensed Open Source library. Installation From 61236436edff210ca53b617aa075aa6b332d9782 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 13 Oct 2016 08:23:49 +0200 Subject: [PATCH 0213/1161] use uopz for travis and PHP 7 --- .travis.yml | 7 +++++-- composer.json | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8182db271..de0d100cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,13 @@ php: - 5.5 - 5.6 - 7.0 + - nightly - hhvm matrix: allow_failures: - php: hhvm - - php: 7.0 + - php: nightly fast_finish: true sudo: false @@ -21,10 +22,12 @@ cache: - $HOME/.composer/cache before_install: + - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]]; then pecl install uopz; fi + - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]]; then echo "extension=uopz.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - composer self-update install: travis_retry composer install --no-interaction --prefer-source script: - vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run - - vendor/bin/phpunit + - vendor/bin/phpunit --verbose diff --git a/composer.json b/composer.json index 2cd8e610c..ec7fea556 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ ], "require-dev": { "friendsofphp/php-cs-fixer": "^1.8.0", - "phpunit/phpunit": "^4.6.6" + "phpunit/phpunit": "^4.8 || ^5.0" }, "require": { "php": ">=5.2.4", From 04b58903e29af0bd3f2110bb0b8a471928bfb30f Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 13 Oct 2016 08:56:15 +0200 Subject: [PATCH 0214/1161] use getMockBuilder, fix deprecation warning with recent PHPUnit --- test/Raven/Tests/ClientTest.php | 4 ++- test/Raven/Tests/ErrorHandlerTest.php | 36 ++++++++++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 8700f4ca6..0f7bac00c 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -475,7 +475,9 @@ public function testProcessDoesCallProcessors() { $data = array("key"=>"value"); - $processor = $this->getMock('Processor', array('process')); + $processor = $this->getMockBuilder('Processor') + ->setMethods(array('process')) + ->getMock(); $processor->expects($this->once()) ->method('process') ->with($data); diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 5dfdec2df..d6e8890ab 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -40,7 +40,9 @@ public function tearDown() public function testErrorsAreLoggedAsExceptions() { - $client = $this->getMock('Client', array('captureException', 'sendUnsentErrors')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException', 'sendUnsentErrors')) + ->getMock(); $client->expects($this->once()) ->method('captureException') ->with($this->isInstanceOf('ErrorException')); @@ -51,7 +53,9 @@ public function testErrorsAreLoggedAsExceptions() public function testExceptionsAreLogged() { - $client = $this->getMock('Client', array('captureException')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); $client->expects($this->once()) ->method('captureException') ->with($this->isInstanceOf('ErrorException')); @@ -64,7 +68,9 @@ public function testExceptionsAreLogged() public function testErrorHandlerPassErrorReportingPass() { - $client = $this->getMock('Client', array('captureException')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); $client->expects($this->once()) ->method('captureException'); @@ -77,7 +83,9 @@ public function testErrorHandlerPassErrorReportingPass() public function testErrorHandlerPropagates() { - $client = $this->getMock('Client', array('captureException')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); $client->expects($this->never()) ->method('captureException'); @@ -92,7 +100,9 @@ public function testErrorHandlerPropagates() public function testErrorHandlerRespectsErrorReportingDefault() { - $client = $this->getMock('Client', array('captureException')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); $client->expects($this->once()) ->method('captureException'); @@ -111,7 +121,9 @@ public function testErrorHandlerRespectsErrorReportingDefault() // defer to respecting the error reporting settings. public function testSilentErrorsAreNotReportedWithGlobal() { - $client = $this->getMock('Client', array('captureException')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); $client->expects($this->never()) ->method('captureException'); @@ -130,7 +142,9 @@ public function testSilentErrorsAreNotReportedWithGlobal() // defer to respecting the error reporting settings. public function testSilentErrorsAreNotReportedWithLocal() { - $client = $this->getMock('Client', array('captureException')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); $client->expects($this->never()) ->method('captureException'); @@ -145,7 +159,9 @@ public function testSilentErrorsAreNotReportedWithLocal() public function testErrorHandlerDefaultsErrorReporting() { - $client = $this->getMock('Client', array('captureException')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); $client->expects($this->never()) ->method('captureException'); @@ -159,7 +175,9 @@ public function testErrorHandlerDefaultsErrorReporting() public function testFluidInterface() { - $client = $this->getMock('Client', array('captureException')); + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); $handler = new Raven_ErrorHandler($client); $result = $handler->registerErrorHandler(); $this->assertEquals($result, $handler); From 09de00d2fb3b7f664eb140b23b5ddcbf941b085b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 16 Oct 2016 20:57:23 -0700 Subject: [PATCH 0215/1161] Utilize built-in shell escaping --- lib/Raven/Client.php | 30 +++++++++++++++++------------- test/Raven/Tests/ClientTest.php | 12 ++++++++++++ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 41fb3cf47..796a7ab0c 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -881,31 +881,35 @@ private function send_http($url, $data, $headers=array()) } } - /** - * Send the cURL to Sentry asynchronously. No errors will be returned from cURL - * - * @param string $url URL of the Sentry instance to log to - * @param array $data Associative array of data to log - * @param array $headers Associative array of headers - * @return bool - */ - private function send_http_asynchronous_curl_exec($url, $data, $headers) + protected function buildCurlCommand($url, $data, $headers) { // TODO(dcramer): support ca_cert $cmd = $this->curl_path.' -X POST '; foreach ($headers as $key => $value) { - $cmd .= '-H \''. $key. ': '. $value. '\' '; + $cmd .= '-H ' . escapeshellarg(key) . ': '. escapeshellarg($value). ' '; } - $cmd .= '-d \''. $data .'\' '; - $cmd .= '\''. $url .'\' '; + $cmd .= '-d ' . escapeshellarg($data) . ' '; + $cmd .= escapeshellarg($url) . ' '; $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send) if (!$this->verify_ssl) { $cmd .= '-k '; } $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background - exec($cmd); + return $cmd; + } + /** + * Send the cURL to Sentry asynchronously. No errors will be returned from cURL + * + * @param string $url URL of the Sentry instance to log to + * @param array $data Associative array of data to log + * @param array $headers Associative array of headers + * @return bool + */ + private function send_http_asynchronous_curl_exec($url, $data, $headers) + { + exec($this->buildCurlCommand($url, $data, $headers)); return true; // The exec method is just fire and forget, so just assume it always works } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 0f7bac00c..5eec4c743 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -54,6 +54,10 @@ public function get_user_data() { return parent::get_user_data(); } + public function buildCurlCommand($url, $data, $headers) + { + return parent::buildCurlCommand($url, $data, $headers); + } // short circuit breadcrumbs public function registerDefaultBreadcrumbHandlers() { @@ -902,6 +906,14 @@ public function testSanitizeUser() 'email' => 'foo@example.com', ))); } + + public function testBuildCurlCommandEscapesInput() + { + $data = '{"foo": "\'; ls;"}'; + $client = new Dummy_Raven_Client(); + $result = $client->buildCurlCommand('http://foo.com', $data, array()); + $this->assertEquals($result, 'curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &'); + } /** * Set the server array to the test values, check the current url * From 4a1431efc9bf7e7b9c2f9e91cd1fe2df9dd457d4 Mon Sep 17 00:00:00 2001 From: Robin Breuker Date: Tue, 1 Nov 2016 17:05:47 +0100 Subject: [PATCH 0216/1161] Fixes #367 - array_walk_recursive being possibly called with a string in case http['data'] is a raw string. --- lib/Raven/SanitizeDataProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/SanitizeDataProcessor.php index b0f514387..72d52c522 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/SanitizeDataProcessor.php @@ -80,7 +80,7 @@ public function sanitizeHttp(&$data) $cookies[$this->session_cookie_name] = self::MASK; } } - if (!empty($http['data'])) { + if (!empty($http['data']) && is_array($http['data'])) { array_walk_recursive($http['data'], array($this, 'sanitize')); } } From 58a0b21a6bc2c3aa0935a0a6f136e258f1982533 Mon Sep 17 00:00:00 2001 From: David Geistert Date: Thu, 3 Nov 2016 18:24:24 +0100 Subject: [PATCH 0217/1161] Add an error_types option to the Raven Client With this error_types option it's now possible to set directly which kinds of errors should be reported. --- lib/Raven/Client.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 796a7ab0c..3af74e68e 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -35,10 +35,11 @@ class Raven_Client public $severity_map; public $store_errors_for_bulk_send = false; - private $error_handler; + protected $error_handler; + protected $error_types; - private $serializer; - private $reprSerializer; + protected $serializer; + protected $reprSerializer; public function __construct($options_or_dsn=null, $options=array()) { @@ -88,6 +89,7 @@ public function __construct($options_or_dsn=null, $options=array()) $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); $this->transport = Raven_Util::get($options, 'transport', null); $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null); + $this->error_types = Raven_Util::get($options, 'error_types', null); // app path is used to determine if code is part of your application $this->setAppPath(Raven_Util::get($options, 'app_path', null)); @@ -134,7 +136,7 @@ public function install() if ($this->error_handler) { throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this))); } - $this->error_handler = new Raven_ErrorHandler($this); + $this->error_handler = new Raven_ErrorHandler($this, false, $this->error_types); $this->error_handler->registerExceptionHandler(); $this->error_handler->registerErrorHandler(); $this->error_handler->registerShutdownFunction(); From ec4024316c84c7457db90d4343db60149cebae0f Mon Sep 17 00:00:00 2001 From: Alex Dallaway Date: Fri, 11 Nov 2016 10:12:20 -0500 Subject: [PATCH 0218/1161] Added missing dollar sign to 'key' variable inside Client::buildCurlCommand() --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 796a7ab0c..809f4166b 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -886,7 +886,7 @@ protected function buildCurlCommand($url, $data, $headers) // TODO(dcramer): support ca_cert $cmd = $this->curl_path.' -X POST '; foreach ($headers as $key => $value) { - $cmd .= '-H ' . escapeshellarg(key) . ': '. escapeshellarg($value). ' '; + $cmd .= '-H ' . escapeshellarg($key) . ': '. escapeshellarg($value). ' '; } $cmd .= '-d ' . escapeshellarg($data) . ' '; $cmd .= escapeshellarg($url) . ' '; From d18299be7505682057796ac887edb5b890d2d7a9 Mon Sep 17 00:00:00 2001 From: Igor Santos Date: Tue, 15 Nov 2016 17:00:32 -0200 Subject: [PATCH 0219/1161] fixing small notices on the ErrorHandler - missing return point on handleError() - wrong return and missing param on PHPDoc of registerErrorHandler() - missing documented prop $client --- lib/Raven/ErrorHandler.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index ed19fb28d..77954ab9c 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -31,6 +31,8 @@ class Raven_ErrorHandler private $old_error_handler; private $call_existing_error_handler = false; private $reservedMemory; + /** @var Raven_Client */ + private $client; private $send_errors_last = false; private $fatal_error_types = array( E_ERROR, @@ -106,6 +108,7 @@ public function handleError($type, $message, $file = '', $line = 0, $context = a return false; } } + return true; } public function handleFatalError() @@ -146,7 +149,8 @@ public function registerExceptionHandler($call_existing = true) * * @param bool $call_existing Call any existing errors handlers after processing * this instance. - * @return array + * @param array $error_types All error types that should be sent. + * @return $this */ public function registerErrorHandler($call_existing = true, $error_types = null) { From 858458001ee7c5ec341e721fd5d2e60274919ea6 Mon Sep 17 00:00:00 2001 From: Alex Dallaway Date: Tue, 22 Nov 2016 11:24:07 -0500 Subject: [PATCH 0220/1161] Fixed CURL header escaping --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 809f4166b..67c2f4574 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -886,7 +886,7 @@ protected function buildCurlCommand($url, $data, $headers) // TODO(dcramer): support ca_cert $cmd = $this->curl_path.' -X POST '; foreach ($headers as $key => $value) { - $cmd .= '-H ' . escapeshellarg($key) . ': '. escapeshellarg($value). ' '; + $cmd .= '-H ' . escapeshellarg($key.': '.$value). ' '; } $cmd .= '-d ' . escapeshellarg($data) . ' '; $cmd .= escapeshellarg($url) . ' '; From 087d5d1b1d4d266d45b3a0d25c020091bbfedd72 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 23 Nov 2016 18:53:11 +0100 Subject: [PATCH 0221/1161] Allow raven to function without session extension --- lib/Raven/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index a52f8fd9a..dd10d9618 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -573,7 +573,7 @@ protected function get_user_data() { $user = $this->context->user; if ($user === null) { - if (!session_id()) { + if (!function_exists('session_id') || !session_id()) { return array(); } $user = array( From c66fcc4c6d4d95dabd25c62ed42177af10a71f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ndor=20Bal=C3=A1zs?= Date: Tue, 29 Nov 2016 09:59:56 +0100 Subject: [PATCH 0222/1161] Serialize stdClass to array --- lib/Raven/Serializer.php | 9 ++++----- test/Raven/Tests/SerializerTest.php | 13 +++++++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 2184cfe27..43401ef77 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -60,18 +60,17 @@ public function __construct($mb_detect_order = null) */ public function serialize($value, $max_depth=3, $_depth=0) { - if (is_object($value) || is_resource($value)) { - return $this->serializeValue($value); - } elseif ($_depth < $max_depth && is_array($value)) { + $className = is_object($value) ? get_class($value) : null; + $toArray = is_array($value) || $className === 'stdClass'; + if ($toArray && $_depth < $max_depth) { $new = array(); foreach ($value as $k => $v) { $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); } return $new; - } else { - return $this->serializeValue($value); } + return $this->serializeValue($value); } protected function serializeString($value) diff --git a/test/Raven/Tests/SerializerTest.php b/test/Raven/Tests/SerializerTest.php index 04e3cda01..006c6448e 100644 --- a/test/Raven/Tests/SerializerTest.php +++ b/test/Raven/Tests/SerializerTest.php @@ -24,12 +24,21 @@ public function testArraysAreArrays() $this->assertEquals(array('1', '2', '3'), $result); } + public function testStdClassAreArrays() + { + $serializer = new Raven_Serializer(); + $input = new stdClass(); + $input->foo = 'BAR'; + $result = $serializer->serialize($input); + $this->assertEquals(array('foo' => 'BAR'), $result); + } + public function testObjectsAreStrings() { $serializer = new Raven_Serializer(); - $input = new Raven_StacktraceTestObject(); + $input = new Raven_SerializerTestObject(); $result = $serializer->serialize($input); - $this->assertEquals('Object Raven_StacktraceTestObject', $result); + $this->assertEquals('Object Raven_SerializerTestObject', $result); } public function testIntsAreInts() From 5b0146b5402adc4701e164f95a5afcfd70bf1b4f Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 8 Dec 2016 19:24:15 -0800 Subject: [PATCH 0223/1161] Make Monolog an optional dependency Fixes GH-375 --- composer.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index ec7fea556..a34bd16b6 100644 --- a/composer.json +++ b/composer.json @@ -13,12 +13,15 @@ ], "require-dev": { "friendsofphp/php-cs-fixer": "^1.8.0", - "phpunit/phpunit": "^4.8 || ^5.0" + "phpunit/phpunit": "^4.8 || ^5.0", + "monolog/monolog": "*" }, "require": { "php": ">=5.2.4", - "ext-curl": "*", - "monolog/monolog": "*" + "ext-curl": "*" + }, + "suggest": { + "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" }, "conflict": { "raven/raven": "*" From 8a5bff2caacd9eaf850b1d49159a1ff31e49bbac Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 8 Dec 2016 19:27:33 -0800 Subject: [PATCH 0224/1161] Merge user_context by default Fixes GH-353 --- lib/Raven/Client.php | 9 +++++++-- test/Raven/Tests/ClientTest.php | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index dd10d9618..15feae29c 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -1168,10 +1168,15 @@ public function onShutdown() * Sets user context. * * @param array $data Associative array of user data + * @param bool $merge Merge existing context with new context */ - public function user_context($data) + public function user_context($data, $merge=true) { - $this->context->user = $data; + if ($merge && $this->context->user !== null) { + $this->context->user = array_merge($this->context->user, $data); + } else { + $this->context->user = $data; + } } /** diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 5eec4c743..cadab6836 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -914,6 +914,24 @@ public function testBuildCurlCommandEscapesInput() $result = $client->buildCurlCommand('http://foo.com', $data, array()); $this->assertEquals($result, 'curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &'); } + + public function testUserContextWithoutMerge() + { + $client = new Dummy_Raven_Client(); + $client->user_context(array('foo' => 'bar'), false); + $client->user_context(array('baz' => 'bar'), false); + $this->assertEquals($client->context->user, array('baz' => 'bar')); + } + + public function testUserContextWithMerge() + { + $client = new Dummy_Raven_Client(); + $client->user_context(array('foo' => 'bar'), true); + $client->user_context(array('baz' => 'bar'), true); + $this->assertEquals($client->context->user, array('foo' => 'bar', 'baz' => 'bar')); + } + + /** * Set the server array to the test values, check the current url * From dfe13ac505a8dea1dd7eae8541b650d12fc6e238 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 8 Dec 2016 19:39:27 -0800 Subject: [PATCH 0225/1161] Changes in 1.6.0 --- CHANGES | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index 0b2c1f817..e589e5cff 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,13 @@ +1.6.0 +----- + +- Improved serialization of certain types to be more restrictive. +- ``error_types`` can now be configured via ``RavenClient``. +- Class serialization has been expanded to include attributes. +- The session extension is no longer required. +- Monolog is no longer a required dependency. +- ``user_context`` now merges by default. + 1.5.0 ----- From b9e7b00b0203e21cf560858eb5043b6d9dbd327a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 8 Dec 2016 19:40:59 -0800 Subject: [PATCH 0226/1161] 1.7.x-dev --- CHANGES | 3 +++ composer.json | 2 +- lib/Raven/Client.php | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e589e5cff..f3bc5dfaf 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +1.7.0 +----- + 1.6.0 ----- diff --git a/composer.json b/composer.json index a34bd16b6..fe3247202 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 15feae29c..8a2862577 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,7 +16,7 @@ class Raven_Client { - const VERSION = '1.6.x-dev'; + const VERSION = '1.7.x-dev'; const PROTOCOL = '6'; From ba5e24c3ede46f961d231e48c2d885c45e83fafe Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 14 Dec 2016 13:56:38 -0800 Subject: [PATCH 0227/1161] Correct handling of null in user_context (fixes GH-388) --- lib/Raven/Client.php | 4 ++++ test/Raven/Tests/ClientTest.php | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 8a2862577..7ae73f96d 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -1173,6 +1173,10 @@ public function onShutdown() public function user_context($data, $merge=true) { if ($merge && $this->context->user !== null) { + // bail if data is null + if (!$data) { + return; + } $this->context->user = array_merge($this->context->user, $data); } else { $this->context->user = $data; diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index cadab6836..0e3cebee1 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -931,6 +931,13 @@ public function testUserContextWithMerge() $this->assertEquals($client->context->user, array('foo' => 'bar', 'baz' => 'bar')); } + public function testUserContextWithMergeAndNull() + { + $client = new Dummy_Raven_Client(); + $client->user_context(array('foo' => 'bar'), true); + $client->user_context(null, true); + $this->assertEquals($client->context->user, array('foo' => 'bar')); + } /** * Set the server array to the test values, check the current url From 534e12f5e2b0ec1b3e51ae9d38e2c472b2425453 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 Jan 2017 13:14:10 -0800 Subject: [PATCH 0228/1161] Fix _convertPath on Windows (refs GH-392) --- lib/Raven/Client.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 7ae73f96d..a7289220c 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,6 +16,8 @@ class Raven_Client { + const PARDIR = DIRECTORY_SEPARATOR; + const VERSION = '1.7.x-dev'; const PROTOCOL = '6'; @@ -180,8 +182,8 @@ private function _convertPath($value) // we need app_path to have a trailing slash otherwise // base path detection becomes complex if the same // prefix is matched - if (substr($path, 0, 1) === '/' && substr($path, -1, 1) !== '/') { - $path = $path . '/'; + if (substr($path, 0, 1) === self::PARDIR && substr($path, -1, 1) !== self::PARDIR) { + $path = $path . self::PARDIR; } return $path; } From b40ebd0d13d718f2fb4fc2f5c64bd2d07318f0aa Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 2 Jan 2017 13:41:51 -0800 Subject: [PATCH 0229/1161] Various fixes for windows path normalization (fixes GH-393) --- lib/Raven/Client.php | 2 +- lib/Raven/Stacktrace.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index a7289220c..d1953d245 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -170,7 +170,7 @@ public function setEnvironment($value) private function getDefaultPrefixes() { $value = get_include_path(); - return explode(':', $value); + return explode(PATH_SEPARATOR, $value); } private function _convertPath($value) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index c3c339830..66d299fdc 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -79,10 +79,11 @@ public static function get_stack_info($frames, // detect in_app based on app path if ($app_path) { - $in_app = (bool)(substr($abs_path, 0, strlen($app_path)) === $app_path); + $norm_abs_path = @realpath($abs_path) ?: $abs_path; + $in_app = (bool)(substr($norm_abs_path, 0, strlen($app_path)) === $app_path); if ($in_app && $excluded_app_paths) { foreach ($excluded_app_paths as $path) { - if (substr($abs_path, 0, strlen($path)) === $path) { + if (substr($norm_abs_path, 0, strlen($path)) === $path) { $in_app = false; break; } From cd04e761c77cb06a25f31daa9eb3ec7a733e402a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 25 Jan 2017 10:57:20 -0800 Subject: [PATCH 0230/1161] Improve argument handling in serialization - Ensure references do not get modified - Standardize truncation logic --- lib/Raven/Stacktrace.php | 36 +++++++++++++++-------- test/Raven/Tests/StacktraceTest.php | 45 ++++++++++++----------------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 66d299fdc..09b4df00b 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -122,10 +122,7 @@ public static function get_default_context($frame, $frame_arg_limit = Raven_Clie $i = 1; $args = array(); foreach ($frame['args'] as $arg) { - if (is_string($arg) || is_numeric($arg)) { - $arg = substr($arg, 0, $frame_arg_limit); - } - $args['param'.$i] = $arg; + $args['param'.$i] = self::serialize_argument($arg, $frame_arg_limit); $i++; } return $args; @@ -157,7 +154,9 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client return array(); } else { // Sanitize the file path - return array('param1' => $frame['args'][0]); + return array( + 'param1' => self::serialize_argument($frame['args'][0], $frame_arg_limit), + ); } } try { @@ -180,15 +179,9 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client $args = array(); foreach ($frame['args'] as $i => $arg) { + $arg = self::serialize_argument($arg, $frame_arg_limit); if (isset($params[$i])) { // Assign the argument by the parameter name - if (is_array($arg)) { - foreach ($arg as $key => $value) { - if (is_string($value) || is_numeric($value)) { - $arg[$key] = substr($value, 0, $frame_arg_limit); - } - } - } $args[$params[$i]->name] = $arg; } else { $args['param'.$i] = $arg; @@ -198,6 +191,25 @@ public static function get_frame_context($frame, $frame_arg_limit = Raven_Client return $args; } + private static function serialize_argument($arg, $frame_arg_limit) + { + if (is_array($arg)) { + $_arg = array(); + foreach ($arg as $key => $value) { + if (is_string($value) || is_numeric($value)) { + $_arg[$key] = substr($value, 0, $frame_arg_limit); + } else { + $_arg[$key] = $value; + } + } + return $_arg; + } elseif (is_string($arg) || is_numeric($arg)) { + return substr($arg, 0, $frame_arg_limit); + } else { + return $arg; + } + } + private static function strip_prefixes($filename, $prefixes) { if ($prefixes === null) { diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index ae98203f6..25a5eaf32 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -71,41 +71,34 @@ public function testSimpleTrace() public function testDoesNotModifyCaptureVars() { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - "args"=> array( - "friend", - ), - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "args"=> array( - "/tmp/a.php", - ), - "function" => "include_once", - ), - ); // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. // Modification of these would be really bad, since if control is returned (non-fatal error) we'll have altered the state of things! - $originalFoo = "bloopblarp"; - $iAmFoo = $originalFoo; - $vars = array( - "foo" => &$iAmFoo + $originalFoo = 'bloopblarp'; + $newFoo = $originalFoo; + $nestedArray = array( + 'key' => 'xxxxxxxxxx', ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, $vars, 5); + $frame = array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 9, + "args"=> array( + &$newFoo, + &$nestedArray, + ), + "function" => "a_test", + ); + + $result = Raven_Stacktrace::get_frame_context($frame, 5); // Check we haven't modified our vars. - $this->assertEquals($originalFoo, $vars['foo']); + $this->assertEquals($originalFoo, 'bloopblarp'); + $this->assertEquals($nestedArray['key'], 'xxxxxxxxxx'); - $frame = $frames[1]; // Check that we did truncate the variable in our output - $this->assertEquals(strlen($frame['vars']['foo']), 5); + $this->assertEquals($result['param1'], 'bloop'); + $this->assertEquals($result['param2']['key'], 'xxxxx'); } public function testDoesFixFrameInfo() From 23832e90ffbb949819acd08fb768aaeaaa21d955 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 25 Jan 2017 12:55:31 -0800 Subject: [PATCH 0231/1161] Note serialization changes --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index f3bc5dfaf..153689742 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ 1.7.0 ----- +- Corrected some issues with argument serialization in stacktraces. + 1.6.0 ----- From 621378f8ad2b5579dd8e4883d52abaef0126a513 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 2 Feb 2017 23:21:09 -0800 Subject: [PATCH 0232/1161] Correct handling of fatal errors (fixes GH-401) --- lib/Raven/ErrorHandler.php | 13 ++++++++++++- test/Raven/Tests/ErrorHandlerTest.php | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 77954ab9c..98223f952 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -61,12 +61,18 @@ public function __construct($client, $send_errors_last = false, $error_types = n $this->client = $client; $this->error_types = $error_types; + $this->fatal_error_types = array_reduce($this->fatal_error_types, array($this, 'bitwiseOr')); if ($send_errors_last) { $this->send_errors_last = true; $this->client->store_errors_for_bulk_send = true; } } + public function bitwiseOr($a, $b) + { + return $a | $b; + } + public function handleException($e, $isError = false, $vars = null) { $e->event_id = $this->client->captureException($e, null, null, $vars); @@ -119,7 +125,7 @@ public function handleFatalError() return; } - if ($error['type'] & $this->fatal_error_types) { + if ($this->shouldCaptureFatalError($error['type'])) { $e = new ErrorException( @$error['message'], 0, @$error['type'], @$error['file'], @$error['line'] @@ -128,6 +134,11 @@ public function handleFatalError() } } + public function shouldCaptureFatalError($type) + { + return $type & $this->fatal_error_types; + } + /** * Register a handler which will intercept unhnalded exceptions and report them to the * associated Sentry client. diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index d6e8890ab..5d9ee4ba6 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -157,6 +157,18 @@ public function testSilentErrorsAreNotReportedWithLocal() $handler->handleFatalError(); } + public function testShouldCaptureFatalErrorBehavior() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $handler = new Raven_ErrorHandler($client); + + $this->assertEquals($handler->shouldCaptureFatalError(E_ERROR), true); + + $this->assertEquals($handler->shouldCaptureFatalError(E_WARNING), false); + } + public function testErrorHandlerDefaultsErrorReporting() { $client = $this->getMockBuilder('Client') From 90dd335c7c9e6e34f65c8329f5177ff053e92f7c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 14 Dec 2016 13:57:25 -0800 Subject: [PATCH 0233/1161] Changes for 1.6.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 153689742..6a35a135e 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,11 @@ - Corrected some issues with argument serialization in stacktraces. +1.6.1 +----- + +- Correct handling of null in ``user_context``. + 1.6.0 ----- From abe428f58bfd16601c1febe295967656c67e12f0 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 2 Feb 2017 23:27:14 -0800 Subject: [PATCH 0234/1161] Changes for 1.6.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 6a35a135e..487f386c5 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,11 @@ - Corrected some issues with argument serialization in stacktraces. +1.6.2 +----- + +- Fixed behavior where fatal errors weren't correctly being reported in most situations. + 1.6.1 ----- From 362b11bf04a5ad95e90d889af6890732bd40930b Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Mon, 13 Feb 2017 19:22:27 +0300 Subject: [PATCH 0235/1161] Properties should be described in class. It's not enough to describe them in the constructor Few phpdoc fixes Few string escape fixes There is no "\b" literal in PHP: http://php.net/manual/en/language.types.string.php setProcessorOptions should be described in parent class because it's called in Raven_Client::setProcessorsFromOptions Change links to documentations --- lib/Raven/Client.php | 117 ++++++++++++++++-- lib/Raven/Compat.php | 11 +- lib/Raven/Context.php | 13 ++ lib/Raven/CurlHandler.php | 4 +- lib/Raven/Processor.php | 14 +++ lib/Raven/SanitizeDataProcessor.php | 7 +- lib/Raven/Serializer.php | 10 ++ lib/Raven/Stacktrace.php | 4 +- lib/Raven/TransactionStack.php | 4 + lib/Raven/Util.php | 5 + test/Raven/Tests/ClientTest.php | 2 +- test/Raven/Tests/ErrorHandlerTest.php | 2 + .../Raven/Tests/SanitizeDataProcessorTest.php | 8 +- test/Raven/Tests/StacktraceTest.php | 3 + 14 files changed, 184 insertions(+), 20 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index d1953d245..ae8c31f9b 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -34,6 +34,9 @@ class Raven_Client public $breadcrumbs; public $context; public $extra_data; + /** + * @var array|null + */ public $severity_map; public $store_errors_for_bulk_send = false; @@ -43,6 +46,61 @@ class Raven_Client protected $serializer; protected $reprSerializer; + /** + * @var string + */ + protected $app_path; + /** + * @var array + */ + protected $prefixes; + /** + * @var array + */ + protected $excluded_app_paths; + /** + * @var Callable + */ + protected $transport; + + var $logger; + var $server; + var $secret_key; + var $public_key; + var $project; + var $auto_log_stacks; + var $name; + var $site; + var $tags; + var $release; + var $environment; + var $trace; + var $timeout; + var $message_limit; + var $exclude; + var $http_proxy; + protected $send_callback; + var $curl_method; + var $curl_path; + var $curl_ipv4; + var $ca_cert; + var $verify_ssl; + var $curl_ssl_version; + var $trust_x_forwarded_proto; + var $mb_detect_order; + /** + * @var Raven_Processor[] + */ + var $processors; + /** + * @var string|integer|null + */ + var $_lasterror; + var $_last_event_id; + var $_user; + var $_pending_events; + var $sdk; + public function __construct($options_or_dsn=null, $options=array()) { if (is_array($options_or_dsn)) { @@ -217,11 +275,16 @@ public function setExcludedAppPaths($value) } return $this; } + public function getPrefixes() { return $this->prefixes; } + /** + * @param array $value + * @return $this + */ public function setPrefixes($value) { $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value; @@ -261,7 +324,8 @@ public function getUserAgent() * and is responsible for encoding the data, authenticating, and sending * the data to the upstream Sentry server. * - * @param function $value Function to be called + * @param Callable $value Function to be called + * @return Raven_Client */ public function setTransport($value) { @@ -269,6 +333,9 @@ public function setTransport($value) return $this; } + /** + * @return string[]|Raven_Processor[] + */ public static function getDefaultProcessors() { return array( @@ -281,12 +348,16 @@ public static function getDefaultProcessors() * sent to Sentry. * * @param $options - * @return array + * @return Raven_Processor[] */ public function setProcessorsFromOptions($options) { $processors = array(); foreach (Raven_util::get($options, 'processors', self::getDefaultProcessors()) as $processor) { + /** + * @var Raven_Processor $new_processor + * @var Raven_Processor|string $processor + */ $new_processor = new $processor($this); if (isset($options['processorOptions']) && is_array($options['processorOptions'])) { @@ -349,6 +420,9 @@ public function getLastError() /** * Given an identifier, returns a Sentry searchable string. + * + * @param mixed $ident + * @return mixed */ public function getIdent($ident) { @@ -357,7 +431,13 @@ public function getIdent($ident) } /** - * Deprecated + * @param string $message The message (primary description) for the event. + * @param array $params params to use when formatting the message. + * @param string $level Log level group + * @param boolean|array $stack + * @param mixed $vars + * @return string|null + * @deprecated */ public function message($message, $params=array(), $level=self::INFO, $stack=false, $vars = null) @@ -366,7 +446,9 @@ public function message($message, $params=array(), $level=self::INFO, } /** - * Deprecated + * @param Exception $exception + * @return string|null + * @deprecated */ public function exception($exception) { @@ -379,6 +461,9 @@ public function exception($exception) * @param string $message The message (primary description) for the event. * @param array $params params to use when formatting the message. * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param boolean|array $stack + * @param mixed $vars + * @return string|null */ public function captureMessage($message, $params=array(), $data=array(), $stack=false, $vars = null) @@ -415,6 +500,9 @@ public function captureMessage($message, $params=array(), $data=array(), * * @param Exception $exception The Exception object. * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param mixed $logger + * @param mixed $vars + * @return string|null */ public function captureException($exception, $data=null, $logger=null, $vars=null) { @@ -483,11 +571,12 @@ public function captureException($exception, $data=null, $logger=null, $vars=nul /** * Capture the most recent error (obtained with ``error_get_last``). + * @return string|null */ public function captureLastError() { if (null === $error = error_get_last()) { - return; + return null; } $e = new ErrorException( @@ -500,6 +589,9 @@ public function captureLastError() /** * Log an query to sentry + * @param string|null $query + * @param string $level + * @param string $engine */ public function captureQuery($query, $level=self::INFO, $engine = '') { @@ -756,6 +848,10 @@ public function sendUnsentErrors() } } + /** + * @param string $data + * @return string|boolean + */ public function encode(&$data) { $message = Raven_Compat::json_encode($data); @@ -795,7 +891,8 @@ public function send(&$data) } if ($this->transport) { - return call_user_func($this->transport, $this, $data); + call_user_func($this->transport, $this, $data); + return; } $message = $this->encode($data); @@ -813,7 +910,7 @@ public function send(&$data) * Send data to Sentry * * @param string $url Full URL to Sentry - * @param array $data Associative array of data to log + * @param array|string $data Associative array of data to log * @param array $headers Associative array of headers */ private function send_remote($url, $data, $headers=array()) @@ -871,7 +968,7 @@ protected function get_curl_options() * Send the message over http to the sentry url given * * @param string $url URL of the Sentry instance to log to - * @param array $data Associative array of data to log + * @param array|string $data Associative array of data to log * @param array $headers Associative array of headers */ private function send_http($url, $data, $headers=array()) @@ -907,7 +1004,7 @@ protected function buildCurlCommand($url, $data, $headers) * Send the cURL to Sentry asynchronously. No errors will be returned from cURL * * @param string $url URL of the Sentry instance to log to - * @param array $data Associative array of data to log + * @param array|string $data Associative array of data to log * @param array $headers Associative array of headers * @return bool */ @@ -921,7 +1018,7 @@ private function send_http_asynchronous_curl_exec($url, $data, $headers) * Send a blocking cURL to Sentry and check for errors from cURL * * @param string $url URL of the Sentry instance to log to - * @param array $data Associative array of data to log + * @param array|string $data Associative array of data to log * @param array $headers Associative array of headers * @return bool */ diff --git a/lib/Raven/Compat.php b/lib/Raven/Compat.php index 197009050..6930f8735 100644 --- a/lib/Raven/Compat.php +++ b/lib/Raven/Compat.php @@ -36,7 +36,7 @@ public static function hash_hmac($algo, $data, $key, $raw_output=false) /** * Implementation from 'KC Cloyd'. - * See http://nl2.php.net/manual/en/function.hash-hmac.php + * See http://php.net/manual/en/function.hash-hmac.php */ public static function _hash_hmac($algo, $data, $key, $raw_output=false) { @@ -66,6 +66,9 @@ public static function _hash_hmac($algo, $data, $key, $raw_output=false) /** * Note that we discard the options given to be compatible * with PHP < 5.3 + * @param mixed $value + * @param integer $options + * @return string */ public static function json_encode($value, $options=0) { @@ -79,12 +82,14 @@ public static function json_encode($value, $options=0) /** * Implementation taken from * http://www.mike-griffiths.co.uk/php-json_encode-alternative/ + * @param mixed $value + * @return string */ public static function _json_encode($value) { static $jsonReplaces = array( - array('\\', '/', "\n", "\t", "\r", "\b", "\f", '"'), - array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"')); + array('\\', '/', "\n", "\t", "\r", "\f", '"'), + array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\f', '\"')); if (is_null($value)) { return 'null'; diff --git a/lib/Raven/Context.php b/lib/Raven/Context.php index ef56adb4a..9394d95d4 100644 --- a/lib/Raven/Context.php +++ b/lib/Raven/Context.php @@ -6,6 +6,19 @@ */ class Raven_Context { + /** + * @var array + */ + var $tags; + /** + * @var array + */ + var $extra; + /** + * @var array|null + */ + var $user; + public function __construct() { $this->clear(); diff --git a/lib/Raven/CurlHandler.php b/lib/Raven/CurlHandler.php index 10449af71..ec12b9e73 100644 --- a/lib/Raven/CurlHandler.php +++ b/lib/Raven/CurlHandler.php @@ -84,7 +84,9 @@ public function join($timeout=null) } while ($timeout !== 0 && time() - $start < $timeout); } - // http://se2.php.net/manual/en/function.curl-multi-exec.php + /** + * @doc http://php.net/manual/en/function.curl-multi-exec.php + */ private function select() { do { diff --git a/lib/Raven/Processor.php b/lib/Raven/Processor.php index 7e37400d1..16264732a 100644 --- a/lib/Raven/Processor.php +++ b/lib/Raven/Processor.php @@ -6,6 +6,13 @@ */ abstract class Raven_Processor { + protected $client; + /** + * Raven_Processor constructor. + * + * @param Raven_Client $client + * @codeCoverageIgnore + */ public function __construct(Raven_Client $client) { $this->client = $client; @@ -17,4 +24,11 @@ public function __construct(Raven_Client $client) * @param array $data Array of log data */ abstract public function process(&$data); + + /** + * Override the default processor options + * + * @param array $options Associative array of processor options + */ + abstract public function setProcessorOptions(array $options); } diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/SanitizeDataProcessor.php index 72d52c522..40ed44b2a 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/SanitizeDataProcessor.php @@ -11,13 +11,13 @@ class Raven_SanitizeDataProcessor extends Raven_Processor const FIELDS_RE = '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i'; const VALUES_RE = '/^(?:\d[ -]*?){13,16}$/'; - private $client; private $fields_re; private $values_re; + protected $session_cookie_name; public function __construct(Raven_Client $client) { - $this->client = $client; + parent::__construct($client); $this->fields_re = self::FIELDS_RE; $this->values_re = self::VALUES_RE; $this->session_cookie_name = ini_get('session.name'); @@ -64,6 +64,9 @@ public function sanitize(&$item, $key) } } + /** @noinspection PhpInconsistentReturnPointsInspection + * @param array $data + */ public function sanitizeException(&$data) { foreach ($data['exception']['values'] as &$value) { diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 43401ef77..2814fec86 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -54,9 +54,15 @@ public function __construct($mb_detect_order = null) $this->mb_detect_order = $mb_detect_order; } } + /** * Serialize an object (recursively) into something safe for data * sanitization and encoding. + * + * @param mixed $value + * @param integer $max_depth + * @param integer $_depth + * @return string|boolean|double|integer|null|object|array */ public function serialize($value, $max_depth=3, $_depth=0) { @@ -94,6 +100,10 @@ protected function serializeString($value) return $value; } + /** + * @param mixed $value + * @return string|boolean|double|integer|null + */ protected function serializeValue($value) { if (is_null($value) || is_bool($value) || is_float($value) || is_integer($value)) { diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 09b4df00b..10a00ceba 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -240,7 +240,7 @@ private static function read_source_file($filename, $lineno, $context_lines = 5) // Code which is eval'ed have a modified filename.. Extract the // correct filename + linenumber from the string. $matches = array(); - $matched = preg_match("/^(.*?)\((\d+)\) : eval\(\)'d code$/", + $matched = preg_match("/^(.*?)\\((\\d+)\\) : eval\\(\\)'d code$/", $filename, $matches); if ($matched) { $frame['filename'] = $filename = $matches[1]; @@ -251,7 +251,7 @@ private static function read_source_file($filename, $lineno, $context_lines = 5) // "() : runtime-created function" // Extract the correct filename + linenumber from the string. $matches = array(); - $matched = preg_match("/^(.*?)\((\d+)\) : runtime-created function$/", + $matched = preg_match("/^(.*?)\\((\\d+)\\) : runtime-created function$/", $filename, $matches); if ($matched) { $frame['filename'] = $filename = $matches[1]; diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index 5d428c723..0c0f1ea4c 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -34,6 +34,10 @@ public function push($context) $this->stack[] = $context; } + /** @noinspection PhpInconsistentReturnPointsInspection + * @param string|null $context + * @return mixed + */ public function pop($context=null) { if (!$context) { diff --git a/lib/Raven/Util.php b/lib/Raven/Util.php index dbd2d11d0..2de2b48df 100644 --- a/lib/Raven/Util.php +++ b/lib/Raven/Util.php @@ -21,6 +21,11 @@ class Raven_Util * Because we love Python, this works much like dict.get() in Python. * * Returns $var from $array if set, otherwise returns $default. + * + * @param array $array + * @param string $var + * @param mixed $default + * @return mixed */ public static function get($array, $var, $default=null) { diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 0e3cebee1..45e7a2a4e 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -943,7 +943,7 @@ public function testUserContextWithMergeAndNull() * Set the server array to the test values, check the current url * * @dataProvider currentUrlProvider - * @param array $serverData + * @param array $serverVars * @param array $options * @param string $expected - the url expected * @param string $message - fail message diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 5d9ee4ba6..cd512d9e6 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -12,6 +12,8 @@ class Raven_Tests_ErrorHandlerTest extends PHPUnit_Framework_TestCase { private $errorLevel; + private $errorHandlerCalled; + private $existingErrorHandler; public function setUp() { diff --git a/test/Raven/Tests/SanitizeDataProcessorTest.php b/test/Raven/Tests/SanitizeDataProcessorTest.php index bc22b8431..a70120e22 100644 --- a/test/Raven/Tests/SanitizeDataProcessorTest.php +++ b/test/Raven/Tests/SanitizeDataProcessorTest.php @@ -83,7 +83,7 @@ public function testDoesFilterCreditCard() } /** - * @covers setProcessorOptions + * @covers Raven_SanitizeDataProcessor::setProcessorOptions * */ public function testSettingProcessorOptions() @@ -115,6 +115,9 @@ public function testSettingProcessorOptions() public function testOverrideOptions($processorOptions, $client_options, $dsn) { $client = new Dummy_Raven_Client($dsn, $client_options); + /** + * @var Raven_SanitizeDataProcessor $processor + */ $processor = $client->processors[0]; $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); @@ -151,6 +154,9 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) ); $client = new Dummy_Raven_Client($dsn, $client_options); + /** + * @var Raven_SanitizeDataProcessor $processor + */ $processor = $client->processors[0]; $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 25a5eaf32..4dfd701a1 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -209,6 +209,9 @@ public function testWithEvaldCode() $trace = $ex->getTrace(); $frames = Raven_Stacktrace::get_stack_info($trace); } + /** + * @var array $frames + */ $this->assertEquals($frames[count($frames) -1]['filename'], __FILE__); } } From e890a395b62b0d7a97909bb1aaffb73370712006 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Mon, 13 Feb 2017 20:20:46 +0300 Subject: [PATCH 0236/1161] "@return $this" is bad because this is not a typename Wrap code style in PHPDoc and describes of functions --- lib/Raven/Breadcrumbs/ErrorHandler.php | 4 +- lib/Raven/Breadcrumbs/MonologHandler.php | 15 ++- lib/Raven/Client.php | 117 ++++++++++++----------- lib/Raven/Compat.php | 12 ++- lib/Raven/CurlHandler.php | 6 +- lib/Raven/ErrorHandler.php | 12 +-- lib/Raven/Processor.php | 2 +- lib/Raven/SanitizeDataProcessor.php | 6 +- lib/Raven/Serializer.php | 12 +-- lib/Raven/TransactionStack.php | 2 +- lib/Raven/Util.php | 2 +- 11 files changed, 99 insertions(+), 91 deletions(-) diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index d48ab1dc5..eb340c224 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -11,15 +11,13 @@ class Raven_Breadcrumbs_ErrorHandler /** * @param Raven_Client $ravenClient - * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not */ public function __construct(Raven_Client $ravenClient) { $this->ravenClient = $ravenClient; } - public function handleError($code, $message, $file = '', $line = 0, $context=array()) + public function handleError($code, $message, $file = '', $line = 0, $context = array()) { $this->ravenClient->breadcrumbs->record(array( 'category' => 'error_reporting', diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 2f5811680..a6d20307d 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -29,7 +29,7 @@ class Raven_Breadcrumbs_MonologHandler extends AbstractProcessingHandler /** * @param Raven_Client $ravenClient * @param int $level The minimum logging level at which this handler will be triggered - * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) { @@ -38,13 +38,17 @@ public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $ $this->ravenClient = $ravenClient; } + /** + * @param string $message + * @return array|null + */ protected function parseException($message) { - if (!preg_match($this->excMatch, $message, $matches)) { - return; + if (preg_match($this->excMatch, $message, $matches)) { + return array($matches[1], $matches[2]); } - return array($matches[1], $matches[2]); + return null; } /** @@ -58,6 +62,9 @@ protected function write(array $record) } if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + /** + * @var Exception $exc + */ $exc = $record['context']['exception']; $crumb = array( 'type' => 'error', diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ae8c31f9b..43362f046 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -93,7 +93,7 @@ class Raven_Client */ var $processors; /** - * @var string|integer|null + * @var string|int|null */ var $_lasterror; var $_last_event_id; @@ -101,7 +101,7 @@ class Raven_Client var $_pending_events; var $sdk; - public function __construct($options_or_dsn=null, $options=array()) + public function __construct($options_or_dsn = null, $options = array()) { if (is_array($options_or_dsn)) { $options = array_merge($options_or_dsn, $options); @@ -283,7 +283,7 @@ public function getPrefixes() /** * @param array $value - * @return $this + * @return Raven_Client */ public function setPrefixes($value) { @@ -373,8 +373,8 @@ public function setProcessorsFromOptions($options) /** * Parses a Raven-compatible DSN and returns an array of its values. * - * @param string $dsn Raven compatible DSN: http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn - * @return array parsed DSN + * @param string $dsn Raven compatible DSN: http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn + * @return array parsed DSN */ public static function parseDSN($dsn) { @@ -384,7 +384,7 @@ public static function parseDSN($dsn) throw new InvalidArgumentException('Unsupported Sentry DSN scheme: ' . (!empty($scheme) ? $scheme : '')); } $netloc = (isset($url['host']) ? $url['host'] : null); - $netloc.= (isset($url['port']) ? ':'.$url['port'] : null); + $netloc .= (isset($url['port']) ? ':'.$url['port'] : null); $rawpath = (isset($url['path']) ? $url['path'] : null); if ($rawpath) { $pos = strrpos($rawpath, '/', 1); @@ -431,16 +431,16 @@ public function getIdent($ident) } /** - * @param string $message The message (primary description) for the event. - * @param array $params params to use when formatting the message. - * @param string $level Log level group - * @param boolean|array $stack - * @param mixed $vars + * @param string $message The message (primary description) for the event. + * @param array $params params to use when formatting the message. + * @param string $level Log level group + * @param bool|array $stack + * @param mixed $vars * @return string|null * @deprecated */ - public function message($message, $params=array(), $level=self::INFO, - $stack=false, $vars = null) + public function message($message, $params = array(), $level = self::INFO, + $stack = false, $vars = null) { return $this->captureMessage($message, $params, $level, $stack, $vars); } @@ -458,15 +458,15 @@ public function exception($exception) /** * Log a message to sentry * - * @param string $message The message (primary description) for the event. - * @param array $params params to use when formatting the message. - * @param array $data Additional attributes to pass with this event (see Sentry docs). - * @param boolean|array $stack - * @param mixed $vars + * @param string $message The message (primary description) for the event. + * @param array $params params to use when formatting the message. + * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param bool|array $stack + * @param mixed $vars * @return string|null */ - public function captureMessage($message, $params=array(), $data=array(), - $stack=false, $vars = null) + public function captureMessage($message, $params = array(), $data = array(), + $stack = false, $vars = null) { // Gracefully handle messages which contain formatting characters, but were not // intended to be used with formatting. @@ -499,12 +499,12 @@ public function captureMessage($message, $params=array(), $data=array(), * Log an exception to sentry * * @param Exception $exception The Exception object. - * @param array $data Additional attributes to pass with this event (see Sentry docs). - * @param mixed $logger - * @param mixed $vars + * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param mixed $logger + * @param mixed $vars * @return string|null */ - public function captureException($exception, $data=null, $logger=null, $vars=null) + public function captureException($exception, $data = null, $logger = null, $vars = null) { $has_chained_exceptions = version_compare(PHP_VERSION, '5.3.0', '>='); @@ -589,11 +589,12 @@ public function captureLastError() /** * Log an query to sentry + * * @param string|null $query - * @param string $level - * @param string $engine + * @param string $level + * @param string $engine */ - public function captureQuery($query, $level=self::INFO, $engine = '') + public function captureQuery($query, $level = self::INFO, $engine = '') { $data = array( 'message' => $query, @@ -827,7 +828,7 @@ public function sanitize(&$data) /** * Process data through all defined Raven_Processor sub-classes * - * @param array $data Associative array of data to log + * @param array $data Associative array of data to log */ public function process(&$data) { @@ -850,7 +851,7 @@ public function sendUnsentErrors() /** * @param string $data - * @return string|boolean + * @return string|bool */ public function encode(&$data) { @@ -909,11 +910,11 @@ public function send(&$data) /** * Send data to Sentry * - * @param string $url Full URL to Sentry + * @param string $url Full URL to Sentry * @param array|string $data Associative array of data to log - * @param array $headers Associative array of headers + * @param array $headers Associative array of headers */ - private function send_remote($url, $data, $headers=array()) + private function send_remote($url, $data, $headers = array()) { $parts = parse_url($url); $parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null); @@ -967,11 +968,11 @@ protected function get_curl_options() /** * Send the message over http to the sentry url given * - * @param string $url URL of the Sentry instance to log to - * @param array|string $data Associative array of data to log - * @param array $headers Associative array of headers + * @param string $url URL of the Sentry instance to log to + * @param array|string $data Associative array of data to log + * @param array $headers Associative array of headers */ - private function send_http($url, $data, $headers=array()) + private function send_http($url, $data, $headers = array()) { if ($this->curl_method == 'async') { $this->_curl_handler->enqueue($url, $data, $headers); @@ -1003,9 +1004,9 @@ protected function buildCurlCommand($url, $data, $headers) /** * Send the cURL to Sentry asynchronously. No errors will be returned from cURL * - * @param string $url URL of the Sentry instance to log to + * @param string $url URL of the Sentry instance to log to * @param array|string $data Associative array of data to log - * @param array $headers Associative array of headers + * @param array $headers Associative array of headers * @return bool */ private function send_http_asynchronous_curl_exec($url, $data, $headers) @@ -1017,9 +1018,9 @@ private function send_http_asynchronous_curl_exec($url, $data, $headers) /** * Send a blocking cURL to Sentry and check for errors from cURL * - * @param string $url URL of the Sentry instance to log to - * @param array|string $data Associative array of data to log - * @param array $headers Associative array of headers + * @param string $url URL of the Sentry instance to log to + * @param array|string $data Associative array of data to log + * @param array $headers Associative array of headers * @return bool */ private function send_http_synchronous($url, $data, $headers) @@ -1067,10 +1068,10 @@ private function send_http_synchronous($url, $data, $headers) /** * Generate a Sentry authorization header string * - * @param string $timestamp Timestamp when the event occurred - * @param string $client HTTP client name (not Raven_Client object) - * @param string $api_key Sentry API key - * @param string $secret_key Sentry API key + * @param string $timestamp Timestamp when the event occurred + * @param string $client HTTP client name (not Raven_Client object) + * @param string $api_key Sentry API key + * @param string $secret_key Sentry API key * @return string */ protected function get_auth_header($timestamp, $client, $api_key, $secret_key) @@ -1177,8 +1178,8 @@ protected function isHttps() /** * Get the value of a key from $_SERVER * - * @param string $key Key whose value you wish to obtain - * @return string Key's value + * @param string $key Key whose value you wish to obtain + * @return string Key's value */ private function _server_variable($key) { @@ -1192,8 +1193,8 @@ private function _server_variable($key) /** * Translate a PHP Error constant into a Sentry log level group * - * @param string $severity PHP E_$x error constant - * @return string Sentry log level group + * @param string $severity PHP E_$x error constant + * @return string Sentry log level group */ public function translateSeverity($severity) { @@ -1239,11 +1240,11 @@ public function registerSeverityMap($map) * Convenience function for setting a user's ID and Email * * @deprecated - * @param string $id User's ID - * @param string|null $email User's email - * @param array $data Additional user data + * @param string $id User's ID + * @param string|null $email User's email + * @param array $data Additional user data */ - public function set_user_data($id, $email=null, $data=array()) + public function set_user_data($id, $email = null, $data = array()) { $user = array('id' => $id); if (isset($email)) { @@ -1266,10 +1267,10 @@ public function onShutdown() /** * Sets user context. * - * @param array $data Associative array of user data - * @param bool $merge Merge existing context with new context + * @param array $data Associative array of user data + * @param bool $merge Merge existing context with new context */ - public function user_context($data, $merge=true) + public function user_context($data, $merge = true) { if ($merge && $this->context->user !== null) { // bail if data is null @@ -1285,7 +1286,7 @@ public function user_context($data, $merge=true) /** * Appends tags context. * - * @param array $data Associative array of tags + * @param array $data Associative array of tags */ public function tags_context($data) { @@ -1295,7 +1296,7 @@ public function tags_context($data) /** * Appends additional context. * - * @param array $data Associative array of extra data + * @param array $data Associative array of extra data */ public function extra_context($data) { diff --git a/lib/Raven/Compat.php b/lib/Raven/Compat.php index 6930f8735..977208f2b 100644 --- a/lib/Raven/Compat.php +++ b/lib/Raven/Compat.php @@ -25,7 +25,7 @@ public static function _gethostname() return php_uname('n'); } - public static function hash_hmac($algo, $data, $key, $raw_output=false) + public static function hash_hmac($algo, $data, $key, $raw_output = false) { if (function_exists('hash_hmac')) { return hash_hmac($algo, $data, $key, $raw_output); @@ -36,9 +36,9 @@ public static function hash_hmac($algo, $data, $key, $raw_output=false) /** * Implementation from 'KC Cloyd'. - * See http://php.net/manual/en/function.hash-hmac.php + * @doc http://php.net/manual/en/function.hash-hmac.php */ - public static function _hash_hmac($algo, $data, $key, $raw_output=false) + public static function _hash_hmac($algo, $data, $key, $raw_output = false) { $algo = strtolower($algo); $pack = 'H'.strlen($algo('test')); @@ -66,11 +66,12 @@ public static function _hash_hmac($algo, $data, $key, $raw_output=false) /** * Note that we discard the options given to be compatible * with PHP < 5.3 + * * @param mixed $value - * @param integer $options + * @param int $options * @return string */ - public static function json_encode($value, $options=0) + public static function json_encode($value, $options = 0) { if (function_exists('json_encode')) { return json_encode($value); @@ -82,6 +83,7 @@ public static function json_encode($value, $options=0) /** * Implementation taken from * http://www.mike-griffiths.co.uk/php-json_encode-alternative/ + * * @param mixed $value * @return string */ diff --git a/lib/Raven/CurlHandler.php b/lib/Raven/CurlHandler.php index ec12b9e73..c2ed47fff 100644 --- a/lib/Raven/CurlHandler.php +++ b/lib/Raven/CurlHandler.php @@ -22,7 +22,7 @@ class Raven_CurlHandler private $options; private $requests; - public function __construct($options, $join_timeout=5) + public function __construct($options, $join_timeout = 5) { $this->options = $options; $this->multi_handle = curl_multi_init(); @@ -37,7 +37,7 @@ public function __destruct() $this->join(); } - public function enqueue($url, $data=null, $headers=array()) + public function enqueue($url, $data = null, $headers = array()) { $ch = curl_init(); @@ -69,7 +69,7 @@ public function enqueue($url, $data=null, $headers=array()) return $fd; } - public function join($timeout=null) + public function join($timeout = null) { if (!isset($timeout)) { $timeout = $this->join_timeout; diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 98223f952..2cbefd14f 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -145,7 +145,7 @@ public function shouldCaptureFatalError($type) * * @param bool $call_existing Call any existing exception handlers after processing * this instance. - * @return $this + * @return Raven_ErrorHandler */ public function registerExceptionHandler($call_existing = true) { @@ -158,10 +158,10 @@ public function registerExceptionHandler($call_existing = true) * Register a handler which will intercept standard PHP errors and report them to the * associated Sentry client. * - * @param bool $call_existing Call any existing errors handlers after processing - * this instance. - * @param array $error_types All error types that should be sent. - * @return $this + * @param bool $call_existing Call any existing errors handlers after processing + * this instance. + * @param array $error_types All error types that should be sent. + * @return Raven_ErrorHandler */ public function registerErrorHandler($call_existing = true, $error_types = null) { @@ -179,7 +179,7 @@ public function registerErrorHandler($call_existing = true, $error_types = null) * * @param int $reservedMemorySize Number of kilobytes memory space to reserve, * which is utilized when handling fatal errors. - * @return $this + * @return Raven_ErrorHandler */ public function registerShutdownFunction($reservedMemorySize = 10) { diff --git a/lib/Raven/Processor.php b/lib/Raven/Processor.php index 16264732a..afc2beeff 100644 --- a/lib/Raven/Processor.php +++ b/lib/Raven/Processor.php @@ -21,7 +21,7 @@ public function __construct(Raven_Client $client) /** * Process and sanitize data, modifying the existing value if necessary. * - * @param array $data Array of log data + * @param array $data Array of log data */ abstract public function process(&$data); diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/SanitizeDataProcessor.php index 40ed44b2a..55caff328 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/SanitizeDataProcessor.php @@ -26,7 +26,7 @@ public function __construct(Raven_Client $client) /** * Override the default processor options * - * @param array $options Associative array of processor options + * @param array $options Associative array of processor options */ public function setProcessorOptions(array $options) { @@ -42,8 +42,8 @@ public function setProcessorOptions(array $options) /** * Replace any array values with our mask if the field name or the value matches a respective regex * - * @param mixed $item Associative array value - * @param string $key Associative array key + * @param mixed $item Associative array value + * @param string $key Associative array key */ public function sanitize(&$item, $key) { diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 2814fec86..77cd4d20f 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -43,7 +43,7 @@ class Raven_Serializer * * @var string */ - private $mb_detect_order= self::DEFAULT_MB_DETECT_ORDER; + private $mb_detect_order = self::DEFAULT_MB_DETECT_ORDER; /** * @param null|string $mb_detect_order @@ -60,11 +60,11 @@ public function __construct($mb_detect_order = null) * sanitization and encoding. * * @param mixed $value - * @param integer $max_depth - * @param integer $_depth - * @return string|boolean|double|integer|null|object|array + * @param int $max_depth + * @param int $_depth + * @return string|bool|double|int|null|object|array */ - public function serialize($value, $max_depth=3, $_depth=0) + public function serialize($value, $max_depth = 3, $_depth = 0) { $className = is_object($value) ? get_class($value) : null; $toArray = is_array($value) || $className === 'stdClass'; @@ -102,7 +102,7 @@ protected function serializeString($value) /** * @param mixed $value - * @return string|boolean|double|integer|null + * @return string|bool|double|int|null */ protected function serializeValue($value) { diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index 0c0f1ea4c..75ebc1635 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -38,7 +38,7 @@ public function push($context) * @param string|null $context * @return mixed */ - public function pop($context=null) + public function pop($context = null) { if (!$context) { return array_pop($this->stack); diff --git a/lib/Raven/Util.php b/lib/Raven/Util.php index 2de2b48df..8f684b527 100644 --- a/lib/Raven/Util.php +++ b/lib/Raven/Util.php @@ -27,7 +27,7 @@ class Raven_Util * @param mixed $default * @return mixed */ - public static function get($array, $var, $default=null) + public static function get($array, $var, $default = null) { if (isset($array[$var])) { return $array[$var]; From 6162cbc12ba45ce31b5a2399d198c35f964e2e4e Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Mon, 13 Feb 2017 20:30:03 +0300 Subject: [PATCH 0237/1161] Methods without "$this" should be static Static methods should be called with static for IoC/LSB --- lib/Raven/Client.php | 30 +++++++++++++++--------------- test/Raven/Tests/ClientTest.php | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 43362f046..be55030c5 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -128,7 +128,7 @@ public function __construct($options_or_dsn = null, $options = array()) $this->project = Raven_Util::get($options, 'project', 1); $this->auto_log_stacks = (bool) Raven_Util::get($options, 'auto_log_stacks', false); $this->name = Raven_Util::get($options, 'name', Raven_Compat::gethostname()); - $this->site = Raven_Util::get($options, 'site', $this->_server_variable('SERVER_NAME')); + $this->site = Raven_Util::get($options, 'site', self::_server_variable('SERVER_NAME')); $this->tags = Raven_Util::get($options, 'tags', array()); $this->release = Raven_Util::get($options, 'release', null); $this->environment = Raven_Util::get($options, 'environment', null); @@ -155,7 +155,7 @@ public function __construct($options_or_dsn = null, $options = array()) $this->setAppPath(Raven_Util::get($options, 'app_path', null)); $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null)); // a list of prefixes used to coerce absolute paths into relative - $this->setPrefixes(Raven_Util::get($options, 'prefixes', $this->getDefaultPrefixes())); + $this->setPrefixes(Raven_Util::get($options, 'prefixes', static::getDefaultPrefixes())); $this->processors = $this->setProcessorsFromOptions($options); $this->_lasterror = null; @@ -225,13 +225,13 @@ public function setEnvironment($value) return $this; } - private function getDefaultPrefixes() + private static function getDefaultPrefixes() { $value = get_include_path(); return explode(PATH_SEPARATOR, $value); } - private function _convertPath($value) + private static function _convertPath($value) { $path = @realpath($value); if ($path === false) { @@ -254,7 +254,7 @@ public function getAppPath() public function setAppPath($value) { if ($value) { - $this->app_path = $this->_convertPath($value); + $this->app_path = static::_convertPath($value); } else { $this->app_path = null; } @@ -312,7 +312,7 @@ public function getServerEndpoint($value) return $this->server; } - public function getUserAgent() + public static function getUserAgent() { return 'sentry-php/' . self::VERSION; } @@ -353,7 +353,7 @@ public static function getDefaultProcessors() public function setProcessorsFromOptions($options) { $processors = array(); - foreach (Raven_util::get($options, 'processors', self::getDefaultProcessors()) as $processor) { + foreach (Raven_util::get($options, 'processors', static::getDefaultProcessors()) as $processor) { /** * @var Raven_Processor $new_processor * @var Raven_Processor|string $processor @@ -642,9 +642,9 @@ protected function get_http_data() } $result = array( - 'method' => $this->_server_variable('REQUEST_METHOD'), + 'method' => self::_server_variable('REQUEST_METHOD'), 'url' => $this->get_current_url(), - 'query_string' => $this->_server_variable('QUERY_STRING'), + 'query_string' => self::_server_variable('QUERY_STRING'), ); // dont set this as an empty array as PHP will treat it as a numeric array @@ -717,7 +717,7 @@ public function capture($data, $stack = null, $vars = null) $data['extra'] = array(); } if (!isset($data['event_id'])) { - $data['event_id'] = $this->uuid4(); + $data['event_id'] = static::uuid4(); } if (isset($data['message'])) { @@ -899,7 +899,7 @@ public function send(&$data) $message = $this->encode($data); $headers = array( - 'User-Agent' => $this->getUserAgent(), + 'User-Agent' => static::getUserAgent(), 'X-Sentry-Auth' => $this->getAuthHeader(), 'Content-Type' => 'application/octet-stream' ); @@ -1074,7 +1074,7 @@ private function send_http_synchronous($url, $data, $headers) * @param string $secret_key Sentry API key * @return string */ - protected function get_auth_header($timestamp, $client, $api_key, $secret_key) + protected static function get_auth_header($timestamp, $client, $api_key, $secret_key) { $header = array( sprintf('sentry_timestamp=%F', $timestamp), @@ -1097,7 +1097,7 @@ protected function get_auth_header($timestamp, $client, $api_key, $secret_key) public function getAuthHeader() { $timestamp = microtime(true); - return $this->get_auth_header($timestamp, $this->getUserAgent(), $this->public_key, $this->secret_key); + return $this->get_auth_header($timestamp, static::getUserAgent(), $this->public_key, $this->secret_key); } /** @@ -1105,7 +1105,7 @@ public function getAuthHeader() * * @return string */ - private function uuid4() + private static function uuid4() { $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // 32 bits for "time_low" @@ -1181,7 +1181,7 @@ protected function isHttps() * @param string $key Key whose value you wish to obtain * @return string Key's value */ - private function _server_variable($key) + private static function _server_variable($key) { if (isset($_SERVER[$key])) { return $_SERVER[$key]; diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 45e7a2a4e..67d371b42 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -42,7 +42,7 @@ public function is_http_request() { return true; } - public function get_auth_header($timestamp, $client, $api_key, $secret_key) + public static function get_auth_header($timestamp, $client, $api_key, $secret_key) { return parent::get_auth_header($timestamp, $client, $api_key, $secret_key); } From 33b9a363dfdc48ca0b79494e89c75b3926c8d4fb Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Mon, 13 Feb 2017 20:44:30 +0300 Subject: [PATCH 0238/1161] Most of methods in Raven_Client SHOULD NOT be static because we can not override behavior of the class --- lib/Raven/Client.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index be55030c5..f0f93e28b 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -143,7 +143,7 @@ public function __construct($options_or_dsn = null, $options = array()) $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync'); $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl'); $this->curl_ipv4 = Raven_Util::get($options, 'curl_ipv4', true); - $this->ca_cert = Raven_Util::get($options, 'ca_cert', $this->get_default_ca_cert()); + $this->ca_cert = Raven_Util::get($options, 'ca_cert', static::get_default_ca_cert()); $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true); $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); @@ -914,14 +914,14 @@ public function send(&$data) * @param array|string $data Associative array of data to log * @param array $headers Associative array of headers */ - private function send_remote($url, $data, $headers = array()) + protected function send_remote($url, $data, $headers = array()) { $parts = parse_url($url); $parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null); $this->send_http($url, $data, $headers); } - protected function get_default_ca_cert() + protected static function get_default_ca_cert() { return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem'; } @@ -972,7 +972,7 @@ protected function get_curl_options() * @param array|string $data Associative array of data to log * @param array $headers Associative array of headers */ - private function send_http($url, $data, $headers = array()) + protected function send_http($url, $data, $headers = array()) { if ($this->curl_method == 'async') { $this->_curl_handler->enqueue($url, $data, $headers); @@ -1009,7 +1009,7 @@ protected function buildCurlCommand($url, $data, $headers) * @param array $headers Associative array of headers * @return bool */ - private function send_http_asynchronous_curl_exec($url, $data, $headers) + protected function send_http_asynchronous_curl_exec($url, $data, $headers) { exec($this->buildCurlCommand($url, $data, $headers)); return true; // The exec method is just fire and forget, so just assume it always works @@ -1023,7 +1023,7 @@ private function send_http_asynchronous_curl_exec($url, $data, $headers) * @param array $headers Associative array of headers * @return bool */ - private function send_http_synchronous($url, $data, $headers) + protected function send_http_synchronous($url, $data, $headers) { $new_headers = array(); foreach ($headers as $key => $value) { @@ -1105,7 +1105,7 @@ public function getAuthHeader() * * @return string */ - private static function uuid4() + protected static function uuid4() { $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // 32 bits for "time_low" From 9f77d2701c25c8c035aa993d06e23d2854044435 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 03:24:37 +0300 Subject: [PATCH 0239/1161] temporary commit --- lib/Raven/Client.php | 44 +- lib/Raven/Compat.php | 2 + test/Raven/Tests/ClientTest.php | 812 ++++++++++++++++++++++++++- test/Raven/Tests/IntegrationTest.php | 2 +- 4 files changed, 844 insertions(+), 16 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index f0f93e28b..9a273fbc6 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -32,6 +32,9 @@ class Raven_Client const MESSAGE_LIMIT = 1024; public $breadcrumbs; + /** + * @var Raven_Context + */ public $context; public $extra_data; /** @@ -51,11 +54,11 @@ class Raven_Client */ protected $app_path; /** - * @var array + * @var string[] */ protected $prefixes; /** - * @var array + * @var string[]|null */ protected $excluded_app_paths; /** @@ -64,6 +67,9 @@ class Raven_Client protected $transport; var $logger; + /** + * @var string Full URL to Sentry + */ var $server; var $secret_key; var $public_key; @@ -100,6 +106,10 @@ class Raven_Client var $_user; var $_pending_events; var $sdk; + /** + * @var Raven_CurlHandler + */ + protected $_curl_handler; public function __construct($options_or_dsn = null, $options = array()) { @@ -177,8 +187,10 @@ public function __construct($options_or_dsn = null, $options = array()) } $this->transaction = new Raven_TransactionStack(); - if ($this->is_http_request() && isset($_SERVER['PATH_INFO'])) { + if (self::is_http_request() && isset($_SERVER['PATH_INFO'])) { + // @codeCoverageIgnoreStart $this->transaction->push($_SERVER['PATH_INFO']); + // @codeCoverageIgnoreEnd } if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { @@ -268,11 +280,7 @@ public function getExcludedAppPaths() public function setExcludedAppPaths($value) { - if ($value) { - $this->excluded_app_paths = $value ? array_map(array($this, '_convertPath'), $value) : $value; - } else { - $this->excluded_app_paths = null; - } + $this->excluded_app_paths = $value ? array_map(array($this, '_convertPath'), $value) : null; return $this; } @@ -307,7 +315,7 @@ public function getTransport() return $this->transport; } - public function getServerEndpoint($value) + public function getServerEndpoint($value = '') { return $this->server; } @@ -423,6 +431,7 @@ public function getLastError() * * @param mixed $ident * @return mixed + * @codeCoverageIgnore */ public function getIdent($ident) { @@ -438,6 +447,7 @@ public function getIdent($ident) * @param mixed $vars * @return string|null * @deprecated + * @codeCoverageIgnore */ public function message($message, $params = array(), $level = self::INFO, $stack = false, $vars = null) @@ -449,6 +459,7 @@ public function message($message, $params = array(), $level = self::INFO, * @param Exception $exception * @return string|null * @deprecated + * @codeCoverageIgnore */ public function exception($exception) { @@ -537,7 +548,9 @@ public function captureException($exception, $data = null, $logger = null, $vars // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) if (!class_exists('Raven_Stacktrace')) { + // @codeCoverageIgnoreStart spl_autoload_call('Raven_Stacktrace'); + // @codeCoverageIgnoreEnd } $exc_data['stacktrace'] = array( @@ -624,7 +637,11 @@ protected function registerDefaultBreadcrumbHandlers() $handler->install(); } - protected function is_http_request() + /** + * @return bool + * @codeCoverageIgnore + */ + protected static function is_http_request() { return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; } @@ -726,7 +743,7 @@ public function capture($data, $stack = null, $vars = null) $data = array_merge($this->get_default_data(), $data); - if ($this->is_http_request()) { + if (self::is_http_request()) { $data = array_merge($this->get_http_data(), $data); } @@ -850,7 +867,7 @@ public function sendUnsentErrors() } /** - * @param string $data + * @param array $data * @return string|bool */ public function encode(&$data) @@ -860,7 +877,9 @@ public function encode(&$data) if (function_exists('json_last_error_msg')) { $this->_lasterror = json_last_error_msg(); } else { + // @codeCoverageIgnoreStart $this->_lasterror = json_last_error(); + // @codeCoverageIgnoreEnd } return false; } @@ -1243,6 +1262,7 @@ public function registerSeverityMap($map) * @param string $id User's ID * @param string|null $email User's email * @param array $data Additional user data + * @codeCoverageIgnore */ public function set_user_data($id, $email = null, $data = array()) { diff --git a/lib/Raven/Compat.php b/lib/Raven/Compat.php index 977208f2b..87269cfec 100644 --- a/lib/Raven/Compat.php +++ b/lib/Raven/Compat.php @@ -77,7 +77,9 @@ public static function json_encode($value, $options = 0) return json_encode($value); } + // @codeCoverageIgnoreStart return self::_json_encode($value); + // @codeCoverageIgnoreEnd } /** diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 67d371b42..f226c415d 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -38,7 +38,7 @@ public function send(&$data) } $this->__sent_events[] = $data; } - public function is_http_request() + public static function is_http_request() { return true; } @@ -74,6 +74,77 @@ public function test_get_current_url() } } +class Dummy_Raven_Client_With_Overrided_Direct_Send extends Raven_Client +{ + var $_send_http_asynchronous_curl_exec_called = false; + var $_send_http_synchronous = false; + var $_set_url; + var $_set_data; + var $_set_headers; + + function send_http_asynchronous_curl_exec($url, $data, $headers) + { + $this->_send_http_asynchronous_curl_exec_called = true; + $this->_set_url = $url; + $this->_set_data = $data; + $this->_set_headers = $headers; + } + + function send_http_synchronous($url, $data, $headers) + { + $this->_send_http_synchronous = true; + $this->_set_url = $url; + $this->_set_data = $data; + $this->_set_headers = $headers; + } + + function get_curl_options() + { + $options = parent::get_curl_options(); + + return $options; + } + + function get_curl_handler() + { + return $this->_curl_handler; + } + + function set_curl_handler(Raven_CurlHandler $value) + { + $this->_curl_handler = $value; + } +} + +class Dummy_Raven_CurlHandler extends Raven_CurlHandler +{ + var $_set_url; + var $_set_data; + var $_set_headers; + var $_enqueue_called = false; + var $_join_called = false; + + function __construct($options = array(), $join_timeout = 5) + { + parent::__construct($options, $join_timeout); + } + + function enqueue($url, $data = null, $headers = array()) + { + $this->_enqueue_called = true; + $this->_set_url = $url; + $this->_set_data = $data; + $this->_set_headers = $headers; + + return 0; + } + + function join($timeout = null) + { + $this->_join_called = true; + } +} + class Raven_Tests_ClientTest extends PHPUnit_Framework_TestCase { private function create_exception() @@ -183,6 +254,9 @@ public function testParseDSNMissingSecretKey() Raven_Client::ParseDSN('http://public@example.com/1'); } + /** + * @covers Raven_Client::__construct + */ public function testDsnFirstArgument() { $client = new Dummy_Raven_Client('http://public:secret@example.com/1'); @@ -193,6 +267,9 @@ public function testDsnFirstArgument() $this->assertEquals($client->secret_key, 'secret'); } + /** + * @covers Raven_Client::__construct + */ public function testDsnFirstArgumentWithOptions() { $client = new Dummy_Raven_Client('http://public:secret@example.com/1', array( @@ -206,6 +283,9 @@ public function testDsnFirstArgumentWithOptions() $this->assertEquals($client->site, 'foo'); } + /** + * @covers Raven_Client::__construct + */ public function testOptionsFirstArgument() { $client = new Dummy_Raven_Client(array( @@ -217,6 +297,9 @@ public function testOptionsFirstArgument() } + /** + * @covers Raven_Client::__construct + */ public function testDsnInOptionsFirstArg() { $client = new Dummy_Raven_Client(array( @@ -229,6 +312,9 @@ public function testDsnInOptionsFirstArg() $this->assertEquals($client->secret_key, 'secret'); } + /** + * @covers Raven_Client::__construct + */ public function testDsnInOptionsSecondArg() { $client = new Dummy_Raven_Client(null, array( @@ -241,6 +327,9 @@ public function testDsnInOptionsSecondArg() $this->assertEquals($client->secret_key, 'secret'); } + /** + * @covers Raven_Client::__construct + */ public function testOptionsFirstArgumentWithOptions() { $client = new Dummy_Raven_Client(array( @@ -254,6 +343,9 @@ public function testOptionsFirstArgumentWithOptions() $this->assertEquals($client->site, 'foo'); } + /** + * @covers Raven_Client::captureMessage + */ public function testOptionsExtraData() { $client = new Dummy_Raven_Client(array('extra' => array('foo' => 'bar'))); @@ -265,6 +357,23 @@ public function testOptionsExtraData() $this->assertEquals($event['extra']['foo'], 'bar'); } + /** + * @covers Raven_Client::captureMessage + */ + public function testOptionsExtraDataWithNull() + { + $client = new Dummy_Raven_Client(array('extra' => array('foo' => 'bar'))); + + $client->captureMessage('Test Message %s', array('foo'), null); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['extra']['foo'], 'bar'); + } + + /** + * @covers Raven_Client::captureMessage + */ public function testEmptyExtraData() { $client = new Dummy_Raven_Client(array('extra' => array())); @@ -276,6 +385,9 @@ public function testEmptyExtraData() $this->assertEquals(array_key_exists('extra', $event), false); } + /** + * @covers Raven_Client::captureMessage + */ public function testCaptureMessageDoesHandleUninterpolatedMessage() { $client = new Dummy_Raven_Client(); @@ -287,6 +399,9 @@ public function testCaptureMessageDoesHandleUninterpolatedMessage() $this->assertEquals($event['message'], 'Test Message %s'); } + /** + * @covers Raven_Client::captureMessage + */ public function testCaptureMessageDoesHandleInterpolatedMessage() { $client = new Dummy_Raven_Client(); @@ -298,6 +413,9 @@ public function testCaptureMessageDoesHandleInterpolatedMessage() $this->assertEquals($event['message'], 'Test Message foo'); } + /** + * @covers Raven_Client::captureMessage + */ public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() { $client = new Dummy_Raven_Client(); @@ -313,6 +431,9 @@ public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() $this->assertEquals($event['message'], 'Test Message foo'); } + /** + * @covers Raven_Client::captureMessage + */ public function testCaptureMessageSetsInterface() { $client = new Dummy_Raven_Client(); @@ -326,8 +447,12 @@ public function testCaptureMessageSetsInterface() 'params' => array('foo'), 'formatted' => 'Test Message foo', )); + $this->assertEquals('Test Message foo', $event['message']); } + /** + * @covers Raven_Client::captureMessage + */ public function testCaptureMessageHandlesOptionsAsThirdArg() { $client = new Dummy_Raven_Client(); @@ -341,8 +466,12 @@ public function testCaptureMessageHandlesOptionsAsThirdArg() $event = array_pop($events); $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); $this->assertEquals($event['extra']['foo'], 'bar'); + $this->assertEquals('Test Message foo', $event['message']); } + /** + * @covers Raven_Client::captureMessage + */ public function testCaptureMessageHandlesLevelAsThirdArg() { $client = new Dummy_Raven_Client(); @@ -352,8 +481,12 @@ public function testCaptureMessageHandlesLevelAsThirdArg() $this->assertEquals(count($events), 1); $event = array_pop($events); $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); + $this->assertEquals('Test Message foo', $event['message']); } + /** + * @covers Raven_Client::captureException + */ public function testCaptureExceptionSetsInterfaces() { # TODO: it'd be nice if we could mock the stacktrace extraction function here @@ -381,6 +514,9 @@ public function testCaptureExceptionSetsInterfaces() $this->assertFalse(empty($frame['post_context'])); } + /** + * @covers Raven_Client::captureException + */ public function testCaptureExceptionChainedException() { if (version_compare(PHP_VERSION, '5.3.0', '<')) { @@ -402,6 +538,9 @@ public function testCaptureExceptionChainedException() $this->assertEquals($exc['values'][1]['value'], 'Child exc'); } + /** + * @covers Raven_Client::captureException + */ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() { if (version_compare(PHP_VERSION, '5.3.0', '<')) { @@ -428,6 +567,9 @@ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); } + /** + * @covers Raven_Client::captureException + */ public function testCaptureExceptionHandlesOptionsAsSecondArg() { $client = new Dummy_Raven_Client(); @@ -439,6 +581,9 @@ public function testCaptureExceptionHandlesOptionsAsSecondArg() $this->assertEquals($event['culprit'], 'test'); } + /** + * @covers Raven_Client::captureException + */ public function testCaptureExceptionHandlesExcludeOption() { $client = new Dummy_Raven_Client(array( @@ -450,6 +595,9 @@ public function testCaptureExceptionHandlesExcludeOption() $this->assertEquals(count($events), 0); } + /** + * @covers Raven_Client::captureException + */ public function testCaptureExceptionInvalidUTF8() { $client = new Dummy_Raven_Client(); @@ -466,6 +614,9 @@ public function testCaptureExceptionInvalidUTF8() $this->assertNotEquals($message, false, $client->getLastError()); } + /** + * @covers Raven_Client::__construct + */ public function testDoesRegisterProcessors() { $client = new Dummy_Raven_Client(array( @@ -491,6 +642,10 @@ public function testProcessDoesCallProcessors() $client->process($data); } + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::getDefaultProcessors + */ public function testDefaultProcessorsAreUsed() { $client = new Dummy_Raven_Client(); @@ -499,6 +654,9 @@ public function testDefaultProcessorsAreUsed() $this->assertEquals(count($client->processors), count($defaults)); } + /** + * @covers Raven_Client::getDefaultProcessors + */ public function testDefaultProcessorsContainSanitizeDataProcessor() { $defaults = Dummy_Raven_Client::getDefaultProcessors(); @@ -506,6 +664,10 @@ public function testDefaultProcessorsContainSanitizeDataProcessor() $this->assertTrue(in_array('Raven_SanitizeDataProcessor', $defaults)); } + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::get_default_data + */ public function testGetDefaultData() { $client = new Dummy_Raven_Client(); @@ -528,6 +690,7 @@ public function testGetDefaultData() /** * @backupGlobals + * @covers Raven_Client::get_http_data */ public function testGetHttpData() { @@ -579,6 +742,10 @@ public function testGetHttpData() $this->assertEquals($expected, $client->get_http_data()); } + /** + * @covers Raven_Client::user_context + * @covers Raven_Client::get_user_data + */ public function testGetUserDataWithSetUser() { $client = new Dummy_Raven_Client(); @@ -590,6 +757,7 @@ public function testGetUserDataWithSetUser() 'username' => 'my_user', ); + // @todo перепиÑать $client->set_user_data($id, $email, $user); $expected = array( @@ -603,6 +771,9 @@ public function testGetUserDataWithSetUser() $this->assertEquals($expected, $client->get_user_data()); } + /** + * @covers Raven_Client::get_user_data + */ public function testGetUserDataWithNoUser() { $client = new Dummy_Raven_Client(); @@ -615,7 +786,10 @@ public function testGetUserDataWithNoUser() $this->assertEquals($expected, $client->get_user_data()); } - public function testGetAuthHeader() + /** + * @covers Raven_Client::get_auth_header + */ + public function testGet_Auth_Header() { $client = new Dummy_Raven_Client(); @@ -629,6 +803,24 @@ public function testGetAuthHeader() $this->assertEquals($expected, $client->get_auth_header($timestamp, 'sentry-php/test', 'publickey', 'secretkey')); } + /** + * @covers Raven_Client::getAuthHeader + */ + public function testGetAuthHeader() + { + $client = new Dummy_Raven_Client(); + $ts1 = microtime(true); + $header = $client->getAuthHeader(); + $ts2 = microtime(true); + $this->assertEquals(1, preg_match('/sentry_timestamp=([0-9.]+)/', $header, $a)); + $this->assertRegExp('/^[0-9]+(\\.[0-9]+)?$/', $a[1]); + $this->assertGreaterThanOrEqual($ts1, (double)$a[1]); + $this->assertLessThanOrEqual($ts2, (double)$a[1]); + } + + /** + * @covers Raven_Client::captureMessage + */ public function testCaptureMessageWithUserContext() { $client = new Dummy_Raven_Client(); @@ -644,6 +836,9 @@ public function testCaptureMessageWithUserContext() ), $event['user']); } + /** + * @covers Raven_Client::captureMessage + */ public function testCaptureMessageWithUnserializableUserData() { $client = new Dummy_Raven_Client(); @@ -659,9 +854,13 @@ public function testCaptureMessageWithUnserializableUserData() $events = $client->getSentEvents(); // we're just asserting that this goes off without a hitch $this->assertEquals(1, count($events)); - $event = array_pop($events); + array_pop($events); } + /** + * @covers Raven_Client::captureMessage + * @covers Raven_Client::tags_context + */ public function testCaptureMessageWithTagsContext() { $client = new Dummy_Raven_Client(); @@ -680,6 +879,10 @@ public function testCaptureMessageWithTagsContext() ), $event['tags']); } + /** + * @covers Raven_Client::captureMessage + * @covers Raven_Client::extra_context + */ public function testCaptureMessageWithExtraContext() { $client = new Dummy_Raven_Client(); @@ -698,6 +901,9 @@ public function testCaptureMessageWithExtraContext() ), $event['extra']); } + /** + * @covers Raven_Client::captureException + */ public function testCaptureExceptionContainingLatin1() { // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order @@ -753,9 +959,14 @@ public function testCaptureExceptionInLatin1File() $this->assertEquals($found, true); } + /** + * @covers Raven_Client::captureLastError + */ public function testCaptureLastError() { $client = new Dummy_Raven_Client(); + $this->assertNull($client->captureLastError()); + $this->assertEquals(0, count($client->getSentEvents())); @$undefined; @@ -766,6 +977,9 @@ public function testCaptureLastError() $this->assertEquals($event['exception']['values'][0]['value'], 'Undefined variable: undefined'); } + /** + * @covers Raven_Client::getLastEventID + */ public function testGetLastEventID() { $client = new Dummy_Raven_Client(); @@ -773,6 +987,9 @@ public function testGetLastEventID() $this->assertEquals($client->getLastEventID(), 'abc'); } + /** + * @covers Raven_Client::setTransport + */ public function testCustomTransport() { $events = array(); @@ -788,6 +1005,9 @@ public function testCustomTransport() $this->assertEquals(count($events), 1); } + /** + * @covers Raven_Client::setAppPath + */ public function testAppPathLinux() { $client = new Dummy_Raven_Client(); @@ -800,6 +1020,9 @@ public function testAppPathLinux() $this->assertEquals($client->getAppPath(), '/foo/baz/'); } + /** + * @covers Raven_Client::setAppPath + */ public function testAppPathWindows() { $client = new Dummy_Raven_Client(); @@ -837,6 +1060,9 @@ public function cb3(&$data) return true; } + /** + * @covers Raven_Client::send + */ public function testSendCallback() { $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb1'))); @@ -856,6 +1082,9 @@ public function testSendCallback() $this->assertEquals(empty($events[0]['message']), true); } + /** + * @covers Raven_Client::sanitize + */ public function testSanitizeExtra() { $client = new Dummy_Raven_Client(); @@ -879,6 +1108,9 @@ public function testSanitizeExtra() ))); } + /** + * @covers Raven_Client::sanitize + */ public function testSanitizeTags() { $client = new Dummy_Raven_Client(); @@ -894,6 +1126,9 @@ public function testSanitizeTags() ))); } + /** + * @covers Raven_Client::sanitize + */ public function testSanitizeUser() { $client = new Dummy_Raven_Client(); @@ -907,14 +1142,30 @@ public function testSanitizeUser() ))); } + /** + * @covers Raven_Client::buildCurlCommand + */ public function testBuildCurlCommandEscapesInput() { $data = '{"foo": "\'; ls;"}'; $client = new Dummy_Raven_Client(); $result = $client->buildCurlCommand('http://foo.com', $data, array()); $this->assertEquals($result, 'curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &'); + + $result = $client->buildCurlCommand('http://foo.com', $data, array('key' => 'value')); + $this->assertEquals($result, 'curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &'); + + $client->verify_ssl = false; + $result = $client->buildCurlCommand('http://foo.com', $data, array()); + $this->assertEquals($result, 'curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &'); + + $result = $client->buildCurlCommand('http://foo.com', $data, array('key' => 'value')); + $this->assertEquals($result, 'curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &'); } + /** + * @covers Raven_Client::user_context + */ public function testUserContextWithoutMerge() { $client = new Dummy_Raven_Client(); @@ -923,6 +1174,9 @@ public function testUserContextWithoutMerge() $this->assertEquals($client->context->user, array('baz' => 'bar')); } + /** + * @covers Raven_Client::user_context + */ public function testUserContextWithMerge() { $client = new Dummy_Raven_Client(); @@ -931,6 +1185,9 @@ public function testUserContextWithMerge() $this->assertEquals($client->context->user, array('foo' => 'bar', 'baz' => 'bar')); } + /** + * @covers Raven_Client::user_context + */ public function testUserContextWithMergeAndNull() { $client = new Dummy_Raven_Client(); @@ -947,6 +1204,8 @@ public function testUserContextWithMergeAndNull() * @param array $options * @param string $expected - the url expected * @param string $message - fail message + * @covers Raven_Client::get_current_url + * @covers Raven_Client::isHttps */ public function testCurrentUrl($serverVars, $options, $expected, $message) { @@ -1027,4 +1286,551 @@ public function currentUrlProvider() ) ); } + + /** + * @covers Raven_Client::uuid4() + */ + public function testUuid4() + { + $method = new ReflectionMethod('Raven_Client', 'uuid4'); + $method->setAccessible(true); + for ($i = 0; $i < 1000; $i++) { + $this->assertRegExp('/^[0-9a-z-]+$/', $method->invoke(null)); + } + } + + /** + * @covers Raven_Client::getEnvironment + * @covers Raven_Client::setEnvironment + * @covers Raven_Client::getRelease + * @covers Raven_Client::setRelease + * @covers Raven_Client::getAppPath + * @covers Raven_Client::setAppPath + * @covers Raven_Client::getExcludedAppPaths + * @covers Raven_Client::setExcludedAppPaths + * @covers Raven_Client::getPrefixes + * @covers Raven_Client::setPrefixes + * @covers Raven_Client::getSendCallback + * @covers Raven_Client::setSendCallback + * @covers Raven_Client::getTransport + * @covers Raven_Client::setTransport + * @covers Raven_Client::getServerEndpoint + * @covers Raven_Client::getLastError + * @covers Raven_Client::getLastEventID + * @covers Raven_Client::get_extra_data + * @covers Raven_Client::setProcessors + */ + public function testGettersAndSetters() + { + $client = new Dummy_Raven_Client(); + $property_method__convert_path = new ReflectionMethod('Raven_Client', '_convertPath'); + $property_method__convert_path->setAccessible(true); + // @todo завиÑит от верÑии php. Ð’Ñтавить Ñюда closure + $callable = array($this, 'stabClosureVoid'); + + $data = array( + array('environment', null, 'value',), + array('environment', null, null,), + array('release', null, 'value',), + array('release', null, null,), + array('app_path', null, 'value', $property_method__convert_path->invoke($client, 'value')), + array('app_path', null, null,), + array('app_path', null, false, null,), + array('excluded_app_paths', null, array('value'), + array($property_method__convert_path->invoke($client, 'value'))), + array('excluded_app_paths', null, array(), null), + array('excluded_app_paths', null, null), + array('prefixes', null, array('value'), array($property_method__convert_path->invoke($client, 'value'))), + array('prefixes', null, array()), + array('send_callback', null, $callable), + array('send_callback', null, null), + array('transport', null, $callable), + array('transport', null, null), + array('server', 'ServerEndpoint', 'http://example.com/'), + array('server', 'ServerEndpoint', 'http://example.org/'), + array('_lasterror', null, null,), + array('_lasterror', null, 'value',), + array('_lasterror', null, mt_rand(100, 999),), + array('_last_event_id', null, mt_rand(100, 999),), + array('_last_event_id', null, 'value',), + array('extra_data', '_extra_data', array('key' => 'value'),), + array('processors', 'processors', array(),), + array('processors', 'processors', array('key' => 'value'),), + ); + foreach ($data as &$datum) { + $this->subTestGettersAndSettersDatum($client, $datum); + } + foreach ($data as &$datum) { + $client = new Dummy_Raven_Client(); + $this->subTestGettersAndSettersDatum($client, $datum); + } + } + + private function subTestGettersAndSettersDatum(Raven_Client $client, $datum) + { + if (count($datum) == 3) { + list($property_name, $function_name, $value_in) = $datum; + $value_out = $value_in; + } else { + list($property_name, $function_name, $value_in, $value_out) = $datum; + } + if (is_null($function_name)) { + $function_name = str_replace('_', '', $property_name); + } + + $method_get_name = 'get'.$function_name; + $method_set_name = 'set'.$function_name; + $property = new ReflectionProperty('Raven_Client', $property_name); + $property->setAccessible(true); + + if (method_exists($client, $method_set_name)) { + $setter_output = $client->$method_set_name($value_in); + if (!is_null($setter_output) and is_object($setter_output)) { + // chaining call test + $this->assertEquals(spl_object_hash($client), spl_object_hash($setter_output)); + } + $actual_value = $property->getValue($client); + $this->assertMixedValueAndArray($value_out, $actual_value); + } + + if (method_exists($client, $method_get_name)) { + $property->setValue($client, $value_out); + $reflection = new ReflectionMethod('Raven_Client', $method_get_name); + if ($reflection->isPublic()) { + $actual_value = $client->$method_get_name(); + $this->assertMixedValueAndArray($value_out, $actual_value); + } + } + } + + private function assertMixedValueAndArray($expected_value, $actual_value) + { + if (is_null($expected_value)) { + $this->assertNull($actual_value); + } elseif ($expected_value === true) { + $this->assertTrue($actual_value); + } elseif ($expected_value === false) { + $this->assertFalse($actual_value); + } elseif (is_string($expected_value) or is_integer($expected_value) or is_double($expected_value)) { + $this->assertEquals($expected_value, $actual_value); + } elseif (is_array($expected_value)) { + $this->assertInternalType('array', $actual_value); + $this->assertEquals(count($expected_value), count($actual_value)); + foreach ($expected_value as $key => $value) { + $this->assertArrayHasKey($key, $actual_value); + $this->assertMixedValueAndArray($value, $actual_value[$key]); + } + } elseif (is_callable($expected_value) or is_object($expected_value)) { + $this->assertEquals(spl_object_hash($expected_value), spl_object_hash($actual_value)); + } + } + + /** + * @covers Raven_Client::_convertPath + */ + function test_convertPath() + { + $property = new ReflectionMethod('Raven_Client', '_convertPath'); + $property->setAccessible(true); + // @todo + } + + /** + * @covers Raven_Client::getDefaultProcessors + */ + function testGetDefaultProcessors() + { + foreach (Raven_Client::getDefaultProcessors() as $class_name) { + $this->assertInternalType('string', $class_name); + $this->assertTrue(class_exists($class_name)); + $reflection = new ReflectionClass($class_name); + $this->assertTrue($reflection->isSubclassOf('Raven_Processor')); + $this->assertFalse($reflection->isAbstract()); + } + } + + /** + * @covers Raven_Client::get_default_ca_cert + */ + function testGet_default_ca_cert() + { + $reflection = new ReflectionMethod('Raven_Client', 'get_default_ca_cert'); + $reflection->setAccessible(true); + $this->assertFileExists($reflection->invoke(null)); + } + + /** + * @covers Raven_Client::translateSeverity + * @covers Raven_Client::registerSeverityMap + */ + function testTranslateSeverity() + { + $reflection = new ReflectionProperty('Raven_Client', 'severity_map'); + $reflection->setAccessible(true); + $client = new Dummy_Raven_Client(); + + $predefined = array(E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, + E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR, E_USER_WARNING, + E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR,); + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + $predefined[] = E_DEPRECATED; + $predefined[] = E_USER_DEPRECATED; + } + $predefined_values = array('debug', 'info', 'warning', 'warning', 'error', 'fatal',); + + // step 1 + foreach ($predefined as &$key) { + $this->assertContains($client->translateSeverity($key), $predefined_values); + } + $this->assertEquals('error', $client->translateSeverity(123456)); + // step 2 + $client->registerSeverityMap(array()); + $this->assertMixedValueAndArray(array(), $reflection->getValue($client)); + foreach ($predefined as &$key) { + $this->assertContains($client->translateSeverity($key), $predefined_values); + } + $this->assertEquals('error', $client->translateSeverity(123456)); + $this->assertEquals('error', $client->translateSeverity(123456)); + // step 3 + $client->registerSeverityMap(array(123456 => 'foo',)); + $this->assertMixedValueAndArray(array(123456 => 'foo'), $reflection->getValue($client)); + foreach ($predefined as &$key) { + $this->assertContains($client->translateSeverity($key), $predefined_values); + } + $this->assertEquals('foo', $client->translateSeverity(123456)); + $this->assertEquals('error', $client->translateSeverity(123457)); + // step 4 + $client->registerSeverityMap(array(E_USER_ERROR => 'bar',)); + $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); + $this->assertEquals('error', $client->translateSeverity(123456)); + $this->assertEquals('error', $client->translateSeverity(123457)); + // step 5 + $client->registerSeverityMap(array(E_USER_ERROR => 'bar', 123456 => 'foo',)); + $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); + $this->assertEquals('foo', $client->translateSeverity(123456)); + $this->assertEquals('error', $client->translateSeverity(123457)); + } + + /** + * @covers Raven_Client::getUserAgent + */ + function testGetUserAgent() + { + $this->assertRegExp('|^[0-9a-z./_-]+$|i', Raven_Client::getUserAgent()); + } + + function testCaptureExceptionWithLogger() + { + $client = new Dummy_Raven_Client(); + $client->captureException(new Exception(), null, 'foobar'); + + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals('foobar', $event['logger']); + } + + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::send + * @covers Raven_Client::send_remote + * @covers Raven_Client::send_http + */ + function testCurl_method() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $client->captureMessage('foobar'); + $this->assertTrue($client->_send_http_synchronous); + $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + + // step 2 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'exec', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $client->captureMessage('foobar'); + $this->assertFalse($client->_send_http_synchronous); + $this->assertTrue($client->_send_http_asynchronous_curl_exec_called); + } + + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::send + * @covers Raven_Client::send_remote + * @covers Raven_Client::send_http + */ + function testCurl_method_async() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'async', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $object = $client->get_curl_handler(); + $this->assertInternalType('object', $object); + $this->assertEquals('Raven_CurlHandler', get_class($object)); + + $reflection = new ReflectionProperty('Raven_CurlHandler', 'options'); + $reflection->setAccessible(true); + $this->assertEquals($client->get_curl_options(), $reflection->getValue($object)); + + // step 2 + $ch = new Dummy_Raven_CurlHandler(); + $client->set_curl_handler($ch); + $client->captureMessage('foobar'); + $this->assertFalse($client->_send_http_synchronous); + $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + $this->assertTrue($ch->_enqueue_called); + } + + /** + * @backupGlobals + * @covers Raven_Client::__construct + */ + function testConstructWithServerDSN() + { + $_SERVER['SENTRY_DSN'] = 'http://public:secret@example.com/1'; + $client = new Dummy_Raven_Client(); + $this->assertEquals($client->project, 1); + $this->assertEquals($client->server, 'http://example.com/api/1/store/'); + $this->assertEquals($client->public_key, 'public'); + $this->assertEquals($client->secret_key, 'secret'); + } + + /** + * @backupGlobals + * @covers Raven_Client::_server_variable + */ + function test_server_variable() + { + $method = new ReflectionMethod('Raven_Client', '_server_variable'); + $method->setAccessible(true); + foreach ($_SERVER as $key => $value) { + $actual = $method->invoke(null, $key); + $this->assertNotNull($actual); + $this->assertEquals($value, $actual); + } + foreach (array('foo', 'bar', 'foobar', '123456', 'SomeLongNonExistedKey') as $key => $value) { + if (!isset($_SERVER[$key])) { + $actual = $method->invoke(null, $key); + $this->assertNotNull($actual); + $this->assertEquals('', $actual); + } + unset($_SERVER[$key]); + $actual = $method->invoke(null, $key); + $this->assertNotNull($actual); + $this->assertEquals('', $actual); + } + } + + function testEncode() + { + $client = new Dummy_Raven_Client(); + $data_broken = array(); + for ($i = 0; $i < 1024; $i++) { + $data_broken = array($data_broken); + } + $value = $client->encode($data_broken); + $this->assertFalse($value); + unset($data_broken); + + $data = array('some' => (object)array('value' => 'data'), 'foo' => array('bar', null, 123), false); + $json_stringify = Raven_Compat::json_encode($data); + $value = $client->encode($data); + $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value); + $decoded = base64_decode($value); + if (function_exists("gzcompress")) { + $decoded = gzuncompress($decoded); + } + + $this->assertEquals($json_stringify, $decoded); + } + + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::registerDefaultBreadcrumbHandlers + */ + function testRegisterDefaultBreadcrumbHandlers() + { + $previous = set_error_handler(array($this, 'stabClosureErrorHandler'), E_USER_NOTICE); + new Raven_Client(null, array()); + $this->_closure_called = false; + trigger_error('foobar', E_USER_NOTICE); + $u = $this->_closure_called; + $debug_backtrace = $this->_debug_backtrace; + set_error_handler($previous, E_ALL); + $this->assertTrue($u); + $this->assertEquals('Raven_Breadcrumbs_ErrorHandler', $debug_backtrace[2]['class']); + } + + private $_closure_called = false; + + function stabClosureVoid() + { + $this->_closure_called = true; + } + + function stabClosureNull() + { + $this->_closure_called = true; + + return null; + } + + function stabClosureFalse() + { + $this->_closure_called = true; + + return false; + } + + private $_debug_backtrace = array(); + + function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $context = array()) + { + $this->_closure_called = true; + $this->_debug_backtrace = debug_backtrace(); + + return true; + } + + /** + * @covers Raven_Client::onShutdown + * @covers Raven_Client::sendUnsentErrors + */ + function testOnShutdown() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $this->assertEquals(0, count($client->_pending_events)); + $client->_pending_events[] = array('foo' => 'bar'); + $client->sendUnsentErrors(); + $this->assertTrue($client->_send_http_synchronous); + $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + $this->assertEquals(0, count($client->_pending_events)); + + // step 2 + $client->_send_http_synchronous = false; + $client->_send_http_asynchronous_curl_exec_called = false; + + $client->store_errors_for_bulk_send = true; + $client->captureMessage('foobar'); + $this->assertEquals(1, count($client->_pending_events)); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + $client->_send_http_synchronous = false; + $client->_send_http_asynchronous_curl_exec_called = false; + + // step 3 + $client->onShutdown(); + $this->assertTrue($client->_send_http_synchronous); + $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + $this->assertEquals(0, count($client->_pending_events)); + + // step 1 + $client = null; + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'async', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $ch = new Dummy_Raven_CurlHandler(); + $client->set_curl_handler($ch); + $client->captureMessage('foobar'); + $client->onShutdown(); + $client = null; + $this->assertTrue($ch->_join_called); + } + + /** + * @covers Raven_Client::send + */ + function testNonWorkingSendSendCallback() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $this->_closure_called = false; + $client->setSendCallback(array($this, 'stabClosureNull')); + $this->assertFalse($this->_closure_called); + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertTrue($this->_closure_called); + $this->assertTrue($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + // step 2 + $this->_closure_called = false; + $client->_send_http_synchronous = false; + $client->_send_http_asynchronous_curl_exec_called = false; + $client->setSendCallback(array($this, 'stabClosureFalse')); + $this->assertFalse($this->_closure_called); + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertTrue($this->_closure_called); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + } + + /** + * @covers Raven_Client::send + */ + function testNonWorkingSendDSNEmpty() + { + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $client->server = null; + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + } + + /** + * @covers Raven_Client::send + */ + function testNonWorkingSendSetTransport() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $this->_closure_called = false; + $client->setTransport(array($this, 'stabClosureNull')); + $this->assertFalse($this->_closure_called); + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertTrue($this->_closure_called); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + // step 2 + $this->_closure_called = false; + $client->setSendCallback(array($this, 'stabClosureFalse')); + $this->assertFalse($this->_closure_called); + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertTrue($this->_closure_called); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + } } diff --git a/test/Raven/Tests/IntegrationTest.php b/test/Raven/Tests/IntegrationTest.php index 9625ad023..be5f67b75 100644 --- a/test/Raven/Tests/IntegrationTest.php +++ b/test/Raven/Tests/IntegrationTest.php @@ -24,7 +24,7 @@ public function send(&$data) } $this->__sent_events[] = $data; } - public function is_http_request() + public static function is_http_request() { return true; } From 955a05640ce6fe8bf7112ec4d6beb53d21f7414e Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 13:35:06 +0300 Subject: [PATCH 0240/1161] Code Coverage for Raven_Client --- test/Raven/Tests/ClientTest.php | 284 ++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index f226c415d..5c5069340 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -116,6 +116,44 @@ function set_curl_handler(Raven_CurlHandler $value) } } +class Dummy_Raven_Client_No_Http extends Dummy_Raven_Client +{ + /** + * @return bool + */ + public static function is_http_request() + { + return false; + } +} + +class Dummy_Raven_Client_With_Sync_Override extends Raven_Client +{ + private static $_test_data = null; + + static function get_test_data() + { + if (is_null(self::$_test_data)) { + self::$_test_data = ''; + for ($i = 0; $i < 128; $i++) { + self::$_test_data .= chr(mt_rand(ord('a'), ord('z'))); + } + } + + return self::$_test_data; + } + + static function test_filename() + { + return sys_get_temp_dir().'/clientraven.tmp'; + } + + protected function buildCurlCommand($url, $data, $headers) + { + return 'echo '.escapeshellarg(self::get_test_data()).' > '.self::test_filename(); + } +} + class Dummy_Raven_CurlHandler extends Raven_CurlHandler { var $_set_url; @@ -147,6 +185,14 @@ function join($timeout = null) class Raven_Tests_ClientTest extends PHPUnit_Framework_TestCase { + function tearDown() + { + parent::tearDown(); + if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { + unlink(Dummy_Raven_Client_With_Sync_Override::test_filename()); + } + } + private function create_exception() { try { @@ -1142,6 +1188,64 @@ public function testSanitizeUser() ))); } + /** + * @covers Raven_Client::sanitize + */ + public function testSanitizeRequest() + { + $client = new Dummy_Raven_Client(); + $data = array('request' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, array(2), 3 + ), + ), + )); + $client->sanitize($data); + + $this->assertEquals($data, array('request' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, 'Array of length 1', 3 + ), + ), + ))); + } + + /** + * @covers Raven_Client::sanitize + */ + public function testSanitizeContexts() + { + $client = new Dummy_Raven_Client(); + $data = array('contexts' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, array( + 'foo' => 'bar', + 'level4' => array(array('level5', 'level5 a'), 2), + ), 3 + ), + ), + )); + $client->sanitize($data); + + $this->assertEquals($data, array('contexts' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, array( + 'foo' => 'bar', + 'level4' => array('Array of length 2', 2), + ), 3 + ), + ), + ))); + } + /** * @covers Raven_Client::buildCurlCommand */ @@ -1707,6 +1811,7 @@ function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $contex /** * @covers Raven_Client::onShutdown * @covers Raven_Client::sendUnsentErrors + * @covers Raven_Client::capture */ function testOnShutdown() { @@ -1833,4 +1938,183 @@ function testNonWorkingSendSetTransport() $this->assertTrue($this->_closure_called); $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); } + + /** + * @covers Raven_Client::get_user_data + */ + function testGet_user_data() + { + // step 1 + $client = new Dummy_Raven_Client(); + $output = $client->get_user_data(); + $this->assertInternalType('array', $output); + $this->assertArrayHasKey('user', $output); + $this->assertArrayHasKey('id', $output['user']); + $session_old = $_SESSION; + + // step 2 + $session_id = session_id(); + session_write_close(); + session_id(''); + $output = $client->get_user_data(); + $this->assertInternalType('array', $output); + $this->assertEquals(0, count($output)); + + // step 3 + session_id($session_id); + @session_start(array('use_cookies' => false,)); + $_SESSION = array('foo' => 'bar'); + $output = $client->get_user_data(); + $this->assertInternalType('array', $output); + $this->assertArrayHasKey('user', $output); + $this->assertArrayHasKey('id', $output['user']); + $this->assertArrayHasKey('data', $output['user']); + $this->assertArrayHasKey('foo', $output['user']['data']); + $this->assertEquals('bar', $output['user']['data']['foo']); + $_SESSION = $session_old; + } + + /** + * @covers Raven_Client::capture + * @covers Raven_Client::setRelease + * @covers Raven_Client::setEnvironment + */ + public function testCaptureLevel() + { + foreach ([Raven_Client::MESSAGE_LIMIT * 3, 100] as $length) { + $message = ''; + for ($i = 0; $i < $length; $i++) { + $message .= chr($i % 256); + } + $client = new Dummy_Raven_Client(); + $client->capture(array('message' => $message,)); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + + $this->assertEquals('error', $event['level']); + $this->assertEquals(substr($message, 0, min(Raven_Client::MESSAGE_LIMIT, $length)), $event['message']); + $this->assertArrayNotHasKey('release', $event); + $this->assertArrayNotHasKey('environment', $event); + } + + $client = new Dummy_Raven_Client(); + $client->capture(array('message' => 'foobar',)); + $events = $client->getSentEvents(); + $event = array_pop($events); + $input = $client->get_http_data(); + $this->assertEquals($input['request'], $event['request']); + $this->assertArrayNotHasKey('release', $event); + $this->assertArrayNotHasKey('environment', $event); + + $client = new Dummy_Raven_Client(); + $client->capture(array('message' => 'foobar', 'request' => array('foo' => 'bar'),)); + $events = $client->getSentEvents(); + $event = array_pop($events); + $this->assertEquals(array('foo' => 'bar'), $event['request']); + $this->assertArrayNotHasKey('release', $event); + $this->assertArrayNotHasKey('environment', $event); + + foreach ([false, true] as $u1) { + foreach ([false, true] as $u2) { + $client = new Dummy_Raven_Client(); + if ($u1) { + $client->setRelease('foo'); + } + if ($u2) { + $client->setEnvironment('bar'); + } + $client->capture(array('message' => 'foobar',)); + $events = $client->getSentEvents(); + $event = array_pop($events); + if ($u1) { + $this->assertEquals('foo', $event['release']); + } else { + $this->assertArrayNotHasKey('release', $event); + } + if ($u2) { + $this->assertEquals('bar', $event['environment']); + } else { + $this->assertArrayNotHasKey('environment', $event); + } + } + } + } + + /** + * @covers Raven_Client::capture + */ + public function testCaptureNoUserAndRequest() + { + $client = new Dummy_Raven_Client_No_Http(null, array( + 'install_default_breadcrumb_handlers' => false, + )); + $session_id = session_id(); + session_write_close(); + session_id(''); + $client->capture(array('user' => '', 'request' => '')); + $events = $client->getSentEvents(); + $event = array_pop($events); + $this->assertArrayNotHasKey('user', $event); + $this->assertArrayNotHasKey('request', $event); + + // step 3 + session_id($session_id); + @session_start(array('use_cookies' => false,)); + } + + /** + * @covers Raven_Client::capture + */ + public function testCaptureNonEmptyBreadcrumb() + { + $client = new Dummy_Raven_Client(); + $ts1 = microtime(true); + $client->breadcrumbs->record(array('foo' => 'bar')); + $client->breadcrumbs->record(array('honey' => 'clover')); + $client->capture(array()); + $events = $client->getSentEvents(); + $event = array_pop($events); + foreach ($event['breadcrumbs'] as &$crumb) { + $this->assertGreaterThanOrEqual($ts1, $crumb['timestamp']); + unset($crumb['timestamp']); + } + $this->assertEquals(array( + array('foo' => 'bar'), + array('honey' => 'clover'), + ), $event['breadcrumbs']); + } + + + /** + * @covers Raven_Client::capture + */ + public function testCaptureAutoLogStacks() + { + $client = new Dummy_Raven_Client(); + $client->capture(array('auto_log_stacks' => true), true); + $events = $client->getSentEvents(); + $event = array_pop($events); + $this->assertArrayHasKey('stacktrace', $event); + $this->assertInternalType('array', $event['stacktrace']['frames']); + } + + /** + * @covers Raven_Client::send_http_asynchronous_curl_exec + */ + public function testSend_http_asynchronous_curl_exec(){ + $client = new Dummy_Raven_Client_With_Sync_Override( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'exec', + 'install_default_breadcrumb_handlers' => false, + ) + ); + if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { + unlink(Dummy_Raven_Client_With_Sync_Override::test_filename()); + } + $client->captureMessage('foobar'); + $test_data = Dummy_Raven_Client_With_Sync_Override::get_test_data(); + $this->assertStringEqualsFile(Dummy_Raven_Client_With_Sync_Override::test_filename(), $test_data."\n"); + } + } From 15b97153e8bcd88448b4e4f0882261133d2a6078 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 13:54:56 +0300 Subject: [PATCH 0241/1161] Static call: second pack --- lib/Raven/Breadcrumbs.php | 11 +++++++++++ lib/Raven/Client.php | 8 ++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php index d1a994654..37d3eca2c 100644 --- a/lib/Raven/Breadcrumbs.php +++ b/lib/Raven/Breadcrumbs.php @@ -16,6 +16,14 @@ class Raven_Breadcrumbs { + var $count; + var $pos; + var $size; + /** + * @var array[] + */ + var $buffer; + public function __construct($size = 100) { $this->count = 0; @@ -34,6 +42,9 @@ public function record($crumb) $this->count++; } + /** + * @return array[] + */ public function fetch() { $results = array(); diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index be55030c5..21455d958 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -177,8 +177,10 @@ public function __construct($options_or_dsn = null, $options = array()) } $this->transaction = new Raven_TransactionStack(); - if ($this->is_http_request() && isset($_SERVER['PATH_INFO'])) { + if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) { + // @codeCoverageIgnoreStart $this->transaction->push($_SERVER['PATH_INFO']); + // @codeCoverageIgnoreEnd } if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { @@ -726,7 +728,7 @@ public function capture($data, $stack = null, $vars = null) $data = array_merge($this->get_default_data(), $data); - if ($this->is_http_request()) { + if (static::is_http_request()) { $data = array_merge($this->get_http_data(), $data); } @@ -776,7 +778,9 @@ public function capture($data, $stack = null, $vars = null) if (!empty($stack)) { // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) if (!class_exists('Raven_Stacktrace')) { + // @codeCoverageIgnoreStart spl_autoload_call('Raven_Stacktrace'); + // @codeCoverageIgnoreEnd } if (!isset($data['stacktrace']) && !isset($data['exception'])) { From be54caa394074d9182c0fefbfd016038a454913c Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 14:29:20 +0300 Subject: [PATCH 0242/1161] Code coverage for Raven_Compat and fix for code coverage of Raven_TransactionStack --- lib/Raven/Compat.php | 6 ++++++ lib/Raven/TransactionStack.php | 2 ++ test/Raven/Tests/CompatTest.php | 23 +++++++++++++++++++++++ test/Raven/Tests/TransactionStackTest.php | 6 ++++++ 4 files changed, 37 insertions(+) diff --git a/lib/Raven/Compat.php b/lib/Raven/Compat.php index 87269cfec..940a5eb9e 100644 --- a/lib/Raven/Compat.php +++ b/lib/Raven/Compat.php @@ -36,6 +36,12 @@ public static function hash_hmac($algo, $data, $key, $raw_output = false) /** * Implementation from 'KC Cloyd'. + * + * @param string $algo Name of selected hashing algorithm + * @param string $data Message to be hashed + * @param string $key Shared secret key used for generating the HMAC variant of the message digest + * @param bool $raw_output Must be binary + * @return string * @doc http://php.net/manual/en/function.hash-hmac.php */ public static function _hash_hmac($algo, $data, $key, $raw_output = false) diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index 75ebc1635..9446809b1 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -48,5 +48,7 @@ public function pop($context = null) return $context; } } + // @codeCoverageIgnoreStart } + // @codeCoverageIgnoreEnd } diff --git a/test/Raven/Tests/CompatTest.php b/test/Raven/Tests/CompatTest.php index 93f7c411c..93711d532 100644 --- a/test/Raven/Tests/CompatTest.php +++ b/test/Raven/Tests/CompatTest.php @@ -24,6 +24,26 @@ public function test_hash_hmac() $result = Raven_Compat::_hash_hmac('sha1', 'foo', 'bar'); $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); + + $long_key = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; + $result = Raven_Compat::_hash_hmac('md5', 'data', $long_key); + $this->assertEquals('951038f9ab8a10c929ab6dbc5f927207', $result); + + $result = Raven_Compat::_hash_hmac('sha1', 'data', $long_key); + $this->assertEquals('cbf0d1ca10d211da2bc15cb3b579ecfebf3056d2', $result); + + $result = Raven_Compat::_hash_hmac('md5', 'foobar', $long_key); + $this->assertEquals('5490f3cddeb9665bce3239cbc4c15e2c', $result); + + $result = Raven_Compat::_hash_hmac('sha1', 'foobar', $long_key); + $this->assertEquals('5729f50ff2fbb8f8bf81d7a86f69a89f7574697c', $result); + + + $result = Raven_Compat::_hash_hmac('md5', 'foo', $long_key); + $this->assertEquals('ab193328035cbd3a48dea9d64ba92736', $result); + + $result = Raven_Compat::_hash_hmac('sha1', 'foo', $long_key); + $this->assertEquals('8f883d0755115314930968496573f27735eb0c41', $result); } public function test_json_encode() @@ -42,5 +62,8 @@ public function test_json_encode() $result = Raven_Compat::_json_encode(array(array())); $this->assertEquals('[[]]', $result); + + $result = Raven_Compat::_json_encode(array(null, false, true, 1.5)); + $this->assertEquals('[null,false,true,1.5]', $result); } } diff --git a/test/Raven/Tests/TransactionStackTest.php b/test/Raven/Tests/TransactionStackTest.php index 838626c29..199418072 100644 --- a/test/Raven/Tests/TransactionStackTest.php +++ b/test/Raven/Tests/TransactionStackTest.php @@ -16,6 +16,8 @@ public function testSimple() { $stack = new Raven_TransactionStack(); $stack->push('hello'); + /** @noinspection PhpVoidFunctionResultUsedInspection */ + /** @noinspection PhpUnusedLocalVariableInspection */ $foo = $stack->push('foo'); $stack->push('bar'); $stack->push('world'); @@ -25,5 +27,9 @@ public function testSimple() $this->assertEquals($stack->peek(), 'hello'); $this->assertEquals($stack->pop(), 'hello'); $this->assertEquals($stack->peek(), null); + + $stack->clear(); + $this->assertInternalType('array', $stack->stack); + $this->assertEquals(0, count($stack->stack)); } } From 92d6cb10bc4c2d5962f3f82989f908dade1c2fa8 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 15:22:01 +0300 Subject: [PATCH 0243/1161] Code coverage for Raven_ReprSerializer and Raven_Serializer --- lib/Raven/Serializer.php | 2 + test/Raven/Tests/ReprSerializerTest.php | 43 +++++++++++++++-- test/Raven/Tests/SerializerTest.php | 62 +++++++++++++++++++++---- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 77cd4d20f..3889d0bd6 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -122,6 +122,7 @@ protected function serializeValue($value) /** * @return string + * @codeCoverageIgnore */ public function getMbDetectOrder() { @@ -132,6 +133,7 @@ public function getMbDetectOrder() * @param string $mb_detect_order * * @return Raven_Serializer + * @codeCoverageIgnore */ public function setMbDetectOrder($mb_detect_order) { diff --git a/test/Raven/Tests/ReprSerializerTest.php b/test/Raven/Tests/ReprSerializerTest.php index dc8ed525e..8c8c17633 100644 --- a/test/Raven/Tests/ReprSerializerTest.php +++ b/test/Raven/Tests/ReprSerializerTest.php @@ -32,7 +32,7 @@ public function testIntsAreInts() $serializer = new Raven_ReprSerializer(); $input = 1; $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); + $this->assertInternalType('string', $result); $this->assertEquals(1, $result); } @@ -41,7 +41,7 @@ public function testFloats() $serializer = new Raven_ReprSerializer(); $input = 1.5; $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); + $this->assertInternalType('string', $result); $this->assertEquals('1.5', $result); } @@ -50,12 +50,11 @@ public function testBooleans() $serializer = new Raven_ReprSerializer(); $input = true; $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); $this->assertEquals('true', $result); $input = false; $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); + $this->assertInternalType('string', $result); $this->assertEquals('false', $result); } @@ -64,7 +63,7 @@ public function testNull() $serializer = new Raven_ReprSerializer(); $input = null; $result = $serializer->serialize($input); - $this->assertTrue(is_string($result)); + $this->assertInternalType('string', $result); $this->assertEquals('null', $result); } @@ -76,4 +75,38 @@ public function testRecursionMaxDepth() $result = $serializer->serialize($input, 3); $this->assertEquals(array(array(array('Array of length 1'))), $result); } + + /** + * @covers Raven_ReprSerializer::serializeValue + */ + public function testSerializeValueResource() + { + $serializer = new Raven_ReprSerializer(); + $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); + $fo = fopen($filename, 'wb'); + + $result = $serializer->serialize($fo); + $this->assertInternalType('string', $result); + $this->assertEquals('Resource stream', $result); + } + + /** + * @covers Raven_ReprSerializer::serializeValue + */ + public function testSerializeRoundedFloat() + { + $serializer = new Raven_ReprSerializer(); + + $result = $serializer->serialize((double)1); + $this->assertInternalType('string', $result); + $this->assertEquals('1.0', $result); + + $result = $serializer->serialize((double)floor(5 / 2)); + $this->assertInternalType('string', $result); + $this->assertEquals('2.0', $result); + + $result = $serializer->serialize((double)floor(12345.678901234)); + $this->assertInternalType('string', $result); + $this->assertEquals('12345.0', $result); + } } diff --git a/test/Raven/Tests/SerializerTest.php b/test/Raven/Tests/SerializerTest.php index 006c6448e..88f40811e 100644 --- a/test/Raven/Tests/SerializerTest.php +++ b/test/Raven/Tests/SerializerTest.php @@ -46,7 +46,7 @@ public function testIntsAreInts() $serializer = new Raven_Serializer(); $input = 1; $result = $serializer->serialize($input); - $this->assertTrue(is_integer($result)); + $this->assertInternalType('integer', $result); $this->assertEquals(1, $result); } @@ -55,7 +55,7 @@ public function testFloats() $serializer = new Raven_Serializer(); $input = 1.5; $result = $serializer->serialize($input); - $this->assertTrue(is_float($result)); + $this->assertInternalType('double', $result); $this->assertEquals(1.5, $result); } @@ -64,13 +64,11 @@ public function testBooleans() $serializer = new Raven_Serializer(); $input = true; $result = $serializer->serialize($input); - $this->assertTrue(is_bool($result)); - $this->assertEquals(true, $result); + $this->assertTrue($result); $input = false; $result = $serializer->serialize($input); - $this->assertTrue(is_bool($result)); - $this->assertEquals(false, $result); + $this->assertFalse($result); } public function testNull() @@ -78,8 +76,7 @@ public function testNull() $serializer = new Raven_Serializer(); $input = null; $result = $serializer->serialize($input); - $this->assertTrue(is_null($result)); - $this->assertEquals(null, $result); + $this->assertNull($result); } public function testRecursionMaxDepth() @@ -98,4 +95,53 @@ public function testObjectInArray() $result = $serializer->serialize($input); $this->assertEquals(array('foo' => 'Object Raven_Serializer'), $result); } + + /** + * @covers Raven_Serializer::serializeString + */ + public function testBrokenEncoding() + { + $serializer = new Raven_Serializer(); + foreach (['7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90'] as $key) { + $input = pack('H*', $key); + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + if (function_exists('mb_detect_encoding')) { + $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); + } + } + } + + /** + * @covers Raven_Serializer::serializeString + */ + public function testLongString() + { + $serializer = new Raven_Serializer(); + for ($i = 0; $i < 100; $i++) { + foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { + $input = ''; + for ($i = 0; $i < $length; $i++) { + $input .= chr(mt_rand(0, 255)); + } + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(1024, strlen($result)); + } + } + } + + /** + * @covers Raven_Serializer::serializeValue + */ + public function testSerializeValueResource() + { + $serializer = new Raven_Serializer(); + $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); + $fo = fopen($filename, 'wb'); + + $result = $serializer->serialize($fo); + $this->assertInternalType('string', $result); + $this->assertEquals('Resource stream', $result); + } } From dfe3dc7bc4200246ca983857b33655adee60f962 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 15:52:43 +0300 Subject: [PATCH 0244/1161] Raven_Tests_ClientTest: expected <-> actual swap --- test/Raven/Tests/ClientTest.php | 218 ++++++++++++++++---------------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 5c5069340..1df354017 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -219,40 +219,40 @@ public function testParseDSNHttp() { $result = Raven_Client::ParseDSN('http://public:secret@example.com/1'); - $this->assertEquals($result['project'], 1); - $this->assertEquals($result['server'], 'http://example.com/api/1/store/'); - $this->assertEquals($result['public_key'], 'public'); - $this->assertEquals($result['secret_key'], 'secret'); + $this->assertEquals(1, $result['project']); + $this->assertEquals('http://example.com/api/1/store/', $result['server']); + $this->assertEquals('public', $result['public_key']); + $this->assertEquals('secret', $result['secret_key']); } public function testParseDSNHttps() { $result = Raven_Client::ParseDSN('https://public:secret@example.com/1'); - $this->assertEquals($result['project'], 1); - $this->assertEquals($result['server'], 'https://example.com/api/1/store/'); - $this->assertEquals($result['public_key'], 'public'); - $this->assertEquals($result['secret_key'], 'secret'); + $this->assertEquals(1, $result['project']); + $this->assertEquals('https://example.com/api/1/store/', $result['server']); + $this->assertEquals('public', $result['public_key']); + $this->assertEquals('secret', $result['secret_key']); } public function testParseDSNPath() { $result = Raven_Client::ParseDSN('http://public:secret@example.com/app/1'); - $this->assertEquals($result['project'], 1); - $this->assertEquals($result['server'], 'http://example.com/app/api/1/store/'); - $this->assertEquals($result['public_key'], 'public'); - $this->assertEquals($result['secret_key'], 'secret'); + $this->assertEquals(1, $result['project']); + $this->assertEquals('http://example.com/app/api/1/store/', $result['server']); + $this->assertEquals('public', $result['public_key']); + $this->assertEquals('secret', $result['secret_key']); } public function testParseDSNPort() { $result = Raven_Client::ParseDSN('http://public:secret@example.com:9000/app/1'); - $this->assertEquals($result['project'], 1); - $this->assertEquals($result['server'], 'http://example.com:9000/app/api/1/store/'); - $this->assertEquals($result['public_key'], 'public'); - $this->assertEquals($result['secret_key'], 'secret'); + $this->assertEquals(1, $result['project']); + $this->assertEquals('http://example.com:9000/app/api/1/store/', $result['server']); + $this->assertEquals('public', $result['public_key']); + $this->assertEquals('secret', $result['secret_key']); } public function testParseDSNInvalidScheme() @@ -307,10 +307,10 @@ public function testDsnFirstArgument() { $client = new Dummy_Raven_Client('http://public:secret@example.com/1'); - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); } /** @@ -322,11 +322,11 @@ public function testDsnFirstArgumentWithOptions() 'site' => 'foo', )); - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); - $this->assertEquals($client->site, 'foo'); + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); + $this->assertEquals('foo', $client->site); } /** @@ -339,7 +339,7 @@ public function testOptionsFirstArgument() 'project' => 1, )); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); + $this->assertEquals('http://example.com/api/1/store/', $client->server); } @@ -352,10 +352,10 @@ public function testDsnInOptionsFirstArg() 'dsn' => 'http://public:secret@example.com/1', )); - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); } /** @@ -367,10 +367,10 @@ public function testDsnInOptionsSecondArg() 'dsn' => 'http://public:secret@example.com/1', )); - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); } /** @@ -385,8 +385,8 @@ public function testOptionsFirstArgumentWithOptions() 'site' => 'foo', )); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->site, 'foo'); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('foo', $client->site); } /** @@ -398,9 +398,9 @@ public function testOptionsExtraData() $client->captureMessage('Test Message %s', array('foo')); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['extra']['foo'], 'bar'); + $this->assertEquals('bar', $event['extra']['foo']); } /** @@ -412,9 +412,9 @@ public function testOptionsExtraDataWithNull() $client->captureMessage('Test Message %s', array('foo'), null); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['extra']['foo'], 'bar'); + $this->assertEquals('bar', $event['extra']['foo']); } /** @@ -426,7 +426,7 @@ public function testEmptyExtraData() $client->captureMessage('Test Message %s', array('foo')); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); $this->assertEquals(array_key_exists('extra', $event), false); } @@ -440,9 +440,9 @@ public function testCaptureMessageDoesHandleUninterpolatedMessage() $client->captureMessage('Test Message %s'); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['message'], 'Test Message %s'); + $this->assertEquals('Test Message %s', $event['message']); } /** @@ -454,9 +454,9 @@ public function testCaptureMessageDoesHandleInterpolatedMessage() $client->captureMessage('Test Message %s', array('foo')); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['message'], 'Test Message foo'); + $this->assertEquals('Test Message foo', $event['message']); } /** @@ -471,10 +471,10 @@ public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() $client->captureMessage('Test Message %s', array('foo')); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['release'], 20160909144742); - $this->assertEquals($event['message'], 'Test Message foo'); + $this->assertEquals(20160909144742, $event['release']); + $this->assertEquals('Test Message foo', $event['message']); } /** @@ -486,13 +486,13 @@ public function testCaptureMessageSetsInterface() $client->captureMessage('Test Message %s', array('foo')); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['sentry.interfaces.Message'], array( + $this->assertEquals(array( 'message' => 'Test Message %s', 'params' => array('foo'), 'formatted' => 'Test Message foo', - )); + ), $event['sentry.interfaces.Message']); $this->assertEquals('Test Message foo', $event['message']); } @@ -508,10 +508,10 @@ public function testCaptureMessageHandlesOptionsAsThirdArg() 'extra' => array('foo' => 'bar') )); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); - $this->assertEquals($event['extra']['foo'], 'bar'); + $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); + $this->assertEquals('bar', $event['extra']['foo']); $this->assertEquals('Test Message foo', $event['message']); } @@ -524,9 +524,9 @@ public function testCaptureMessageHandlesLevelAsThirdArg() $client->captureMessage('Test Message %s', array('foo'), Dummy_Raven_Client::WARNING); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); + $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); $this->assertEquals('Test Message foo', $event['message']); } @@ -541,21 +541,21 @@ public function testCaptureExceptionSetsInterfaces() $client->captureException($ex); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); $exc = $event['exception']; - $this->assertEquals(count($exc['values']), 1); - $this->assertEquals($exc['values'][0]['value'], 'Foo bar'); - $this->assertEquals($exc['values'][0]['type'], 'Exception'); + $this->assertEquals(1, count($exc['values'])); + $this->assertEquals('Foo bar', $exc['values'][0]['value']); + $this->assertEquals('Exception', $exc['values'][0]['type']); $this->assertFalse(empty($exc['values'][0]['stacktrace']['frames'])); $frames = $exc['values'][0]['stacktrace']['frames']; $frame = $frames[count($frames) - 1]; $this->assertTrue($frame['lineno'] > 0); - $this->assertEquals($frame['function'], 'create_exception'); + $this->assertEquals('create_exception', $frame['function']); $this->assertFalse(isset($frame['vars'])); - $this->assertEquals($frame['context_line'], ' throw new Exception(\'Foo bar\');'); + $this->assertEquals(' throw new Exception(\'Foo bar\');', $frame['context_line']); $this->assertFalse(empty($frame['pre_context'])); $this->assertFalse(empty($frame['post_context'])); } @@ -575,13 +575,13 @@ public function testCaptureExceptionChainedException() $client->captureException($ex); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); $exc = $event['exception']; - $this->assertEquals(count($exc['values']), 2); - $this->assertEquals($exc['values'][0]['value'], 'Foo bar'); - $this->assertEquals($exc['values'][1]['value'], 'Child exc'); + $this->assertEquals(2, count($exc['values'])); + $this->assertEquals('Foo bar', $exc['values'][0]['value']); + $this->assertEquals('Child exc', $exc['values'][1]['value']); } /** @@ -604,13 +604,13 @@ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() $events = $client->getSentEvents(); $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::ERROR); + $this->assertEquals(Dummy_Raven_Client::ERROR, $event['level']); $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::INFO); + $this->assertEquals(Dummy_Raven_Client::INFO, $event['level']); $event = array_pop($events); - $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); + $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); } /** @@ -622,9 +622,9 @@ public function testCaptureExceptionHandlesOptionsAsSecondArg() $ex = $this->create_exception(); $client->captureException($ex, array('culprit' => 'test')); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['culprit'], 'test'); + $this->assertEquals('test', $event['culprit']); } /** @@ -638,7 +638,7 @@ public function testCaptureExceptionHandlesExcludeOption() $ex = $this->create_exception(); $client->captureException($ex, 'test'); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 0); + $this->assertEquals(0, count($events)); } /** @@ -653,7 +653,7 @@ public function testCaptureExceptionInvalidUTF8() $client->captureException($ex); } $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); // if this fails to encode it returns false $message = $client->encode($events[0]); @@ -668,8 +668,8 @@ public function testDoesRegisterProcessors() $client = new Dummy_Raven_Client(array( 'processors' => array('Raven_SanitizeDataProcessor'), )); - $this->assertEquals(count($client->processors), 1); - $this->assertTrue($client->processors[0] instanceof Raven_SanitizeDataProcessor); + $this->assertEquals(1, count($client->processors)); + $this->assertInstanceOf('Raven_SanitizeDataProcessor', $client->processors[0]); } public function testProcessDoesCallProcessors() @@ -697,7 +697,7 @@ public function testDefaultProcessorsAreUsed() $client = new Dummy_Raven_Client(); $defaults = Dummy_Raven_Client::getDefaultProcessors(); - $this->assertEquals(count($client->processors), count($defaults)); + $this->assertEquals(count($defaults), count($client->processors)); } /** @@ -971,7 +971,7 @@ public function testCaptureExceptionContainingLatin1() $events = $client->getSentEvents(); $event = array_pop($events); - $this->assertEquals($event['exception']['values'][0]['value'], $utf8String); + $this->assertEquals($utf8String, $event['exception']['values'][0]['value']); } @@ -1002,7 +1002,7 @@ public function testCaptureExceptionInLatin1File() } } - $this->assertEquals($found, true); + $this->assertTrue($found); } /** @@ -1020,7 +1020,7 @@ public function testCaptureLastError() $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals($event['exception']['values'][0]['value'], 'Undefined variable: undefined'); + $this->assertEquals('Undefined variable: undefined', $event['exception']['values'][0]['value']); } /** @@ -1030,7 +1030,7 @@ public function testGetLastEventID() { $client = new Dummy_Raven_Client(); $client->capture(array('message' => 'test', 'event_id' => 'abc')); - $this->assertEquals($client->getLastEventID(), 'abc'); + $this->assertEquals('abc', $client->getLastEventID()); } /** @@ -1048,7 +1048,7 @@ public function testCustomTransport() $events[] = $data; }); $client->capture(array('message' => 'test', 'event_id' => 'abc')); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); } /** @@ -1059,11 +1059,11 @@ public function testAppPathLinux() $client = new Dummy_Raven_Client(); $client->setAppPath('/foo/bar'); - $this->assertEquals($client->getAppPath(), '/foo/bar/'); + $this->assertEquals('/foo/bar/', $client->getAppPath()); $client->setAppPath('/foo/baz/'); - $this->assertEquals($client->getAppPath(), '/foo/baz/'); + $this->assertEquals('/foo/baz/', $client->getAppPath()); } /** @@ -1074,7 +1074,7 @@ public function testAppPathWindows() $client = new Dummy_Raven_Client(); $client->setAppPath('C:\\foo\\bar\\'); - $this->assertEquals($client->getAppPath(), 'C:\\foo\\bar\\'); + $this->assertEquals('C:\\foo\\bar\\', $client->getAppPath()); } /** @@ -1125,7 +1125,7 @@ public function testSendCallback() $client->captureMessage('test'); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); - $this->assertEquals(empty($events[0]['message']), true); + $this->assertTrue(empty($events[0]['message'])); } /** @@ -1144,14 +1144,14 @@ public function testSanitizeExtra() )); $client->sanitize($data); - $this->assertEquals($data, array('extra' => array( + $this->assertEquals(array('extra' => array( 'context' => array( 'line' => 1216, 'stack' => array( 1, 'Array of length 1', 3 ), ), - ))); + )), $data); } /** @@ -1166,10 +1166,10 @@ public function testSanitizeTags() )); $client->sanitize($data); - $this->assertEquals($data, array('tags' => array( + $this->assertEquals(array('tags' => array( 'foo' => 'bar', 'baz' => 'Array', - ))); + )), $data); } /** @@ -1183,9 +1183,9 @@ public function testSanitizeUser() )); $client->sanitize($data); - $this->assertEquals($data, array('user' => array( + $this->assertEquals(array('user' => array( 'email' => 'foo@example.com', - ))); + )), $data); } /** @@ -1204,14 +1204,14 @@ public function testSanitizeRequest() )); $client->sanitize($data); - $this->assertEquals($data, array('request' => array( + $this->assertEquals(array('request' => array( 'context' => array( 'line' => 1216, 'stack' => array( 1, 'Array of length 1', 3 ), ), - ))); + )), $data); } /** @@ -1233,7 +1233,7 @@ public function testSanitizeContexts() )); $client->sanitize($data); - $this->assertEquals($data, array('contexts' => array( + $this->assertEquals(array('contexts' => array( 'context' => array( 'line' => 1216, 'stack' => array( @@ -1243,7 +1243,7 @@ public function testSanitizeContexts() ), 3 ), ), - ))); + )), $data); } /** @@ -1254,17 +1254,17 @@ public function testBuildCurlCommandEscapesInput() $data = '{"foo": "\'; ls;"}'; $client = new Dummy_Raven_Client(); $result = $client->buildCurlCommand('http://foo.com', $data, array()); - $this->assertEquals($result, 'curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &'); + $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); $result = $client->buildCurlCommand('http://foo.com', $data, array('key' => 'value')); - $this->assertEquals($result, 'curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &'); + $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); $client->verify_ssl = false; $result = $client->buildCurlCommand('http://foo.com', $data, array()); - $this->assertEquals($result, 'curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &'); + $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); $result = $client->buildCurlCommand('http://foo.com', $data, array('key' => 'value')); - $this->assertEquals($result, 'curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &'); + $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); } /** @@ -1275,7 +1275,7 @@ public function testUserContextWithoutMerge() $client = new Dummy_Raven_Client(); $client->user_context(array('foo' => 'bar'), false); $client->user_context(array('baz' => 'bar'), false); - $this->assertEquals($client->context->user, array('baz' => 'bar')); + $this->assertEquals(array('baz' => 'bar'), $client->context->user); } /** @@ -1286,7 +1286,7 @@ public function testUserContextWithMerge() $client = new Dummy_Raven_Client(); $client->user_context(array('foo' => 'bar'), true); $client->user_context(array('baz' => 'bar'), true); - $this->assertEquals($client->context->user, array('foo' => 'bar', 'baz' => 'bar')); + $this->assertEquals(array('foo' => 'bar', 'baz' => 'bar'), $client->context->user); } /** @@ -1297,7 +1297,7 @@ public function testUserContextWithMergeAndNull() $client = new Dummy_Raven_Client(); $client->user_context(array('foo' => 'bar'), true); $client->user_context(null, true); - $this->assertEquals($client->context->user, array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $client->context->user); } /** @@ -1629,7 +1629,7 @@ function testCaptureExceptionWithLogger() $client->captureException(new Exception(), null, 'foobar'); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); $this->assertEquals('foobar', $event['logger']); } @@ -1686,7 +1686,7 @@ function testCurl_method_async() $reflection = new ReflectionProperty('Raven_CurlHandler', 'options'); $reflection->setAccessible(true); - $this->assertEquals($client->get_curl_options(), $reflection->getValue($object)); + $this->assertEquals($reflection->getValue($object), $client->get_curl_options()); // step 2 $ch = new Dummy_Raven_CurlHandler(); @@ -1705,10 +1705,10 @@ function testConstructWithServerDSN() { $_SERVER['SENTRY_DSN'] = 'http://public:secret@example.com/1'; $client = new Dummy_Raven_Client(); - $this->assertEquals($client->project, 1); - $this->assertEquals($client->server, 'http://example.com/api/1/store/'); - $this->assertEquals($client->public_key, 'public'); - $this->assertEquals($client->secret_key, 'secret'); + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); } /** @@ -1989,7 +1989,7 @@ public function testCaptureLevel() $client = new Dummy_Raven_Client(); $client->capture(array('message' => $message,)); $events = $client->getSentEvents(); - $this->assertEquals(count($events), 1); + $this->assertEquals(1, count($events)); $event = array_pop($events); $this->assertEquals('error', $event['level']); From 6697ddf64896d90f216417ed39219cdc40395991 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 16:27:30 +0300 Subject: [PATCH 0245/1161] Method and properties visibility Old style array initialize Code style --- lib/Raven/Breadcrumbs.php | 8 +- lib/Raven/Client.php | 60 ++++++------ lib/Raven/Context.php | 6 +- test/Raven/Tests/ClientTest.php | 144 ++++++++++++++-------------- test/Raven/Tests/SerializerTest.php | 6 +- 5 files changed, 112 insertions(+), 112 deletions(-) diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php index 37d3eca2c..12f39902e 100644 --- a/lib/Raven/Breadcrumbs.php +++ b/lib/Raven/Breadcrumbs.php @@ -16,13 +16,13 @@ class Raven_Breadcrumbs { - var $count; - var $pos; - var $size; + public $count; + public $pos; + public $size; /** * @var array[] */ - var $buffer; + public $buffer; public function __construct($size = 100) { diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c50a4e8fe..be361e670 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -66,46 +66,46 @@ class Raven_Client */ protected $transport; - var $logger; + public $logger; /** * @var string Full URL to Sentry */ - var $server; - var $secret_key; - var $public_key; - var $project; - var $auto_log_stacks; - var $name; - var $site; - var $tags; - var $release; - var $environment; - var $trace; - var $timeout; - var $message_limit; - var $exclude; - var $http_proxy; + public $server; + public $secret_key; + public $public_key; + public $project; + public $auto_log_stacks; + public $name; + public $site; + public $tags; + public $release; + public $environment; + public $trace; + public $timeout; + public $message_limit; + public $exclude; + public $http_proxy; protected $send_callback; - var $curl_method; - var $curl_path; - var $curl_ipv4; - var $ca_cert; - var $verify_ssl; - var $curl_ssl_version; - var $trust_x_forwarded_proto; - var $mb_detect_order; + public $curl_method; + public $curl_path; + public $curl_ipv4; + public $ca_cert; + public $verify_ssl; + public $curl_ssl_version; + public $trust_x_forwarded_proto; + public $mb_detect_order; /** * @var Raven_Processor[] */ - var $processors; + public $processors; /** * @var string|int|null */ - var $_lasterror; - var $_last_event_id; - var $_user; - var $_pending_events; - var $sdk; + public $_lasterror; + public $_last_event_id; + public $_user; + public $_pending_events; + public $sdk; /** * @var Raven_CurlHandler */ diff --git a/lib/Raven/Context.php b/lib/Raven/Context.php index 9394d95d4..7487da4a6 100644 --- a/lib/Raven/Context.php +++ b/lib/Raven/Context.php @@ -9,15 +9,15 @@ class Raven_Context /** * @var array */ - var $tags; + public $tags; /** * @var array */ - var $extra; + public $extra; /** * @var array|null */ - var $user; + public $user; public function __construct() { diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 1df354017..e76e75bf7 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -76,13 +76,13 @@ public function test_get_current_url() class Dummy_Raven_Client_With_Overrided_Direct_Send extends Raven_Client { - var $_send_http_asynchronous_curl_exec_called = false; - var $_send_http_synchronous = false; - var $_set_url; - var $_set_data; - var $_set_headers; + public $_send_http_asynchronous_curl_exec_called = false; + public $_send_http_synchronous = false; + public $_set_url; + public $_set_data; + public $_set_headers; - function send_http_asynchronous_curl_exec($url, $data, $headers) + public function send_http_asynchronous_curl_exec($url, $data, $headers) { $this->_send_http_asynchronous_curl_exec_called = true; $this->_set_url = $url; @@ -90,7 +90,7 @@ function send_http_asynchronous_curl_exec($url, $data, $headers) $this->_set_headers = $headers; } - function send_http_synchronous($url, $data, $headers) + public function send_http_synchronous($url, $data, $headers) { $this->_send_http_synchronous = true; $this->_set_url = $url; @@ -98,19 +98,19 @@ function send_http_synchronous($url, $data, $headers) $this->_set_headers = $headers; } - function get_curl_options() + public function get_curl_options() { $options = parent::get_curl_options(); return $options; } - function get_curl_handler() + public function get_curl_handler() { return $this->_curl_handler; } - function set_curl_handler(Raven_CurlHandler $value) + public function set_curl_handler(Raven_CurlHandler $value) { $this->_curl_handler = $value; } @@ -131,7 +131,7 @@ class Dummy_Raven_Client_With_Sync_Override extends Raven_Client { private static $_test_data = null; - static function get_test_data() + public static function get_test_data() { if (is_null(self::$_test_data)) { self::$_test_data = ''; @@ -143,7 +143,7 @@ static function get_test_data() return self::$_test_data; } - static function test_filename() + public static function test_filename() { return sys_get_temp_dir().'/clientraven.tmp'; } @@ -156,18 +156,18 @@ protected function buildCurlCommand($url, $data, $headers) class Dummy_Raven_CurlHandler extends Raven_CurlHandler { - var $_set_url; - var $_set_data; - var $_set_headers; - var $_enqueue_called = false; - var $_join_called = false; + public $_set_url; + public $_set_data; + public $_set_headers; + public $_enqueue_called = false; + public $_join_called = false; - function __construct($options = array(), $join_timeout = 5) + public function __construct($options = array(), $join_timeout = 5) { parent::__construct($options, $join_timeout); } - function enqueue($url, $data = null, $headers = array()) + public function enqueue($url, $data = null, $headers = array()) { $this->_enqueue_called = true; $this->_set_url = $url; @@ -177,7 +177,7 @@ function enqueue($url, $data = null, $headers = array()) return 0; } - function join($timeout = null) + public function join($timeout = null) { $this->_join_called = true; } @@ -185,7 +185,7 @@ function join($timeout = null) class Raven_Tests_ClientTest extends PHPUnit_Framework_TestCase { - function tearDown() + public function tearDown() { parent::tearDown(); if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { @@ -1433,13 +1433,13 @@ public function testGettersAndSetters() $callable = array($this, 'stabClosureVoid'); $data = array( - array('environment', null, 'value',), - array('environment', null, null,), - array('release', null, 'value',), - array('release', null, null,), + array('environment', null, 'value', ), + array('environment', null, null, ), + array('release', null, 'value', ), + array('release', null, null, ), array('app_path', null, 'value', $property_method__convert_path->invoke($client, 'value')), - array('app_path', null, null,), - array('app_path', null, false, null,), + array('app_path', null, null, ), + array('app_path', null, false, null, ), array('excluded_app_paths', null, array('value'), array($property_method__convert_path->invoke($client, 'value'))), array('excluded_app_paths', null, array(), null), @@ -1452,14 +1452,14 @@ public function testGettersAndSetters() array('transport', null, null), array('server', 'ServerEndpoint', 'http://example.com/'), array('server', 'ServerEndpoint', 'http://example.org/'), - array('_lasterror', null, null,), - array('_lasterror', null, 'value',), - array('_lasterror', null, mt_rand(100, 999),), - array('_last_event_id', null, mt_rand(100, 999),), - array('_last_event_id', null, 'value',), - array('extra_data', '_extra_data', array('key' => 'value'),), - array('processors', 'processors', array(),), - array('processors', 'processors', array('key' => 'value'),), + array('_lasterror', null, null, ), + array('_lasterror', null, 'value', ), + array('_lasterror', null, mt_rand(100, 999), ), + array('_last_event_id', null, mt_rand(100, 999), ), + array('_last_event_id', null, 'value', ), + array('extra_data', '_extra_data', array('key' => 'value'), ), + array('processors', 'processors', array(), ), + array('processors', 'processors', array('key' => 'value'), ), ); foreach ($data as &$datum) { $this->subTestGettersAndSettersDatum($client, $datum); @@ -1532,7 +1532,7 @@ private function assertMixedValueAndArray($expected_value, $actual_value) /** * @covers Raven_Client::_convertPath */ - function test_convertPath() + public function test_convertPath() { $property = new ReflectionMethod('Raven_Client', '_convertPath'); $property->setAccessible(true); @@ -1542,7 +1542,7 @@ function test_convertPath() /** * @covers Raven_Client::getDefaultProcessors */ - function testGetDefaultProcessors() + public function testGetDefaultProcessors() { foreach (Raven_Client::getDefaultProcessors() as $class_name) { $this->assertInternalType('string', $class_name); @@ -1556,7 +1556,7 @@ function testGetDefaultProcessors() /** * @covers Raven_Client::get_default_ca_cert */ - function testGet_default_ca_cert() + public function testGet_default_ca_cert() { $reflection = new ReflectionMethod('Raven_Client', 'get_default_ca_cert'); $reflection->setAccessible(true); @@ -1567,7 +1567,7 @@ function testGet_default_ca_cert() * @covers Raven_Client::translateSeverity * @covers Raven_Client::registerSeverityMap */ - function testTranslateSeverity() + public function testTranslateSeverity() { $reflection = new ReflectionProperty('Raven_Client', 'severity_map'); $reflection->setAccessible(true); @@ -1575,12 +1575,12 @@ function testTranslateSeverity() $predefined = array(E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR, E_USER_WARNING, - E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR,); + E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR, ); if (version_compare(PHP_VERSION, '5.3.0', '>=')) { $predefined[] = E_DEPRECATED; $predefined[] = E_USER_DEPRECATED; } - $predefined_values = array('debug', 'info', 'warning', 'warning', 'error', 'fatal',); + $predefined_values = array('debug', 'info', 'warning', 'warning', 'error', 'fatal', ); // step 1 foreach ($predefined as &$key) { @@ -1596,7 +1596,7 @@ function testTranslateSeverity() $this->assertEquals('error', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123456)); // step 3 - $client->registerSeverityMap(array(123456 => 'foo',)); + $client->registerSeverityMap(array(123456 => 'foo', )); $this->assertMixedValueAndArray(array(123456 => 'foo'), $reflection->getValue($client)); foreach ($predefined as &$key) { $this->assertContains($client->translateSeverity($key), $predefined_values); @@ -1604,12 +1604,12 @@ function testTranslateSeverity() $this->assertEquals('foo', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); // step 4 - $client->registerSeverityMap(array(E_USER_ERROR => 'bar',)); + $client->registerSeverityMap(array(E_USER_ERROR => 'bar', )); $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); $this->assertEquals('error', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); // step 5 - $client->registerSeverityMap(array(E_USER_ERROR => 'bar', 123456 => 'foo',)); + $client->registerSeverityMap(array(E_USER_ERROR => 'bar', 123456 => 'foo', )); $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); $this->assertEquals('foo', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); @@ -1618,12 +1618,12 @@ function testTranslateSeverity() /** * @covers Raven_Client::getUserAgent */ - function testGetUserAgent() + public function testGetUserAgent() { $this->assertRegExp('|^[0-9a-z./_-]+$|i', Raven_Client::getUserAgent()); } - function testCaptureExceptionWithLogger() + public function testCaptureExceptionWithLogger() { $client = new Dummy_Raven_Client(); $client->captureException(new Exception(), null, 'foobar'); @@ -1640,7 +1640,7 @@ function testCaptureExceptionWithLogger() * @covers Raven_Client::send_remote * @covers Raven_Client::send_http */ - function testCurl_method() + public function testCurl_method() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( @@ -1671,7 +1671,7 @@ function testCurl_method() * @covers Raven_Client::send_remote * @covers Raven_Client::send_http */ - function testCurl_method_async() + public function testCurl_method_async() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( @@ -1701,7 +1701,7 @@ function testCurl_method_async() * @backupGlobals * @covers Raven_Client::__construct */ - function testConstructWithServerDSN() + public function testConstructWithServerDSN() { $_SERVER['SENTRY_DSN'] = 'http://public:secret@example.com/1'; $client = new Dummy_Raven_Client(); @@ -1715,7 +1715,7 @@ function testConstructWithServerDSN() * @backupGlobals * @covers Raven_Client::_server_variable */ - function test_server_variable() + public function test_server_variable() { $method = new ReflectionMethod('Raven_Client', '_server_variable'); $method->setAccessible(true); @@ -1737,7 +1737,7 @@ function test_server_variable() } } - function testEncode() + public function testEncode() { $client = new Dummy_Raven_Client(); $data_broken = array(); @@ -1764,7 +1764,7 @@ function testEncode() * @covers Raven_Client::__construct * @covers Raven_Client::registerDefaultBreadcrumbHandlers */ - function testRegisterDefaultBreadcrumbHandlers() + public function testRegisterDefaultBreadcrumbHandlers() { $previous = set_error_handler(array($this, 'stabClosureErrorHandler'), E_USER_NOTICE); new Raven_Client(null, array()); @@ -1779,19 +1779,19 @@ function testRegisterDefaultBreadcrumbHandlers() private $_closure_called = false; - function stabClosureVoid() + public function stabClosureVoid() { $this->_closure_called = true; } - function stabClosureNull() + public function stabClosureNull() { $this->_closure_called = true; return null; } - function stabClosureFalse() + public function stabClosureFalse() { $this->_closure_called = true; @@ -1800,7 +1800,7 @@ function stabClosureFalse() private $_debug_backtrace = array(); - function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $context = array()) + public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $context = array()) { $this->_closure_called = true; $this->_debug_backtrace = debug_backtrace(); @@ -1813,7 +1813,7 @@ function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $contex * @covers Raven_Client::sendUnsentErrors * @covers Raven_Client::capture */ - function testOnShutdown() + public function testOnShutdown() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( @@ -1865,7 +1865,7 @@ function testOnShutdown() /** * @covers Raven_Client::send */ - function testNonWorkingSendSendCallback() + public function testNonWorkingSendSendCallback() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( @@ -1896,7 +1896,7 @@ function testNonWorkingSendSendCallback() /** * @covers Raven_Client::send */ - function testNonWorkingSendDSNEmpty() + public function testNonWorkingSendDSNEmpty() { $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( 'http://public:secret@example.com/1', array( @@ -1913,7 +1913,7 @@ function testNonWorkingSendDSNEmpty() /** * @covers Raven_Client::send */ - function testNonWorkingSendSetTransport() + public function testNonWorkingSendSetTransport() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( @@ -1942,7 +1942,7 @@ function testNonWorkingSendSetTransport() /** * @covers Raven_Client::get_user_data */ - function testGet_user_data() + public function testGet_user_data() { // step 1 $client = new Dummy_Raven_Client(); @@ -1962,7 +1962,7 @@ function testGet_user_data() // step 3 session_id($session_id); - @session_start(array('use_cookies' => false,)); + @session_start(array('use_cookies' => false, )); $_SESSION = array('foo' => 'bar'); $output = $client->get_user_data(); $this->assertInternalType('array', $output); @@ -1981,13 +1981,13 @@ function testGet_user_data() */ public function testCaptureLevel() { - foreach ([Raven_Client::MESSAGE_LIMIT * 3, 100] as $length) { + foreach (array(Raven_Client::MESSAGE_LIMIT * 3, 100) as $length) { $message = ''; for ($i = 0; $i < $length; $i++) { $message .= chr($i % 256); } $client = new Dummy_Raven_Client(); - $client->capture(array('message' => $message,)); + $client->capture(array('message' => $message, )); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -1999,7 +1999,7 @@ public function testCaptureLevel() } $client = new Dummy_Raven_Client(); - $client->capture(array('message' => 'foobar',)); + $client->capture(array('message' => 'foobar', )); $events = $client->getSentEvents(); $event = array_pop($events); $input = $client->get_http_data(); @@ -2008,15 +2008,15 @@ public function testCaptureLevel() $this->assertArrayNotHasKey('environment', $event); $client = new Dummy_Raven_Client(); - $client->capture(array('message' => 'foobar', 'request' => array('foo' => 'bar'),)); + $client->capture(array('message' => 'foobar', 'request' => array('foo' => 'bar'), )); $events = $client->getSentEvents(); $event = array_pop($events); $this->assertEquals(array('foo' => 'bar'), $event['request']); $this->assertArrayNotHasKey('release', $event); $this->assertArrayNotHasKey('environment', $event); - foreach ([false, true] as $u1) { - foreach ([false, true] as $u2) { + foreach (array(false, true) as $u1) { + foreach (array(false, true) as $u2) { $client = new Dummy_Raven_Client(); if ($u1) { $client->setRelease('foo'); @@ -2024,7 +2024,7 @@ public function testCaptureLevel() if ($u2) { $client->setEnvironment('bar'); } - $client->capture(array('message' => 'foobar',)); + $client->capture(array('message' => 'foobar', )); $events = $client->getSentEvents(); $event = array_pop($events); if ($u1) { @@ -2060,7 +2060,7 @@ public function testCaptureNoUserAndRequest() // step 3 session_id($session_id); - @session_start(array('use_cookies' => false,)); + @session_start(array('use_cookies' => false, )); } /** @@ -2102,7 +2102,8 @@ public function testCaptureAutoLogStacks() /** * @covers Raven_Client::send_http_asynchronous_curl_exec */ - public function testSend_http_asynchronous_curl_exec(){ + public function testSend_http_asynchronous_curl_exec() + { $client = new Dummy_Raven_Client_With_Sync_Override( 'http://public:secret@example.com/1', array( 'curl_method' => 'exec', @@ -2116,5 +2117,4 @@ public function testSend_http_asynchronous_curl_exec(){ $test_data = Dummy_Raven_Client_With_Sync_Override::get_test_data(); $this->assertStringEqualsFile(Dummy_Raven_Client_With_Sync_Override::test_filename(), $test_data."\n"); } - } diff --git a/test/Raven/Tests/SerializerTest.php b/test/Raven/Tests/SerializerTest.php index 88f40811e..9d8a025fe 100644 --- a/test/Raven/Tests/SerializerTest.php +++ b/test/Raven/Tests/SerializerTest.php @@ -102,12 +102,12 @@ public function testObjectInArray() public function testBrokenEncoding() { $serializer = new Raven_Serializer(); - foreach (['7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90'] as $key) { + foreach (array('7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90') as $key) { $input = pack('H*', $key); $result = $serializer->serialize($input); $this->assertInternalType('string', $result); if (function_exists('mb_detect_encoding')) { - $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); + $this->assertContains(mb_detect_encoding($result), array('ASCII', 'UTF-8')); } } } @@ -119,7 +119,7 @@ public function testLongString() { $serializer = new Raven_Serializer(); for ($i = 0; $i < 100; $i++) { - foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { + foreach (array(100, 1000, 1010, 1024, 1050, 1100, 10000) as $length) { $input = ''; for ($i = 0; $i < $length; $i++) { $input .= chr(mt_rand(0, 255)); From d7fd83cf0e183a100bb7c6f479f07994ae178e7a Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 16:55:59 +0300 Subject: [PATCH 0246/1161] Raven_Tests_ClientTest::testEncode small split check --- test/Raven/Tests/ClientTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index e76e75bf7..010104608 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -1753,11 +1753,13 @@ public function testEncode() $value = $client->encode($data); $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value); $decoded = base64_decode($value); + $this->assertInternalType('string', $decoded, 'Can not use base64 decode on the encoded blob'); if (function_exists("gzcompress")) { $decoded = gzuncompress($decoded); + $this->assertEquals($json_stringify, $decoded, 'Can not decompress compressed blob'); + } else { + $this->assertEquals($json_stringify, $decoded); } - - $this->assertEquals($json_stringify, $decoded); } /** From 2c1ea09dbaed1cb8f937fe7fff4965d01df8d159 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 18:33:31 +0300 Subject: [PATCH 0247/1161] Raven_Compat::_json_encode was incompatible with native function while we want to encode too depth value --- lib/Raven/Compat.php | 46 +++++++++++++++++++++++++++------ test/Raven/Tests/ClientTest.php | 21 ++++++++++++--- test/Raven/Tests/CompatTest.php | 44 +++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 12 deletions(-) diff --git a/lib/Raven/Compat.php b/lib/Raven/Compat.php index 940a5eb9e..4d5f719aa 100644 --- a/lib/Raven/Compat.php +++ b/lib/Raven/Compat.php @@ -75,27 +75,48 @@ public static function _hash_hmac($algo, $data, $key, $raw_output = false) * * @param mixed $value * @param int $options + * @param int $depth Set the maximum depth * @return string */ - public static function json_encode($value, $options = 0) + public static function json_encode($value, $options = 0, $depth = 512) { if (function_exists('json_encode')) { - return json_encode($value); + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + return json_encode($value); + } elseif (version_compare(PHP_VERSION, '5.5.0', '<')) { + return json_encode($value, $options); + } else { + return json_encode($value, $options, $depth); + } } // @codeCoverageIgnoreStart - return self::_json_encode($value); + return self::_json_encode($value, $depth); // @codeCoverageIgnoreEnd } + /** + * @param mixed $value + * @param int $depth Set the maximum depth + * @return string|false + */ + public static function _json_encode($value, $depth = 513) + { + if (ini_get('xdebug.extended_info') !== false) { + ini_set('xdebug.max_nesting_level', 2048); + } + return self::_json_encode_lowlevel($value, $depth); + } + /** * Implementation taken from * http://www.mike-griffiths.co.uk/php-json_encode-alternative/ * * @param mixed $value - * @return string + * @param int $depth Set the maximum depth + * @return string|false */ - public static function _json_encode($value) + private static function _json_encode_lowlevel($value, $depth) { static $jsonReplaces = array( array('\\', '/', "\n", "\t", "\r", "\f", '"'), @@ -112,7 +133,6 @@ public static function _json_encode($value) } if (is_scalar($value)) { - // Always use '.' for floats. if (is_float($value)) { return floatval(str_replace(',', '.', strval($value))); @@ -123,6 +143,8 @@ public static function _json_encode($value) } else { return $value; } + } elseif ($depth <= 1) { + return false; } $isList = true; @@ -135,13 +157,21 @@ public static function _json_encode($value) $result = array(); if ($isList) { foreach ($value as $v) { - $result[] = self::_json_encode($v); + $this_value = self::_json_encode($v, $depth - 1); + if ($this_value === false) { + return false; + } + $result[] = $this_value; } return '[' . join(',', $result) . ']'; } else { foreach ($value as $k => $v) { - $result[] = self::_json_encode($k) . ':' . self::_json_encode($v); + $this_value = self::_json_encode($v, $depth - 1); + if ($this_value === false) { + return false; + } + $result[] = self::_json_encode($k, $depth - 1).':'.$this_value; } return '{' . join(',', $result) . '}'; diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 010104608..752854ef3 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -1737,7 +1737,7 @@ public function test_server_variable() } } - public function testEncode() + public function testEncodeTooDepth() { $client = new Dummy_Raven_Client(); $data_broken = array(); @@ -1745,13 +1745,26 @@ public function testEncode() $data_broken = array($data_broken); } $value = $client->encode($data_broken); - $this->assertFalse($value); - unset($data_broken); + if (!function_exists('json_encode') or version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertFalse($value, 'Broken data encoded successfully with '. + (function_exists('json_encode') ? 'native method' : 'Raven_Compat::_json_encode')); + } else { + if ($value !== false) { + $this->markTestSkipped(); + } else { + $this->assertEquals('eJyLjh4Fo2AUjFgQOwpGwSgYuQAA3Q7g1w==', $value, 'Native json_encode error'); + } + } + } + public function testEncode() + { + $client = new Dummy_Raven_Client(); $data = array('some' => (object)array('value' => 'data'), 'foo' => array('bar', null, 123), false); $json_stringify = Raven_Compat::json_encode($data); $value = $client->encode($data); - $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value); + $this->assertNotFalse($value); + $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value, 'Raven_Client::encode returned malformed data'); $decoded = base64_decode($value); $this->assertInternalType('string', $decoded, 'Can not use base64 decode on the encoded blob'); if (function_exists("gzcompress")) { diff --git a/test/Raven/Tests/CompatTest.php b/test/Raven/Tests/CompatTest.php index 93711d532..4d558d605 100644 --- a/test/Raven/Tests/CompatTest.php +++ b/test/Raven/Tests/CompatTest.php @@ -66,4 +66,48 @@ public function test_json_encode() $result = Raven_Compat::_json_encode(array(null, false, true, 1.5)); $this->assertEquals('[null,false,true,1.5]', $result); } + + /** + * @covers Raven_Compat::_json_encode + * @covers Raven_Compat::_json_encode_lowlevel + * + * I show you how deep the rabbit hole goes + */ + public function test_json_encode_with_broken_data() + { + $data_broken_named = array(); + $data_broken_named_510 = null; + $data_broken_named_511 = null; + + $data_broken = array(); + $data_broken_510 = null; + $data_broken_511 = null; + for ($i = 0; $i < 1024; $i++) { + $data_broken = array($data_broken); + $data_broken_named = array('a' => $data_broken_named); + switch ($i) { + case 510: + $data_broken_510 = $data_broken; + $data_broken_named_510 = $data_broken_named; + break; + case 511: + $data_broken_511 = $data_broken; + $data_broken_named_511 = $data_broken_named; + break; + } + } + $value_1024 = Raven_Compat::_json_encode($data_broken); + $value_510 = Raven_Compat::_json_encode($data_broken_510); + $value_511 = Raven_Compat::_json_encode($data_broken_511); + $this->assertFalse($value_1024, 'Broken data encoded successfully with Raven_Compat::_json_encode'); + $this->assertNotFalse($value_510); + $this->assertFalse($value_511); + + $value_1024 = Raven_Compat::_json_encode($data_broken_named); + $value_510 = Raven_Compat::_json_encode($data_broken_named_510); + $value_511 = Raven_Compat::_json_encode($data_broken_named_511); + $this->assertFalse($value_1024, 'Broken data encoded successfully with Raven_Compat::_json_encode'); + $this->assertNotFalse($value_510); + $this->assertFalse($value_511); + } } From e5115a173cd5845fd776af569dcb9406db942115 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 20:28:13 +0300 Subject: [PATCH 0248/1161] Tests: Fix for non development environment --- test/Raven/Tests/ClientTest.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 752854ef3..9837d90fa 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -641,11 +641,14 @@ public function testCaptureExceptionHandlesExcludeOption() $this->assertEquals(0, count($events)); } - /** - * @covers Raven_Client::captureException - */ public function testCaptureExceptionInvalidUTF8() { + if (version_compare(PHP_VERSION, '7.0.0', '>=')) { + if (ini_get('zend.assertions') != 1) { + $this->markTestSkipped('Production environment does not execute asserts'); + return; + } + } $client = new Dummy_Raven_Client(); try { invalid_encoding(); @@ -657,7 +660,7 @@ public function testCaptureExceptionInvalidUTF8() // if this fails to encode it returns false $message = $client->encode($events[0]); - $this->assertNotEquals($message, false, $client->getLastError()); + $this->assertNotFalse($message, $client->getLastError()); } /** From e3e8fc721b0f0099a76fff606e4434fc2c5ca32e Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 20:44:19 +0300 Subject: [PATCH 0249/1161] Fixes for tests use debug_backtrace for PHP 7 Environment what hide call_user_func call --- test/Raven/Tests/ClientTest.php | 13 ++++- test/Raven/Tests/StacktraceTest.php | 78 +++++++++++++++++++---------- 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 9837d90fa..bad211540 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -643,7 +643,7 @@ public function testCaptureExceptionHandlesExcludeOption() public function testCaptureExceptionInvalidUTF8() { - if (version_compare(PHP_VERSION, '7.0.0', '>=')) { + if (version_compare(PHP_VERSION, '7.0', '>=')) { if (ini_get('zend.assertions') != 1) { $this->markTestSkipped('Production environment does not execute asserts'); return; @@ -1792,7 +1792,16 @@ public function testRegisterDefaultBreadcrumbHandlers() $debug_backtrace = $this->_debug_backtrace; set_error_handler($previous, E_ALL); $this->assertTrue($u); - $this->assertEquals('Raven_Breadcrumbs_ErrorHandler', $debug_backtrace[2]['class']); + if (isset($debug_backtrace[1]['function']) and ($debug_backtrace[1]['function'] == 'call_user_func') + and version_compare(PHP_VERSION, '7.0', '>=') + ) { + $offset = 2; + } elseif (version_compare(PHP_VERSION, '7.0', '>=')) { + $offset = 1; + } else { + $offset = 2; + } + $this->assertEquals('Raven_Breadcrumbs_ErrorHandler', $debug_backtrace[$offset]['class']); } private $_closure_called = false; diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 4dfd701a1..26f6068c8 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -30,7 +30,12 @@ public function testCanTraceParamContext() { $stack = raven_test_create_stacktrace(array('biz', 'baz'), 0); - $frame = $stack[2]; + if (isset($stack[0]['function']) and ($stack[0]['function'] == 'call_user_func')) { + $offset = 2; + } else { + $offset = 1; + } + $frame = $stack[$offset]; $params = Raven_Stacktrace::get_frame_context($frame); $this->assertEquals($params['args'], array('biz', 'baz')); $this->assertEquals($params['times'], 0); @@ -39,20 +44,20 @@ public function testCanTraceParamContext() public function testSimpleTrace() { $stack = array( - array( - 'file' => dirname(__FILE__) . '/resources/a.php', - 'line' => 9, - 'function' => 'a_test', - 'args' => array('friend'), - ), - array( - 'file' => dirname(__FILE__) . '/resources/b.php', - 'line' => 2, - 'args' => array( - dirname(__FILE__) . '/resources/a.php', + array( + 'file' => dirname(__FILE__).'/resources/a.php', + 'line' => 9, + 'function' => 'a_test', + 'args' => array('friend'), ), - 'function' => 'include_once', - ) + array( + 'file' => dirname(__FILE__).'/resources/b.php', + 'line' => 2, + 'args' => array( + dirname(__FILE__).'/resources/a.php', + ), + 'function' => 'include_once', + ) ); $frames = Raven_Stacktrace::get_stack_info($stack, true); @@ -113,18 +118,39 @@ public function testDoesFixFrameInfo() $frames = Raven_Stacktrace::get_stack_info($stack, true); // just grab the last few frames $frames = array_slice($frames, -6); - $frame = $frames[0]; - $this->assertEquals('raven_test_create_stacktrace', $frame['function']); - $frame = $frames[1]; - $this->assertEquals('raven_test_recurse', $frame['function']); - $frame = $frames[2]; - $this->assertEquals('call_user_func', $frame['function']); - $frame = $frames[3]; - $this->assertEquals('raven_test_recurse', $frame['function']); - $frame = $frames[4]; - $this->assertEquals('call_user_func', $frame['function']); - $frame = $frames[5]; - $this->assertEquals('raven_test_recurse', $frame['function']); + $skip_call_user_func_fix = false; + if (version_compare(PHP_VERSION, '7.0', '>=')) { + $skip_call_user_func_fix = true; + foreach ($frames as &$frame) { + if (isset($frame['function']) and ($frame['function'] == 'call_user_func')) { + $skip_call_user_func_fix = false; + break; + } + } + unset($frame); + } + + if ($skip_call_user_func_fix) { + $frame = $frames[3]; + $this->assertEquals('raven_test_create_stacktrace', $frame['function']); + $frame = $frames[4]; + $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[5]; + $this->assertEquals('raven_test_recurse', $frame['function']); + } else { + $frame = $frames[0]; + $this->assertEquals('raven_test_create_stacktrace', $frame['function']); + $frame = $frames[1]; + $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[2]; + $this->assertEquals('call_user_func', $frame['function']); + $frame = $frames[3]; + $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[4]; + $this->assertEquals('call_user_func', $frame['function']); + $frame = $frames[5]; + $this->assertEquals('raven_test_recurse', $frame['function']); + } } public function testInApp() From 555df2d2c2f6bd13a68217d87258f935c83770a5 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 21:33:33 +0300 Subject: [PATCH 0250/1161] Fix for Raven_Tests_ClientTest::testCaptureLastError --- test/Raven/Tests/ClientTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index bad211540..6b64d9adb 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -1013,6 +1013,9 @@ public function testCaptureExceptionInLatin1File() */ public function testCaptureLastError() { + if (function_exists('error_clear_last')) { + error_clear_last(); + } $client = new Dummy_Raven_Client(); $this->assertNull($client->captureLastError()); $this->assertEquals(0, count($client->getSentEvents())); From 7b56a080d86270520617a4a247b111b18a2fedc9 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 14 Feb 2017 21:57:28 +0300 Subject: [PATCH 0251/1161] Travis.yml: php 7.1, permutations for xdebug and assertions --- .travis.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.travis.yml b/.travis.yml index de0d100cb..7c3f5ff77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,14 +6,36 @@ php: - 5.5 - 5.6 - 7.0 + - 7.1 - nightly - hhvm matrix: allow_failures: + - php: 7.1 - php: hhvm - php: nightly fast_finish: true + include: + - php: 7.0 + env: REMOVE_XDEBUG="0" DISABLE_ASSERTIONS="1" + - php: 7.0 + env: REMOVE_XDEBUG="1" DISABLE_ASSERTIONS="1" + - php: 7.1 + env: REMOVE_XDEBUG="0" DISABLE_ASSERTIONS="1" + - php: 7.1 + env: REMOVE_XDEBUG="1" DISABLE_ASSERTIONS="1" + - php: nightly + env: REMOVE_XDEBUG="0" DISABLE_ASSERTIONS="1" + - php: nightly + env: REMOVE_XDEBUG="1" DISABLE_ASSERTIONS="1" + exclude: + - php: hhvm + env: REMOVE_XDEBUG="1" + +env: + - REMOVE_XDEBUG="0" + - REMOVE_XDEBUG="1" sudo: false @@ -22,6 +44,8 @@ cache: - $HOME/.composer/cache before_install: + - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi + - if [ "$DISABLE_ASSERTIONS" = "1" ]; then echo 'zend.assertions = -1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]]; then pecl install uopz; fi - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]]; then echo "extension=uopz.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - composer self-update From 0e966c844ff9f715d54c70b2959b409fa39a03dc Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Wed, 15 Feb 2017 14:58:33 +0300 Subject: [PATCH 0252/1161] Save Sentry response to protected property Curl instance stored in protected property Shutdown handler SHOULD be set in separate method and we MUST be able to suppress it --- lib/Raven/Client.php | 117 ++++++++++++++++++++++++++------ test/Raven/Tests/ClientTest.php | 82 +++++++++++++++++++++- 2 files changed, 178 insertions(+), 21 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index be361e670..dffd5358a 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -102,6 +102,10 @@ class Raven_Client * @var string|int|null */ public $_lasterror; + /** + * @var object|null + */ + protected $_last_sentry_error; public $_last_event_id; public $_user; public $_pending_events; @@ -110,6 +114,14 @@ class Raven_Client * @var Raven_CurlHandler */ protected $_curl_handler; + /** + * @var resource|null + */ + protected $_curl_instance; + /** + * @var bool + */ + protected $_shutdown_function_has_been_set; public function __construct($options_or_dsn = null, $options = array()) { @@ -169,11 +181,14 @@ public function __construct($options_or_dsn = null, $options = array()) $this->processors = $this->setProcessorsFromOptions($options); $this->_lasterror = null; + $this->_last_sentry_error = null; + $this->_curl_instance = null; $this->_last_event_id = null; $this->_user = null; $this->_pending_events = array(); $this->context = new Raven_Context(); $this->breadcrumbs = new Raven_Breadcrumbs(); + $this->_shutdown_function_has_been_set = false; $this->sdk = Raven_Util::get($options, 'sdk', array( 'name' => 'sentry-php', @@ -197,7 +212,25 @@ public function __construct($options_or_dsn = null, $options = array()) $this->registerDefaultBreadcrumbHandlers(); } - register_shutdown_function(array($this, 'onShutdown')); + if (Raven_Util::get($options, 'install_shutdown_handler', true)) { + $this->registerShutdownFunction(); + } + } + + public function __destruct() + { + // Force close curl resource + $this->close_curl_resource(); + } + + /** + * Destruct all objects contain link to this object + * + * This method can not delete shutdown handler + */ + public function close_all_children_link() + { + $this->processors = array(); } /** @@ -637,6 +670,14 @@ protected function registerDefaultBreadcrumbHandlers() $handler->install(); } + protected function registerShutdownFunction() + { + if (!$this->_shutdown_function_has_been_set) { + $this->_shutdown_function_has_been_set = true; + register_shutdown_function(array($this, 'onShutdown')); + } + } + /** * @return bool * @codeCoverageIgnore @@ -1053,35 +1094,47 @@ protected function send_http_synchronous($url, $data, $headers) // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216) $new_headers[] = 'Expect:'; - $curl = curl_init($url); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_HTTPHEADER, $new_headers); - curl_setopt($curl, CURLOPT_POSTFIELDS, $data); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + if (is_null($this->_curl_instance)) { + $this->_curl_instance = curl_init($url); + } + curl_setopt($this->_curl_instance, CURLOPT_POST, 1); + curl_setopt($this->_curl_instance, CURLOPT_HTTPHEADER, $new_headers); + curl_setopt($this->_curl_instance, CURLOPT_POSTFIELDS, $data); + curl_setopt($this->_curl_instance, CURLOPT_RETURNTRANSFER, true); $options = $this->get_curl_options(); - $ca_cert = $options[CURLOPT_CAINFO]; - unset($options[CURLOPT_CAINFO]); - curl_setopt_array($curl, $options); + if (isset($options[CURLOPT_CAINFO])) { + $ca_cert = $options[CURLOPT_CAINFO]; + unset($options[CURLOPT_CAINFO]); + } else { + $ca_cert = null; + } + curl_setopt_array($this->_curl_instance, $options); - curl_exec($curl); + $buffer = curl_exec($this->_curl_instance); - $errno = curl_errno($curl); + $errno = curl_errno($this->_curl_instance); // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE - if ($errno == 60 || $errno == 77) { - curl_setopt($curl, CURLOPT_CAINFO, $ca_cert); - curl_exec($curl); + if ((($errno == 60) || ($errno == 77)) && !is_null($ca_cert)) { + curl_setopt($this->_curl_instance, CURLOPT_CAINFO, $ca_cert); + $buffer = curl_exec($this->_curl_instance); + } + if ($errno != 0) { + $this->_lasterror = curl_error($this->_curl_instance); + $this->_last_sentry_error = null; + return false; } - $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $code = curl_getinfo($this->_curl_instance, CURLINFO_HTTP_CODE); $success = ($code == 200); - if (!$success) { - // It'd be nice just to raise an exception here, but it's not very PHP-like - $this->_lasterror = curl_error($curl); - } else { + if ($success) { $this->_lasterror = null; + $this->_last_sentry_error = null; + } else { + // It'd be nice just to raise an exception here, but it's not very PHP-like + $this->_lasterror = curl_error($this->_curl_instance); + $this->_last_sentry_error = @json_decode($buffer); } - curl_close($curl); return $success; } @@ -1332,4 +1385,28 @@ public function setProcessors(array $processors) { $this->processors = $processors; } + + /** + * @return object|null + */ + public function getLastSentryError() + { + return $this->_last_sentry_error; + } + + /** + * @return bool + */ + public function getShutdownFunctionHasBeenSet() + { + return $this->_shutdown_function_has_been_set; + } + + public function close_curl_resource() + { + if (!is_null($this->_curl_instance)) { + curl_close($this->_curl_instance); + $this->_curl_instance = null; + } + } } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 6b64d9adb..5ead08d1e 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -25,11 +25,14 @@ function invalid_encoding() class Dummy_Raven_Client extends Raven_Client { private $__sent_events = array(); + public $dummy_breadcrumbs_handlers_has_set = false; + public $dummy_shutdown_handlers_has_set = false; public function getSentEvents() { return $this->__sent_events; } + public function send(&$data) { if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { @@ -38,29 +41,41 @@ public function send(&$data) } $this->__sent_events[] = $data; } + public static function is_http_request() { return true; } + public static function get_auth_header($timestamp, $client, $api_key, $secret_key) { return parent::get_auth_header($timestamp, $client, $api_key, $secret_key); } + public function get_http_data() { return parent::get_http_data(); } + public function get_user_data() { return parent::get_user_data(); } + public function buildCurlCommand($url, $data, $headers) { return parent::buildCurlCommand($url, $data, $headers); } + // short circuit breadcrumbs public function registerDefaultBreadcrumbHandlers() { + $this->dummy_breadcrumbs_handlers_has_set = true; + } + + public function registerShutdownFunction() + { + $this->dummy_shutdown_handlers_has_set = true; } /** @@ -81,6 +96,7 @@ class Dummy_Raven_Client_With_Overrided_Direct_Send extends Raven_Client public $_set_url; public $_set_data; public $_set_headers; + public static $_close_curl_resource_called = false; public function send_http_asynchronous_curl_exec($url, $data, $headers) { @@ -114,6 +130,12 @@ public function set_curl_handler(Raven_CurlHandler $value) { $this->_curl_handler = $value; } + + public function close_curl_resource() + { + parent::close_curl_resource(); + self::$_close_curl_resource_called = true; + } } class Dummy_Raven_Client_No_Http extends Dummy_Raven_Client @@ -342,7 +364,6 @@ public function testOptionsFirstArgument() $this->assertEquals('http://example.com/api/1/store/', $client->server); } - /** * @covers Raven_Client::__construct */ @@ -1429,6 +1450,8 @@ public function testUuid4() * @covers Raven_Client::getLastEventID * @covers Raven_Client::get_extra_data * @covers Raven_Client::setProcessors + * @covers Raven_Client::getLastSentryError + * @covers Raven_Client::getShutdownFunctionHasBeenSet */ public function testGettersAndSetters() { @@ -1461,11 +1484,14 @@ public function testGettersAndSetters() array('_lasterror', null, null, ), array('_lasterror', null, 'value', ), array('_lasterror', null, mt_rand(100, 999), ), + array('_last_sentry_error', null, (object)array('error' => 'test',), ), array('_last_event_id', null, mt_rand(100, 999), ), array('_last_event_id', null, 'value', ), array('extra_data', '_extra_data', array('key' => 'value'), ), array('processors', 'processors', array(), ), array('processors', 'processors', array('key' => 'value'), ), + array('_shutdown_function_has_been_set', null, true), + array('_shutdown_function_has_been_set', null, false), ); foreach ($data as &$datum) { $this->subTestGettersAndSettersDatum($client, $datum); @@ -1969,6 +1995,43 @@ public function testNonWorkingSendSetTransport() $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); } + /** + * @covers Raven_Client::__construct + */ + public function test__construct_handlers() + { + foreach (array(true, false) as $u1) { + foreach (array(true, false) as $u2) { + $client = new Dummy_Raven_Client( + null, array( + 'install_default_breadcrumb_handlers' => $u1, + 'install_shutdown_handler' => $u2, + ) + ); + $this->assertEquals($u1, $client->dummy_breadcrumbs_handlers_has_set); + $this->assertEquals($u2, $client->dummy_shutdown_handlers_has_set); + } + } + } + + /** + * @covers Raven_Client::__destruct + * @covers Raven_Client::close_all_children_link + */ + public function test__destruct_calls_close_functions() + { + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'install_default_breadcrumb_handlers' => false, + 'install_shutdown_handler' => false, + ) + ); + $client::$_close_curl_resource_called = false; + $client->close_all_children_link(); + unset($client); + $this->assertTrue(Dummy_Raven_Client_With_Overrided_Direct_Send::$_close_curl_resource_called); + } + /** * @covers Raven_Client::get_user_data */ @@ -2147,4 +2210,21 @@ public function testSend_http_asynchronous_curl_exec() $test_data = Dummy_Raven_Client_With_Sync_Override::get_test_data(); $this->assertStringEqualsFile(Dummy_Raven_Client_With_Sync_Override::test_filename(), $test_data."\n"); } + + /** + * @covers Raven_Client::close_curl_resource + */ + public function testClose_curl_resource() + { + $raven = new Dummy_Raven_Client(); + $reflection = new ReflectionProperty('Raven_Client', '_curl_instance'); + $reflection->setAccessible(true); + $ch = curl_init(); + $reflection->setValue($raven, $ch); + unset($ch); + + $this->assertInternalType('resource', $reflection->getValue($raven)); + $raven->close_curl_resource(); + $this->assertNull($reflection->getValue($raven)); + } } From c6a2c3bd7eb0f90cc9dc55d8fd8b137fa1ab7b44 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Wed, 15 Feb 2017 16:10:38 +0300 Subject: [PATCH 0253/1161] Test fix Delete some strange constant in Raven_Client --- lib/Raven/Client.php | 6 ++---- test/Raven/Tests/ClientTest.php | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index dffd5358a..a13cb10e9 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,8 +16,6 @@ class Raven_Client { - const PARDIR = DIRECTORY_SEPARATOR; - const VERSION = '1.7.x-dev'; const PROTOCOL = '6'; @@ -285,8 +283,8 @@ private static function _convertPath($value) // we need app_path to have a trailing slash otherwise // base path detection becomes complex if the same // prefix is matched - if (substr($path, 0, 1) === self::PARDIR && substr($path, -1, 1) !== self::PARDIR) { - $path = $path . self::PARDIR; + if (substr($path, 0, 1) === DIRECTORY_SEPARATOR && substr($path, -1) !== DIRECTORY_SEPARATOR) { + $path = $path.DIRECTORY_SEPARATOR; } return $path; } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 5ead08d1e..63d8b99cd 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -822,19 +822,13 @@ public function testGetUserDataWithSetUser() $id = 'unique_id'; $email = 'foo@example.com'; - - $user = array( - 'username' => 'my_user', - ); - - // @todo перепиÑать - $client->set_user_data($id, $email, $user); + $client->user_context(array('id' => $id, 'email' => $email, 'username' => 'my_user', )); $expected = array( 'user' => array( - 'id' => 'unique_id', + 'id' => $id, 'username' => 'my_user', - 'email' => 'foo@example.com', + 'email' => $email, ) ); @@ -1458,7 +1452,6 @@ public function testGettersAndSetters() $client = new Dummy_Raven_Client(); $property_method__convert_path = new ReflectionMethod('Raven_Client', '_convertPath'); $property_method__convert_path->setAccessible(true); - // @todo завиÑит от верÑии php. Ð’Ñтавить Ñюда closure $callable = array($this, 'stabClosureVoid'); $data = array( @@ -1568,7 +1561,13 @@ public function test_convertPath() { $property = new ReflectionMethod('Raven_Client', '_convertPath'); $property->setAccessible(true); - // @todo + + $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar')); + $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar/')); + $this->assertEquals('foo/bar', $property->invoke(null, 'foo/bar')); + $this->assertEquals('foo/bar/', $property->invoke(null, 'foo/bar/')); + $this->assertEquals(dirname(__DIR__).'/', $property->invoke(null, __DIR__.'/../')); + $this->assertEquals(dirname(dirname(__DIR__)).'/', $property->invoke(null, __DIR__.'/../../')); } /** From 548abf2fdb627fa0a6e1990a4c99f00a608131d0 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Wed, 15 Feb 2017 16:24:29 +0300 Subject: [PATCH 0254/1161] Wrap too long lines --- lib/Raven/Autoloader.php | 4 +++- lib/Raven/Client.php | 34 ++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php index d5adf1a58..d2a2f8cc0 100644 --- a/lib/Raven/Autoloader.php +++ b/lib/Raven/Autoloader.php @@ -36,7 +36,9 @@ public static function autoload($class) return; } - if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) { + $file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php'; + if (is_file($file)) { + /** @noinspection PhpIncludeInspection */ require $file; } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index a13cb10e9..ebfd7bb24 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -400,7 +400,9 @@ public function setProcessorsFromOptions($options) $new_processor = new $processor($this); if (isset($options['processorOptions']) && is_array($options['processorOptions'])) { - if (isset($options['processorOptions'][$processor]) && method_exists($processor, 'setProcessorOptions')) { + if (isset($options['processorOptions'][$processor]) + && method_exists($processor, 'setProcessorOptions') + ) { $new_processor->setProcessorOptions($options['processorOptions'][$processor]); } } @@ -412,15 +414,20 @@ public function setProcessorsFromOptions($options) /** * Parses a Raven-compatible DSN and returns an array of its values. * - * @param string $dsn Raven compatible DSN: http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn + * @param string $dsn Raven compatible DSN * @return array parsed DSN + * + * @doc http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn */ public static function parseDSN($dsn) { $url = parse_url($dsn); $scheme = (isset($url['scheme']) ? $url['scheme'] : ''); if (!in_array($scheme, array('http', 'https'))) { - throw new InvalidArgumentException('Unsupported Sentry DSN scheme: ' . (!empty($scheme) ? $scheme : '')); + throw new InvalidArgumentException( + 'Unsupported Sentry DSN scheme: '. + (!empty($scheme) ? $scheme : '') + ); } $netloc = (isset($url['host']) ? $url['host'] : null); $netloc .= (isset($url['port']) ? ':'.$url['port'] : null); @@ -691,9 +698,12 @@ protected function get_http_data() foreach ($_SERVER as $key => $value) { if (0 === strpos($key, 'HTTP_')) { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value; + $header_key = + str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); + $headers[$header_key] = $value; } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))))] = $value; + $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); + $headers[$header_key] = $value; } } @@ -942,7 +952,9 @@ public function encode(&$data) */ public function send(&$data) { - if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { + if (is_callable($this->send_callback) + && call_user_func_array($this->send_callback, array(&$data)) === false + ) { // if send_callback returns false, end native send return; } @@ -986,6 +998,10 @@ protected static function get_default_ca_cert() return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem'; } + /** + * @return array + * @doc http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006 + */ protected function get_curl_options() { $options = array( @@ -1010,7 +1026,7 @@ protected function get_curl_options() // some versions of PHP 5.3 don't have this defined correctly if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) { - //see http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006 + //see stackoverflow link in the phpdoc define('CURLOPT_CONNECTTIMEOUT_MS', 156); } @@ -1169,7 +1185,9 @@ protected static function get_auth_header($timestamp, $client, $api_key, $secret public function getAuthHeader() { $timestamp = microtime(true); - return $this->get_auth_header($timestamp, static::getUserAgent(), $this->public_key, $this->secret_key); + return $this->get_auth_header( + $timestamp, static::getUserAgent(), $this->public_key, $this->secret_key + ); } /** From 6cc62615b05022bc7d68536abae6d6ae800c6c6b Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Wed, 15 Feb 2017 11:34:04 +0300 Subject: [PATCH 0255/1161] Nightly PHP does not contain XDebug Move console command to composer.json --- .travis.yml | 8 ++++---- composer.json | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7c3f5ff77..1a49db4bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,11 @@ matrix: env: REMOVE_XDEBUG="1" DISABLE_ASSERTIONS="1" - php: nightly env: REMOVE_XDEBUG="0" DISABLE_ASSERTIONS="1" - - php: nightly - env: REMOVE_XDEBUG="1" DISABLE_ASSERTIONS="1" exclude: - php: hhvm env: REMOVE_XDEBUG="1" + - php: nightly + env: REMOVE_XDEBUG="1" env: - REMOVE_XDEBUG="0" @@ -53,5 +53,5 @@ before_install: install: travis_retry composer install --no-interaction --prefer-source script: - - vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run - - vendor/bin/phpunit --verbose + - composer phpcs + - composer tests diff --git a/composer.json b/composer.json index fe3247202..c713bc32d 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,17 @@ "Raven_" : "lib/" } }, + "scripts": { + "tests": [ + "vendor/bin/phpunit --verbose" + ], + "tests-report": [ + "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-html test/html-report" + ], + "phpcs": [ + "vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run" + ] + }, "extra": { "branch-alias": { "dev-master": "1.7.x-dev" From 798805bde9d29c56ca870401d4ac2f2b3f0abb00 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Thu, 16 Feb 2017 19:02:00 +0300 Subject: [PATCH 0256/1161] Minus PHP 5.2 support --- examples/vanilla/index.php | 12 +- lib/Raven/Autoloader.php | 18 +- lib/Raven/Breadcrumbs.php | 4 +- lib/Raven/Breadcrumbs/ErrorHandler.php | 10 +- lib/Raven/Breadcrumbs/MonologHandler.php | 35 +- lib/Raven/Client.php | 178 +++++----- lib/Raven/Compat.php | 4 +- lib/Raven/Context.php | 5 +- lib/Raven/CurlHandler.php | 4 +- lib/Raven/ErrorHandler.php | 20 +- lib/Raven/Exception.php | 4 +- lib/Raven/Processor.php | 10 +- lib/Raven/ReprSerializer.php | 3 +- lib/Raven/SanitizeDataProcessor.php | 6 +- lib/Raven/Serializer.php | 6 +- lib/Raven/Stacktrace.php | 34 +- lib/Raven/TransactionStack.php | 3 +- lib/Raven/Util.php | 4 +- .../Tests/Breadcrumbs/ErrorHandlerTest.php | 4 +- test/Raven/Tests/Breadcrumbs/MonologTest.php | 8 +- test/Raven/Tests/BreadcrumbsTest.php | 4 +- test/Raven/Tests/ClientTest.php | 310 +++++++++--------- test/Raven/Tests/CompatTest.php | 48 +-- test/Raven/Tests/ErrorHandlerTest.php | 20 +- test/Raven/Tests/IntegrationTest.php | 2 +- test/Raven/Tests/ReprSerializerTest.php | 22 +- .../Raven/Tests/SanitizeDataProcessorTest.php | 50 +-- test/Raven/Tests/SerializerTest.php | 34 +- test/Raven/Tests/StacktraceTest.php | 18 +- test/Raven/Tests/TransactionStackTest.php | 2 +- test/Raven/Tests/UtilTest.php | 4 +- test/bootstrap.php | 2 +- 32 files changed, 462 insertions(+), 426 deletions(-) diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php index c96c0af97..191eddd2d 100644 --- a/examples/vanilla/index.php +++ b/examples/vanilla/index.php @@ -5,15 +5,15 @@ define('SENTRY_DSN', 'https://e9ebbd88548a441288393c457ec90441:399aaee02d454e2ca91351f29bdc3a07@app.getsentry.com/3235'); require_once '../../lib/Raven/Autoloader.php'; -Raven_Autoloader::register(); +\Raven\Autoloader::register(); function setupSentry() { - (new \Raven_Client(SENTRY_DSN)) - ->setAppPath(__DIR__) - ->setRelease(Raven_Client::VERSION) - ->setPrefixes(array(__DIR__)) - ->install(); + $object = new \Raven\Client(SENTRY_DSN); + $object->setAppPath(__DIR__) + ->setRelease(\Raven\Client::VERSION) + ->setPrefixes(array(__DIR__)) + ->install(); } function createCrumbs() diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php index d2a2f8cc0..3b1fd1a42 100644 --- a/lib/Raven/Autoloader.php +++ b/lib/Raven/Autoloader.php @@ -9,20 +9,26 @@ * file that was distributed with this source code. */ +namespace Raven; + /** * Autoloads Raven classes. * * @package raven */ -class Raven_Autoloader +class Autoloader { /** - * Registers Raven_Autoloader as an SPL autoloader. + * Registers \Raven\Autoloader as an SPL autoloader. */ public static function register() { ini_set('unserialize_callback_func', 'spl_autoload_call'); - spl_autoload_register(array('Raven_Autoloader', 'autoload')); + spl_autoload_register( + function ($class) { + Autoloader::autoload($class); + } + ); } /** @@ -32,14 +38,14 @@ public static function register() */ public static function autoload($class) { - if (0 !== strpos($class, 'Raven')) { + if (0 !== strpos($class, 'Raven\\')) { return; } - $file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php'; + $file = dirname(__FILE__).'/../'.str_replace(array('\\', "\0"), array('/', ''), $class).'.php'; if (is_file($file)) { /** @noinspection PhpIncludeInspection */ - require $file; + require_once $file; } } } diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php index 12f39902e..fe9f879de 100644 --- a/lib/Raven/Breadcrumbs.php +++ b/lib/Raven/Breadcrumbs.php @@ -8,13 +8,15 @@ * file that was distributed with this source code. */ +namespace Raven; + /** * Raven Breadcrumbs * * @package raven */ -class Raven_Breadcrumbs +class Breadcrumbs { public $count; public $pos; diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index eb340c224..93346bf25 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -1,18 +1,20 @@ ravenClient = $ravenClient; } diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index a6d20307d..2c53125e4 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -1,37 +1,38 @@ Raven_Client::DEBUG, - Logger::INFO => Raven_Client::INFO, - Logger::NOTICE => Raven_Client::INFO, - Logger::WARNING => Raven_Client::WARNING, - Logger::ERROR => Raven_Client::ERROR, - Logger::CRITICAL => Raven_Client::FATAL, - Logger::ALERT => Raven_Client::FATAL, - Logger::EMERGENCY => Raven_Client::FATAL, + Logger::DEBUG => \Raven\Client::DEBUG, + Logger::INFO => \Raven\Client::INFO, + Logger::NOTICE => \Raven\Client::INFO, + Logger::WARNING => \Raven\Client::WARNING, + Logger::ERROR => \Raven\Client::ERROR, + Logger::CRITICAL => \Raven\Client::FATAL, + Logger::ALERT => \Raven\Client::FATAL, + Logger::EMERGENCY => \Raven\Client::FATAL, ); private $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; /** - * @var Raven_Client the client object that sends the message to the server + * @var \Raven\Client the client object that sends the message to the server */ protected $ravenClient; /** - * @param Raven_Client $ravenClient - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param \Raven\Client $ravenClient + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + public function __construct(\Raven\Client $ravenClient, $level = Logger::DEBUG, $bubble = true) { parent::__construct($level, $bubble); @@ -63,7 +64,7 @@ protected function write(array $record) if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { /** - * @var Exception $exc + * @var \Exception $exc */ $exc = $record['context']['exception']; $crumb = array( diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ebfd7bb24..3f4aa771d 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -8,13 +8,15 @@ * file that was distributed with this source code. */ +namespace Raven; + /** * Raven PHP Client * * @package raven */ -class Raven_Client +class Client { const VERSION = '1.7.x-dev'; @@ -31,7 +33,7 @@ class Raven_Client public $breadcrumbs; /** - * @var Raven_Context + * @var \Raven\Context */ public $context; public $extra_data; @@ -93,7 +95,7 @@ class Raven_Client public $trust_x_forwarded_proto; public $mb_detect_order; /** - * @var Raven_Processor[] + * @var \Raven\Processor[] */ public $processors; /** @@ -109,7 +111,7 @@ class Raven_Client public $_pending_events; public $sdk; /** - * @var Raven_CurlHandler + * @var \Raven\CurlHandler */ protected $_curl_handler; /** @@ -141,41 +143,41 @@ public function __construct($options_or_dsn = null, $options = array()) $options = array_merge($options, self::parseDSN($dsn)); } - $this->logger = Raven_Util::get($options, 'logger', 'php'); - $this->server = Raven_Util::get($options, 'server'); - $this->secret_key = Raven_Util::get($options, 'secret_key'); - $this->public_key = Raven_Util::get($options, 'public_key'); - $this->project = Raven_Util::get($options, 'project', 1); - $this->auto_log_stacks = (bool) Raven_Util::get($options, 'auto_log_stacks', false); - $this->name = Raven_Util::get($options, 'name', Raven_Compat::gethostname()); - $this->site = Raven_Util::get($options, 'site', self::_server_variable('SERVER_NAME')); - $this->tags = Raven_Util::get($options, 'tags', array()); - $this->release = Raven_Util::get($options, 'release', null); - $this->environment = Raven_Util::get($options, 'environment', null); - $this->trace = (bool) Raven_Util::get($options, 'trace', true); - $this->timeout = Raven_Util::get($options, 'timeout', 2); - $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT); - $this->exclude = Raven_Util::get($options, 'exclude', array()); + $this->logger = \Raven\Util::get($options, 'logger', 'php'); + $this->server = \Raven\Util::get($options, 'server'); + $this->secret_key = \Raven\Util::get($options, 'secret_key'); + $this->public_key = \Raven\Util::get($options, 'public_key'); + $this->project = \Raven\Util::get($options, 'project', 1); + $this->auto_log_stacks = (bool) \Raven\Util::get($options, 'auto_log_stacks', false); + $this->name = \Raven\Util::get($options, 'name', \Raven\Compat::gethostname()); + $this->site = \Raven\Util::get($options, 'site', self::_server_variable('SERVER_NAME')); + $this->tags = \Raven\Util::get($options, 'tags', array()); + $this->release = \Raven\Util::get($options, 'release', null); + $this->environment = \Raven\Util::get($options, 'environment', null); + $this->trace = (bool) \Raven\Util::get($options, 'trace', true); + $this->timeout = \Raven\Util::get($options, 'timeout', 2); + $this->message_limit = \Raven\Util::get($options, 'message_limit', self::MESSAGE_LIMIT); + $this->exclude = \Raven\Util::get($options, 'exclude', array()); $this->severity_map = null; - $this->http_proxy = Raven_Util::get($options, 'http_proxy'); - $this->extra_data = Raven_Util::get($options, 'extra', array()); - $this->send_callback = Raven_Util::get($options, 'send_callback', null); - $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync'); - $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl'); - $this->curl_ipv4 = Raven_Util::get($options, 'curl_ipv4', true); - $this->ca_cert = Raven_Util::get($options, 'ca_cert', static::get_default_ca_cert()); - $this->verify_ssl = Raven_Util::get($options, 'verify_ssl', true); - $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); - $this->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); - $this->transport = Raven_Util::get($options, 'transport', null); - $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null); - $this->error_types = Raven_Util::get($options, 'error_types', null); + $this->http_proxy = \Raven\Util::get($options, 'http_proxy'); + $this->extra_data = \Raven\Util::get($options, 'extra', array()); + $this->send_callback = \Raven\Util::get($options, 'send_callback', null); + $this->curl_method = \Raven\Util::get($options, 'curl_method', 'sync'); + $this->curl_path = \Raven\Util::get($options, 'curl_path', 'curl'); + $this->curl_ipv4 = \Raven\Util::get($options, 'curl_ipv4', true); + $this->ca_cert = \Raven\Util::get($options, 'ca_cert', static::get_default_ca_cert()); + $this->verify_ssl = \Raven\Util::get($options, 'verify_ssl', true); + $this->curl_ssl_version = \Raven\Util::get($options, 'curl_ssl_version'); + $this->trust_x_forwarded_proto = \Raven\Util::get($options, 'trust_x_forwarded_proto'); + $this->transport = \Raven\Util::get($options, 'transport', null); + $this->mb_detect_order = \Raven\Util::get($options, 'mb_detect_order', null); + $this->error_types = \Raven\Util::get($options, 'error_types', null); // app path is used to determine if code is part of your application - $this->setAppPath(Raven_Util::get($options, 'app_path', null)); - $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null)); + $this->setAppPath(\Raven\Util::get($options, 'app_path', null)); + $this->setExcludedAppPaths(\Raven\Util::get($options, 'excluded_app_paths', null)); // a list of prefixes used to coerce absolute paths into relative - $this->setPrefixes(Raven_Util::get($options, 'prefixes', static::getDefaultPrefixes())); + $this->setPrefixes(\Raven\Util::get($options, 'prefixes', static::getDefaultPrefixes())); $this->processors = $this->setProcessorsFromOptions($options); $this->_lasterror = null; @@ -184,33 +186,33 @@ public function __construct($options_or_dsn = null, $options = array()) $this->_last_event_id = null; $this->_user = null; $this->_pending_events = array(); - $this->context = new Raven_Context(); - $this->breadcrumbs = new Raven_Breadcrumbs(); + $this->context = new \Raven\Context(); + $this->breadcrumbs = new \Raven\Breadcrumbs(); $this->_shutdown_function_has_been_set = false; - $this->sdk = Raven_Util::get($options, 'sdk', array( + $this->sdk = \Raven\Util::get($options, 'sdk', array( 'name' => 'sentry-php', 'version' => self::VERSION, )); - $this->serializer = new Raven_Serializer($this->mb_detect_order); - $this->reprSerializer = new Raven_ReprSerializer($this->mb_detect_order); + $this->serializer = new \Raven\Serializer($this->mb_detect_order); + $this->reprSerializer = new \Raven\ReprSerializer($this->mb_detect_order); if ($this->curl_method == 'async') { - $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); + $this->_curl_handler = new \Raven\CurlHandler($this->get_curl_options()); } - $this->transaction = new Raven_TransactionStack(); + $this->transaction = new \Raven\TransactionStack(); if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) { // @codeCoverageIgnoreStart $this->transaction->push($_SERVER['PATH_INFO']); // @codeCoverageIgnoreEnd } - if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { + if (\Raven\Util::get($options, 'install_default_breadcrumb_handlers', true)) { $this->registerDefaultBreadcrumbHandlers(); } - if (Raven_Util::get($options, 'install_shutdown_handler', true)) { + if (\Raven\Util::get($options, 'install_shutdown_handler', true)) { $this->registerShutdownFunction(); } } @@ -237,9 +239,9 @@ public function close_all_children_link() public function install() { if ($this->error_handler) { - throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this))); + throw new \Raven\Exception(sprintf('%s->install() must only be called once', get_class($this))); } - $this->error_handler = new Raven_ErrorHandler($this, false, $this->error_types); + $this->error_handler = new \Raven\ErrorHandler($this, false, $this->error_types); $this->error_handler->registerExceptionHandler(); $this->error_handler->registerErrorHandler(); $this->error_handler->registerShutdownFunction(); @@ -322,7 +324,7 @@ public function getPrefixes() /** * @param array $value - * @return Raven_Client + * @return \Raven\Client */ public function setPrefixes($value) { @@ -364,7 +366,7 @@ public static function getUserAgent() * the data to the upstream Sentry server. * * @param Callable $value Function to be called - * @return Raven_Client + * @return \Raven\Client */ public function setTransport($value) { @@ -373,29 +375,29 @@ public function setTransport($value) } /** - * @return string[]|Raven_Processor[] + * @return string[]|\Raven\Processor[] */ public static function getDefaultProcessors() { return array( - 'Raven_SanitizeDataProcessor', + '\Raven\SanitizeDataProcessor', ); } /** - * Sets the Raven_Processor sub-classes to be used when data is processed before being + * Sets the \Raven\Processor sub-classes to be used when data is processed before being * sent to Sentry. * * @param $options - * @return Raven_Processor[] + * @return \Raven\Processor[] */ public function setProcessorsFromOptions($options) { $processors = array(); - foreach (Raven_util::get($options, 'processors', static::getDefaultProcessors()) as $processor) { + foreach (\Raven\util::get($options, 'processors', static::getDefaultProcessors()) as $processor) { /** - * @var Raven_Processor $new_processor - * @var Raven_Processor|string $processor + * @var \Raven\Processor $new_processor + * @var \Raven\Processor|string $processor */ $new_processor = new $processor($this); @@ -424,7 +426,7 @@ public static function parseDSN($dsn) $url = parse_url($dsn); $scheme = (isset($url['scheme']) ? $url['scheme'] : ''); if (!in_array($scheme, array('http', 'https'))) { - throw new InvalidArgumentException( + throw new \InvalidArgumentException( 'Unsupported Sentry DSN scheme: '. (!empty($scheme) ? $scheme : '') ); @@ -448,7 +450,7 @@ public static function parseDSN($dsn) $username = (isset($url['user']) ? $url['user'] : null); $password = (isset($url['pass']) ? $url['pass'] : null); if (empty($netloc) || empty($project) || empty($username) || empty($password)) { - throw new InvalidArgumentException('Invalid Sentry DSN: ' . $dsn); + throw new \InvalidArgumentException('Invalid Sentry DSN: ' . $dsn); } return array( @@ -547,10 +549,10 @@ public function captureMessage($message, $params = array(), $data = array(), /** * Log an exception to sentry * - * @param Exception $exception The Exception object. - * @param array $data Additional attributes to pass with this event (see Sentry docs). - * @param mixed $logger - * @param mixed $vars + * @param \Exception $exception The Exception object. + * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param mixed $logger + * @param mixed $vars * @return string|null */ public function captureException($exception, $data = null, $logger = null, $vars = null) @@ -585,14 +587,14 @@ public function captureException($exception, $data = null, $logger = null, $vars array_unshift($trace, $frame_where_exception_thrown); // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('Raven_Stacktrace')) { + if (!class_exists('\Raven\Stacktrace')) { // @codeCoverageIgnoreStart - spl_autoload_call('Raven_Stacktrace'); + spl_autoload_call('\Raven\Stacktrace'); // @codeCoverageIgnoreEnd } $exc_data['stacktrace'] = array( - 'frames' => Raven_Stacktrace::get_stack_info( + 'frames' => \Raven\Stacktrace::get_stack_info( $trace, $this->trace, $vars, $this->message_limit, $this->prefixes, $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer ), @@ -630,7 +632,7 @@ public function captureLastError() return null; } - $e = new ErrorException( + $e = new \ErrorException( @$error['message'], 0, @$error['type'], @$error['file'], @$error['line'] ); @@ -671,7 +673,7 @@ public function getLastEventID() protected function registerDefaultBreadcrumbHandlers() { - $handler = new Raven_Breadcrumbs_ErrorHandler($this); + $handler = new \Raven\Breadcrumbs\ErrorHandler($this); $handler->install(); } @@ -841,15 +843,15 @@ public function capture($data, $stack = null, $vars = null) if (!empty($stack)) { // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('Raven_Stacktrace')) { + if (!class_exists('\Raven\Stacktrace')) { // @codeCoverageIgnoreStart - spl_autoload_call('Raven_Stacktrace'); + spl_autoload_call('\Raven\Stacktrace'); // @codeCoverageIgnoreEnd } if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = array( - 'frames' => Raven_Stacktrace::get_stack_info( + 'frames' => \Raven\Stacktrace::get_stack_info( $stack, $this->trace, $vars, $this->message_limit, $this->prefixes, $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer ), @@ -894,7 +896,7 @@ public function sanitize(&$data) } /** - * Process data through all defined Raven_Processor sub-classes + * Process data through all defined \Raven\Processor sub-classes * * @param array $data Associative array of data to log */ @@ -923,7 +925,7 @@ public function sendUnsentErrors() */ public function encode(&$data) { - $message = Raven_Compat::json_encode($data); + $message = \Raven\Compat::json_encode($data); if ($message === false) { if (function_exists('json_last_error_msg')) { $this->_lasterror = json_last_error_msg(); @@ -1157,7 +1159,7 @@ protected function send_http_synchronous($url, $data, $headers) * Generate a Sentry authorization header string * * @param string $timestamp Timestamp when the event occurred - * @param string $client HTTP client name (not Raven_Client object) + * @param string $client HTTP client name (not \Raven\Client object) * @param string $api_key Sentry API key * @param string $secret_key Sentry API key * @return string @@ -1292,27 +1294,27 @@ public function translateSeverity($severity) return $this->severity_map[$severity]; } switch ($severity) { - case E_ERROR: return Raven_Client::ERROR; - case E_WARNING: return Raven_Client::WARN; - case E_PARSE: return Raven_Client::ERROR; - case E_NOTICE: return Raven_Client::INFO; - case E_CORE_ERROR: return Raven_Client::ERROR; - case E_CORE_WARNING: return Raven_Client::WARN; - case E_COMPILE_ERROR: return Raven_Client::ERROR; - case E_COMPILE_WARNING: return Raven_Client::WARN; - case E_USER_ERROR: return Raven_Client::ERROR; - case E_USER_WARNING: return Raven_Client::WARN; - case E_USER_NOTICE: return Raven_Client::INFO; - case E_STRICT: return Raven_Client::INFO; - case E_RECOVERABLE_ERROR: return Raven_Client::ERROR; + case E_ERROR: return \Raven\Client::ERROR; + case E_WARNING: return \Raven\Client::WARN; + case E_PARSE: return \Raven\Client::ERROR; + case E_NOTICE: return \Raven\Client::INFO; + case E_CORE_ERROR: return \Raven\Client::ERROR; + case E_CORE_WARNING: return \Raven\Client::WARN; + case E_COMPILE_ERROR: return \Raven\Client::ERROR; + case E_COMPILE_WARNING: return \Raven\Client::WARN; + case E_USER_ERROR: return \Raven\Client::ERROR; + case E_USER_WARNING: return \Raven\Client::WARN; + case E_USER_NOTICE: return \Raven\Client::INFO; + case E_STRICT: return \Raven\Client::INFO; + case E_RECOVERABLE_ERROR: return \Raven\Client::ERROR; } if (version_compare(PHP_VERSION, '5.3.0', '>=')) { switch ($severity) { - case E_DEPRECATED: return Raven_Client::WARN; - case E_USER_DEPRECATED: return Raven_Client::WARN; + case E_DEPRECATED: return \Raven\Client::WARN; + case E_USER_DEPRECATED: return \Raven\Client::WARN; } } - return Raven_Client::ERROR; + return \Raven\Client::ERROR; } /** diff --git a/lib/Raven/Compat.php b/lib/Raven/Compat.php index 4d5f719aa..c0dc58d0e 100644 --- a/lib/Raven/Compat.php +++ b/lib/Raven/Compat.php @@ -9,7 +9,9 @@ * file that was distributed with this source code. */ -class Raven_Compat +namespace Raven; + +class Compat { public static function gethostname() { diff --git a/lib/Raven/Context.php b/lib/Raven/Context.php index 7487da4a6..de79166ec 100644 --- a/lib/Raven/Context.php +++ b/lib/Raven/Context.php @@ -1,10 +1,13 @@ registerExceptionHandler(); * $error_handler->registerErrorHandler(); * $error_handler->registerShutdownFunction(); @@ -24,14 +26,14 @@ // TODO(dcramer): deprecate default error types in favor of runtime configuration // unless a reason can be determined that making them dynamic is better. They // currently are not used outside of the fatal handler. -class Raven_ErrorHandler +class ErrorHandler { private $old_exception_handler; private $call_existing_exception_handler = false; private $old_error_handler; private $call_existing_error_handler = false; private $reservedMemory; - /** @var Raven_Client */ + /** @var \Raven\Client */ private $client; private $send_errors_last = false; private $fatal_error_types = array( @@ -95,7 +97,7 @@ public function handleError($type, $message, $file = '', $line = 0, $context = a $error_types = error_reporting(); } if ($error_types & $type) { - $e = new ErrorException($message, 0, $type, $file, $line); + $e = new \ErrorException($message, 0, $type, $file, $line); $this->handleException($e, true, $context); } } @@ -126,7 +128,7 @@ public function handleFatalError() } if ($this->shouldCaptureFatalError($error['type'])) { - $e = new ErrorException( + $e = new \ErrorException( @$error['message'], 0, @$error['type'], @$error['file'], @$error['line'] ); @@ -145,7 +147,7 @@ public function shouldCaptureFatalError($type) * * @param bool $call_existing Call any existing exception handlers after processing * this instance. - * @return Raven_ErrorHandler + * @return \Raven\ErrorHandler */ public function registerExceptionHandler($call_existing = true) { @@ -161,7 +163,7 @@ public function registerExceptionHandler($call_existing = true) * @param bool $call_existing Call any existing errors handlers after processing * this instance. * @param array $error_types All error types that should be sent. - * @return Raven_ErrorHandler + * @return \Raven\ErrorHandler */ public function registerErrorHandler($call_existing = true, $error_types = null) { @@ -179,7 +181,7 @@ public function registerErrorHandler($call_existing = true, $error_types = null) * * @param int $reservedMemorySize Number of kilobytes memory space to reserve, * which is utilized when handling fatal errors. - * @return Raven_ErrorHandler + * @return \Raven\ErrorHandler */ public function registerShutdownFunction($reservedMemorySize = 10) { diff --git a/lib/Raven/Exception.php b/lib/Raven/Exception.php index a6199f4c1..b88ec3849 100644 --- a/lib/Raven/Exception.php +++ b/lib/Raven/Exception.php @@ -1,4 +1,6 @@ client = $client; } diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php index dc704695b..eca2b47d1 100644 --- a/lib/Raven/ReprSerializer.php +++ b/lib/Raven/ReprSerializer.php @@ -8,13 +8,14 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace Raven; /** * Serializes a value into a representation that should reasonably suggest * both the type and value, and be serializable into JSON. * @package raven */ -class Raven_ReprSerializer extends Raven_Serializer +class ReprSerializer extends \Raven\Serializer { protected function serializeValue($value) { diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/SanitizeDataProcessor.php index 55caff328..a607a4a72 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/SanitizeDataProcessor.php @@ -1,11 +1,13 @@ fields_re = self::FIELDS_RE; diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 3889d0bd6..6dabc7050 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -15,6 +15,8 @@ * limitations under the License. */ +namespace Raven; + /** * This helper is based on code from Facebook's Phabricator project * @@ -24,7 +26,7 @@ * * @package raven */ -class Raven_Serializer +class Serializer { /* * The default mb detect order @@ -132,7 +134,7 @@ public function getMbDetectOrder() /** * @param string $mb_detect_order * - * @return Raven_Serializer + * @return \Raven\Serializer * @codeCoverageIgnore */ public function setMbDetectOrder($mb_detect_order) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 10a00ceba..cb436dd46 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -1,10 +1,12 @@ seek($target); $cur_lineno = $target+1; @@ -278,7 +280,7 @@ private static function read_source_file($filename, $lineno, $context_lines = 5) } $file->next(); } - } catch (RuntimeException $exc) { + } catch (\RuntimeException $exc) { return $frame; } diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index 9446809b1..2b8d5e733 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -7,8 +7,9 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace Raven; -class Raven_TransactionStack +class TransactionStack { public function __construct() { diff --git a/lib/Raven/Util.php b/lib/Raven/Util.php index 8f684b527..4fe8e570f 100644 --- a/lib/Raven/Util.php +++ b/lib/Raven/Util.php @@ -9,13 +9,15 @@ * file that was distributed with this source code. */ +namespace Raven; + /** * Utilities * * @package raven */ -class Raven_Util +class Util { /** * Because we love Python, this works much like dict.get() in Python. diff --git a/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php b/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php index b5c993783..f01b9c593 100644 --- a/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php +++ b/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php @@ -13,11 +13,11 @@ class Raven_Tests_ErrorHandlerBreadcrumbHandlerTest extends PHPUnit_Framework_Te { public function testSimple() { - $client = new \Raven_Client(array( + $client = new \Raven\Client(array( 'install_default_breadcrumb_handlers' => false, )); - $handler = new \Raven_Breadcrumbs_ErrorHandler($client); + $handler = new \Raven\Breadcrumbs\ErrorHandler($client); $handler->handleError(E_WARNING, 'message'); $crumbs = $client->breadcrumbs->fetch(); diff --git a/test/Raven/Tests/Breadcrumbs/MonologTest.php b/test/Raven/Tests/Breadcrumbs/MonologTest.php index 1c26f1107..0edaeac41 100644 --- a/test/Raven/Tests/Breadcrumbs/MonologTest.php +++ b/test/Raven/Tests/Breadcrumbs/MonologTest.php @@ -35,10 +35,10 @@ protected function getSampleErrorMessage() public function testSimple() { - $client = new \Raven_Client(array( + $client = new \Raven\Client(array( 'install_default_breadcrumb_handlers' => false, )); - $handler = new \Raven_Breadcrumbs_MonologHandler($client); + $handler = new \Raven\Breadcrumbs\MonologHandler($client); $logger = new Monolog\Logger('sentry'); $logger->pushHandler($handler); @@ -53,10 +53,10 @@ public function testSimple() public function testErrorInMessage() { - $client = new \Raven_Client(array( + $client = new \Raven\Client(array( 'install_default_breadcrumb_handlers' => false, )); - $handler = new \Raven_Breadcrumbs_MonologHandler($client); + $handler = new \Raven\Breadcrumbs\MonologHandler($client); $logger = new Monolog\Logger('sentry'); $logger->pushHandler($handler); diff --git a/test/Raven/Tests/BreadcrumbsTest.php b/test/Raven/Tests/BreadcrumbsTest.php index 5046459da..95724a8bf 100644 --- a/test/Raven/Tests/BreadcrumbsTest.php +++ b/test/Raven/Tests/BreadcrumbsTest.php @@ -13,7 +13,7 @@ class Raven_Tests_BreadcrumbsTest extends PHPUnit_Framework_TestCase { public function testBuffer() { - $breadcrumbs = new Raven_Breadcrumbs(10); + $breadcrumbs = new \Raven\Breadcrumbs(10); for ($i = 0; $i <= 10; $i++) { $breadcrumbs->record(array('message' => $i)); } @@ -28,7 +28,7 @@ public function testBuffer() public function testJson() { - $breadcrumbs = new Raven_Breadcrumbs(1); + $breadcrumbs = new \Raven\Breadcrumbs(1); $breadcrumbs->record(array('message' => 'test')); $json = $breadcrumbs->to_json(); diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 63d8b99cd..26035d3e3 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -22,7 +22,7 @@ function invalid_encoding() // XXX: Is there a better way to stub the client? -class Dummy_Raven_Client extends Raven_Client +class Dummy_Raven_Client extends \Raven\Client { private $__sent_events = array(); public $dummy_breadcrumbs_handlers_has_set = false; @@ -89,7 +89,7 @@ public function test_get_current_url() } } -class Dummy_Raven_Client_With_Overrided_Direct_Send extends Raven_Client +class Dummy_Raven_Client_With_Overrided_Direct_Send extends \Raven\Client { public $_send_http_asynchronous_curl_exec_called = false; public $_send_http_synchronous = false; @@ -126,7 +126,7 @@ public function get_curl_handler() return $this->_curl_handler; } - public function set_curl_handler(Raven_CurlHandler $value) + public function set_curl_handler(\Raven\CurlHandler $value) { $this->_curl_handler = $value; } @@ -149,7 +149,7 @@ public static function is_http_request() } } -class Dummy_Raven_Client_With_Sync_Override extends Raven_Client +class Dummy_Raven_Client_With_Sync_Override extends \Raven\Client { private static $_test_data = null; @@ -176,7 +176,7 @@ protected function buildCurlCommand($url, $data, $headers) } } -class Dummy_Raven_CurlHandler extends Raven_CurlHandler +class Dummy_Raven_CurlHandler extends \Raven\CurlHandler { public $_set_url; public $_set_data; @@ -239,7 +239,7 @@ private function create_chained_exception() public function testParseDSNHttp() { - $result = Raven_Client::ParseDSN('http://public:secret@example.com/1'); + $result = \Raven\Client::ParseDSN('http://public:secret@example.com/1'); $this->assertEquals(1, $result['project']); $this->assertEquals('http://example.com/api/1/store/', $result['server']); @@ -249,7 +249,7 @@ public function testParseDSNHttp() public function testParseDSNHttps() { - $result = Raven_Client::ParseDSN('https://public:secret@example.com/1'); + $result = \Raven\Client::ParseDSN('https://public:secret@example.com/1'); $this->assertEquals(1, $result['project']); $this->assertEquals('https://example.com/api/1/store/', $result['server']); @@ -259,7 +259,7 @@ public function testParseDSNHttps() public function testParseDSNPath() { - $result = Raven_Client::ParseDSN('http://public:secret@example.com/app/1'); + $result = \Raven\Client::ParseDSN('http://public:secret@example.com/app/1'); $this->assertEquals(1, $result['project']); $this->assertEquals('http://example.com/app/api/1/store/', $result['server']); @@ -269,7 +269,7 @@ public function testParseDSNPath() public function testParseDSNPort() { - $result = Raven_Client::ParseDSN('http://public:secret@example.com:9000/app/1'); + $result = \Raven\Client::ParseDSN('http://public:secret@example.com:9000/app/1'); $this->assertEquals(1, $result['project']); $this->assertEquals('http://example.com:9000/app/api/1/store/', $result['server']); @@ -280,7 +280,7 @@ public function testParseDSNPort() public function testParseDSNInvalidScheme() { try { - Raven_Client::ParseDSN('gopher://public:secret@/1'); + \Raven\Client::ParseDSN('gopher://public:secret@/1'); $this->fail(); } catch (Exception $e) { return; @@ -290,7 +290,7 @@ public function testParseDSNInvalidScheme() public function testParseDSNMissingNetloc() { try { - Raven_Client::ParseDSN('http://public:secret@/1'); + \Raven\Client::ParseDSN('http://public:secret@/1'); $this->fail(); } catch (Exception $e) { return; @@ -300,7 +300,7 @@ public function testParseDSNMissingNetloc() public function testParseDSNMissingProject() { try { - Raven_Client::ParseDSN('http://public:secret@example.com'); + \Raven\Client::ParseDSN('http://public:secret@example.com'); $this->fail(); } catch (Exception $e) { return; @@ -312,18 +312,18 @@ public function testParseDSNMissingProject() */ public function testParseDSNMissingPublicKey() { - Raven_Client::ParseDSN('http://:secret@example.com/1'); + \Raven\Client::ParseDSN('http://:secret@example.com/1'); } /** * @expectedException InvalidArgumentException */ public function testParseDSNMissingSecretKey() { - Raven_Client::ParseDSN('http://public@example.com/1'); + \Raven\Client::ParseDSN('http://public@example.com/1'); } /** - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function testDsnFirstArgument() { @@ -336,7 +336,7 @@ public function testDsnFirstArgument() } /** - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function testDsnFirstArgumentWithOptions() { @@ -352,7 +352,7 @@ public function testDsnFirstArgumentWithOptions() } /** - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function testOptionsFirstArgument() { @@ -365,7 +365,7 @@ public function testOptionsFirstArgument() } /** - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function testDsnInOptionsFirstArg() { @@ -380,7 +380,7 @@ public function testDsnInOptionsFirstArg() } /** - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function testDsnInOptionsSecondArg() { @@ -395,7 +395,7 @@ public function testDsnInOptionsSecondArg() } /** - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function testOptionsFirstArgumentWithOptions() { @@ -411,7 +411,7 @@ public function testOptionsFirstArgumentWithOptions() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testOptionsExtraData() { @@ -425,7 +425,7 @@ public function testOptionsExtraData() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testOptionsExtraDataWithNull() { @@ -439,7 +439,7 @@ public function testOptionsExtraDataWithNull() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testEmptyExtraData() { @@ -453,7 +453,7 @@ public function testEmptyExtraData() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testCaptureMessageDoesHandleUninterpolatedMessage() { @@ -467,7 +467,7 @@ public function testCaptureMessageDoesHandleUninterpolatedMessage() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testCaptureMessageDoesHandleInterpolatedMessage() { @@ -481,7 +481,7 @@ public function testCaptureMessageDoesHandleInterpolatedMessage() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() { @@ -499,7 +499,7 @@ public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testCaptureMessageSetsInterface() { @@ -518,7 +518,7 @@ public function testCaptureMessageSetsInterface() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testCaptureMessageHandlesOptionsAsThirdArg() { @@ -537,7 +537,7 @@ public function testCaptureMessageHandlesOptionsAsThirdArg() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testCaptureMessageHandlesLevelAsThirdArg() { @@ -552,7 +552,7 @@ public function testCaptureMessageHandlesLevelAsThirdArg() } /** - * @covers Raven_Client::captureException + * @covers \Raven\Client::captureException */ public function testCaptureExceptionSetsInterfaces() { @@ -582,7 +582,7 @@ public function testCaptureExceptionSetsInterfaces() } /** - * @covers Raven_Client::captureException + * @covers \Raven\Client::captureException */ public function testCaptureExceptionChainedException() { @@ -606,7 +606,7 @@ public function testCaptureExceptionChainedException() } /** - * @covers Raven_Client::captureException + * @covers \Raven\Client::captureException */ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() { @@ -635,7 +635,7 @@ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() } /** - * @covers Raven_Client::captureException + * @covers \Raven\Client::captureException */ public function testCaptureExceptionHandlesOptionsAsSecondArg() { @@ -649,7 +649,7 @@ public function testCaptureExceptionHandlesOptionsAsSecondArg() } /** - * @covers Raven_Client::captureException + * @covers \Raven\Client::captureException */ public function testCaptureExceptionHandlesExcludeOption() { @@ -685,15 +685,15 @@ public function testCaptureExceptionInvalidUTF8() } /** - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function testDoesRegisterProcessors() { $client = new Dummy_Raven_Client(array( - 'processors' => array('Raven_SanitizeDataProcessor'), + 'processors' => array('\Raven\\SanitizeDataProcessor'), )); $this->assertEquals(1, count($client->processors)); - $this->assertInstanceOf('Raven_SanitizeDataProcessor', $client->processors[0]); + $this->assertInstanceOf('\Raven\\SanitizeDataProcessor', $client->processors[0]); } public function testProcessDoesCallProcessors() @@ -713,8 +713,8 @@ public function testProcessDoesCallProcessors() } /** - * @covers Raven_Client::__construct - * @covers Raven_Client::getDefaultProcessors + * @covers \Raven\Client::__construct + * @covers \Raven\Client::getDefaultProcessors */ public function testDefaultProcessorsAreUsed() { @@ -725,18 +725,18 @@ public function testDefaultProcessorsAreUsed() } /** - * @covers Raven_Client::getDefaultProcessors + * @covers \Raven\Client::getDefaultProcessors */ public function testDefaultProcessorsContainSanitizeDataProcessor() { $defaults = Dummy_Raven_Client::getDefaultProcessors(); - $this->assertTrue(in_array('Raven_SanitizeDataProcessor', $defaults)); + $this->assertTrue(in_array('\Raven\\SanitizeDataProcessor', $defaults)); } /** - * @covers Raven_Client::__construct - * @covers Raven_Client::get_default_data + * @covers \Raven\Client::__construct + * @covers \Raven\Client::get_default_data */ public function testGetDefaultData() { @@ -760,7 +760,7 @@ public function testGetDefaultData() /** * @backupGlobals - * @covers Raven_Client::get_http_data + * @covers \Raven\Client::get_http_data */ public function testGetHttpData() { @@ -813,8 +813,8 @@ public function testGetHttpData() } /** - * @covers Raven_Client::user_context - * @covers Raven_Client::get_user_data + * @covers \Raven\Client::user_context + * @covers \Raven\Client::get_user_data */ public function testGetUserDataWithSetUser() { @@ -836,7 +836,7 @@ public function testGetUserDataWithSetUser() } /** - * @covers Raven_Client::get_user_data + * @covers \Raven\Client::get_user_data */ public function testGetUserDataWithNoUser() { @@ -851,7 +851,7 @@ public function testGetUserDataWithNoUser() } /** - * @covers Raven_Client::get_auth_header + * @covers \Raven\Client::get_auth_header */ public function testGet_Auth_Header() { @@ -868,7 +868,7 @@ public function testGet_Auth_Header() } /** - * @covers Raven_Client::getAuthHeader + * @covers \Raven\Client::getAuthHeader */ public function testGetAuthHeader() { @@ -883,7 +883,7 @@ public function testGetAuthHeader() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testCaptureMessageWithUserContext() { @@ -901,7 +901,7 @@ public function testCaptureMessageWithUserContext() } /** - * @covers Raven_Client::captureMessage + * @covers \Raven\Client::captureMessage */ public function testCaptureMessageWithUnserializableUserData() { @@ -922,8 +922,8 @@ public function testCaptureMessageWithUnserializableUserData() } /** - * @covers Raven_Client::captureMessage - * @covers Raven_Client::tags_context + * @covers \Raven\Client::captureMessage + * @covers \Raven\Client::tags_context */ public function testCaptureMessageWithTagsContext() { @@ -944,8 +944,8 @@ public function testCaptureMessageWithTagsContext() } /** - * @covers Raven_Client::captureMessage - * @covers Raven_Client::extra_context + * @covers \Raven\Client::captureMessage + * @covers \Raven\Client::extra_context */ public function testCaptureMessageWithExtraContext() { @@ -966,7 +966,7 @@ public function testCaptureMessageWithExtraContext() } /** - * @covers Raven_Client::captureException + * @covers \Raven\Client::captureException */ public function testCaptureExceptionContainingLatin1() { @@ -1024,7 +1024,7 @@ public function testCaptureExceptionInLatin1File() } /** - * @covers Raven_Client::captureLastError + * @covers \Raven\Client::captureLastError */ public function testCaptureLastError() { @@ -1045,7 +1045,7 @@ public function testCaptureLastError() } /** - * @covers Raven_Client::getLastEventID + * @covers \Raven\Client::getLastEventID */ public function testGetLastEventID() { @@ -1055,14 +1055,14 @@ public function testGetLastEventID() } /** - * @covers Raven_Client::setTransport + * @covers \Raven\Client::setTransport */ public function testCustomTransport() { $events = array(); // transport test requires default client - $client = new Raven_Client('https://public:secret@sentry.example.com/1', array( + $client = new \Raven\Client('https://public:secret@sentry.example.com/1', array( 'install_default_breadcrumb_handlers' => false, )); $client->setTransport(function ($client, $data) use (&$events) { @@ -1073,7 +1073,7 @@ public function testCustomTransport() } /** - * @covers Raven_Client::setAppPath + * @covers \Raven\Client::setAppPath */ public function testAppPathLinux() { @@ -1088,7 +1088,7 @@ public function testAppPathLinux() } /** - * @covers Raven_Client::setAppPath + * @covers \Raven\Client::setAppPath */ public function testAppPathWindows() { @@ -1099,7 +1099,7 @@ public function testAppPathWindows() } /** - * @expectedException Raven_Exception + * @expectedException Raven\Exception * @expectedExceptionMessage Raven_Client->install() must only be called once */ public function testCannotInstallTwice() @@ -1128,7 +1128,7 @@ public function cb3(&$data) } /** - * @covers Raven_Client::send + * @covers \Raven\Client::send */ public function testSendCallback() { @@ -1150,7 +1150,7 @@ public function testSendCallback() } /** - * @covers Raven_Client::sanitize + * @covers \Raven\Client::sanitize */ public function testSanitizeExtra() { @@ -1176,7 +1176,7 @@ public function testSanitizeExtra() } /** - * @covers Raven_Client::sanitize + * @covers \Raven\Client::sanitize */ public function testSanitizeTags() { @@ -1194,7 +1194,7 @@ public function testSanitizeTags() } /** - * @covers Raven_Client::sanitize + * @covers \Raven\Client::sanitize */ public function testSanitizeUser() { @@ -1210,7 +1210,7 @@ public function testSanitizeUser() } /** - * @covers Raven_Client::sanitize + * @covers \Raven\Client::sanitize */ public function testSanitizeRequest() { @@ -1236,7 +1236,7 @@ public function testSanitizeRequest() } /** - * @covers Raven_Client::sanitize + * @covers \Raven\Client::sanitize */ public function testSanitizeContexts() { @@ -1268,7 +1268,7 @@ public function testSanitizeContexts() } /** - * @covers Raven_Client::buildCurlCommand + * @covers \Raven\Client::buildCurlCommand */ public function testBuildCurlCommandEscapesInput() { @@ -1289,7 +1289,7 @@ public function testBuildCurlCommandEscapesInput() } /** - * @covers Raven_Client::user_context + * @covers \Raven\Client::user_context */ public function testUserContextWithoutMerge() { @@ -1300,7 +1300,7 @@ public function testUserContextWithoutMerge() } /** - * @covers Raven_Client::user_context + * @covers \Raven\Client::user_context */ public function testUserContextWithMerge() { @@ -1311,7 +1311,7 @@ public function testUserContextWithMerge() } /** - * @covers Raven_Client::user_context + * @covers \Raven\Client::user_context */ public function testUserContextWithMergeAndNull() { @@ -1329,8 +1329,8 @@ public function testUserContextWithMergeAndNull() * @param array $options * @param string $expected - the url expected * @param string $message - fail message - * @covers Raven_Client::get_current_url - * @covers Raven_Client::isHttps + * @covers \Raven\Client::get_current_url + * @covers \Raven\Client::isHttps */ public function testCurrentUrl($serverVars, $options, $expected, $message) { @@ -1413,11 +1413,11 @@ public function currentUrlProvider() } /** - * @covers Raven_Client::uuid4() + * @covers \Raven\Client::uuid4() */ public function testUuid4() { - $method = new ReflectionMethod('Raven_Client', 'uuid4'); + $method = new ReflectionMethod('\Raven\\Client', 'uuid4'); $method->setAccessible(true); for ($i = 0; $i < 1000; $i++) { $this->assertRegExp('/^[0-9a-z-]+$/', $method->invoke(null)); @@ -1425,32 +1425,32 @@ public function testUuid4() } /** - * @covers Raven_Client::getEnvironment - * @covers Raven_Client::setEnvironment - * @covers Raven_Client::getRelease - * @covers Raven_Client::setRelease - * @covers Raven_Client::getAppPath - * @covers Raven_Client::setAppPath - * @covers Raven_Client::getExcludedAppPaths - * @covers Raven_Client::setExcludedAppPaths - * @covers Raven_Client::getPrefixes - * @covers Raven_Client::setPrefixes - * @covers Raven_Client::getSendCallback - * @covers Raven_Client::setSendCallback - * @covers Raven_Client::getTransport - * @covers Raven_Client::setTransport - * @covers Raven_Client::getServerEndpoint - * @covers Raven_Client::getLastError - * @covers Raven_Client::getLastEventID - * @covers Raven_Client::get_extra_data - * @covers Raven_Client::setProcessors - * @covers Raven_Client::getLastSentryError - * @covers Raven_Client::getShutdownFunctionHasBeenSet + * @covers \Raven\Client::getEnvironment + * @covers \Raven\Client::setEnvironment + * @covers \Raven\Client::getRelease + * @covers \Raven\Client::setRelease + * @covers \Raven\Client::getAppPath + * @covers \Raven\Client::setAppPath + * @covers \Raven\Client::getExcludedAppPaths + * @covers \Raven\Client::setExcludedAppPaths + * @covers \Raven\Client::getPrefixes + * @covers \Raven\Client::setPrefixes + * @covers \Raven\Client::getSendCallback + * @covers \Raven\Client::setSendCallback + * @covers \Raven\Client::getTransport + * @covers \Raven\Client::setTransport + * @covers \Raven\Client::getServerEndpoint + * @covers \Raven\Client::getLastError + * @covers \Raven\Client::getLastEventID + * @covers \Raven\Client::get_extra_data + * @covers \Raven\Client::setProcessors + * @covers \Raven\Client::getLastSentryError + * @covers \Raven\Client::getShutdownFunctionHasBeenSet */ public function testGettersAndSetters() { $client = new Dummy_Raven_Client(); - $property_method__convert_path = new ReflectionMethod('Raven_Client', '_convertPath'); + $property_method__convert_path = new ReflectionMethod('\Raven\\Client', '_convertPath'); $property_method__convert_path->setAccessible(true); $callable = array($this, 'stabClosureVoid'); @@ -1495,7 +1495,7 @@ public function testGettersAndSetters() } } - private function subTestGettersAndSettersDatum(Raven_Client $client, $datum) + private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) { if (count($datum) == 3) { list($property_name, $function_name, $value_in) = $datum; @@ -1509,7 +1509,7 @@ private function subTestGettersAndSettersDatum(Raven_Client $client, $datum) $method_get_name = 'get'.$function_name; $method_set_name = 'set'.$function_name; - $property = new ReflectionProperty('Raven_Client', $property_name); + $property = new ReflectionProperty('\Raven\\Client', $property_name); $property->setAccessible(true); if (method_exists($client, $method_set_name)) { @@ -1524,7 +1524,7 @@ private function subTestGettersAndSettersDatum(Raven_Client $client, $datum) if (method_exists($client, $method_get_name)) { $property->setValue($client, $value_out); - $reflection = new ReflectionMethod('Raven_Client', $method_get_name); + $reflection = new ReflectionMethod('\Raven\Client', $method_get_name); if ($reflection->isPublic()) { $actual_value = $client->$method_get_name(); $this->assertMixedValueAndArray($value_out, $actual_value); @@ -1555,11 +1555,11 @@ private function assertMixedValueAndArray($expected_value, $actual_value) } /** - * @covers Raven_Client::_convertPath + * @covers \Raven\Client::_convertPath */ public function test_convertPath() { - $property = new ReflectionMethod('Raven_Client', '_convertPath'); + $property = new ReflectionMethod('\Raven\Client', '_convertPath'); $property->setAccessible(true); $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar')); @@ -1571,36 +1571,36 @@ public function test_convertPath() } /** - * @covers Raven_Client::getDefaultProcessors + * @covers \Raven\Client::getDefaultProcessors */ public function testGetDefaultProcessors() { - foreach (Raven_Client::getDefaultProcessors() as $class_name) { + foreach (\Raven\Client::getDefaultProcessors() as $class_name) { $this->assertInternalType('string', $class_name); $this->assertTrue(class_exists($class_name)); $reflection = new ReflectionClass($class_name); - $this->assertTrue($reflection->isSubclassOf('Raven_Processor')); + $this->assertTrue($reflection->isSubclassOf('\Raven\\Processor')); $this->assertFalse($reflection->isAbstract()); } } /** - * @covers Raven_Client::get_default_ca_cert + * @covers \Raven\Client::get_default_ca_cert */ public function testGet_default_ca_cert() { - $reflection = new ReflectionMethod('Raven_Client', 'get_default_ca_cert'); + $reflection = new ReflectionMethod('\Raven\Client', 'get_default_ca_cert'); $reflection->setAccessible(true); $this->assertFileExists($reflection->invoke(null)); } /** - * @covers Raven_Client::translateSeverity - * @covers Raven_Client::registerSeverityMap + * @covers \Raven\Client::translateSeverity + * @covers \Raven\Client::registerSeverityMap */ public function testTranslateSeverity() { - $reflection = new ReflectionProperty('Raven_Client', 'severity_map'); + $reflection = new ReflectionProperty('\Raven\Client', 'severity_map'); $reflection->setAccessible(true); $client = new Dummy_Raven_Client(); @@ -1647,11 +1647,11 @@ public function testTranslateSeverity() } /** - * @covers Raven_Client::getUserAgent + * @covers \Raven\Client::getUserAgent */ public function testGetUserAgent() { - $this->assertRegExp('|^[0-9a-z./_-]+$|i', Raven_Client::getUserAgent()); + $this->assertRegExp('|^[0-9a-z./_-]+$|i', \Raven\Client::getUserAgent()); } public function testCaptureExceptionWithLogger() @@ -1666,10 +1666,10 @@ public function testCaptureExceptionWithLogger() } /** - * @covers Raven_Client::__construct - * @covers Raven_Client::send - * @covers Raven_Client::send_remote - * @covers Raven_Client::send_http + * @covers \Raven\Client::__construct + * @covers \Raven\Client::send + * @covers \Raven\Client::send_remote + * @covers \Raven\Client::send_http */ public function testCurl_method() { @@ -1697,10 +1697,10 @@ public function testCurl_method() } /** - * @covers Raven_Client::__construct - * @covers Raven_Client::send - * @covers Raven_Client::send_remote - * @covers Raven_Client::send_http + * @covers \Raven\Client::__construct + * @covers \Raven\Client::send + * @covers \Raven\Client::send_remote + * @covers \Raven\Client::send_http */ public function testCurl_method_async() { @@ -1713,9 +1713,9 @@ public function testCurl_method_async() ); $object = $client->get_curl_handler(); $this->assertInternalType('object', $object); - $this->assertEquals('Raven_CurlHandler', get_class($object)); + $this->assertEquals('Raven\\CurlHandler', get_class($object)); - $reflection = new ReflectionProperty('Raven_CurlHandler', 'options'); + $reflection = new ReflectionProperty('Raven\\CurlHandler', 'options'); $reflection->setAccessible(true); $this->assertEquals($reflection->getValue($object), $client->get_curl_options()); @@ -1730,7 +1730,7 @@ public function testCurl_method_async() /** * @backupGlobals - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function testConstructWithServerDSN() { @@ -1744,11 +1744,11 @@ public function testConstructWithServerDSN() /** * @backupGlobals - * @covers Raven_Client::_server_variable + * @covers \Raven\Client::_server_variable */ public function test_server_variable() { - $method = new ReflectionMethod('Raven_Client', '_server_variable'); + $method = new ReflectionMethod('\Raven\Client', '_server_variable'); $method->setAccessible(true); foreach ($_SERVER as $key => $value) { $actual = $method->invoke(null, $key); @@ -1778,7 +1778,7 @@ public function testEncodeTooDepth() $value = $client->encode($data_broken); if (!function_exists('json_encode') or version_compare(PHP_VERSION, '5.5.0', '>=')) { $this->assertFalse($value, 'Broken data encoded successfully with '. - (function_exists('json_encode') ? 'native method' : 'Raven_Compat::_json_encode')); + (function_exists('json_encode') ? 'native method' : '\Raven\\Compat::_json_encode')); } else { if ($value !== false) { $this->markTestSkipped(); @@ -1792,10 +1792,10 @@ public function testEncode() { $client = new Dummy_Raven_Client(); $data = array('some' => (object)array('value' => 'data'), 'foo' => array('bar', null, 123), false); - $json_stringify = Raven_Compat::json_encode($data); + $json_stringify = \Raven\Compat::json_encode($data); $value = $client->encode($data); $this->assertNotFalse($value); - $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value, 'Raven_Client::encode returned malformed data'); + $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value, '\Raven\\Client::encode returned malformed data'); $decoded = base64_decode($value); $this->assertInternalType('string', $decoded, 'Can not use base64 decode on the encoded blob'); if (function_exists("gzcompress")) { @@ -1807,13 +1807,13 @@ public function testEncode() } /** - * @covers Raven_Client::__construct - * @covers Raven_Client::registerDefaultBreadcrumbHandlers + * @covers \Raven\Client::__construct + * @covers \Raven\Client::registerDefaultBreadcrumbHandlers */ public function testRegisterDefaultBreadcrumbHandlers() { $previous = set_error_handler(array($this, 'stabClosureErrorHandler'), E_USER_NOTICE); - new Raven_Client(null, array()); + new \Raven\Client(null, array()); $this->_closure_called = false; trigger_error('foobar', E_USER_NOTICE); $u = $this->_closure_called; @@ -1829,7 +1829,7 @@ public function testRegisterDefaultBreadcrumbHandlers() } else { $offset = 2; } - $this->assertEquals('Raven_Breadcrumbs_ErrorHandler', $debug_backtrace[$offset]['class']); + $this->assertEquals('Raven\\Breadcrumbs\\ErrorHandler', $debug_backtrace[$offset]['class']); } private $_closure_called = false; @@ -1864,9 +1864,9 @@ public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, } /** - * @covers Raven_Client::onShutdown - * @covers Raven_Client::sendUnsentErrors - * @covers Raven_Client::capture + * @covers \Raven\Client::onShutdown + * @covers \Raven\Client::sendUnsentErrors + * @covers \Raven\Client::capture */ public function testOnShutdown() { @@ -1918,7 +1918,7 @@ public function testOnShutdown() } /** - * @covers Raven_Client::send + * @covers \Raven\Client::send */ public function testNonWorkingSendSendCallback() { @@ -1949,7 +1949,7 @@ public function testNonWorkingSendSendCallback() } /** - * @covers Raven_Client::send + * @covers \Raven\Client::send */ public function testNonWorkingSendDSNEmpty() { @@ -1966,7 +1966,7 @@ public function testNonWorkingSendDSNEmpty() } /** - * @covers Raven_Client::send + * @covers \Raven\Client::send */ public function testNonWorkingSendSetTransport() { @@ -1995,7 +1995,7 @@ public function testNonWorkingSendSetTransport() } /** - * @covers Raven_Client::__construct + * @covers \Raven\Client::__construct */ public function test__construct_handlers() { @@ -2014,8 +2014,8 @@ public function test__construct_handlers() } /** - * @covers Raven_Client::__destruct - * @covers Raven_Client::close_all_children_link + * @covers \Raven\Client::__destruct + * @covers \Raven\Client::close_all_children_link */ public function test__destruct_calls_close_functions() { @@ -2032,7 +2032,7 @@ public function test__destruct_calls_close_functions() } /** - * @covers Raven_Client::get_user_data + * @covers \Raven\Client::get_user_data */ public function testGet_user_data() { @@ -2067,13 +2067,13 @@ public function testGet_user_data() } /** - * @covers Raven_Client::capture - * @covers Raven_Client::setRelease - * @covers Raven_Client::setEnvironment + * @covers \Raven\Client::capture + * @covers \Raven\Client::setRelease + * @covers \Raven\Client::setEnvironment */ public function testCaptureLevel() { - foreach (array(Raven_Client::MESSAGE_LIMIT * 3, 100) as $length) { + foreach (array(\Raven\Client::MESSAGE_LIMIT * 3, 100) as $length) { $message = ''; for ($i = 0; $i < $length; $i++) { $message .= chr($i % 256); @@ -2085,7 +2085,7 @@ public function testCaptureLevel() $event = array_pop($events); $this->assertEquals('error', $event['level']); - $this->assertEquals(substr($message, 0, min(Raven_Client::MESSAGE_LIMIT, $length)), $event['message']); + $this->assertEquals(substr($message, 0, min(\Raven\Client::MESSAGE_LIMIT, $length)), $event['message']); $this->assertArrayNotHasKey('release', $event); $this->assertArrayNotHasKey('environment', $event); } @@ -2134,7 +2134,7 @@ public function testCaptureLevel() } /** - * @covers Raven_Client::capture + * @covers \Raven\Client::capture */ public function testCaptureNoUserAndRequest() { @@ -2156,7 +2156,7 @@ public function testCaptureNoUserAndRequest() } /** - * @covers Raven_Client::capture + * @covers \Raven\Client::capture */ public function testCaptureNonEmptyBreadcrumb() { @@ -2179,7 +2179,7 @@ public function testCaptureNonEmptyBreadcrumb() /** - * @covers Raven_Client::capture + * @covers \Raven\Client::capture */ public function testCaptureAutoLogStacks() { @@ -2192,7 +2192,7 @@ public function testCaptureAutoLogStacks() } /** - * @covers Raven_Client::send_http_asynchronous_curl_exec + * @covers \Raven\Client::send_http_asynchronous_curl_exec */ public function testSend_http_asynchronous_curl_exec() { @@ -2211,12 +2211,12 @@ public function testSend_http_asynchronous_curl_exec() } /** - * @covers Raven_Client::close_curl_resource + * @covers \Raven\Client::close_curl_resource */ public function testClose_curl_resource() { $raven = new Dummy_Raven_Client(); - $reflection = new ReflectionProperty('Raven_Client', '_curl_instance'); + $reflection = new ReflectionProperty('\Raven\Client', '_curl_instance'); $reflection->setAccessible(true); $ch = curl_init(); $reflection->setValue($raven, $ch); diff --git a/test/Raven/Tests/CompatTest.php b/test/Raven/Tests/CompatTest.php index 4d558d605..451692b86 100644 --- a/test/Raven/Tests/CompatTest.php +++ b/test/Raven/Tests/CompatTest.php @@ -13,63 +13,63 @@ class Raven_Tests_CompatTest extends PHPUnit_Framework_TestCase { public function test_gethostname() { - $this->assertEquals(Raven_Compat::gethostname(), Raven_Compat::_gethostname()); - $this->assertTrue(strlen(Raven_Compat::_gethostname()) > 0); + $this->assertEquals(\Raven\Compat::gethostname(), \Raven\Compat::_gethostname()); + $this->assertTrue(strlen(\Raven\Compat::_gethostname()) > 0); } public function test_hash_hmac() { - $result = Raven_Compat::hash_hmac('sha1', 'foo', 'bar'); + $result = \Raven\Compat::hash_hmac('sha1', 'foo', 'bar'); $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); - $result = Raven_Compat::_hash_hmac('sha1', 'foo', 'bar'); + $result = \Raven\Compat::_hash_hmac('sha1', 'foo', 'bar'); $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); $long_key = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; - $result = Raven_Compat::_hash_hmac('md5', 'data', $long_key); + $result = \Raven\Compat::_hash_hmac('md5', 'data', $long_key); $this->assertEquals('951038f9ab8a10c929ab6dbc5f927207', $result); - $result = Raven_Compat::_hash_hmac('sha1', 'data', $long_key); + $result = \Raven\Compat::_hash_hmac('sha1', 'data', $long_key); $this->assertEquals('cbf0d1ca10d211da2bc15cb3b579ecfebf3056d2', $result); - $result = Raven_Compat::_hash_hmac('md5', 'foobar', $long_key); + $result = \Raven\Compat::_hash_hmac('md5', 'foobar', $long_key); $this->assertEquals('5490f3cddeb9665bce3239cbc4c15e2c', $result); - $result = Raven_Compat::_hash_hmac('sha1', 'foobar', $long_key); + $result = \Raven\Compat::_hash_hmac('sha1', 'foobar', $long_key); $this->assertEquals('5729f50ff2fbb8f8bf81d7a86f69a89f7574697c', $result); - $result = Raven_Compat::_hash_hmac('md5', 'foo', $long_key); + $result = \Raven\Compat::_hash_hmac('md5', 'foo', $long_key); $this->assertEquals('ab193328035cbd3a48dea9d64ba92736', $result); - $result = Raven_Compat::_hash_hmac('sha1', 'foo', $long_key); + $result = \Raven\Compat::_hash_hmac('sha1', 'foo', $long_key); $this->assertEquals('8f883d0755115314930968496573f27735eb0c41', $result); } public function test_json_encode() { - $result = Raven_Compat::json_encode(array('foo' => array('bar' => 1))); + $result = \Raven\Compat::json_encode(array('foo' => array('bar' => 1))); $this->assertEquals('{"foo":{"bar":1}}', $result); - $result = Raven_Compat::_json_encode(array('foo' => array('bar' => 1))); + $result = \Raven\Compat::_json_encode(array('foo' => array('bar' => 1))); $this->assertEquals('{"foo":{"bar":1}}', $result); - $result = Raven_Compat::_json_encode(array(1, 2, 3, 4, 'foo', 'bar')); + $result = \Raven\Compat::_json_encode(array(1, 2, 3, 4, 'foo', 'bar')); $this->assertEquals('[1,2,3,4,"foo","bar"]', $result); - $result = Raven_Compat::_json_encode(array(1, 'foo', 'foobar' => 'bar')); + $result = \Raven\Compat::_json_encode(array(1, 'foo', 'foobar' => 'bar')); $this->assertEquals('{0:1,1:"foo","foobar":"bar"}', $result); - $result = Raven_Compat::_json_encode(array(array())); + $result = \Raven\Compat::_json_encode(array(array())); $this->assertEquals('[[]]', $result); - $result = Raven_Compat::_json_encode(array(null, false, true, 1.5)); + $result = \Raven\Compat::_json_encode(array(null, false, true, 1.5)); $this->assertEquals('[null,false,true,1.5]', $result); } /** - * @covers Raven_Compat::_json_encode - * @covers Raven_Compat::_json_encode_lowlevel + * @covers \Raven\Compat::_json_encode + * @covers \Raven\Compat::_json_encode_lowlevel * * I show you how deep the rabbit hole goes */ @@ -96,16 +96,16 @@ public function test_json_encode_with_broken_data() break; } } - $value_1024 = Raven_Compat::_json_encode($data_broken); - $value_510 = Raven_Compat::_json_encode($data_broken_510); - $value_511 = Raven_Compat::_json_encode($data_broken_511); + $value_1024 = \Raven\Compat::_json_encode($data_broken); + $value_510 = \Raven\Compat::_json_encode($data_broken_510); + $value_511 = \Raven\Compat::_json_encode($data_broken_511); $this->assertFalse($value_1024, 'Broken data encoded successfully with Raven_Compat::_json_encode'); $this->assertNotFalse($value_510); $this->assertFalse($value_511); - $value_1024 = Raven_Compat::_json_encode($data_broken_named); - $value_510 = Raven_Compat::_json_encode($data_broken_named_510); - $value_511 = Raven_Compat::_json_encode($data_broken_named_511); + $value_1024 = \Raven\Compat::_json_encode($data_broken_named); + $value_510 = \Raven\Compat::_json_encode($data_broken_named_510); + $value_511 = \Raven\Compat::_json_encode($data_broken_named_511); $this->assertFalse($value_1024, 'Broken data encoded successfully with Raven_Compat::_json_encode'); $this->assertNotFalse($value_510); $this->assertFalse($value_511); diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index cd512d9e6..4b89fb6d6 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -49,7 +49,7 @@ public function testErrorsAreLoggedAsExceptions() ->method('captureException') ->with($this->isInstanceOf('ErrorException')); - $handler = new Raven_ErrorHandler($client, E_ALL); + $handler = new \Raven\ErrorHandler($client, E_ALL); $handler->handleError(E_WARNING, 'message'); } @@ -64,7 +64,7 @@ public function testExceptionsAreLogged() $e = new ErrorException('message', 0, E_WARNING, '', 0); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $handler->handleException($e); } @@ -76,7 +76,7 @@ public function testErrorHandlerPassErrorReportingPass() $client->expects($this->once()) ->method('captureException'); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $handler->registerErrorHandler(false, -1); error_reporting(E_USER_WARNING); @@ -91,7 +91,7 @@ public function testErrorHandlerPropagates() $client->expects($this->never()) ->method('captureException'); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $handler->registerErrorHandler(true, E_DEPRECATED); error_reporting(E_USER_WARNING); @@ -110,7 +110,7 @@ public function testErrorHandlerRespectsErrorReportingDefault() error_reporting(E_DEPRECATED); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $handler->registerErrorHandler(true); error_reporting(E_ALL); @@ -131,7 +131,7 @@ public function testSilentErrorsAreNotReportedWithGlobal() error_reporting(E_ALL); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $handler->registerErrorHandler(true); @$undefined; @@ -150,7 +150,7 @@ public function testSilentErrorsAreNotReportedWithLocal() $client->expects($this->never()) ->method('captureException'); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $handler->registerErrorHandler(true, E_ALL); @$my_array[2]; @@ -164,7 +164,7 @@ public function testShouldCaptureFatalErrorBehavior() $client = $this->getMockBuilder('Client') ->setMethods(array('captureException')) ->getMock(); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $this->assertEquals($handler->shouldCaptureFatalError(E_ERROR), true); @@ -181,7 +181,7 @@ public function testErrorHandlerDefaultsErrorReporting() error_reporting(E_USER_ERROR); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $handler->registerErrorHandler(false); trigger_error('Warning', E_USER_WARNING); @@ -192,7 +192,7 @@ public function testFluidInterface() $client = $this->getMockBuilder('Client') ->setMethods(array('captureException')) ->getMock(); - $handler = new Raven_ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); $result = $handler->registerErrorHandler(); $this->assertEquals($result, $handler); $result = $handler->registerExceptionHandler(); diff --git a/test/Raven/Tests/IntegrationTest.php b/test/Raven/Tests/IntegrationTest.php index be5f67b75..57c074c5f 100644 --- a/test/Raven/Tests/IntegrationTest.php +++ b/test/Raven/Tests/IntegrationTest.php @@ -8,7 +8,7 @@ * file that was distributed with this source code. */ -class DummyIntegration_Raven_Client extends Raven_Client +class DummyIntegration_Raven_Client extends \Raven\Client { private $__sent_events = array(); diff --git a/test/Raven/Tests/ReprSerializerTest.php b/test/Raven/Tests/ReprSerializerTest.php index 8c8c17633..2db3ca8f9 100644 --- a/test/Raven/Tests/ReprSerializerTest.php +++ b/test/Raven/Tests/ReprSerializerTest.php @@ -13,7 +13,7 @@ class Raven_Tests_ReprSerializerTest extends PHPUnit_Framework_TestCase { public function testArraysAreArrays() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $input = array(1, 2, 3); $result = $serializer->serialize($input); $this->assertEquals(array('1', '2', '3'), $result); @@ -21,7 +21,7 @@ public function testArraysAreArrays() public function testObjectsAreStrings() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $input = new Raven_StacktraceTestObject(); $result = $serializer->serialize($input); $this->assertEquals('Object Raven_StacktraceTestObject', $result); @@ -29,7 +29,7 @@ public function testObjectsAreStrings() public function testIntsAreInts() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $input = 1; $result = $serializer->serialize($input); $this->assertInternalType('string', $result); @@ -38,7 +38,7 @@ public function testIntsAreInts() public function testFloats() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $input = 1.5; $result = $serializer->serialize($input); $this->assertInternalType('string', $result); @@ -47,7 +47,7 @@ public function testFloats() public function testBooleans() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $input = true; $result = $serializer->serialize($input); $this->assertEquals('true', $result); @@ -60,7 +60,7 @@ public function testBooleans() public function testNull() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $input = null; $result = $serializer->serialize($input); $this->assertInternalType('string', $result); @@ -69,7 +69,7 @@ public function testNull() public function testRecursionMaxDepth() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $input = array(); $input[] = &$input; $result = $serializer->serialize($input, 3); @@ -77,11 +77,11 @@ public function testRecursionMaxDepth() } /** - * @covers Raven_ReprSerializer::serializeValue + * @covers \Raven\ReprSerializer::serializeValue */ public function testSerializeValueResource() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); $fo = fopen($filename, 'wb'); @@ -91,11 +91,11 @@ public function testSerializeValueResource() } /** - * @covers Raven_ReprSerializer::serializeValue + * @covers \Raven\ReprSerializer::serializeValue */ public function testSerializeRoundedFloat() { - $serializer = new Raven_ReprSerializer(); + $serializer = new \Raven\ReprSerializer(); $result = $serializer->serialize((double)1); $this->assertInternalType('string', $result); diff --git a/test/Raven/Tests/SanitizeDataProcessorTest.php b/test/Raven/Tests/SanitizeDataProcessorTest.php index a70120e22..4f8c8d83d 100644 --- a/test/Raven/Tests/SanitizeDataProcessorTest.php +++ b/test/Raven/Tests/SanitizeDataProcessorTest.php @@ -33,20 +33,20 @@ public function testDoesFilterHttpData() ); $client = new Dummy_Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); + $processor = new \Raven\SanitizeDataProcessor($client); $processor->process($data); $vars = $data['request']['data']; $this->assertEquals($vars['foo'], 'bar'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['password']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['the_secret']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['a_password_here']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['mypasswd']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['authorization']); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['password']); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['the_secret']); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['a_password_here']); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['mypasswd']); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['authorization']); $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['card_number']['0']); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['card_number']['0']); } public function testDoesFilterSessionId() @@ -60,11 +60,11 @@ public function testDoesFilterSessionId() ); $client = new Dummy_Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); + $processor = new \Raven\SanitizeDataProcessor($client); $processor->process($data); $cookies = $data['request']['cookies']; - $this->assertEquals($cookies[ini_get('session.name')], Raven_SanitizeDataProcessor::MASK); + $this->assertEquals($cookies[ini_get('session.name')], \Raven\SanitizeDataProcessor::MASK); } public function testDoesFilterCreditCard() @@ -76,20 +76,20 @@ public function testDoesFilterCreditCard() ); $client = new Dummy_Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); + $processor = new \Raven\SanitizeDataProcessor($client); $processor->process($data); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $data['extra']['ccnumba']); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $data['extra']['ccnumba']); } /** - * @covers Raven_SanitizeDataProcessor::setProcessorOptions + * @covers \Raven\SanitizeDataProcessor::setProcessorOptions * */ public function testSettingProcessorOptions() { $client = new Dummy_Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); + $processor = new \Raven\SanitizeDataProcessor($client); $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); @@ -116,13 +116,13 @@ public function testOverrideOptions($processorOptions, $client_options, $dsn) { $client = new Dummy_Raven_Client($dsn, $client_options); /** - * @var Raven_SanitizeDataProcessor $processor + * @var \Raven\SanitizeDataProcessor $processor */ $processor = $client->processors[0]; - $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf('\Raven\\SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['\Raven\\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['\Raven\\SanitizeDataProcessor']['values_re'], 'overwrote values'); } /** @@ -155,13 +155,13 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $client = new Dummy_Raven_Client($dsn, $client_options); /** - * @var Raven_SanitizeDataProcessor $processor + * @var \Raven\SanitizeDataProcessor $processor */ $processor = $client->processors[0]; - $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf('\Raven\\SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['\Raven\\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['\Raven\\SanitizeDataProcessor']['values_re'], 'overwrote values'); $processor->process($data); @@ -172,9 +172,9 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $this->assertEquals($vars['a_password_here'], 'hello', 'did not alter a_password_here'); $this->assertEquals($vars['mypasswd'], 'hello', 'did not alter mypasswd'); $this->assertEquals($vars['authorization'], 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', 'did not alter authorization'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['api_token'], 'masked api_token'); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['api_token'], 'masked api_token'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['card_number']['0'], 'masked card_number[0]'); + $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['card_number']['0'], 'masked card_number[0]'); $this->assertEquals($vars['card_number']['1'], $vars['card_number']['1'], 'did not alter card_number[1]'); } @@ -186,14 +186,14 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) public static function overrideDataProvider() { $processorOptions = array( - 'Raven_SanitizeDataProcessor' => array( + '\Raven\\SanitizeDataProcessor' => array( 'fields_re' => '/(api_token)/i', 'values_re' => '/^(?:\d[ -]*?){15,16}$/' ) ); $client_options = array( - 'processors' => array('Raven_SanitizeDataProcessor'), + 'processors' => array('\Raven\\SanitizeDataProcessor'), 'processorOptions' => $processorOptions ); diff --git a/test/Raven/Tests/SerializerTest.php b/test/Raven/Tests/SerializerTest.php index 9d8a025fe..29604b4f8 100644 --- a/test/Raven/Tests/SerializerTest.php +++ b/test/Raven/Tests/SerializerTest.php @@ -18,7 +18,7 @@ class Raven_Tests_SerializerTest extends PHPUnit_Framework_TestCase { public function testArraysAreArrays() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $input = array(1, 2, 3); $result = $serializer->serialize($input); $this->assertEquals(array('1', '2', '3'), $result); @@ -26,7 +26,7 @@ public function testArraysAreArrays() public function testStdClassAreArrays() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $input = new stdClass(); $input->foo = 'BAR'; $result = $serializer->serialize($input); @@ -35,7 +35,7 @@ public function testStdClassAreArrays() public function testObjectsAreStrings() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $input = new Raven_SerializerTestObject(); $result = $serializer->serialize($input); $this->assertEquals('Object Raven_SerializerTestObject', $result); @@ -43,7 +43,7 @@ public function testObjectsAreStrings() public function testIntsAreInts() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $input = 1; $result = $serializer->serialize($input); $this->assertInternalType('integer', $result); @@ -52,7 +52,7 @@ public function testIntsAreInts() public function testFloats() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $input = 1.5; $result = $serializer->serialize($input); $this->assertInternalType('double', $result); @@ -61,7 +61,7 @@ public function testFloats() public function testBooleans() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $input = true; $result = $serializer->serialize($input); $this->assertTrue($result); @@ -73,7 +73,7 @@ public function testBooleans() public function testNull() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $input = null; $result = $serializer->serialize($input); $this->assertNull($result); @@ -81,7 +81,7 @@ public function testNull() public function testRecursionMaxDepth() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $input = array(); $input[] = &$input; $result = $serializer->serialize($input, 3); @@ -90,18 +90,18 @@ public function testRecursionMaxDepth() public function testObjectInArray() { - $serializer = new Raven_Serializer(); - $input = array('foo' => new Raven_Serializer()); + $serializer = new \Raven\Serializer(); + $input = array('foo' => new \Raven\Serializer()); $result = $serializer->serialize($input); - $this->assertEquals(array('foo' => 'Object Raven_Serializer'), $result); + $this->assertEquals(array('foo' => 'Object Raven\\Serializer'), $result); } /** - * @covers Raven_Serializer::serializeString + * @covers \Raven\Serializer::serializeString */ public function testBrokenEncoding() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); foreach (array('7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90') as $key) { $input = pack('H*', $key); $result = $serializer->serialize($input); @@ -113,11 +113,11 @@ public function testBrokenEncoding() } /** - * @covers Raven_Serializer::serializeString + * @covers \Raven\Serializer::serializeString */ public function testLongString() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); for ($i = 0; $i < 100; $i++) { foreach (array(100, 1000, 1010, 1024, 1050, 1100, 10000) as $length) { $input = ''; @@ -132,11 +132,11 @@ public function testLongString() } /** - * @covers Raven_Serializer::serializeValue + * @covers \Raven\Serializer::serializeValue */ public function testSerializeValueResource() { - $serializer = new Raven_Serializer(); + $serializer = new \Raven\Serializer(); $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); $fo = fopen($filename, 'wb'); diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 26f6068c8..35cd94efc 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -36,7 +36,7 @@ public function testCanTraceParamContext() $offset = 1; } $frame = $stack[$offset]; - $params = Raven_Stacktrace::get_frame_context($frame); + $params = \Raven\Stacktrace::get_frame_context($frame); $this->assertEquals($params['args'], array('biz', 'baz')); $this->assertEquals($params['times'], 0); } @@ -60,7 +60,7 @@ public function testSimpleTrace() ) ); - $frames = Raven_Stacktrace::get_stack_info($stack, true); + $frames = \Raven\Stacktrace::get_stack_info($stack, true); $frame = $frames[0]; $this->assertEquals(2, $frame['lineno']); @@ -95,7 +95,7 @@ public function testDoesNotModifyCaptureVars() "function" => "a_test", ); - $result = Raven_Stacktrace::get_frame_context($frame, 5); + $result = \Raven\Stacktrace::get_frame_context($frame, 5); // Check we haven't modified our vars. $this->assertEquals($originalFoo, 'bloopblarp'); @@ -115,7 +115,7 @@ public function testDoesFixFrameInfo() */ $stack = raven_test_create_stacktrace(); - $frames = Raven_Stacktrace::get_stack_info($stack, true); + $frames = \Raven\Stacktrace::get_stack_info($stack, true); // just grab the last few frames $frames = array_slice($frames, -6); $skip_call_user_func_fix = false; @@ -168,7 +168,7 @@ public function testInApp() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, 0, null, dirname(__FILE__)); + $frames = \Raven\Stacktrace::get_stack_info($stack, true, null, 0, null, dirname(__FILE__)); $this->assertEquals($frames[0]['in_app'], true); $this->assertEquals($frames[1]['in_app'], true); @@ -189,7 +189,7 @@ public function testInAppWithExclusion() ), ); - $frames = Raven_Stacktrace::get_stack_info( + $frames = \Raven\Stacktrace::get_stack_info( $stack, true, null, 0, null, dirname(__FILE__) . '/', array(dirname(__FILE__) . '/resources/bar/')); @@ -208,7 +208,7 @@ public function testBasePath() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack, true, null, 0, array(dirname(__FILE__) . '/')); + $frames = \Raven\Stacktrace::get_stack_info($stack, true, null, 0, array(dirname(__FILE__) . '/')); $this->assertEquals($frames[0]['filename'], 'resources/a.php'); } @@ -223,7 +223,7 @@ public function testNoBasePath() ), ); - $frames = Raven_Stacktrace::get_stack_info($stack); + $frames = \Raven\Stacktrace::get_stack_info($stack); $this->assertEquals($frames[0]['filename'], dirname(__FILE__) . '/resources/a.php'); } @@ -233,7 +233,7 @@ public function testWithEvaldCode() eval("throw new Exception('foobar');"); } catch (Exception $ex) { $trace = $ex->getTrace(); - $frames = Raven_Stacktrace::get_stack_info($trace); + $frames = \Raven\Stacktrace::get_stack_info($trace); } /** * @var array $frames diff --git a/test/Raven/Tests/TransactionStackTest.php b/test/Raven/Tests/TransactionStackTest.php index 199418072..f706fd95f 100644 --- a/test/Raven/Tests/TransactionStackTest.php +++ b/test/Raven/Tests/TransactionStackTest.php @@ -14,7 +14,7 @@ class Raven_Tests_TransactionStackTest extends PHPUnit_Framework_TestCase { public function testSimple() { - $stack = new Raven_TransactionStack(); + $stack = new \Raven\TransactionStack(); $stack->push('hello'); /** @noinspection PhpVoidFunctionResultUsedInspection */ /** @noinspection PhpUnusedLocalVariableInspection */ diff --git a/test/Raven/Tests/UtilTest.php b/test/Raven/Tests/UtilTest.php index 856b77097..885c8bda3 100644 --- a/test/Raven/Tests/UtilTest.php +++ b/test/Raven/Tests/UtilTest.php @@ -19,14 +19,14 @@ class Raven_Tests_UtilTest extends PHPUnit_Framework_TestCase public function testGetReturnsDefaultOnMissing() { $input = array('foo' => 'bar'); - $result = Raven_Util::get($input, 'baz', 'foo'); + $result = \Raven\Util::get($input, 'baz', 'foo'); $this->assertEquals('foo', $result); } public function testGetReturnsPresentValuesEvenWhenEmpty() { $input = array('foo' => ''); - $result = Raven_Util::get($input, 'foo', 'bar'); + $result = \Raven\Util::get($input, 'foo', 'bar'); $this->assertEquals('', $result); } } diff --git a/test/bootstrap.php b/test/bootstrap.php index 1544bc388..c94a3112d 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -14,4 +14,4 @@ session_start(); require_once dirname(__FILE__).'/../lib/Raven/Autoloader.php'; -Raven_Autoloader::register(); +\Raven\Autoloader::register(); From dcb52a2b87d0b96e2d12f067925e4d6e094f63f4 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Thu, 16 Feb 2017 19:25:28 +0300 Subject: [PATCH 0257/1161] Suggested extension for overrided functions Incorrect prefix in autoloader --- composer.json | 3 +++ lib/Raven/Autoloader.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c713bc32d..930e24f3b 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,9 @@ "ext-curl": "*" }, "suggest": { + "ext-hash": "*", + "ext-json": "*", + "ext-mbstring": "*", "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" }, "conflict": { diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php index d2a2f8cc0..3e5694b62 100644 --- a/lib/Raven/Autoloader.php +++ b/lib/Raven/Autoloader.php @@ -32,7 +32,7 @@ public static function register() */ public static function autoload($class) { - if (0 !== strpos($class, 'Raven')) { + if (substr($class, 0, 6) == 'Raven_') { return; } From ca05e6239564d14ee86955a905935fc80b6c7d1f Mon Sep 17 00:00:00 2001 From: GrayMissing Date: Fri, 17 Feb 2017 13:42:51 +0800 Subject: [PATCH 0258/1161] fix autoload raven class failed --- lib/Raven/Autoloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php index 3e5694b62..f4caa6f97 100644 --- a/lib/Raven/Autoloader.php +++ b/lib/Raven/Autoloader.php @@ -32,7 +32,7 @@ public static function register() */ public static function autoload($class) { - if (substr($class, 0, 6) == 'Raven_') { + if (substr($class, 0, 6) !== 'Raven_') { return; } From 8cb94e545df95005f70bba8ed58400088b8861d4 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Sat, 18 Feb 2017 19:40:20 +0300 Subject: [PATCH 0259/1161] DeprecatedClasses loading from Legacy.php Deleted deprecated calls from classes --- bin/sentry | 8 +- composer.json | 12 +-- lib/Raven/Autoloader.php | 6 +- lib/Raven/Client.php | 4 +- lib/Raven/Compat.php | 4 + lib/Raven/Legacy.php | 113 +++++++++++++++++++++++++ test/Raven/Tests/DeprecatedClasses.php | 15 ++++ test/bootstrap.php | 2 +- 8 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 lib/Raven/Legacy.php create mode 100644 test/Raven/Tests/DeprecatedClasses.php diff --git a/bin/sentry b/bin/sentry index 4694cfcb4..e55634d1b 100755 --- a/bin/sentry +++ b/bin/sentry @@ -6,8 +6,8 @@ error_reporting(E_ALL | E_STRICT); // TODO: if we could get rid of this and have composer figure things out it'd make it // a bit more sane -require(dirname(__file__) . '/../lib/Raven/Autoloader.php'); -Raven_Autoloader::register(); +require(__DIR__ . '/../lib/Raven/Autoloader.php'); +\Raven\Autoloader::register(); function raven_cli_test($command, $args) { @@ -27,12 +27,12 @@ function cmd_test($dsn) // Parse DSN as a test try { - $parsed = Raven_Client::parseDSN($dsn); + \Raven\Client::parseDSN($dsn); } catch (InvalidArgumentException $ex) { exit("ERROR: There was an error parsing your DSN:\n " . $ex->getMessage()); } - $client = new Raven_Client($dsn, array( + $client = new \Raven\Client($dsn, array( 'trace' => true, 'curl_method' => 'sync', 'app_path' => realpath(__DIR__ . '/..'), diff --git a/composer.json b/composer.json index 930e24f3b..85d8bd3b7 100644 --- a/composer.json +++ b/composer.json @@ -17,13 +17,13 @@ "monolog/monolog": "*" }, "require": { - "php": ">=5.2.4", - "ext-curl": "*" - }, - "suggest": { + "php": ">=5.3.2", "ext-hash": "*", "ext-json": "*", "ext-mbstring": "*", + "ext-curl": "*" + }, + "suggest": { "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" }, "conflict": { @@ -33,8 +33,8 @@ "bin/sentry" ], "autoload": { - "psr-0" : { - "Raven_" : "lib/" + "psr-4" : { + "Raven\\" : "lib/Raven/" } }, "scripts": { diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php index e7aec5c10..4e9d955fe 100644 --- a/lib/Raven/Autoloader.php +++ b/lib/Raven/Autoloader.php @@ -38,7 +38,11 @@ function ($class) { */ public static function autoload($class) { - if (substr($class, 0, 6) !== 'Raven\\') { + if (substr($class, 0, 6) == 'Raven_') { + // legacy call + require_once 'Legacy.php'; + return; + } elseif (substr($class, 0, 6) !== 'Raven\\') { return; } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 3f4aa771d..3e3de2d61 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -149,7 +149,7 @@ public function __construct($options_or_dsn = null, $options = array()) $this->public_key = \Raven\Util::get($options, 'public_key'); $this->project = \Raven\Util::get($options, 'project', 1); $this->auto_log_stacks = (bool) \Raven\Util::get($options, 'auto_log_stacks', false); - $this->name = \Raven\Util::get($options, 'name', \Raven\Compat::gethostname()); + $this->name = \Raven\Util::get($options, 'name', gethostname()); $this->site = \Raven\Util::get($options, 'site', self::_server_variable('SERVER_NAME')); $this->tags = \Raven\Util::get($options, 'tags', array()); $this->release = \Raven\Util::get($options, 'release', null); @@ -925,7 +925,7 @@ public function sendUnsentErrors() */ public function encode(&$data) { - $message = \Raven\Compat::json_encode($data); + $message = json_encode($data); if ($message === false) { if (function_exists('json_last_error_msg')) { $this->_lasterror = json_last_error_msg(); diff --git a/lib/Raven/Compat.php b/lib/Raven/Compat.php index c0dc58d0e..6ba58457a 100644 --- a/lib/Raven/Compat.php +++ b/lib/Raven/Compat.php @@ -11,6 +11,10 @@ namespace Raven; +/** + * @package Raven + * @deprecated + */ class Compat { public static function gethostname() diff --git a/lib/Raven/Legacy.php b/lib/Raven/Legacy.php new file mode 100644 index 000000000..72ca95bd5 --- /dev/null +++ b/lib/Raven/Legacy.php @@ -0,0 +1,113 @@ +assertTrue(class_exists($class)); + } + } +} diff --git a/test/bootstrap.php b/test/bootstrap.php index c94a3112d..853dd6be9 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -13,5 +13,5 @@ session_start(); -require_once dirname(__FILE__).'/../lib/Raven/Autoloader.php'; +require_once __DIR__.'/../lib/Raven/Autoloader.php'; \Raven\Autoloader::register(); From c47c17016ef48895c3ea25966bd1e285e314f59c Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Sat, 18 Feb 2017 19:44:33 +0300 Subject: [PATCH 0260/1161] String escaping --- lib/Raven/Client.php | 10 +++---- test/Raven/Tests/ClientTest.php | 30 +++++++++---------- .../Raven/Tests/SanitizeDataProcessorTest.php | 16 +++++----- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 3e3de2d61..7c11c3003 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -380,7 +380,7 @@ public function setTransport($value) public static function getDefaultProcessors() { return array( - '\Raven\SanitizeDataProcessor', + '\\Raven\\SanitizeDataProcessor', ); } @@ -587,9 +587,9 @@ public function captureException($exception, $data = null, $logger = null, $vars array_unshift($trace, $frame_where_exception_thrown); // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('\Raven\Stacktrace')) { + if (!class_exists('\\Raven\\Stacktrace')) { // @codeCoverageIgnoreStart - spl_autoload_call('\Raven\Stacktrace'); + spl_autoload_call('\\Raven\\Stacktrace'); // @codeCoverageIgnoreEnd } @@ -843,9 +843,9 @@ public function capture($data, $stack = null, $vars = null) if (!empty($stack)) { // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('\Raven\Stacktrace')) { + if (!class_exists('\\Raven\\Stacktrace')) { // @codeCoverageIgnoreStart - spl_autoload_call('\Raven\Stacktrace'); + spl_autoload_call('\\Raven\\Stacktrace'); // @codeCoverageIgnoreEnd } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 26035d3e3..7b8616cb8 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -690,10 +690,10 @@ public function testCaptureExceptionInvalidUTF8() public function testDoesRegisterProcessors() { $client = new Dummy_Raven_Client(array( - 'processors' => array('\Raven\\SanitizeDataProcessor'), + 'processors' => array('\\Raven\\SanitizeDataProcessor'), )); $this->assertEquals(1, count($client->processors)); - $this->assertInstanceOf('\Raven\\SanitizeDataProcessor', $client->processors[0]); + $this->assertInstanceOf('\\Raven\\SanitizeDataProcessor', $client->processors[0]); } public function testProcessDoesCallProcessors() @@ -731,7 +731,7 @@ public function testDefaultProcessorsContainSanitizeDataProcessor() { $defaults = Dummy_Raven_Client::getDefaultProcessors(); - $this->assertTrue(in_array('\Raven\\SanitizeDataProcessor', $defaults)); + $this->assertTrue(in_array('\\Raven\\SanitizeDataProcessor', $defaults)); } /** @@ -1417,7 +1417,7 @@ public function currentUrlProvider() */ public function testUuid4() { - $method = new ReflectionMethod('\Raven\\Client', 'uuid4'); + $method = new ReflectionMethod('\\Raven\\Client', 'uuid4'); $method->setAccessible(true); for ($i = 0; $i < 1000; $i++) { $this->assertRegExp('/^[0-9a-z-]+$/', $method->invoke(null)); @@ -1450,7 +1450,7 @@ public function testUuid4() public function testGettersAndSetters() { $client = new Dummy_Raven_Client(); - $property_method__convert_path = new ReflectionMethod('\Raven\\Client', '_convertPath'); + $property_method__convert_path = new ReflectionMethod('\\Raven\\Client', '_convertPath'); $property_method__convert_path->setAccessible(true); $callable = array($this, 'stabClosureVoid'); @@ -1509,7 +1509,7 @@ private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) $method_get_name = 'get'.$function_name; $method_set_name = 'set'.$function_name; - $property = new ReflectionProperty('\Raven\\Client', $property_name); + $property = new ReflectionProperty('\\Raven\\Client', $property_name); $property->setAccessible(true); if (method_exists($client, $method_set_name)) { @@ -1524,7 +1524,7 @@ private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) if (method_exists($client, $method_get_name)) { $property->setValue($client, $value_out); - $reflection = new ReflectionMethod('\Raven\Client', $method_get_name); + $reflection = new ReflectionMethod('\\Raven\Client', $method_get_name); if ($reflection->isPublic()) { $actual_value = $client->$method_get_name(); $this->assertMixedValueAndArray($value_out, $actual_value); @@ -1559,7 +1559,7 @@ private function assertMixedValueAndArray($expected_value, $actual_value) */ public function test_convertPath() { - $property = new ReflectionMethod('\Raven\Client', '_convertPath'); + $property = new ReflectionMethod('\\Raven\Client', '_convertPath'); $property->setAccessible(true); $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar')); @@ -1579,7 +1579,7 @@ public function testGetDefaultProcessors() $this->assertInternalType('string', $class_name); $this->assertTrue(class_exists($class_name)); $reflection = new ReflectionClass($class_name); - $this->assertTrue($reflection->isSubclassOf('\Raven\\Processor')); + $this->assertTrue($reflection->isSubclassOf('\\Raven\\Processor')); $this->assertFalse($reflection->isAbstract()); } } @@ -1589,7 +1589,7 @@ public function testGetDefaultProcessors() */ public function testGet_default_ca_cert() { - $reflection = new ReflectionMethod('\Raven\Client', 'get_default_ca_cert'); + $reflection = new ReflectionMethod('\\Raven\Client', 'get_default_ca_cert'); $reflection->setAccessible(true); $this->assertFileExists($reflection->invoke(null)); } @@ -1600,7 +1600,7 @@ public function testGet_default_ca_cert() */ public function testTranslateSeverity() { - $reflection = new ReflectionProperty('\Raven\Client', 'severity_map'); + $reflection = new ReflectionProperty('\\Raven\Client', 'severity_map'); $reflection->setAccessible(true); $client = new Dummy_Raven_Client(); @@ -1748,7 +1748,7 @@ public function testConstructWithServerDSN() */ public function test_server_variable() { - $method = new ReflectionMethod('\Raven\Client', '_server_variable'); + $method = new ReflectionMethod('\\Raven\Client', '_server_variable'); $method->setAccessible(true); foreach ($_SERVER as $key => $value) { $actual = $method->invoke(null, $key); @@ -1778,7 +1778,7 @@ public function testEncodeTooDepth() $value = $client->encode($data_broken); if (!function_exists('json_encode') or version_compare(PHP_VERSION, '5.5.0', '>=')) { $this->assertFalse($value, 'Broken data encoded successfully with '. - (function_exists('json_encode') ? 'native method' : '\Raven\\Compat::_json_encode')); + (function_exists('json_encode') ? 'native method' : '\\Raven\\Compat::_json_encode')); } else { if ($value !== false) { $this->markTestSkipped(); @@ -1795,7 +1795,7 @@ public function testEncode() $json_stringify = \Raven\Compat::json_encode($data); $value = $client->encode($data); $this->assertNotFalse($value); - $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value, '\Raven\\Client::encode returned malformed data'); + $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value, '\\Raven\\Client::encode returned malformed data'); $decoded = base64_decode($value); $this->assertInternalType('string', $decoded, 'Can not use base64 decode on the encoded blob'); if (function_exists("gzcompress")) { @@ -2216,7 +2216,7 @@ public function testSend_http_asynchronous_curl_exec() public function testClose_curl_resource() { $raven = new Dummy_Raven_Client(); - $reflection = new ReflectionProperty('\Raven\Client', '_curl_instance'); + $reflection = new ReflectionProperty('\\Raven\Client', '_curl_instance'); $reflection->setAccessible(true); $ch = curl_init(); $reflection->setValue($raven, $ch); diff --git a/test/Raven/Tests/SanitizeDataProcessorTest.php b/test/Raven/Tests/SanitizeDataProcessorTest.php index 4f8c8d83d..540891040 100644 --- a/test/Raven/Tests/SanitizeDataProcessorTest.php +++ b/test/Raven/Tests/SanitizeDataProcessorTest.php @@ -120,9 +120,9 @@ public function testOverrideOptions($processorOptions, $client_options, $dsn) */ $processor = $client->processors[0]; - $this->assertInstanceOf('\Raven\\SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['\Raven\\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['\Raven\\SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf('\\Raven\\SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['\\Raven\\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['\\Raven\\SanitizeDataProcessor']['values_re'], 'overwrote values'); } /** @@ -159,9 +159,9 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) */ $processor = $client->processors[0]; - $this->assertInstanceOf('\Raven\\SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['\Raven\\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['\Raven\\SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf('\\Raven\\SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['\\Raven\\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['\\Raven\\SanitizeDataProcessor']['values_re'], 'overwrote values'); $processor->process($data); @@ -186,14 +186,14 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) public static function overrideDataProvider() { $processorOptions = array( - '\Raven\\SanitizeDataProcessor' => array( + '\\Raven\\SanitizeDataProcessor' => array( 'fields_re' => '/(api_token)/i', 'values_re' => '/^(?:\d[ -]*?){15,16}$/' ) ); $client_options = array( - 'processors' => array('\Raven\\SanitizeDataProcessor'), + 'processors' => array('\\Raven\\SanitizeDataProcessor'), 'processorOptions' => $processorOptions ); From f105549022ed4fa1b613806b0f29a6010dc66fad Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Sat, 18 Feb 2017 20:12:58 +0300 Subject: [PATCH 0261/1161] PHP 5.3.3+ E_USER_DEPRECATED (via issuecomment-280859133) --- composer.json | 2 +- lib/Raven/Legacy.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 85d8bd3b7..627f01b6b 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "monolog/monolog": "*" }, "require": { - "php": ">=5.3.2", + "php": ">=5.3.3", "ext-hash": "*", "ext-json": "*", "ext-mbstring": "*", diff --git a/lib/Raven/Legacy.php b/lib/Raven/Legacy.php index 72ca95bd5..2495f537a 100644 --- a/lib/Raven/Legacy.php +++ b/lib/Raven/Legacy.php @@ -1,5 +1,7 @@ Date: Sun, 19 Feb 2017 06:13:21 +0300 Subject: [PATCH 0262/1161] Legacy autoloader for back compatibility --- lib/Raven/Autoloader.php | 2 ++ lib/Raven/Legacy.php | 9 +++++++++ lib/Raven/LegacyAutoloader.php | 19 +++++++++++++++++++ test/Raven/Tests/DeprecatedClasses.php | 9 ++++++++- 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 lib/Raven/LegacyAutoloader.php diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php index 4e9d955fe..626c36fa7 100644 --- a/lib/Raven/Autoloader.php +++ b/lib/Raven/Autoloader.php @@ -53,3 +53,5 @@ public static function autoload($class) } } } + +require_once 'LegacyAutoloader.php'; diff --git a/lib/Raven/Legacy.php b/lib/Raven/Legacy.php index 2495f537a..89772d254 100644 --- a/lib/Raven/Legacy.php +++ b/lib/Raven/Legacy.php @@ -1,5 +1,14 @@ assertTrue(class_exists($class)); } } + + public function testDeprecatedAutoloader() + { + /** @noinspection PhpDeprecationInspection */ + Raven_Autoloader::register(); + } } From 29a9a4d5660ddc9d988c7c02f8ec4137d8708978 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Sun, 19 Feb 2017 07:08:00 +0300 Subject: [PATCH 0263/1161] HHVM CI --- .travis.yml | 15 ++++++++++----- test/Raven/Tests/ClientTest.php | 4 ++++ test/Raven/Tests/StacktraceTest.php | 5 +++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1a49db4bf..89472652d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,12 @@ php: - 7.0 - 7.1 - nightly - - hhvm + - hhvm-3.12 matrix: allow_failures: - php: 7.1 - - php: hhvm + - php: hhvm-3.12 - php: nightly fast_finish: true include: @@ -27,8 +27,13 @@ matrix: env: REMOVE_XDEBUG="1" DISABLE_ASSERTIONS="1" - php: nightly env: REMOVE_XDEBUG="0" DISABLE_ASSERTIONS="1" + - php: hhvm-3.12 + env: REMOVE_XDEBUG="0" HHVM="1" + dist: trusty exclude: - - php: hhvm + - php: hhvm-3.12 + env: REMOVE_XDEBUG="0" + - php: hhvm-3.12 env: REMOVE_XDEBUG="1" - php: nightly env: REMOVE_XDEBUG="1" @@ -46,8 +51,8 @@ cache: before_install: - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi - if [ "$DISABLE_ASSERTIONS" = "1" ]; then echo 'zend.assertions = -1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]]; then pecl install uopz; fi - - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]]; then echo "extension=uopz.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi + - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]] && [ "$HHVM" != "1" ]; then pecl install uopz; fi + - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]] && [ "$HHVM" != "1" ]; then echo "extension=uopz.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - composer self-update install: travis_retry composer install --no-interaction --prefer-source diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 63d8b99cd..faf5ca681 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -1812,6 +1812,10 @@ public function testEncode() */ public function testRegisterDefaultBreadcrumbHandlers() { + if (isset($_ENV['HHVM']) and ($_ENV['HHVM'] == 1)) { + $this->markTestSkipped('HHVM stacktrace behaviour'); + return; + } $previous = set_error_handler(array($this, 'stabClosureErrorHandler'), E_USER_NOTICE); new Raven_Client(null, array()); $this->_closure_called = false; diff --git a/test/Raven/Tests/StacktraceTest.php b/test/Raven/Tests/StacktraceTest.php index 26f6068c8..f967acf07 100644 --- a/test/Raven/Tests/StacktraceTest.php +++ b/test/Raven/Tests/StacktraceTest.php @@ -108,6 +108,11 @@ public function testDoesNotModifyCaptureVars() public function testDoesFixFrameInfo() { + if (isset($_ENV['HHVM']) and ($_ENV['HHVM'] == 1)) { + $this->markTestSkipped('HHVM stacktrace behaviour'); + return; + } + /** * PHP's way of storing backstacks seems bass-ackwards to me * 'function' is not the function you're in; it's any function being From 993df7ef5f3a78c86256883b546456e53066dad7 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Tue, 21 Feb 2017 11:29:30 +0100 Subject: [PATCH 0264/1161] Allow reset the Breadcrumb storage --- lib/Raven/Breadcrumbs.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php index 12f39902e..9f1eebba4 100644 --- a/lib/Raven/Breadcrumbs.php +++ b/lib/Raven/Breadcrumbs.php @@ -25,10 +25,15 @@ class Raven_Breadcrumbs public $buffer; public function __construct($size = 100) + { + $this->size = $size; + $this->reset(); + } + + public function reset() { $this->count = 0; $this->pos = 0; - $this->size = $size; $this->buffer = array(); } From 77916b0a01a0bd7b7d0b35014a9f34c9e257ab63 Mon Sep 17 00:00:00 2001 From: Babak Mahmoudy Date: Wed, 1 Mar 2017 01:51:47 +1100 Subject: [PATCH 0265/1161] Re-throw exception to invoke the native handler when a custom handler is not set --- lib/Raven/ErrorHandler.php | 8 +++-- test/Raven/Tests/ErrorHandlerTest.php | 42 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 2cbefd14f..8742e83c5 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -77,8 +77,12 @@ public function handleException($e, $isError = false, $vars = null) { $e->event_id = $this->client->captureException($e, null, null, $vars); - if (!$isError && $this->call_existing_exception_handler && $this->old_exception_handler) { - call_user_func($this->old_exception_handler, $e); + if (!$isError && $this->call_existing_exception_handler) { + if ($this->old_exception_handler !== null) { + call_user_func($this->old_exception_handler, $e); + } else { + throw $e; + } } } diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index cd512d9e6..8831e3a38 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -100,6 +100,48 @@ public function testErrorHandlerPropagates() $this->assertEquals($this->errorHandlerCalled, 1); } + public function testExceptionHandlerPropagatesToNative() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $client->expects($this->exactly(2)) + ->method('captureException') + ->with($this->isInstanceOf('Exception')); + + $handler = new Raven_ErrorHandler($client); + + set_exception_handler(null); + $handler->registerExceptionHandler(false); + + $testException = new Exception('Test exception'); + + $didRethrow = false; + try { + $handler->handleException($testException); + } catch (Exception $e) { + $didRethrow = true; + } + + $this->assertFalse($didRethrow); + + set_exception_handler(null); + $handler->registerExceptionHandler(true); + + $didRethrow = false; + $rethrownException = null; + try { + $handler->handleException($testException); + } catch (Exception $e) { + $didRethrow = true; + $rethrownException = $e; + } + + $this->assertTrue($didRethrow); + $this->assertSame($testException, $rethrownException); + + } + public function testErrorHandlerRespectsErrorReportingDefault() { $client = $this->getMockBuilder('Client') From ed0286daaae76f32ae0921e832aa582b766f6fbf Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 1 Mar 2017 10:22:42 -0800 Subject: [PATCH 0266/1161] Fix lint issues --- test/Raven/Tests/ErrorHandlerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/test/Raven/Tests/ErrorHandlerTest.php index 8831e3a38..64cfc4c1a 100644 --- a/test/Raven/Tests/ErrorHandlerTest.php +++ b/test/Raven/Tests/ErrorHandlerTest.php @@ -139,7 +139,6 @@ public function testExceptionHandlerPropagatesToNative() $this->assertTrue($didRethrow); $this->assertSame($testException, $rethrownException); - } public function testErrorHandlerRespectsErrorReportingDefault() From 67aeb90b6c9a081885f0a3d44e81a851c7fa31b6 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 1 Mar 2017 10:23:45 -0800 Subject: [PATCH 0267/1161] Note change to exception handler --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 487f386c5..75b449b77 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ ----- - Corrected some issues with argument serialization in stacktraces. +- The default exception handler will now re-raise exceptions when + ``call_existing`` is true and no exception handler is registered. 1.6.2 ----- From d652cdd54e30fdee74bf0edd361007f9eed69b8e Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 27 Feb 2017 12:49:09 -0800 Subject: [PATCH 0268/1161] Collect User.ip_address automatically --- lib/Raven/Client.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ebfd7bb24..fcfda805e 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -740,6 +740,9 @@ protected function get_user_data() $user = array( 'id' => session_id(), ); + if (!empty($_SERVER['REMOTE_ADDR'])) { + $user['ip_address'] = $_SERVER['REMOTE_ADDR']; + } if (!empty($_SESSION)) { $user['data'] = $_SESSION; } From d7bdab3288bdc547277c2590e336467a48adb286 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 1 Mar 2017 11:39:44 -0800 Subject: [PATCH 0269/1161] Expand changelog --- CHANGES | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 75b449b77..c3a884f3c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,9 +1,10 @@ 1.7.0 ----- -- Corrected some issues with argument serialization in stacktraces. +- Corrected some issues with argument serialization in stacktraces (#399). - The default exception handler will now re-raise exceptions when - ``call_existing`` is true and no exception handler is registered. + ``call_existing`` is true and no exception handler is registered (#421). +- Collect User.ip_address automatically (#419). 1.6.2 ----- From f3de726edec07b4c555eb761a85be569ac183b36 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 2 Mar 2017 16:04:02 -0800 Subject: [PATCH 0270/1161] Add sample_rate configuration --- docs/config.rst | 10 ++++++++++ lib/Raven/Client.php | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index 75ba72fac..645493836 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -103,6 +103,16 @@ The following settings are available for the client: '/www/php/lib', )); +.. describe:: sample_rate + + The sampling factor to apply to events. A value of 0.00 will deny sending + any events, and a value of 1.00 will send 100% of events. + + .. code-block:: php + + // send 50% of events + 'sample_rate' => 0.5, + .. describe:: send_callback A function which will be called whenever data is ready to be sent. Within diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index fcfda805e..ee6ae3fe3 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -78,6 +78,7 @@ class Raven_Client public $tags; public $release; public $environment; + public $sample_rate; public $trace; public $timeout; public $message_limit; @@ -152,6 +153,7 @@ public function __construct($options_or_dsn = null, $options = array()) $this->tags = Raven_Util::get($options, 'tags', array()); $this->release = Raven_Util::get($options, 'release', null); $this->environment = Raven_Util::get($options, 'environment', null); + $this->sample_rate = Raven_Util::get($options, 'sample_rate', 1); $this->trace = (bool) Raven_Util::get($options, 'trace', true); $this->timeout = Raven_Util::get($options, 'timeout', 2); $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT); @@ -971,6 +973,11 @@ public function send(&$data) return; } + // should this event be sampled? + if (rand(1, 100) / 100.0 > $this->sample_rate) { + return; + } + $message = $this->encode($data); $headers = array( From 1f16767b3a008c0ea26a9b0c5cfd843cda90ba18 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Sat, 4 Mar 2017 19:49:48 +0300 Subject: [PATCH 0271/1161] All non static method should be protected instead of private All properties should be protected instead of private --- lib/Raven/Breadcrumbs/ErrorHandler.php | 2 +- lib/Raven/Breadcrumbs/MonologHandler.php | 4 ++-- lib/Raven/CurlHandler.php | 10 +++++----- lib/Raven/ErrorHandler.php | 18 +++++++++--------- lib/Raven/SanitizeDataProcessor.php | 4 ++-- lib/Raven/Serializer.php | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index eb340c224..ffb07fdba 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -2,7 +2,7 @@ class Raven_Breadcrumbs_ErrorHandler { - private $existingHandler; + protected $existingHandler; /** * @var Raven_Client the client object that sends the message to the server diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index a6d20307d..52a658dbd 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -8,7 +8,7 @@ class Raven_Breadcrumbs_MonologHandler extends AbstractProcessingHandler /** * Translates Monolog log levels to Raven log levels. */ - private $logLevels = array( + protected $logLevels = array( Logger::DEBUG => Raven_Client::DEBUG, Logger::INFO => Raven_Client::INFO, Logger::NOTICE => Raven_Client::INFO, @@ -19,7 +19,7 @@ class Raven_Breadcrumbs_MonologHandler extends AbstractProcessingHandler Logger::EMERGENCY => Raven_Client::FATAL, ); - private $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; + protected $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; /** * @var Raven_Client the client object that sends the message to the server diff --git a/lib/Raven/CurlHandler.php b/lib/Raven/CurlHandler.php index c2ed47fff..0412a86ba 100644 --- a/lib/Raven/CurlHandler.php +++ b/lib/Raven/CurlHandler.php @@ -17,10 +17,10 @@ // TODO(dcramer): handle ca_cert class Raven_CurlHandler { - private $join_timeout; - private $multi_handle; - private $options; - private $requests; + protected $join_timeout; + protected $multi_handle; + protected $options; + protected $requests; public function __construct($options, $join_timeout = 5) { @@ -87,7 +87,7 @@ public function join($timeout = null) /** * @doc http://php.net/manual/en/function.curl-multi-exec.php */ - private function select() + protected function select() { do { $mrc = curl_multi_exec($this->multi_handle, $active); diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 8742e83c5..49cfe2ca8 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -26,15 +26,15 @@ // currently are not used outside of the fatal handler. class Raven_ErrorHandler { - private $old_exception_handler; - private $call_existing_exception_handler = false; - private $old_error_handler; - private $call_existing_error_handler = false; - private $reservedMemory; + protected $old_exception_handler; + protected $call_existing_exception_handler = false; + protected $old_error_handler; + protected $call_existing_error_handler = false; + protected $reservedMemory; /** @var Raven_Client */ - private $client; - private $send_errors_last = false; - private $fatal_error_types = array( + protected $client; + protected $send_errors_last = false; + protected $fatal_error_types = array( E_ERROR, E_PARSE, E_CORE_ERROR, @@ -49,7 +49,7 @@ class Raven_ErrorHandler * Error types which should be processed by the handler. * A 'null' value implies "whatever error_reporting is at time of error". */ - private $error_types = null; + protected $error_types = null; public function __construct($client, $send_errors_last = false, $error_types = null, $__error_types = null) diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/SanitizeDataProcessor.php index 55caff328..f4717eb06 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/SanitizeDataProcessor.php @@ -11,8 +11,8 @@ class Raven_SanitizeDataProcessor extends Raven_Processor const FIELDS_RE = '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i'; const VALUES_RE = '/^(?:\d[ -]*?){13,16}$/'; - private $fields_re; - private $values_re; + protected $fields_re; + protected $values_re; protected $session_cookie_name; public function __construct(Raven_Client $client) diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 3889d0bd6..c57999aa0 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -43,7 +43,7 @@ class Raven_Serializer * * @var string */ - private $mb_detect_order = self::DEFAULT_MB_DETECT_ORDER; + protected $mb_detect_order = self::DEFAULT_MB_DETECT_ORDER; /** * @param null|string $mb_detect_order From b2350e9911210257499b8175967204c05e895fa9 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Mon, 6 Mar 2017 05:49:19 +0300 Subject: [PATCH 0272/1161] Code coverage for sample rate --- lib/Raven/Client.php | 2 +- test/Raven/Tests/ClientTest.php | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index b4260fe9a..c306308e3 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -976,7 +976,7 @@ public function send(&$data) } // should this event be sampled? - if (rand(1, 100) / 100.0 > $this->sample_rate) { + if (mt_rand(1, 100) / 100.0 > $this->sample_rate) { return; } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index bcbd97b7b..c145d2b0d 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -2230,4 +2230,66 @@ public function testClose_curl_resource() $raven->close_curl_resource(); $this->assertNull($reflection->getValue($raven)); } + + /** + * @covers \Raven\Client::send + */ + public function testSampleRateAbsolute() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + 'sample_rate' => 0, + ) + ); + for ($i = 0; $i < 1000; $i++) { + $client->captureMessage('foobar'); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + } + + // step 2 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + 'sample_rate' => 1, + ) + ); + for ($i = 0; $i < 1000; $i++) { + $client->captureMessage('foobar'); + $this->assertTrue($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + } + } + + /** + * @covers \Raven\Client::send + */ + public function testSampleRatePrc() + { + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + 'sample_rate' => 0.5, + ) + ); + $u_true = false; + $u_false = false; + for ($i = 0; $i < 1000; $i++) { + $client->captureMessage('foobar'); + if ($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called) { + $u_true = true; + } else { + $u_false = true; + } + + if ($u_true or $u_false) { + return; + } + } + + $this->fail('sample_rate=0.5 can not produce fails and successes at the same time'); + } } From 986b0dc58a5951b08302bb4f8901c3caca1b533b Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Mon, 6 Mar 2017 06:01:41 +0300 Subject: [PATCH 0273/1161] Deleted \Raven\Compat --- lib/Raven/Compat.php | 186 -------------------------------- test/Raven/Tests/ClientTest.php | 7 +- test/Raven/Tests/CompatTest.php | 113 ------------------- 3 files changed, 3 insertions(+), 303 deletions(-) delete mode 100644 lib/Raven/Compat.php delete mode 100644 test/Raven/Tests/CompatTest.php diff --git a/lib/Raven/Compat.php b/lib/Raven/Compat.php deleted file mode 100644 index 6ba58457a..000000000 --- a/lib/Raven/Compat.php +++ /dev/null @@ -1,186 +0,0 @@ - $size) { - $key = str_pad(pack($pack, $algo($key)), $size, chr(0x00)); - } else { - $key = str_pad($key, $size, chr(0x00)); - } - - $keyLastPos = strlen($key) - 1; - for ($i = 0; $i < $keyLastPos; $i++) { - $opad[$i] = $opad[$i] ^ $key[$i]; - $ipad[$i] = $ipad[$i] ^ $key[$i]; - } - - $output = $algo($opad.pack($pack, $algo($ipad.$data))); - - return ($raw_output) ? pack($pack, $output) : $output; - } - - /** - * Note that we discard the options given to be compatible - * with PHP < 5.3 - * - * @param mixed $value - * @param int $options - * @param int $depth Set the maximum depth - * @return string - */ - public static function json_encode($value, $options = 0, $depth = 512) - { - if (function_exists('json_encode')) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - return json_encode($value); - } elseif (version_compare(PHP_VERSION, '5.5.0', '<')) { - return json_encode($value, $options); - } else { - return json_encode($value, $options, $depth); - } - } - - // @codeCoverageIgnoreStart - return self::_json_encode($value, $depth); - // @codeCoverageIgnoreEnd - } - - /** - * @param mixed $value - * @param int $depth Set the maximum depth - * @return string|false - */ - public static function _json_encode($value, $depth = 513) - { - if (ini_get('xdebug.extended_info') !== false) { - ini_set('xdebug.max_nesting_level', 2048); - } - return self::_json_encode_lowlevel($value, $depth); - } - - /** - * Implementation taken from - * http://www.mike-griffiths.co.uk/php-json_encode-alternative/ - * - * @param mixed $value - * @param int $depth Set the maximum depth - * @return string|false - */ - private static function _json_encode_lowlevel($value, $depth) - { - static $jsonReplaces = array( - array('\\', '/', "\n", "\t", "\r", "\f", '"'), - array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\f', '\"')); - - if (is_null($value)) { - return 'null'; - } - if ($value === false) { - return 'false'; - } - if ($value === true) { - return 'true'; - } - - if (is_scalar($value)) { - // Always use '.' for floats. - if (is_float($value)) { - return floatval(str_replace(',', '.', strval($value))); - } - if (is_string($value)) { - return sprintf('"%s"', - str_replace($jsonReplaces[0], $jsonReplaces[1], $value)); - } else { - return $value; - } - } elseif ($depth <= 1) { - return false; - } - - $isList = true; - for ($i = 0, reset($value); $i $v) { - $this_value = self::_json_encode($v, $depth - 1); - if ($this_value === false) { - return false; - } - $result[] = self::_json_encode($k, $depth - 1).':'.$this_value; - } - - return '{' . join(',', $result) . '}'; - } - } -} diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index c145d2b0d..5484ad1dd 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -1776,9 +1776,8 @@ public function testEncodeTooDepth() $data_broken = array($data_broken); } $value = $client->encode($data_broken); - if (!function_exists('json_encode') or version_compare(PHP_VERSION, '5.5.0', '>=')) { - $this->assertFalse($value, 'Broken data encoded successfully with '. - (function_exists('json_encode') ? 'native method' : '\\Raven\\Compat::_json_encode')); + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertFalse($value, 'Broken data encoded successfully with native method'); } else { if ($value !== false) { $this->markTestSkipped(); @@ -1792,7 +1791,7 @@ public function testEncode() { $client = new Dummy_Raven_Client(); $data = array('some' => (object)array('value' => 'data'), 'foo' => array('bar', null, 123), false); - $json_stringify = \Raven\Compat::json_encode($data); + $json_stringify = json_encode($data); $value = $client->encode($data); $this->assertNotFalse($value); $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value, '\\Raven\\Client::encode returned malformed data'); diff --git a/test/Raven/Tests/CompatTest.php b/test/Raven/Tests/CompatTest.php deleted file mode 100644 index 451692b86..000000000 --- a/test/Raven/Tests/CompatTest.php +++ /dev/null @@ -1,113 +0,0 @@ -assertEquals(\Raven\Compat::gethostname(), \Raven\Compat::_gethostname()); - $this->assertTrue(strlen(\Raven\Compat::_gethostname()) > 0); - } - - public function test_hash_hmac() - { - $result = \Raven\Compat::hash_hmac('sha1', 'foo', 'bar'); - $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); - - $result = \Raven\Compat::_hash_hmac('sha1', 'foo', 'bar'); - $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); - - $long_key = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; - $result = \Raven\Compat::_hash_hmac('md5', 'data', $long_key); - $this->assertEquals('951038f9ab8a10c929ab6dbc5f927207', $result); - - $result = \Raven\Compat::_hash_hmac('sha1', 'data', $long_key); - $this->assertEquals('cbf0d1ca10d211da2bc15cb3b579ecfebf3056d2', $result); - - $result = \Raven\Compat::_hash_hmac('md5', 'foobar', $long_key); - $this->assertEquals('5490f3cddeb9665bce3239cbc4c15e2c', $result); - - $result = \Raven\Compat::_hash_hmac('sha1', 'foobar', $long_key); - $this->assertEquals('5729f50ff2fbb8f8bf81d7a86f69a89f7574697c', $result); - - - $result = \Raven\Compat::_hash_hmac('md5', 'foo', $long_key); - $this->assertEquals('ab193328035cbd3a48dea9d64ba92736', $result); - - $result = \Raven\Compat::_hash_hmac('sha1', 'foo', $long_key); - $this->assertEquals('8f883d0755115314930968496573f27735eb0c41', $result); - } - - public function test_json_encode() - { - $result = \Raven\Compat::json_encode(array('foo' => array('bar' => 1))); - $this->assertEquals('{"foo":{"bar":1}}', $result); - - $result = \Raven\Compat::_json_encode(array('foo' => array('bar' => 1))); - $this->assertEquals('{"foo":{"bar":1}}', $result); - - $result = \Raven\Compat::_json_encode(array(1, 2, 3, 4, 'foo', 'bar')); - $this->assertEquals('[1,2,3,4,"foo","bar"]', $result); - - $result = \Raven\Compat::_json_encode(array(1, 'foo', 'foobar' => 'bar')); - $this->assertEquals('{0:1,1:"foo","foobar":"bar"}', $result); - - $result = \Raven\Compat::_json_encode(array(array())); - $this->assertEquals('[[]]', $result); - - $result = \Raven\Compat::_json_encode(array(null, false, true, 1.5)); - $this->assertEquals('[null,false,true,1.5]', $result); - } - - /** - * @covers \Raven\Compat::_json_encode - * @covers \Raven\Compat::_json_encode_lowlevel - * - * I show you how deep the rabbit hole goes - */ - public function test_json_encode_with_broken_data() - { - $data_broken_named = array(); - $data_broken_named_510 = null; - $data_broken_named_511 = null; - - $data_broken = array(); - $data_broken_510 = null; - $data_broken_511 = null; - for ($i = 0; $i < 1024; $i++) { - $data_broken = array($data_broken); - $data_broken_named = array('a' => $data_broken_named); - switch ($i) { - case 510: - $data_broken_510 = $data_broken; - $data_broken_named_510 = $data_broken_named; - break; - case 511: - $data_broken_511 = $data_broken; - $data_broken_named_511 = $data_broken_named; - break; - } - } - $value_1024 = \Raven\Compat::_json_encode($data_broken); - $value_510 = \Raven\Compat::_json_encode($data_broken_510); - $value_511 = \Raven\Compat::_json_encode($data_broken_511); - $this->assertFalse($value_1024, 'Broken data encoded successfully with Raven_Compat::_json_encode'); - $this->assertNotFalse($value_510); - $this->assertFalse($value_511); - - $value_1024 = \Raven\Compat::_json_encode($data_broken_named); - $value_510 = \Raven\Compat::_json_encode($data_broken_named_510); - $value_511 = \Raven\Compat::_json_encode($data_broken_named_511); - $this->assertFalse($value_1024, 'Broken data encoded successfully with Raven_Compat::_json_encode'); - $this->assertNotFalse($value_510); - $this->assertFalse($value_511); - } -} From 1f3aee44c5a2c2f2f52e501af021b87e0ce7c2f0 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Mon, 6 Mar 2017 14:00:50 +0300 Subject: [PATCH 0274/1161] Rename folder test to tests --- .gitattributes | 2 +- composer.json | 2 +- phpunit.xml | 4 ++-- .../Raven/Tests/Breadcrumbs/ErrorHandlerTest.php | 0 .../Raven/Tests/Breadcrumbs/MonologTest.php | 0 {test => tests}/Raven/Tests/BreadcrumbsTest.php | 0 {test => tests}/Raven/Tests/ClientTest.php | 0 {test => tests}/Raven/Tests/ErrorHandlerTest.php | 0 {test => tests}/Raven/Tests/IntegrationTest.php | 2 +- {test => tests}/Raven/Tests/ReprSerializerTest.php | 0 .../Raven/Tests/SanitizeDataProcessorTest.php | 0 {test => tests}/Raven/Tests/SerializerTest.php | 0 {test => tests}/Raven/Tests/StacktraceTest.php | 0 .../Raven/Tests/TransactionStackTest.php | 0 {test => tests}/Raven/Tests/UtilTest.php | 0 {test => tests}/Raven/Tests/resources/a.php | 0 {test => tests}/Raven/Tests/resources/b.php | 0 .../resources/captureExceptionInLatin1File.php | 0 {test => tests}/bootstrap.php | 0 {test => tests}/data/binary | Bin 20 files changed, 5 insertions(+), 5 deletions(-) rename {test => tests}/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php (100%) rename {test => tests}/Raven/Tests/Breadcrumbs/MonologTest.php (100%) rename {test => tests}/Raven/Tests/BreadcrumbsTest.php (100%) rename {test => tests}/Raven/Tests/ClientTest.php (100%) rename {test => tests}/Raven/Tests/ErrorHandlerTest.php (100%) rename {test => tests}/Raven/Tests/IntegrationTest.php (94%) rename {test => tests}/Raven/Tests/ReprSerializerTest.php (100%) rename {test => tests}/Raven/Tests/SanitizeDataProcessorTest.php (100%) rename {test => tests}/Raven/Tests/SerializerTest.php (100%) rename {test => tests}/Raven/Tests/StacktraceTest.php (100%) rename {test => tests}/Raven/Tests/TransactionStackTest.php (100%) rename {test => tests}/Raven/Tests/UtilTest.php (100%) rename {test => tests}/Raven/Tests/resources/a.php (100%) rename {test => tests}/Raven/Tests/resources/b.php (100%) rename {test => tests}/Raven/Tests/resources/captureExceptionInLatin1File.php (100%) rename {test => tests}/bootstrap.php (100%) rename {test => tests}/data/binary (100%) diff --git a/.gitattributes b/.gitattributes index 33a59aed3..f4de12e11 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ /examples export-ignore /docs export-ignore -/test export-ignore +/tests export-ignore diff --git a/composer.json b/composer.json index 627f01b6b..bd0f8d9cb 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ "vendor/bin/phpunit --verbose" ], "tests-report": [ - "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-html test/html-report" + "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-html tests/html-report" ], "phpcs": [ "vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run" diff --git a/phpunit.xml b/phpunit.xml index 9024d987c..fb6a4ae60 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,11 +8,11 @@ processIsolation="false" stopOnFailure="false" syntaxCheck="false" - bootstrap="test/bootstrap.php" + bootstrap="tests/bootstrap.php" > - ./test/Raven/ + ./tests/Raven/ diff --git a/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php b/tests/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php similarity index 100% rename from test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php rename to tests/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php diff --git a/test/Raven/Tests/Breadcrumbs/MonologTest.php b/tests/Raven/Tests/Breadcrumbs/MonologTest.php similarity index 100% rename from test/Raven/Tests/Breadcrumbs/MonologTest.php rename to tests/Raven/Tests/Breadcrumbs/MonologTest.php diff --git a/test/Raven/Tests/BreadcrumbsTest.php b/tests/Raven/Tests/BreadcrumbsTest.php similarity index 100% rename from test/Raven/Tests/BreadcrumbsTest.php rename to tests/Raven/Tests/BreadcrumbsTest.php diff --git a/test/Raven/Tests/ClientTest.php b/tests/Raven/Tests/ClientTest.php similarity index 100% rename from test/Raven/Tests/ClientTest.php rename to tests/Raven/Tests/ClientTest.php diff --git a/test/Raven/Tests/ErrorHandlerTest.php b/tests/Raven/Tests/ErrorHandlerTest.php similarity index 100% rename from test/Raven/Tests/ErrorHandlerTest.php rename to tests/Raven/Tests/ErrorHandlerTest.php diff --git a/test/Raven/Tests/IntegrationTest.php b/tests/Raven/Tests/IntegrationTest.php similarity index 94% rename from test/Raven/Tests/IntegrationTest.php rename to tests/Raven/Tests/IntegrationTest.php index 57c074c5f..2b6ea2161 100644 --- a/test/Raven/Tests/IntegrationTest.php +++ b/tests/Raven/Tests/IntegrationTest.php @@ -64,6 +64,6 @@ public function testCaptureSimpleError() $this->assertEquals($exc['value'], 'mkdir(): No such file or directory'); $stack = $exc['stacktrace']['frames']; $lastFrame = $stack[count($stack) - 1]; - $this->assertEquals(@$lastFrame['filename'], 'test/Raven/Tests/IntegrationTest.php'); + $this->assertEquals(@$lastFrame['filename'], 'tests/Raven/Tests/IntegrationTest.php'); } } diff --git a/test/Raven/Tests/ReprSerializerTest.php b/tests/Raven/Tests/ReprSerializerTest.php similarity index 100% rename from test/Raven/Tests/ReprSerializerTest.php rename to tests/Raven/Tests/ReprSerializerTest.php diff --git a/test/Raven/Tests/SanitizeDataProcessorTest.php b/tests/Raven/Tests/SanitizeDataProcessorTest.php similarity index 100% rename from test/Raven/Tests/SanitizeDataProcessorTest.php rename to tests/Raven/Tests/SanitizeDataProcessorTest.php diff --git a/test/Raven/Tests/SerializerTest.php b/tests/Raven/Tests/SerializerTest.php similarity index 100% rename from test/Raven/Tests/SerializerTest.php rename to tests/Raven/Tests/SerializerTest.php diff --git a/test/Raven/Tests/StacktraceTest.php b/tests/Raven/Tests/StacktraceTest.php similarity index 100% rename from test/Raven/Tests/StacktraceTest.php rename to tests/Raven/Tests/StacktraceTest.php diff --git a/test/Raven/Tests/TransactionStackTest.php b/tests/Raven/Tests/TransactionStackTest.php similarity index 100% rename from test/Raven/Tests/TransactionStackTest.php rename to tests/Raven/Tests/TransactionStackTest.php diff --git a/test/Raven/Tests/UtilTest.php b/tests/Raven/Tests/UtilTest.php similarity index 100% rename from test/Raven/Tests/UtilTest.php rename to tests/Raven/Tests/UtilTest.php diff --git a/test/Raven/Tests/resources/a.php b/tests/Raven/Tests/resources/a.php similarity index 100% rename from test/Raven/Tests/resources/a.php rename to tests/Raven/Tests/resources/a.php diff --git a/test/Raven/Tests/resources/b.php b/tests/Raven/Tests/resources/b.php similarity index 100% rename from test/Raven/Tests/resources/b.php rename to tests/Raven/Tests/resources/b.php diff --git a/test/Raven/Tests/resources/captureExceptionInLatin1File.php b/tests/Raven/Tests/resources/captureExceptionInLatin1File.php similarity index 100% rename from test/Raven/Tests/resources/captureExceptionInLatin1File.php rename to tests/Raven/Tests/resources/captureExceptionInLatin1File.php diff --git a/test/bootstrap.php b/tests/bootstrap.php similarity index 100% rename from test/bootstrap.php rename to tests/bootstrap.php diff --git a/test/data/binary b/tests/data/binary similarity index 100% rename from test/data/binary rename to tests/data/binary From cc79a7cd7d86fa71acfe9e1cf2dde5f16d610f99 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 10 Feb 2017 15:20:05 +0100 Subject: [PATCH 0275/1161] Add processors to remove POST data and cookies --- CHANGES | 1 + lib/Raven/Client.php | 2 +- lib/Raven/Processor.php | 29 +++- .../Processor/RemoveCookiesProcessor.php | 35 ++++ .../Processor/RemoveHttpBodyProcessor.php | 30 ++++ lib/Raven/Processor/SanitizeDataProcessor.php | 160 ++++++++++++++++++ lib/Raven/SanitizeDataProcessor.php | 155 ++--------------- test/Raven/Tests/ClientTest.php | 11 +- .../Processor/RemoveCookiesProcessorTest.php | 78 +++++++++ .../Processor/RemoveHttpBodyProcessorTest.php | 121 +++++++++++++ .../SanitizeDataProcessorTest.php | 52 +++--- 11 files changed, 488 insertions(+), 186 deletions(-) create mode 100644 lib/Raven/Processor/RemoveCookiesProcessor.php create mode 100644 lib/Raven/Processor/RemoveHttpBodyProcessor.php create mode 100644 lib/Raven/Processor/SanitizeDataProcessor.php create mode 100644 test/Raven/Tests/Processor/RemoveCookiesProcessorTest.php create mode 100644 test/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php rename test/Raven/Tests/{ => Processor}/SanitizeDataProcessorTest.php (73%) diff --git a/CHANGES b/CHANGES index c3a884f3c..ea0a98ca0 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ - The default exception handler will now re-raise exceptions when ``call_existing`` is true and no exception handler is registered (#421). - Collect User.ip_address automatically (#419). +- Added a processor to remove web cookies and another to remove HTTP body data for POST, PUT, PATCH and DELETE requests. They will be enabled by default in ``2.0`` (#405). 1.6.2 ----- diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ee6ae3fe3..60c83cf7a 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -380,7 +380,7 @@ public function setTransport($value) public static function getDefaultProcessors() { return array( - 'Raven_SanitizeDataProcessor', + 'Raven_Processor_SanitizeDataProcessor', ); } diff --git a/lib/Raven/Processor.php b/lib/Raven/Processor.php index afc2beeff..e7e239ba5 100644 --- a/lib/Raven/Processor.php +++ b/lib/Raven/Processor.php @@ -1,4 +1,5 @@ + */ +final class Raven_Processor_RemoveCookiesProcessor extends Raven_Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request'])) { + if (isset($data['request']['cookies'])) { + $data['request']['cookies'] = self::STRING_MASK; + } + + if (isset($data['request']['headers']) && isset($data['request']['headers']['Cookie'])) { + $data['request']['headers']['Cookie'] = self::STRING_MASK; + } + } + } +} diff --git a/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/lib/Raven/Processor/RemoveHttpBodyProcessor.php new file mode 100644 index 000000000..3952ced10 --- /dev/null +++ b/lib/Raven/Processor/RemoveHttpBodyProcessor.php @@ -0,0 +1,30 @@ + + */ +final class Raven_Processor_RemoveHttpBodyProcessor extends Raven_Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request'], $data['request']['method']) && in_array(strtoupper($data['request']['method']), array('POST', 'PUT', 'PATCH', 'DELETE'))) { + $data['request']['data'] = self::STRING_MASK; + } + } +} diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php new file mode 100644 index 000000000..93cc3851b --- /dev/null +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -0,0 +1,160 @@ +fields_re = self::FIELDS_RE; + $this->values_re = self::VALUES_RE; + $this->session_cookie_name = ini_get('session.name'); + } + + /** + * {@inheritdoc} + */ + public function setProcessorOptions(array $options) + { + if (isset($options['fields_re'])) { + $this->fields_re = $options['fields_re']; + } + + if (isset($options['values_re'])) { + $this->values_re = $options['values_re']; + } + } + + /** + * Replace any array values with our mask if the field name or the value matches a respective regex + * + * @param mixed $item Associative array value + * @param string $key Associative array key + */ + public function sanitize(&$item, $key) + { + if (empty($item)) { + return; + } + + if (preg_match($this->values_re, $item)) { + $item = self::STRING_MASK; + } + + if (empty($key)) { + return; + } + + if (preg_match($this->fields_re, $key)) { + $item = self::STRING_MASK; + } + } + + public function sanitizeException(&$data) + { + foreach ($data['exception']['values'] as &$value) { + return $this->sanitizeStacktrace($value['stacktrace']); + } + } + + public function sanitizeHttp(&$data) + { + $http = &$data['request']; + if (!empty($http['cookies']) && is_array($http['cookies'])) { + $cookies = &$http['cookies']; + if (!empty($cookies[$this->session_cookie_name])) { + $cookies[$this->session_cookie_name] = self::STRING_MASK; + } + } + if (!empty($http['data']) && is_array($http['data'])) { + array_walk_recursive($http['data'], array($this, 'sanitize')); + } + } + + public function sanitizeStacktrace(&$data) + { + foreach ($data['frames'] as &$frame) { + if (empty($frame['vars'])) { + continue; + } + array_walk_recursive($frame['vars'], array($this, 'sanitize')); + } + } + + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (!empty($data['exception'])) { + $this->sanitizeException($data); + } + if (!empty($data['stacktrace'])) { + $this->sanitizeStacktrace($data['stacktrace']); + } + if (!empty($data['request'])) { + $this->sanitizeHttp($data); + } + if (!empty($data['extra'])) { + array_walk_recursive($data['extra'], array($this, 'sanitize')); + } + } + + /** + * @return string + */ + public function getFieldsRe() + { + return $this->fields_re; + } + + /** + * @param string $fields_re + */ + public function setFieldsRe($fields_re) + { + $this->fields_re = $fields_re; + } + + /** + * @return string + */ + public function getValuesRe() + { + return $this->values_re; + } + + /** + * @param string $values_re + */ + public function setValuesRe($values_re) + { + $this->values_re = $values_re; + } +} diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/SanitizeDataProcessor.php index f4717eb06..06e7a9370 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/SanitizeDataProcessor.php @@ -1,148 +1,19 @@ fields_re = self::FIELDS_RE; - $this->values_re = self::VALUES_RE; - $this->session_cookie_name = ini_get('session.name'); - } - - /** - * Override the default processor options - * - * @param array $options Associative array of processor options - */ - public function setProcessorOptions(array $options) - { - if (isset($options['fields_re'])) { - $this->fields_re = $options['fields_re']; - } - - if (isset($options['values_re'])) { - $this->values_re = $options['values_re']; - } - } - - /** - * Replace any array values with our mask if the field name or the value matches a respective regex - * - * @param mixed $item Associative array value - * @param string $key Associative array key - */ - public function sanitize(&$item, $key) - { - if (empty($item)) { - return; - } - - if (preg_match($this->values_re, $item)) { - $item = self::MASK; - } - - if (empty($key)) { - return; - } - if (preg_match($this->fields_re, $key)) { - $item = self::MASK; - } - } +@trigger_error('The '.__NAMESPACE__.'\Raven_SanitizeDataProcessor class is deprecated since version 1.7 and will be removed in 2.0. Use the Raven_Processor_SanitizeDataProcessor class in the same namespace instead.', E_USER_DEPRECATED); - /** @noinspection PhpInconsistentReturnPointsInspection - * @param array $data - */ - public function sanitizeException(&$data) - { - foreach ($data['exception']['values'] as &$value) { - return $this->sanitizeStacktrace($value['stacktrace']); - } - } - - public function sanitizeHttp(&$data) - { - $http = &$data['request']; - if (!empty($http['cookies'])) { - $cookies = &$http['cookies']; - if (!empty($cookies[$this->session_cookie_name])) { - $cookies[$this->session_cookie_name] = self::MASK; - } - } - if (!empty($http['data']) && is_array($http['data'])) { - array_walk_recursive($http['data'], array($this, 'sanitize')); - } - } - - public function sanitizeStacktrace(&$data) - { - foreach ($data['frames'] as &$frame) { - if (empty($frame['vars'])) { - continue; - } - array_walk_recursive($frame['vars'], array($this, 'sanitize')); - } - } - - public function process(&$data) - { - if (!empty($data['exception'])) { - $this->sanitizeException($data); - } - if (!empty($data['stacktrace'])) { - $this->sanitizeStacktrace($data['stacktrace']); - } - if (!empty($data['request'])) { - $this->sanitizeHttp($data); - } - if (!empty($data['extra'])) { - array_walk_recursive($data['extra'], array($this, 'sanitize')); - } - } - - /** - * @return string - */ - public function getFieldsRe() - { - return $this->fields_re; - } - - /** - * @param string $fields_re - */ - public function setFieldsRe($fields_re) - { - $this->fields_re = $fields_re; - } - - /** - * @return string - */ - public function getValuesRe() - { - return $this->values_re; - } - - /** - * @param string $values_re - */ - public function setValuesRe($values_re) - { - $this->values_re = $values_re; - } +/** + * {@inheritdoc} + */ +class Raven_SanitizeDataProcessor extends Raven_Processor_SanitizeDataProcessor +{ } diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index faf5ca681..5fe827613 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -690,10 +690,11 @@ public function testCaptureExceptionInvalidUTF8() public function testDoesRegisterProcessors() { $client = new Dummy_Raven_Client(array( - 'processors' => array('Raven_SanitizeDataProcessor'), + 'processors' => array('Raven_Processor_SanitizeDataProcessor'), )); + $this->assertEquals(1, count($client->processors)); - $this->assertInstanceOf('Raven_SanitizeDataProcessor', $client->processors[0]); + $this->assertInstanceOf('Raven_Processor_SanitizeDataProcessor', $client->processors[0]); } public function testProcessDoesCallProcessors() @@ -729,9 +730,7 @@ public function testDefaultProcessorsAreUsed() */ public function testDefaultProcessorsContainSanitizeDataProcessor() { - $defaults = Dummy_Raven_Client::getDefaultProcessors(); - - $this->assertTrue(in_array('Raven_SanitizeDataProcessor', $defaults)); + $this->assertContains('Raven_Processor_SanitizeDataProcessor', Dummy_Raven_Client::getDefaultProcessors()); } /** @@ -2095,7 +2094,7 @@ public function testCaptureLevel() } $client = new Dummy_Raven_Client(); - $client->capture(array('message' => 'foobar', )); + $client->capture(array('message' => 'foobar')); $events = $client->getSentEvents(); $event = array_pop($events); $input = $client->get_http_data(); diff --git a/test/Raven/Tests/Processor/RemoveCookiesProcessorTest.php b/test/Raven/Tests/Processor/RemoveCookiesProcessorTest.php new file mode 100644 index 000000000..c1fe7f292 --- /dev/null +++ b/test/Raven/Tests/Processor/RemoveCookiesProcessorTest.php @@ -0,0 +1,78 @@ +getMockBuilder('\Raven_Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new Raven_Processor_RemoveCookiesProcessor($client); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'foo' => 'bar', + ), + ), + array( + 'request' => array( + 'foo' => 'bar', + ), + ), + ), + array( + array( + 'request' => array( + 'foo' => 'bar', + 'cookies' => 'baz', + 'headers' => array( + 'Cookie' => 'bar', + 'AnotherHeader' => 'foo', + ), + ), + ), + array( + 'request' => array( + 'foo' => 'bar', + 'cookies' => Raven_Processor::STRING_MASK, + 'headers' => array( + 'Cookie' => Raven_Processor::STRING_MASK, + 'AnotherHeader' => 'foo', + ), + ), + ), + ), + ); + } +} diff --git a/test/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php b/test/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php new file mode 100644 index 000000000..d273eba18 --- /dev/null +++ b/test/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php @@ -0,0 +1,121 @@ +getMockBuilder('\Raven_Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new Raven_Processor_RemoveHttpBodyProcessor($client); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'method' => 'POST', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => Raven_Processor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'PUT', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => Raven_Processor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'PATCH', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => Raven_Processor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'DELETE', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => Raven_Processor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'GET', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + ), + ); + } +} diff --git a/test/Raven/Tests/SanitizeDataProcessorTest.php b/test/Raven/Tests/Processor/SanitizeDataProcessorTest.php similarity index 73% rename from test/Raven/Tests/SanitizeDataProcessorTest.php rename to test/Raven/Tests/Processor/SanitizeDataProcessorTest.php index a70120e22..927255914 100644 --- a/test/Raven/Tests/SanitizeDataProcessorTest.php +++ b/test/Raven/Tests/Processor/SanitizeDataProcessorTest.php @@ -33,20 +33,20 @@ public function testDoesFilterHttpData() ); $client = new Dummy_Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); + $processor = new Raven_Processor_SanitizeDataProcessor($client); $processor->process($data); $vars = $data['request']['data']; $this->assertEquals($vars['foo'], 'bar'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['password']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['the_secret']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['a_password_here']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['mypasswd']); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['authorization']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['password']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['the_secret']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['a_password_here']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['mypasswd']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['authorization']); $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['card_number']['0']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0']); } public function testDoesFilterSessionId() @@ -60,11 +60,11 @@ public function testDoesFilterSessionId() ); $client = new Dummy_Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); + $processor = new Raven_Processor_SanitizeDataProcessor($client); $processor->process($data); $cookies = $data['request']['cookies']; - $this->assertEquals($cookies[ini_get('session.name')], Raven_SanitizeDataProcessor::MASK); + $this->assertEquals($cookies[ini_get('session.name')], Raven_Processor_SanitizeDataProcessor::STRING_MASK); } public function testDoesFilterCreditCard() @@ -76,20 +76,16 @@ public function testDoesFilterCreditCard() ); $client = new Dummy_Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); + $processor = new Raven_Processor_SanitizeDataProcessor($client); $processor->process($data); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $data['extra']['ccnumba']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $data['extra']['ccnumba']); } - /** - * @covers Raven_SanitizeDataProcessor::setProcessorOptions - * - */ public function testSettingProcessorOptions() { $client = new Dummy_Raven_Client(); - $processor = new Raven_SanitizeDataProcessor($client); + $processor = new Raven_Processor_SanitizeDataProcessor($client); $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); @@ -116,13 +112,13 @@ public function testOverrideOptions($processorOptions, $client_options, $dsn) { $client = new Dummy_Raven_Client($dsn, $client_options); /** - * @var Raven_SanitizeDataProcessor $processor + * @var Raven_Processor_SanitizeDataProcessor $processor */ $processor = $client->processors[0]; - $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf('Raven_Processor_SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_Processor_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_Processor_SanitizeDataProcessor']['values_re'], 'overwrote values'); } /** @@ -155,13 +151,13 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $client = new Dummy_Raven_Client($dsn, $client_options); /** - * @var Raven_SanitizeDataProcessor $processor + * @var Raven_Processor_SanitizeDataProcessor $processor */ $processor = $client->processors[0]; - $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf('Raven_Processor_SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_Processor_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_Processor_SanitizeDataProcessor']['values_re'], 'overwrote values'); $processor->process($data); @@ -172,9 +168,9 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $this->assertEquals($vars['a_password_here'], 'hello', 'did not alter a_password_here'); $this->assertEquals($vars['mypasswd'], 'hello', 'did not alter mypasswd'); $this->assertEquals($vars['authorization'], 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', 'did not alter authorization'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['api_token'], 'masked api_token'); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['api_token'], 'masked api_token'); - $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['card_number']['0'], 'masked card_number[0]'); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0'], 'masked card_number[0]'); $this->assertEquals($vars['card_number']['1'], $vars['card_number']['1'], 'did not alter card_number[1]'); } @@ -186,14 +182,14 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) public static function overrideDataProvider() { $processorOptions = array( - 'Raven_SanitizeDataProcessor' => array( + 'Raven_Processor_SanitizeDataProcessor' => array( 'fields_re' => '/(api_token)/i', 'values_re' => '/^(?:\d[ -]*?){15,16}$/' ) ); $client_options = array( - 'processors' => array('Raven_SanitizeDataProcessor'), + 'processors' => array('Raven_Processor_SanitizeDataProcessor'), 'processorOptions' => $processorOptions ); From 3ad647a23caefd29cfeb40681d9177d785662006 Mon Sep 17 00:00:00 2001 From: Austin Pray Date: Tue, 21 Mar 2017 10:56:48 -0500 Subject: [PATCH 0276/1161] Missing "f" in "function" --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 645493836..ddfbababb 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -128,7 +128,7 @@ The following settings are available for the client: .. code-block:: php - $client->setSendCallback(unction($data) { + $client->setSendCallback(function($data) { // dont send events if POST if ($_SERVER['REQUEST_METHOD'] === 'POST') { From a9b5cd2d44b7d89d8277ebb6304f43ad9722e428 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 10 Feb 2017 15:39:41 +0100 Subject: [PATCH 0277/1161] Add a processor to sanitize the HTTP headers --- CHANGES | 1 + .../SanitizeHttpHeadersProcessor.php | 64 ++++++++++++++ .../SanitizeHttpHeadersProcessorTest.php | 83 +++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 lib/Raven/Processor/SanitizeHttpHeadersProcessor.php create mode 100644 test/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php diff --git a/CHANGES b/CHANGES index ea0a98ca0..1c568963e 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,7 @@ ``call_existing`` is true and no exception handler is registered (#421). - Collect User.ip_address automatically (#419). - Added a processor to remove web cookies and another to remove HTTP body data for POST, PUT, PATCH and DELETE requests. They will be enabled by default in ``2.0`` (#405). +- Added a processor to sanitize HTTP headers (e.g. the Authorization header) (#428). 1.6.2 ----- diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php new file mode 100644 index 000000000..c058c1f6c --- /dev/null +++ b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -0,0 +1,64 @@ + + */ +final class Raven_Processor_SanitizeHttpHeadersProcessor extends Raven_Processor +{ + /** + * @var string[] $httpHeadersToSanitize The list of HTTP headers to sanitize + */ + private $httpHeadersToSanitize = array(); + + /** + * {@inheritdoc} + */ + public function __construct(Raven_Client $client) + { + parent::__construct($client); + } + + /** + * {@inheritdoc} + */ + public function setProcessorOptions(array $options) + { + $this->httpHeadersToSanitize = array_merge($this->getDefaultHeaders(), isset($options['sanitize_http_headers']) ? $options['sanitize_http_headers'] : array()); + } + + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request']) && isset($data['request']['headers'])) { + foreach ($data['request']['headers'] as $header => &$value) { + if (in_array($header, $this->httpHeadersToSanitize)) { + $value = self::STRING_MASK; + } + } + } + } + + /** + * Gets the list of default headers that must be sanitized. + * + * @return string[] + */ + private function getDefaultHeaders() + { + return array('Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN'); + } +} diff --git a/test/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php b/test/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php new file mode 100644 index 000000000..a2e56ad5b --- /dev/null +++ b/test/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php @@ -0,0 +1,83 @@ +getMockBuilder('\Raven_Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new Raven_Processor_SanitizeHttpHeadersProcessor($client); + $this->processor->setProcessorOptions(array( + 'sanitize_http_headers' => array('User-Defined-Header'), + )); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'headers' => array( + 'Authorization' => 'foo', + 'AnotherHeader' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'headers' => array( + 'Authorization' => Raven_Processor::STRING_MASK, + 'AnotherHeader' => 'bar', + ), + ), + ), + ), + array( + array( + 'request' => array( + 'headers' => array( + 'User-Defined-Header' => 'foo', + 'AnotherHeader' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'headers' => array( + 'User-Defined-Header' => Raven_Processor::STRING_MASK, + 'AnotherHeader' => 'bar', + ), + ), + ), + ), + ); + } +} From ff41be66c5e05ac96deecd68e8d7268f4bded63d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 26 Feb 2017 23:28:32 +0100 Subject: [PATCH 0278/1161] Add a processor to sanitize code context informations from reported exceptions --- CHANGES | 1 + .../Processor/SanitizeStacktraceProcessor.php | 39 ++++++ .../SanitizeStacktraceProcessorTest.php | 113 ++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 lib/Raven/Processor/SanitizeStacktraceProcessor.php create mode 100644 test/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php diff --git a/CHANGES b/CHANGES index 1c568963e..c8933dc98 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ - Collect User.ip_address automatically (#419). - Added a processor to remove web cookies and another to remove HTTP body data for POST, PUT, PATCH and DELETE requests. They will be enabled by default in ``2.0`` (#405). - Added a processor to sanitize HTTP headers (e.g. the Authorization header) (#428). +- Added a processor to remove ``pre_context``, ``context_line`` and ``post_context`` informations from reported exceptions (#429). 1.6.2 ----- diff --git a/lib/Raven/Processor/SanitizeStacktraceProcessor.php b/lib/Raven/Processor/SanitizeStacktraceProcessor.php new file mode 100644 index 000000000..96e152609 --- /dev/null +++ b/lib/Raven/Processor/SanitizeStacktraceProcessor.php @@ -0,0 +1,39 @@ + + */ +class Raven_Processor_SanitizeStacktraceProcessor extends Raven_Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (!isset($data['exception'], $data['exception']['values'])) { + return; + } + + foreach ($data['exception']['values'] as &$exception) { + if (!isset($exception['stacktrace'])) { + continue; + } + + foreach ($exception['stacktrace']['frames'] as &$frame) { + unset($frame['pre_context'], $frame['context_line'], $frame['post_context']); + } + } + } +} diff --git a/test/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php b/test/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php new file mode 100644 index 000000000..b2342c37a --- /dev/null +++ b/test/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php @@ -0,0 +1,113 @@ +client = $this->getMockBuilder('Raven_Client') + ->setMethods(array_diff($this->getClassMethods('Raven_Client'), array('captureException', 'capture', 'get_default_data', 'get_http_data', 'get_user_data', 'get_extra_data'))) + ->getMock(); + + $this->client->store_errors_for_bulk_send = true; + + $this->processor = new Raven_Processor_SanitizeStacktraceProcessor($this->client); + } + + public function testProcess() + { + try { + throw new \Exception(); + } catch (\Exception $exception) { + $this->client->captureException($exception); + } + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayHasKey('pre_context', $frame); + $this->assertArrayHasKey('context_line', $frame); + $this->assertArrayHasKey('post_context', $frame); + } + } + + $this->processor->process($this->client->_pending_events[0]); + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayNotHasKey('pre_context', $frame); + $this->assertArrayNotHasKey('context_line', $frame); + $this->assertArrayNotHasKey('post_context', $frame); + } + } + } + + public function testProcessWithPreviousException() + { + try { + try { + throw new \Exception('foo'); + } catch (\Exception $exception) { + throw new \Exception('bar', 0, $exception); + } + } catch (\Exception $exception) { + $this->client->captureException($exception); + } + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayHasKey('pre_context', $frame); + $this->assertArrayHasKey('context_line', $frame); + $this->assertArrayHasKey('post_context', $frame); + } + } + + $this->processor->process($this->client->_pending_events[0]); + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayNotHasKey('pre_context', $frame); + $this->assertArrayNotHasKey('context_line', $frame); + $this->assertArrayNotHasKey('post_context', $frame); + } + } + } + + /** + * Gets all the public and abstracts methods of a given class. + * + * @param string $className The FCQN of the class + * + * @return array + */ + private function getClassMethods($className) + { + $class = new ReflectionClass($className); + $methods = array(); + + foreach ($class->getMethods() as $method) { + if ($method->isPublic() || $method->isAbstract()) { + $methods[] = $method->getName(); + } + } + + return $methods; + } +} From 6419f0ec582463720b00d2d14c746100c8fdf549 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 24 Mar 2017 20:36:26 +0100 Subject: [PATCH 0279/1161] Add badges from shields.io --- README.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.rst b/README.rst index b24f25f85..042171968 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,23 @@ sentry-php .. image:: https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master :target: http://travis-ci.org/getsentry/sentry-php + :alt: Build Status + +.. image:: https://img.shields.io/packagist/dt/sentry/sentry.svg?style=flat-square + :target: https://packagist.org/packages/sentry/sentry + :alt: Total Downloads + +.. image:: https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square + :target: https://packagist.org/packages/sentry/sentry + :alt: Downloads per month + +.. image:: https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square + :target: https://packagist.org/packages/sentry/sentry + :alt: Latest stable version + +.. image:: http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square + :target: https://packagist.org/packages/sentry/sentry + :alt: License The official PHP SDK for `Sentry `_. From dc9cab65bed803f9f565d709f70c8d6af9d80bda Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 25 Mar 2017 01:26:45 +0100 Subject: [PATCH 0280/1161] Convert the README and CHANGELOG files from reStructuredText to Markdown --- CHANGES => CHANGELOG.md | 151 +++++++++++++++++----------------------- README.md | 68 ++++++++++++++++++ README.rst | 67 ------------------ 3 files changed, 130 insertions(+), 156 deletions(-) rename CHANGES => CHANGELOG.md (56%) create mode 100644 README.md delete mode 100644 README.rst diff --git a/CHANGES b/CHANGELOG.md similarity index 56% rename from CHANGES rename to CHANGELOG.md index c8933dc98..f86e0ad68 100644 --- a/CHANGES +++ b/CHANGELOG.md @@ -1,196 +1,169 @@ -1.7.0 ------ +# CHANGELOG + +## 1.7.0 - Corrected some issues with argument serialization in stacktraces (#399). -- The default exception handler will now re-raise exceptions when - ``call_existing`` is true and no exception handler is registered (#421). -- Collect User.ip_address automatically (#419). -- Added a processor to remove web cookies and another to remove HTTP body data for POST, PUT, PATCH and DELETE requests. They will be enabled by default in ``2.0`` (#405). +- The default exception handler will now re-raise exceptions when `call_existing` is true and no exception handler is registered (#421). +- Collect `User.ip_address` automatically (#419). +- Added a processor to remove web cookies. It will be enabled by default in `2.0` (#405). +- Added a processor to remove HTTP body data for POST, PUT, PATCH and DELETE requests. It will be enabled by default in `2.0` (#405). - Added a processor to sanitize HTTP headers (e.g. the Authorization header) (#428). -- Added a processor to remove ``pre_context``, ``context_line`` and ``post_context`` informations from reported exceptions (#429). +- Added a processor to remove `pre_context`, `context_line` and `post_context` informations from reported exceptions (#429). -1.6.2 ------ +## 1.6.2 (2017-02-03) - Fixed behavior where fatal errors weren't correctly being reported in most situations. -1.6.1 ------ +## 1.6.1 (2016-12-14) -- Correct handling of null in ``user_context``. +- Correct handling of null in `user_context`. -1.6.0 ------ +## 1.6.0 (2016-12-09) - Improved serialization of certain types to be more restrictive. -- ``error_types`` can now be configured via ``RavenClient``. +- `error_types` can now be configured via `RavenClient`. - Class serialization has been expanded to include attributes. - The session extension is no longer required. - Monolog is no longer a required dependency. -- ``user_context`` now merges by default. +- `user_context` now merges by default. -1.5.0 ------ +## 1.5.0 (2016-09-29) - Added named transaction support. -1.4.0 ------ +## 1.4.0 (2016-09-20) This version primarily overhauls the exception/stacktrace generation to fix a few bugs and improve the quality of data (#359). -- Added ``excluded_app_paths`` config. -- Removed ``shift_vars`` config. -- Correct fatal error handling to only operate on expected types. This also - fixes some behavior with the error suppression operator. +- Added `excluded_app_paths` config. +- Removed `shift_vars` config. +- Correct fatal error handling to only operate on expected types. This also fixes some behavior with the error suppression operator. - Expose anonymous and similar frames in the stacktrace. -- Default ``prefixes`` to PHP's include paths. -- Remove ``module`` usage. +- Default `prefixes` to PHP's include paths. +- Remove `module` usage. - Better handle empty argument context. - Correct alignment of filename (current frame) and function (caller frame) -1.3.0 ------ +## 1.3.0 (2016-12-19) - Fixed an issue causing the error suppression operator to not be respected (#335) - Fixed some serialization behavior (#352) - Fixed an issue with app paths and trailing slashes (#350) - Handle non-latin encoding with source code context line (#345) -1.2.0 ------ +## 1.2.0 (2016-12-08) - Handle non-latin encoding in source code and exception values (#342) - Ensure pending events are sent on shutdown by default (#338) -- Add ``captureLastError`` helper (#334) +- Add `captureLastError` helper (#334) - Dont report duplicate errors with fatal error handler (#334) - Enforce maximum length for string serialization (#329) -1.1.0 ------ +## 1.1.0 (2016-07-30) - Uncoercable values should no longer prevent exceptions from sending to the Sentry server. -- ``install()`` can no longer be called multiple times. +- `install()` can no longer be called multiple times. -1.0.0 ------ +## 1.0.0 (2016-07-28) - Removed deprecated error codes configuration from ErrorHandler. - Removed env data from HTTP interface. -- Removed 'message' attribute from exceptions'. +- Removed `message` attribute from exceptions. - appPath and prefixes are now resolved fully. - Fixed various getter methods requiring invalid args. -- Fixed data mutation with 'send_callback'. +- Fixed data mutation with `send_callback`. -0.22.0 ------- +## 0.22.0 (2016-06-23) - Improve handling of encodings. - Improve resiliency of variable serialization. - Add 'formatted' attribute to Message interface. -0.21.0 ------- +## 0.21.0 (2016-06-10) -- Added ``transport`` option. -- Added ``install()`` shortcut. +- Added `transport` option. +- Added `install()` shortcut. -0.20.0 ------- +## 0.20.0 (2016-06-02) - Handle missing function names on frames. - Remove suppression operator usage in breadcrumbs buffer. - Force serialization of context values. -0.19.0 ------- +## 0.19.0 (2016-05-27) -- Add error_reporting breadcrumb handler. +- Add `error_reporting` breadcrumb handler. -0.18.0 ------- +## 0.18.0 (2016-05-17) - Remove session from serialized data. -- send_callback return value must now be false to prevent capture. +- `send_callback` return value must now be false to prevent capture. - Add various getter/setter methods for configuration. -0.17.0 ------- +## 0.17.0 (2016-05-11) - Don't attempt to serialize fixed SDK inputs. - Improvements to breadcrumbs support in Monolog. -0.16.0 ------- +## 0.16.0 (2016-05-03) - Initial breadcrumbs support with Monolog handler. -0.15.0 ------- +## 0.15.0 (2016-04-29) - Fixed some cases where serialization wouldn't happen. - Added sdk attribute. -0.14.0 ------- +## 0.14.0 (2016-04-27) -- Added ``prefixes`` option for stripping absolute paths. -- Removed ``abs_path`` from stacktraces. -- Added ``app_path`` to specify application root for resolving ``in_app` on frames. -- Moved Laravel support to ``sentry-laravel`` project. +- Added `prefixes` option for stripping absolute paths. +- Removed `abs_path` from stacktraces. +- Added `app_path` to specify application root for resolving `in_app` on frames. +- Moved Laravel support to `sentry-laravel` project. - Fixed duplicate stack computation. -- Added ``dsn`` option to ease configuration. +- Added `dsn` option to ease configuration. - Fixed an issue with the curl async transport. - Improved serialization of values. -0.13.0 ------- +## 0.13.0 (2015-09-09) - Updated API to use new style interfaces. - Remove session cookie in default processor. - Expand docs for Laravel, Symfony2, and Monolog. - Default error types can now be set as part of ErrorHandler configuration. -0.12.1 ------- +## 0.12.1 (2015-07-26) - Dont send empty values for various context. -0.12.0 ------- +## 0.12.0 (2015-05-19) - Bumped protocol version to 6. - Fixed an issue with the async curl handler (GH-216). - Removed UDP transport. -0.11.0 ------- +## 0.11.0 (2015-03-25) -- New configuration parameter: 'release' -- New configuration parameter: 'message_limit' -- New configuration parameter: 'curl_ssl_version' -- New configuration parameter: 'curl_ipv4' -- New configuration parameter: 'verify_ssl' +- New configuration parameter: `release` +- New configuration parameter: `message_limit` +- New configuration parameter: `curl_ssl_version` +- New configuration parameter: `curl_ipv4` +- New configuration parameter: `verify_ssl` - Updated remote endpoint to use modern project-based path. -- Expanded default sanitizer support to include 'auth_pw' attribute. +- Expanded default sanitizer support to include `auth_pw` attribute. -0.10.0 ------- +## 0.10.0 (2014-09-03) -- Added a default certificate bundle which includes common root CA's - as well as getsentry.com's CA. +- Added a default certificate bundle which includes common root CA's as well as getsentry.com's CA. -0.9.1 ------ +## 0.9.1 (2014-08-26) -- Change default curl connection to 'sync' +- Change default curl connection to `sync` - Improve CLI reporting - -0.9.0 ------ +## 0.9.0 (2014-06-04) - Protocol version 5 - Default to asynchronous HTTP handler using curl_multi. diff --git a/README.md b/README.md new file mode 100644 index 000000000..82b14226d --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Sentry for PHP + +[![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master)](http://travis-ci.org/getsentry/sentry-php) +[![Total Downloads](https://img.shields.io/packagist/dt/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Downloads per month](https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Latest stable version](https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![License](http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) + +The Sentry PHP error reporter tracks errors and exceptions that happen during the +execution of your application and provides instant notification with detailed +informations needed to prioritize, identify, reproduce and fix each issue. Learn +more about [automatic PHP error reporting with Sentry](https://sentry.io/for/php/). + +## Features + +- Automatically report (un)handled exceptions and errors +- Send customized diagnostic data +- Process and sanitize data before sending it over the network + +## Usage + +```php +// Instantiate a new client with a compatible DSN and install built-in +// handlers +$client = (new Raven_Client('http://public:secret@example.com/1'))->install(); + +// Capture an exception +$event_id = $client->captureException($ex); + +// Give the user feedback +echo "Sorry, there was an error!"; +echo "Your reference ID is " . $event_id; +``` + +For more information, see our [documentation](https://docs.getsentry.com/hosted/clients/php/). + + +## Integration with frameworks + +Other packages exists to integrate this SDK into the most common frameworks. + +- [Symfony](https://github.com/getsentry/sentry-symfony) +- [Laravel](https://github.com/getsentry/sentry-laravel) + + +## Community + +- Documentation +- Bug Tracker +- Code +- Mailing List +- IRC (irc.freenode.net, #sentry) + + +Contributing +------------ + +Dependencies are managed through composer: + +``` +$ composer install +``` + +Tests can then be run via phpunit: + +``` +$ vendor/bin/phpunit +``` \ No newline at end of file diff --git a/README.rst b/README.rst deleted file mode 100644 index 042171968..000000000 --- a/README.rst +++ /dev/null @@ -1,67 +0,0 @@ -sentry-php -========== - -.. image:: https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master - :target: http://travis-ci.org/getsentry/sentry-php - :alt: Build Status - -.. image:: https://img.shields.io/packagist/dt/sentry/sentry.svg?style=flat-square - :target: https://packagist.org/packages/sentry/sentry - :alt: Total Downloads - -.. image:: https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square - :target: https://packagist.org/packages/sentry/sentry - :alt: Downloads per month - -.. image:: https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square - :target: https://packagist.org/packages/sentry/sentry - :alt: Latest stable version - -.. image:: http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square - :target: https://packagist.org/packages/sentry/sentry - :alt: License - - -The official PHP SDK for `Sentry `_. - -.. code-block:: php - - // Instantiate a new client with a compatible DSN and install built-in - // handlers - $client = (new Raven_Client('http://public:secret@example.com/1'))->install(); - - // Capture an exception - $event_id = $client->captureException($ex); - - // Give the user feedback - echo "Sorry, there was an error!"; - echo "Your reference ID is " . $event_id; - -For more information, see our `documentation `_. - - -Contributing ------------- - -Dependencies are managed through composer: - -:: - - $ composer install - - -Tests can then be run via phpunit: - -:: - - $ vendor/bin/phpunit - - -Resources ---------- - -* `Documentation `_ -* `Bug Tracker `_ -* `Code `_ -* `Mailing List `_ -* `IRC `_ (irc.freenode.net, #sentry) From 9e7ad123c3ebec950fbc12309f5ed28946f9969a Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 25 Mar 2017 20:29:31 +0100 Subject: [PATCH 0281/1161] Add Sentry logo to the README file --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 82b14226d..a5f51c9c3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +

+ + + +

+ # Sentry for PHP [![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master)](http://travis-ci.org/getsentry/sentry-php) @@ -45,11 +51,11 @@ Other packages exists to integrate this SDK into the most common frameworks. ## Community -- Documentation -- Bug Tracker -- Code -- Mailing List -- IRC (irc.freenode.net, #sentry) +- [Documentation](https://docs.getsentry.com/hosted/clients/php/) +- [Bug Tracker](http://github.com/getsentry/sentry-php/issues) +- [Code](http://github.com/getsentry/sentry-php) +- [Mailing List](https://groups.google.com/group/getsentry) +- [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) Contributing From c8fd7d3a8c284ee01bf22ea2a680a2e6e38d03c2 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 27 Mar 2017 19:38:09 +0200 Subject: [PATCH 0282/1161] Link the Sentry logo to the Sentry website In #435 the Sentry logo was added to the README.md but linked to the Symfony website. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5f51c9c3..db0dcdf0e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

@@ -71,4 +71,4 @@ Tests can then be run via phpunit: ``` $ vendor/bin/phpunit -``` \ No newline at end of file +``` From 5e714682a329bf23995ea0d92199175cd951f745 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 29 Mar 2017 00:50:34 +0200 Subject: [PATCH 0283/1161] Merge branch master into 2.0 --- CHANGES => CHANGELOG.md | 150 ++++++++---------- README.md | 74 +++++++++ README.rst | 50 ------ docs/config.rst | 2 +- lib/Raven/Breadcrumbs/ErrorHandler.php | 2 +- lib/Raven/Breadcrumbs/MonologHandler.php | 4 +- lib/Raven/Client.php | 2 +- lib/Raven/CurlHandler.php | 10 +- lib/Raven/ErrorHandler.php | 18 +-- lib/Raven/Processor.php | 29 ++-- .../Processor/RemoveCookiesProcessor.php | 39 +++++ .../Processor/RemoveHttpBodyProcessor.php | 34 ++++ .../{ => Processor}/SanitizeDataProcessor.php | 55 ++++--- .../SanitizeHttpHeadersProcessor.php | 69 ++++++++ .../Processor/SanitizeStacktraceProcessor.php | 43 +++++ lib/Raven/Serializer.php | 2 +- tests/Raven/Tests/ClientTest.php | 11 +- .../Processor/RemoveCookiesProcessorTest.php | 80 ++++++++++ .../Processor/RemoveHttpBodyProcessorTest.php | 123 ++++++++++++++ .../SanitizeDataProcessorTest.php | 52 +++--- .../SanitizeHttpHeadersProcessorTest.php | 85 ++++++++++ .../SanitizeStacktraceProcessorTest.php | 115 ++++++++++++++ 22 files changed, 829 insertions(+), 220 deletions(-) rename CHANGES => CHANGELOG.md (55%) create mode 100644 README.md delete mode 100644 README.rst create mode 100644 lib/Raven/Processor/RemoveCookiesProcessor.php create mode 100644 lib/Raven/Processor/RemoveHttpBodyProcessor.php rename lib/Raven/{ => Processor}/SanitizeDataProcessor.php (75%) create mode 100644 lib/Raven/Processor/SanitizeHttpHeadersProcessor.php create mode 100644 lib/Raven/Processor/SanitizeStacktraceProcessor.php create mode 100644 tests/Raven/Tests/Processor/RemoveCookiesProcessorTest.php create mode 100644 tests/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php rename tests/Raven/Tests/{ => Processor}/SanitizeDataProcessorTest.php (72%) create mode 100644 tests/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php create mode 100644 tests/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php diff --git a/CHANGES b/CHANGELOG.md similarity index 55% rename from CHANGES rename to CHANGELOG.md index c3a884f3c..f86e0ad68 100644 --- a/CHANGES +++ b/CHANGELOG.md @@ -1,193 +1,169 @@ -1.7.0 ------ +# CHANGELOG + +## 1.7.0 - Corrected some issues with argument serialization in stacktraces (#399). -- The default exception handler will now re-raise exceptions when - ``call_existing`` is true and no exception handler is registered (#421). -- Collect User.ip_address automatically (#419). +- The default exception handler will now re-raise exceptions when `call_existing` is true and no exception handler is registered (#421). +- Collect `User.ip_address` automatically (#419). +- Added a processor to remove web cookies. It will be enabled by default in `2.0` (#405). +- Added a processor to remove HTTP body data for POST, PUT, PATCH and DELETE requests. It will be enabled by default in `2.0` (#405). +- Added a processor to sanitize HTTP headers (e.g. the Authorization header) (#428). +- Added a processor to remove `pre_context`, `context_line` and `post_context` informations from reported exceptions (#429). -1.6.2 ------ +## 1.6.2 (2017-02-03) - Fixed behavior where fatal errors weren't correctly being reported in most situations. -1.6.1 ------ +## 1.6.1 (2016-12-14) -- Correct handling of null in ``user_context``. +- Correct handling of null in `user_context`. -1.6.0 ------ +## 1.6.0 (2016-12-09) - Improved serialization of certain types to be more restrictive. -- ``error_types`` can now be configured via ``RavenClient``. +- `error_types` can now be configured via `RavenClient`. - Class serialization has been expanded to include attributes. - The session extension is no longer required. - Monolog is no longer a required dependency. -- ``user_context`` now merges by default. +- `user_context` now merges by default. -1.5.0 ------ +## 1.5.0 (2016-09-29) - Added named transaction support. -1.4.0 ------ +## 1.4.0 (2016-09-20) This version primarily overhauls the exception/stacktrace generation to fix a few bugs and improve the quality of data (#359). -- Added ``excluded_app_paths`` config. -- Removed ``shift_vars`` config. -- Correct fatal error handling to only operate on expected types. This also - fixes some behavior with the error suppression operator. +- Added `excluded_app_paths` config. +- Removed `shift_vars` config. +- Correct fatal error handling to only operate on expected types. This also fixes some behavior with the error suppression operator. - Expose anonymous and similar frames in the stacktrace. -- Default ``prefixes`` to PHP's include paths. -- Remove ``module`` usage. +- Default `prefixes` to PHP's include paths. +- Remove `module` usage. - Better handle empty argument context. - Correct alignment of filename (current frame) and function (caller frame) -1.3.0 ------ +## 1.3.0 (2016-12-19) - Fixed an issue causing the error suppression operator to not be respected (#335) - Fixed some serialization behavior (#352) - Fixed an issue with app paths and trailing slashes (#350) - Handle non-latin encoding with source code context line (#345) -1.2.0 ------ +## 1.2.0 (2016-12-08) - Handle non-latin encoding in source code and exception values (#342) - Ensure pending events are sent on shutdown by default (#338) -- Add ``captureLastError`` helper (#334) +- Add `captureLastError` helper (#334) - Dont report duplicate errors with fatal error handler (#334) - Enforce maximum length for string serialization (#329) -1.1.0 ------ +## 1.1.0 (2016-07-30) - Uncoercable values should no longer prevent exceptions from sending to the Sentry server. -- ``install()`` can no longer be called multiple times. +- `install()` can no longer be called multiple times. -1.0.0 ------ +## 1.0.0 (2016-07-28) - Removed deprecated error codes configuration from ErrorHandler. - Removed env data from HTTP interface. -- Removed 'message' attribute from exceptions'. +- Removed `message` attribute from exceptions. - appPath and prefixes are now resolved fully. - Fixed various getter methods requiring invalid args. -- Fixed data mutation with 'send_callback'. +- Fixed data mutation with `send_callback`. -0.22.0 ------- +## 0.22.0 (2016-06-23) - Improve handling of encodings. - Improve resiliency of variable serialization. - Add 'formatted' attribute to Message interface. -0.21.0 ------- +## 0.21.0 (2016-06-10) -- Added ``transport`` option. -- Added ``install()`` shortcut. +- Added `transport` option. +- Added `install()` shortcut. -0.20.0 ------- +## 0.20.0 (2016-06-02) - Handle missing function names on frames. - Remove suppression operator usage in breadcrumbs buffer. - Force serialization of context values. -0.19.0 ------- +## 0.19.0 (2016-05-27) -- Add error_reporting breadcrumb handler. +- Add `error_reporting` breadcrumb handler. -0.18.0 ------- +## 0.18.0 (2016-05-17) - Remove session from serialized data. -- send_callback return value must now be false to prevent capture. +- `send_callback` return value must now be false to prevent capture. - Add various getter/setter methods for configuration. -0.17.0 ------- +## 0.17.0 (2016-05-11) - Don't attempt to serialize fixed SDK inputs. - Improvements to breadcrumbs support in Monolog. -0.16.0 ------- +## 0.16.0 (2016-05-03) - Initial breadcrumbs support with Monolog handler. -0.15.0 ------- +## 0.15.0 (2016-04-29) - Fixed some cases where serialization wouldn't happen. - Added sdk attribute. -0.14.0 ------- +## 0.14.0 (2016-04-27) -- Added ``prefixes`` option for stripping absolute paths. -- Removed ``abs_path`` from stacktraces. -- Added ``app_path`` to specify application root for resolving ``in_app` on frames. -- Moved Laravel support to ``sentry-laravel`` project. +- Added `prefixes` option for stripping absolute paths. +- Removed `abs_path` from stacktraces. +- Added `app_path` to specify application root for resolving `in_app` on frames. +- Moved Laravel support to `sentry-laravel` project. - Fixed duplicate stack computation. -- Added ``dsn`` option to ease configuration. +- Added `dsn` option to ease configuration. - Fixed an issue with the curl async transport. - Improved serialization of values. -0.13.0 ------- +## 0.13.0 (2015-09-09) - Updated API to use new style interfaces. - Remove session cookie in default processor. - Expand docs for Laravel, Symfony2, and Monolog. - Default error types can now be set as part of ErrorHandler configuration. -0.12.1 ------- +## 0.12.1 (2015-07-26) - Dont send empty values for various context. -0.12.0 ------- +## 0.12.0 (2015-05-19) - Bumped protocol version to 6. - Fixed an issue with the async curl handler (GH-216). - Removed UDP transport. -0.11.0 ------- +## 0.11.0 (2015-03-25) -- New configuration parameter: 'release' -- New configuration parameter: 'message_limit' -- New configuration parameter: 'curl_ssl_version' -- New configuration parameter: 'curl_ipv4' -- New configuration parameter: 'verify_ssl' +- New configuration parameter: `release` +- New configuration parameter: `message_limit` +- New configuration parameter: `curl_ssl_version` +- New configuration parameter: `curl_ipv4` +- New configuration parameter: `verify_ssl` - Updated remote endpoint to use modern project-based path. -- Expanded default sanitizer support to include 'auth_pw' attribute. +- Expanded default sanitizer support to include `auth_pw` attribute. -0.10.0 ------- +## 0.10.0 (2014-09-03) -- Added a default certificate bundle which includes common root CA's - as well as getsentry.com's CA. +- Added a default certificate bundle which includes common root CA's as well as getsentry.com's CA. -0.9.1 ------ +## 0.9.1 (2014-08-26) -- Change default curl connection to 'sync' +- Change default curl connection to `sync` - Improve CLI reporting - -0.9.0 ------ +## 0.9.0 (2014-06-04) - Protocol version 5 - Default to asynchronous HTTP handler using curl_multi. diff --git a/README.md b/README.md new file mode 100644 index 000000000..9d97be56d --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +

+ + + +

+ +# Sentry for PHP + +[![Build Status](https://img.shields.io/travis/getsentry/sentry-php.png?style=flat-square)](http://travis-ci.org/getsentry/sentry-php) +[![Total Downloads](https://img.shields.io/packagist/dt/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Downloads per month](https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Latest stable version](https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![License](http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) + +The Sentry PHP error reporter tracks errors and exceptions that happen during the +execution of your application and provides instant notification with detailed +informations needed to prioritize, identify, reproduce and fix each issue. Learn +more about [automatic PHP error reporting with Sentry](https://sentry.io/for/php/). + +## Features + +- Automatically report (un)handled exceptions and errors +- Send customized diagnostic data +- Process and sanitize data before sending it over the network + +## Usage + +```php +// Instantiate a new client with a compatible DSN and install built-in +// handlers +$client = (new Raven_Client('http://public:secret@example.com/1'))->install(); + +// Capture an exception +$event_id = $client->captureException($ex); + +// Give the user feedback +echo "Sorry, there was an error!"; +echo "Your reference ID is " . $event_id; +``` + +For more information, see our [documentation](https://docs.getsentry.com/hosted/clients/php/). + + +## Integration with frameworks + +Other packages exists to integrate this SDK into the most common frameworks. + +- [Symfony](https://github.com/getsentry/sentry-symfony) +- [Laravel](https://github.com/getsentry/sentry-laravel) + + +## Community + +- [Documentation](https://docs.getsentry.com/hosted/clients/php/) +- [Bug Tracker](http://github.com/getsentry/sentry-php/issues) +- [Code](http://github.com/getsentry/sentry-php) +- [Mailing List](https://groups.google.com/group/getsentry) +- [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) + + +Contributing +------------ + +Dependencies are managed through composer: + +``` +$ composer install +``` + +Tests can then be run via phpunit: + +``` +$ vendor/bin/phpunit +``` \ No newline at end of file diff --git a/README.rst b/README.rst deleted file mode 100644 index b24f25f85..000000000 --- a/README.rst +++ /dev/null @@ -1,50 +0,0 @@ -sentry-php -========== - -.. image:: https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master - :target: http://travis-ci.org/getsentry/sentry-php - - -The official PHP SDK for `Sentry `_. - -.. code-block:: php - - // Instantiate a new client with a compatible DSN and install built-in - // handlers - $client = (new Raven_Client('http://public:secret@example.com/1'))->install(); - - // Capture an exception - $event_id = $client->captureException($ex); - - // Give the user feedback - echo "Sorry, there was an error!"; - echo "Your reference ID is " . $event_id; - -For more information, see our `documentation `_. - - -Contributing ------------- - -Dependencies are managed through composer: - -:: - - $ composer install - - -Tests can then be run via phpunit: - -:: - - $ vendor/bin/phpunit - - -Resources ---------- - -* `Documentation `_ -* `Bug Tracker `_ -* `Code `_ -* `Mailing List `_ -* `IRC `_ (irc.freenode.net, #sentry) diff --git a/docs/config.rst b/docs/config.rst index 645493836..ddfbababb 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -128,7 +128,7 @@ The following settings are available for the client: .. code-block:: php - $client->setSendCallback(unction($data) { + $client->setSendCallback(function($data) { // dont send events if POST if ($_SERVER['REQUEST_METHOD'] === 'POST') { diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index 93346bf25..5c4bda141 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -4,7 +4,7 @@ class ErrorHandler { - private $existingHandler; + protected $existingHandler; /** * @var \Raven\Client the client object that sends the message to the server diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 2c53125e4..d54c35ceb 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -9,7 +9,7 @@ class MonologHandler extends \Monolog\Handler\AbstractProcessingHandler /** * Translates Monolog log levels to Raven log levels. */ - private $logLevels = array( + protected $logLevels = array( Logger::DEBUG => \Raven\Client::DEBUG, Logger::INFO => \Raven\Client::INFO, Logger::NOTICE => \Raven\Client::INFO, @@ -20,7 +20,7 @@ class MonologHandler extends \Monolog\Handler\AbstractProcessingHandler Logger::EMERGENCY => \Raven\Client::FATAL, ); - private $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; + protected $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; /** * @var \Raven\Client the client object that sends the message to the server diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c306308e3..4fbc24031 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -382,7 +382,7 @@ public function setTransport($value) public static function getDefaultProcessors() { return array( - '\\Raven\\SanitizeDataProcessor', + '\\Raven\\Processor\\SanitizeDataProcessor', ); } diff --git a/lib/Raven/CurlHandler.php b/lib/Raven/CurlHandler.php index f1397508c..7bad9581d 100644 --- a/lib/Raven/CurlHandler.php +++ b/lib/Raven/CurlHandler.php @@ -19,10 +19,10 @@ // TODO(dcramer): handle ca_cert class CurlHandler { - private $join_timeout; - private $multi_handle; - private $options; - private $requests; + protected $join_timeout; + protected $multi_handle; + protected $options; + protected $requests; public function __construct($options, $join_timeout = 5) { @@ -89,7 +89,7 @@ public function join($timeout = null) /** * @doc http://php.net/manual/en/function.curl-multi-exec.php */ - private function select() + protected function select() { do { $mrc = curl_multi_exec($this->multi_handle, $active); diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 9876c9b06..eb19d7f8d 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -28,15 +28,15 @@ // currently are not used outside of the fatal handler. class ErrorHandler { - private $old_exception_handler; - private $call_existing_exception_handler = false; - private $old_error_handler; - private $call_existing_error_handler = false; - private $reservedMemory; + protected $old_exception_handler; + protected $call_existing_exception_handler = false; + protected $old_error_handler; + protected $call_existing_error_handler = false; + protected $reservedMemory; /** @var \Raven\Client */ - private $client; - private $send_errors_last = false; - private $fatal_error_types = array( + protected $client; + protected $send_errors_last = false; + protected $fatal_error_types = array( E_ERROR, E_PARSE, E_CORE_ERROR, @@ -51,7 +51,7 @@ class ErrorHandler * Error types which should be processed by the handler. * A 'null' value implies "whatever error_reporting is at time of error". */ - private $error_types = null; + protected $error_types = null; public function __construct($client, $send_errors_last = false, $error_types = null, $__error_types = null) diff --git a/lib/Raven/Processor.php b/lib/Raven/Processor.php index 89bc75f39..f1ca106bd 100644 --- a/lib/Raven/Processor.php +++ b/lib/Raven/Processor.php @@ -1,4 +1,5 @@ + */ +final class RemoveCookiesProcessor extends Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request'])) { + if (isset($data['request']['cookies'])) { + $data['request']['cookies'] = self::STRING_MASK; + } + + if (isset($data['request']['headers']) && isset($data['request']['headers']['Cookie'])) { + $data['request']['headers']['Cookie'] = self::STRING_MASK; + } + } + } +} diff --git a/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/lib/Raven/Processor/RemoveHttpBodyProcessor.php new file mode 100644 index 000000000..09161b250 --- /dev/null +++ b/lib/Raven/Processor/RemoveHttpBodyProcessor.php @@ -0,0 +1,34 @@ + + */ +final class RemoveHttpBodyProcessor extends Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request'], $data['request']['method']) && in_array(strtoupper($data['request']['method']), array('POST', 'PUT', 'PATCH', 'DELETE'))) { + $data['request']['data'] = self::STRING_MASK; + } + } +} diff --git a/lib/Raven/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php similarity index 75% rename from lib/Raven/SanitizeDataProcessor.php rename to lib/Raven/Processor/SanitizeDataProcessor.php index a607a4a72..24668e0bf 100644 --- a/lib/Raven/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -1,5 +1,18 @@ fields_re = self::FIELDS_RE; - $this->values_re = self::VALUES_RE; + + $this->fields_re = self::FIELDS_RE; + $this->values_re = self::VALUES_RE; $this->session_cookie_name = ini_get('session.name'); } /** - * Override the default processor options - * - * @param array $options Associative array of processor options + * {@inheritdoc} */ public function setProcessorOptions(array $options) { @@ -44,8 +59,8 @@ public function setProcessorOptions(array $options) /** * Replace any array values with our mask if the field name or the value matches a respective regex * - * @param mixed $item Associative array value - * @param string $key Associative array key + * @param mixed $item Associative array value + * @param string $key Associative array key */ public function sanitize(&$item, $key) { @@ -54,7 +69,7 @@ public function sanitize(&$item, $key) } if (preg_match($this->values_re, $item)) { - $item = self::MASK; + $item = self::STRING_MASK; } if (empty($key)) { @@ -62,13 +77,10 @@ public function sanitize(&$item, $key) } if (preg_match($this->fields_re, $key)) { - $item = self::MASK; + $item = self::STRING_MASK; } } - /** @noinspection PhpInconsistentReturnPointsInspection - * @param array $data - */ public function sanitizeException(&$data) { foreach ($data['exception']['values'] as &$value) { @@ -79,10 +91,10 @@ public function sanitizeException(&$data) public function sanitizeHttp(&$data) { $http = &$data['request']; - if (!empty($http['cookies'])) { + if (!empty($http['cookies']) && is_array($http['cookies'])) { $cookies = &$http['cookies']; if (!empty($cookies[$this->session_cookie_name])) { - $cookies[$this->session_cookie_name] = self::MASK; + $cookies[$this->session_cookie_name] = self::STRING_MASK; } } if (!empty($http['data']) && is_array($http['data'])) { @@ -100,6 +112,9 @@ public function sanitizeStacktrace(&$data) } } + /** + * {@inheritdoc} + */ public function process(&$data) { if (!empty($data['exception'])) { diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php new file mode 100644 index 000000000..526923635 --- /dev/null +++ b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -0,0 +1,69 @@ + + */ +final class SanitizeHttpHeadersProcessor extends Processor +{ + /** + * @var string[] $httpHeadersToSanitize The list of HTTP headers to sanitize + */ + private $httpHeadersToSanitize = array(); + + /** + * {@inheritdoc} + */ + public function __construct(Client $client) + { + parent::__construct($client); + } + + /** + * {@inheritdoc} + */ + public function setProcessorOptions(array $options) + { + $this->httpHeadersToSanitize = array_merge($this->getDefaultHeaders(), isset($options['sanitize_http_headers']) ? $options['sanitize_http_headers'] : array()); + } + + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request']) && isset($data['request']['headers'])) { + foreach ($data['request']['headers'] as $header => &$value) { + if (in_array($header, $this->httpHeadersToSanitize)) { + $value = self::STRING_MASK; + } + } + } + } + + /** + * Gets the list of default headers that must be sanitized. + * + * @return string[] + */ + private function getDefaultHeaders() + { + return array('Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN'); + } +} diff --git a/lib/Raven/Processor/SanitizeStacktraceProcessor.php b/lib/Raven/Processor/SanitizeStacktraceProcessor.php new file mode 100644 index 000000000..748e8d67d --- /dev/null +++ b/lib/Raven/Processor/SanitizeStacktraceProcessor.php @@ -0,0 +1,43 @@ + + */ +class SanitizeStacktraceProcessor extends Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (!isset($data['exception'], $data['exception']['values'])) { + return; + } + + foreach ($data['exception']['values'] as &$exception) { + if (!isset($exception['stacktrace'])) { + continue; + } + + foreach ($exception['stacktrace']['frames'] as &$frame) { + unset($frame['pre_context'], $frame['context_line'], $frame['post_context']); + } + } + } +} diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 6dabc7050..ce8269a74 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -45,7 +45,7 @@ class Serializer * * @var string */ - private $mb_detect_order = self::DEFAULT_MB_DETECT_ORDER; + protected $mb_detect_order = self::DEFAULT_MB_DETECT_ORDER; /** * @param null|string $mb_detect_order diff --git a/tests/Raven/Tests/ClientTest.php b/tests/Raven/Tests/ClientTest.php index 5484ad1dd..3e801fd51 100644 --- a/tests/Raven/Tests/ClientTest.php +++ b/tests/Raven/Tests/ClientTest.php @@ -690,10 +690,11 @@ public function testCaptureExceptionInvalidUTF8() public function testDoesRegisterProcessors() { $client = new Dummy_Raven_Client(array( - 'processors' => array('\\Raven\\SanitizeDataProcessor'), + 'processors' => array('\\Raven\\Processor\\SanitizeDataProcessor'), )); + $this->assertEquals(1, count($client->processors)); - $this->assertInstanceOf('\\Raven\\SanitizeDataProcessor', $client->processors[0]); + $this->assertInstanceOf('\\Raven\\Processor\\SanitizeDataProcessor', $client->processors[0]); } public function testProcessDoesCallProcessors() @@ -729,9 +730,7 @@ public function testDefaultProcessorsAreUsed() */ public function testDefaultProcessorsContainSanitizeDataProcessor() { - $defaults = Dummy_Raven_Client::getDefaultProcessors(); - - $this->assertTrue(in_array('\\Raven\\SanitizeDataProcessor', $defaults)); + $this->assertContains('\\Raven\\Processor\\SanitizeDataProcessor', Dummy_Raven_Client::getDefaultProcessors()); } /** @@ -2094,7 +2093,7 @@ public function testCaptureLevel() } $client = new Dummy_Raven_Client(); - $client->capture(array('message' => 'foobar', )); + $client->capture(array('message' => 'foobar')); $events = $client->getSentEvents(); $event = array_pop($events); $input = $client->get_http_data(); diff --git a/tests/Raven/Tests/Processor/RemoveCookiesProcessorTest.php b/tests/Raven/Tests/Processor/RemoveCookiesProcessorTest.php new file mode 100644 index 000000000..6660def54 --- /dev/null +++ b/tests/Raven/Tests/Processor/RemoveCookiesProcessorTest.php @@ -0,0 +1,80 @@ +getMockBuilder('Raven\Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new RemoveCookiesProcessor($client); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'foo' => 'bar', + ), + ), + array( + 'request' => array( + 'foo' => 'bar', + ), + ), + ), + array( + array( + 'request' => array( + 'foo' => 'bar', + 'cookies' => 'baz', + 'headers' => array( + 'Cookie' => 'bar', + 'AnotherHeader' => 'foo', + ), + ), + ), + array( + 'request' => array( + 'foo' => 'bar', + 'cookies' => RemoveCookiesProcessor::STRING_MASK, + 'headers' => array( + 'Cookie' => RemoveCookiesProcessor::STRING_MASK, + 'AnotherHeader' => 'foo', + ), + ), + ), + ), + ); + } +} diff --git a/tests/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php b/tests/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php new file mode 100644 index 000000000..a9e44f728 --- /dev/null +++ b/tests/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php @@ -0,0 +1,123 @@ +getMockBuilder('Raven\Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new RemoveHttpBodyProcessor($client); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'method' => 'POST', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => RemoveHttpBodyProcessor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'PUT', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => RemoveHttpBodyProcessor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'PATCH', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => RemoveHttpBodyProcessor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'DELETE', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => RemoveHttpBodyProcessor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'GET', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + ), + ); + } +} diff --git a/tests/Raven/Tests/SanitizeDataProcessorTest.php b/tests/Raven/Tests/Processor/SanitizeDataProcessorTest.php similarity index 72% rename from tests/Raven/Tests/SanitizeDataProcessorTest.php rename to tests/Raven/Tests/Processor/SanitizeDataProcessorTest.php index 540891040..be788bb51 100644 --- a/tests/Raven/Tests/SanitizeDataProcessorTest.php +++ b/tests/Raven/Tests/Processor/SanitizeDataProcessorTest.php @@ -33,20 +33,20 @@ public function testDoesFilterHttpData() ); $client = new Dummy_Raven_Client(); - $processor = new \Raven\SanitizeDataProcessor($client); + $processor = new Raven\Processor\SanitizeDataProcessor($client); $processor->process($data); $vars = $data['request']['data']; $this->assertEquals($vars['foo'], 'bar'); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['password']); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['the_secret']); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['a_password_here']); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['mypasswd']); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['authorization']); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['password']); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['the_secret']); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['a_password_here']); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['mypasswd']); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['authorization']); $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['card_number']['0']); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0']); } public function testDoesFilterSessionId() @@ -60,11 +60,11 @@ public function testDoesFilterSessionId() ); $client = new Dummy_Raven_Client(); - $processor = new \Raven\SanitizeDataProcessor($client); + $processor = new Raven\Processor\SanitizeDataProcessor($client); $processor->process($data); $cookies = $data['request']['cookies']; - $this->assertEquals($cookies[ini_get('session.name')], \Raven\SanitizeDataProcessor::MASK); + $this->assertEquals($cookies[ini_get('session.name')], Raven\Processor\SanitizeDataProcessor::STRING_MASK); } public function testDoesFilterCreditCard() @@ -76,20 +76,16 @@ public function testDoesFilterCreditCard() ); $client = new Dummy_Raven_Client(); - $processor = new \Raven\SanitizeDataProcessor($client); + $processor = new Raven\Processor\SanitizeDataProcessor($client); $processor->process($data); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $data['extra']['ccnumba']); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $data['extra']['ccnumba']); } - /** - * @covers \Raven\SanitizeDataProcessor::setProcessorOptions - * - */ public function testSettingProcessorOptions() { $client = new Dummy_Raven_Client(); - $processor = new \Raven\SanitizeDataProcessor($client); + $processor = new Raven\Processor\SanitizeDataProcessor($client); $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); @@ -116,13 +112,13 @@ public function testOverrideOptions($processorOptions, $client_options, $dsn) { $client = new Dummy_Raven_Client($dsn, $client_options); /** - * @var \Raven\SanitizeDataProcessor $processor + * @var Raven\Processor\SanitizeDataProcessor $processor */ $processor = $client->processors[0]; - $this->assertInstanceOf('\\Raven\\SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['\\Raven\\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['\\Raven\\SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf('Raven\Processor\SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven\Processor\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven\Processor\SanitizeDataProcessor']['values_re'], 'overwrote values'); } /** @@ -155,13 +151,13 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $client = new Dummy_Raven_Client($dsn, $client_options); /** - * @var \Raven\SanitizeDataProcessor $processor + * @var Raven\Processor\SanitizeDataProcessor $processor */ $processor = $client->processors[0]; - $this->assertInstanceOf('\\Raven\\SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['\\Raven\\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['\\Raven\\SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf('Raven\Processor\SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven\Processor\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven\Processor\SanitizeDataProcessor']['values_re'], 'overwrote values'); $processor->process($data); @@ -172,9 +168,9 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $this->assertEquals($vars['a_password_here'], 'hello', 'did not alter a_password_here'); $this->assertEquals($vars['mypasswd'], 'hello', 'did not alter mypasswd'); $this->assertEquals($vars['authorization'], 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', 'did not alter authorization'); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['api_token'], 'masked api_token'); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['api_token'], 'masked api_token'); - $this->assertEquals(\Raven\SanitizeDataProcessor::MASK, $vars['card_number']['0'], 'masked card_number[0]'); + $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0'], 'masked card_number[0]'); $this->assertEquals($vars['card_number']['1'], $vars['card_number']['1'], 'did not alter card_number[1]'); } @@ -186,14 +182,14 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) public static function overrideDataProvider() { $processorOptions = array( - '\\Raven\\SanitizeDataProcessor' => array( + 'Raven\Processor\SanitizeDataProcessor' => array( 'fields_re' => '/(api_token)/i', 'values_re' => '/^(?:\d[ -]*?){15,16}$/' ) ); $client_options = array( - 'processors' => array('\\Raven\\SanitizeDataProcessor'), + 'processors' => array('Raven\Processor\SanitizeDataProcessor'), 'processorOptions' => $processorOptions ); diff --git a/tests/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php b/tests/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php new file mode 100644 index 000000000..77121efe4 --- /dev/null +++ b/tests/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php @@ -0,0 +1,85 @@ +getMockBuilder('Raven\Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new SanitizeHttpHeadersProcessor($client); + $this->processor->setProcessorOptions(array( + 'sanitize_http_headers' => array('User-Defined-Header'), + )); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'headers' => array( + 'Authorization' => 'foo', + 'AnotherHeader' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'headers' => array( + 'Authorization' => SanitizeHttpHeadersProcessor::STRING_MASK, + 'AnotherHeader' => 'bar', + ), + ), + ), + ), + array( + array( + 'request' => array( + 'headers' => array( + 'User-Defined-Header' => 'foo', + 'AnotherHeader' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'headers' => array( + 'User-Defined-Header' => SanitizeHttpHeadersProcessor::STRING_MASK, + 'AnotherHeader' => 'bar', + ), + ), + ), + ), + ); + } +} diff --git a/tests/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php new file mode 100644 index 000000000..3e57263c7 --- /dev/null +++ b/tests/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php @@ -0,0 +1,115 @@ +client = $this->getMockBuilder('Raven\Client') + ->setMethods(array_diff($this->getClassMethods('Raven\Client'), array('captureException', 'capture', 'get_default_data', 'get_http_data', 'get_user_data', 'get_extra_data'))) + ->getMock(); + + $this->client->store_errors_for_bulk_send = true; + + $this->processor = new SanitizeStacktraceProcessor($this->client); + } + + public function testProcess() + { + try { + throw new \Exception(); + } catch (\Exception $exception) { + $this->client->captureException($exception); + } + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayHasKey('pre_context', $frame); + $this->assertArrayHasKey('context_line', $frame); + $this->assertArrayHasKey('post_context', $frame); + } + } + + $this->processor->process($this->client->_pending_events[0]); + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayNotHasKey('pre_context', $frame); + $this->assertArrayNotHasKey('context_line', $frame); + $this->assertArrayNotHasKey('post_context', $frame); + } + } + } + + public function testProcessWithPreviousException() + { + try { + try { + throw new \Exception('foo'); + } catch (\Exception $exception) { + throw new \Exception('bar', 0, $exception); + } + } catch (\Exception $exception) { + $this->client->captureException($exception); + } + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayHasKey('pre_context', $frame); + $this->assertArrayHasKey('context_line', $frame); + $this->assertArrayHasKey('post_context', $frame); + } + } + + $this->processor->process($this->client->_pending_events[0]); + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayNotHasKey('pre_context', $frame); + $this->assertArrayNotHasKey('context_line', $frame); + $this->assertArrayNotHasKey('post_context', $frame); + } + } + } + + /** + * Gets all the public and abstracts methods of a given class. + * + * @param string $className The FCQN of the class + * + * @return array + */ + private function getClassMethods($className) + { + $class = new ReflectionClass($className); + $methods = array(); + + foreach ($class->getMethods() as $method) { + if ($method->isPublic() || $method->isAbstract()) { + $methods[] = $method->getName(); + } + } + + return $methods; + } +} From c5d1465bd8fe66090e171922e32e426b3aea4152 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 31 Mar 2017 23:40:18 +0200 Subject: [PATCH 0284/1161] Use PSR-4 standard for unit tests --- .php_cs | 21 +++--- .travis.yml | 2 - composer.json | 18 +++-- lib/Raven/Autoloader.php | 51 -------------- lib/Raven/Client.php | 4 +- phpunit.xml => phpunit.xml.dist | 20 +++--- .../Breadcrumbs/ErrorHandlerTest.php | 0 .../Tests => }/Breadcrumbs/MonologTest.php | 8 ++- tests/{Raven/Tests => }/BreadcrumbsTest.php | 0 tests/{Raven/Tests => }/ClientTest.php | 66 ++++++++++--------- tests/{Raven/Tests => }/ErrorHandlerTest.php | 14 ++-- tests/{Raven/Tests => }/IntegrationTest.php | 14 ++-- .../Processor/RemoveCookiesProcessorTest.php | 9 ++- .../Processor/RemoveHttpBodyProcessorTest.php | 9 ++- .../Processor/SanitizeDataProcessorTest.php | 54 ++++++++------- .../SanitizeHttpHeadersProcessorTest.php | 9 ++- .../SanitizeStacktraceProcessorTest.php | 13 ++-- .../{Raven/Tests => }/ReprSerializerTest.php | 8 ++- tests/{Raven/Tests => }/SerializerTest.php | 12 ++-- tests/{Raven/Tests => }/StacktraceTest.php | 22 ++++--- .../Tests => }/TransactionStackTest.php | 3 +- tests/{Raven/Tests => }/UtilTest.php | 6 +- tests/bootstrap.php | 3 +- tests/{Raven/Tests => }/resources/a.php | 0 tests/{Raven/Tests => }/resources/b.php | 0 .../captureExceptionInLatin1File.php | 0 26 files changed, 179 insertions(+), 187 deletions(-) delete mode 100644 lib/Raven/Autoloader.php rename phpunit.xml => phpunit.xml.dist (59%) rename tests/{Raven/Tests => }/Breadcrumbs/ErrorHandlerTest.php (100%) rename tests/{Raven/Tests => }/Breadcrumbs/MonologTest.php (94%) rename tests/{Raven/Tests => }/BreadcrumbsTest.php (100%) rename tests/{Raven/Tests => }/ClientTest.php (97%) rename tests/{Raven/Tests => }/ErrorHandlerTest.php (95%) rename tests/{Raven/Tests => }/IntegrationTest.php (81%) rename tests/{Raven/Tests => }/Processor/RemoveCookiesProcessorTest.php (89%) rename tests/{Raven/Tests => }/Processor/RemoveHttpBodyProcessorTest.php (93%) rename tests/{Raven/Tests => }/Processor/SanitizeDataProcessorTest.php (71%) rename tests/{Raven/Tests => }/Processor/SanitizeHttpHeadersProcessorTest.php (90%) rename tests/{Raven/Tests => }/Processor/SanitizeStacktraceProcessorTest.php (87%) rename tests/{Raven/Tests => }/ReprSerializerTest.php (93%) rename tests/{Raven/Tests => }/SerializerTest.php (93%) rename tests/{Raven/Tests => }/StacktraceTest.php (90%) rename tests/{Raven/Tests => }/TransactionStackTest.php (91%) rename tests/{Raven/Tests => }/UtilTest.php (85%) rename tests/{Raven/Tests => }/resources/a.php (100%) rename tests/{Raven/Tests => }/resources/b.php (100%) rename tests/{Raven/Tests => }/resources/captureExceptionInLatin1File.php (100%) diff --git a/.php_cs b/.php_cs index 790c396bf..477a2547f 100644 --- a/.php_cs +++ b/.php_cs @@ -1,12 +1,15 @@ in(__DIR__) -; - -return Symfony\CS\Config\Config::create() - ->setUsingCache(true) - ->setUsingLinter(true) - ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) - ->finder($finder) +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + ]) + ->setRiskyAllowed(true) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__) + ->exclude([ + 'tests/resources', + ]) + ) ; diff --git a/.travis.yml b/.travis.yml index 89472652d..78af94e6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: php php: - - 5.3 - - 5.4 - 5.5 - 5.6 - 7.0 diff --git a/composer.json b/composer.json index bd0f8d9cb..22d3d64de 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "sentry/sentry", "type": "library", "description": "A PHP client for Sentry (http://getsentry.com)", - "keywords": ["log", "logging"], + "keywords": ["log", "logging", "error-monitoring", "error-handler", "crash-reporting", "crash-reports"], "homepage": "http://getsentry.com", "license": "BSD-3-Clause", "authors": [ @@ -12,12 +12,12 @@ } ], "require-dev": { - "friendsofphp/php-cs-fixer": "^1.8.0", + "friendsofphp/php-cs-fixer": "~2.1", "phpunit/phpunit": "^4.8 || ^5.0", "monolog/monolog": "*" }, "require": { - "php": ">=5.3.3", + "php": ">=5.5", "ext-hash": "*", "ext-json": "*", "ext-mbstring": "*", @@ -35,6 +35,14 @@ "autoload": { "psr-4" : { "Raven\\" : "lib/Raven/" + }, + "exclude-from-classmap": [ + "/tests/" + ] + }, + "autoload-dev": { + "psr-4": { + "Raven\\Tests\\": "tests/" } }, "scripts": { @@ -45,12 +53,12 @@ "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-html tests/html-report" ], "phpcs": [ - "vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run" + "vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run" ] }, "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "2.0.x-dev" } } } diff --git a/lib/Raven/Autoloader.php b/lib/Raven/Autoloader.php deleted file mode 100644 index e7aec5c10..000000000 --- a/lib/Raven/Autoloader.php +++ /dev/null @@ -1,51 +0,0 @@ - - - - ./tests/Raven/ - - + + + tests + + - - - ./lib/Raven/ - - + + + lib + + diff --git a/tests/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php b/tests/Breadcrumbs/ErrorHandlerTest.php similarity index 100% rename from tests/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php rename to tests/Breadcrumbs/ErrorHandlerTest.php diff --git a/tests/Raven/Tests/Breadcrumbs/MonologTest.php b/tests/Breadcrumbs/MonologTest.php similarity index 94% rename from tests/Raven/Tests/Breadcrumbs/MonologTest.php rename to tests/Breadcrumbs/MonologTest.php index 0edaeac41..0700a0070 100644 --- a/tests/Raven/Tests/Breadcrumbs/MonologTest.php +++ b/tests/Breadcrumbs/MonologTest.php @@ -9,7 +9,9 @@ * file that was distributed with this source code. */ -class Raven_Tests_MonologBreadcrumbHandlerTest extends PHPUnit_Framework_TestCase +namespace Raven\Tests\Breadcrumbs; + +class MonologTest extends \PHPUnit_Framework_TestCase { protected function getSampleErrorMessage() { @@ -40,7 +42,7 @@ public function testSimple() )); $handler = new \Raven\Breadcrumbs\MonologHandler($client); - $logger = new Monolog\Logger('sentry'); + $logger = new \Monolog\Logger('sentry'); $logger->pushHandler($handler); $logger->addWarning('Foo'); @@ -58,7 +60,7 @@ public function testErrorInMessage() )); $handler = new \Raven\Breadcrumbs\MonologHandler($client); - $logger = new Monolog\Logger('sentry'); + $logger = new \Monolog\Logger('sentry'); $logger->pushHandler($handler); $logger->addError($this->getSampleErrorMessage()); diff --git a/tests/Raven/Tests/BreadcrumbsTest.php b/tests/BreadcrumbsTest.php similarity index 100% rename from tests/Raven/Tests/BreadcrumbsTest.php rename to tests/BreadcrumbsTest.php diff --git a/tests/Raven/Tests/ClientTest.php b/tests/ClientTest.php similarity index 97% rename from tests/Raven/Tests/ClientTest.php rename to tests/ClientTest.php index 3e801fd51..f3a051cb5 100644 --- a/tests/Raven/Tests/ClientTest.php +++ b/tests/ClientTest.php @@ -8,7 +8,9 @@ * file that was distributed with this source code. */ -function simple_function($a=null, $b=null, $c=null) +namespace Raven\Tests; + +function simple_function($a = null, $b = null, $c = null) { assert(0); } @@ -205,7 +207,7 @@ public function join($timeout = null) } } -class Raven_Tests_ClientTest extends PHPUnit_Framework_TestCase +class Raven_Tests_ClientTest extends \PHPUnit_Framework_TestCase { public function tearDown() { @@ -218,8 +220,8 @@ public function tearDown() private function create_exception() { try { - throw new Exception('Foo bar'); - } catch (Exception $ex) { + throw new \Exception('Foo bar'); + } catch (\Exception $ex) { return $ex; } } @@ -227,11 +229,11 @@ private function create_exception() private function create_chained_exception() { try { - throw new Exception('Foo bar'); - } catch (Exception $ex) { + throw new \Exception('Foo bar'); + } catch (\Exception $ex) { try { - throw new Exception('Child exc', 0, $ex); - } catch (Exception $ex2) { + throw new \Exception('Child exc', 0, $ex); + } catch (\Exception $ex2) { return $ex2; } } @@ -282,7 +284,7 @@ public function testParseDSNInvalidScheme() try { \Raven\Client::ParseDSN('gopher://public:secret@/1'); $this->fail(); - } catch (Exception $e) { + } catch (\Exception $e) { return; } } @@ -292,7 +294,7 @@ public function testParseDSNMissingNetloc() try { \Raven\Client::ParseDSN('http://public:secret@/1'); $this->fail(); - } catch (Exception $e) { + } catch (\Exception $e) { return; } } @@ -302,20 +304,20 @@ public function testParseDSNMissingProject() try { \Raven\Client::ParseDSN('http://public:secret@example.com'); $this->fail(); - } catch (Exception $e) { + } catch (\Exception $e) { return; } } /** - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException */ public function testParseDSNMissingPublicKey() { \Raven\Client::ParseDSN('http://:secret@example.com/1'); } /** - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException */ public function testParseDSNMissingSecretKey() { @@ -576,7 +578,7 @@ public function testCaptureExceptionSetsInterfaces() $this->assertTrue($frame['lineno'] > 0); $this->assertEquals('create_exception', $frame['function']); $this->assertFalse(isset($frame['vars'])); - $this->assertEquals(' throw new Exception(\'Foo bar\');', $frame['context_line']); + $this->assertEquals(' throw new \Exception(\'Foo bar\');', $frame['context_line']); $this->assertFalse(empty($frame['pre_context'])); $this->assertFalse(empty($frame['post_context'])); } @@ -615,9 +617,9 @@ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() } $client = new Dummy_Raven_Client(); - $e1 = new ErrorException('First', 0, E_DEPRECATED); - $e2 = new ErrorException('Second', 0, E_NOTICE, __FILE__, __LINE__, $e1); - $e3 = new ErrorException('Third', 0, E_ERROR, __FILE__, __LINE__, $e2); + $e1 = new \ErrorException('First', 0, E_DEPRECATED); + $e2 = new \ErrorException('Second', 0, E_NOTICE, __FILE__, __LINE__, $e1); + $e3 = new \ErrorException('Third', 0, E_ERROR, __FILE__, __LINE__, $e2); $client->captureException($e1); $client->captureException($e2); @@ -673,7 +675,7 @@ public function testCaptureExceptionInvalidUTF8() $client = new Dummy_Raven_Client(); try { invalid_encoding(); - } catch (Exception $ex) { + } catch (\Exception $ex) { $client->captureException($ex); } $events = $client->getSentEvents(); @@ -909,7 +911,7 @@ public function testCaptureMessageWithUnserializableUserData() $client->user_context(array( 'email' => 'foo@example.com', 'data' => array( - 'error' => new Exception('test'), + 'error' => new \Exception('test'), ) )); @@ -1098,7 +1100,7 @@ public function testAppPathWindows() } /** - * @expectedException Raven\Exception + * @expectedException \Raven\Exception * @expectedExceptionMessage Raven_Client->install() must only be called once */ public function testCannotInstallTwice() @@ -1416,7 +1418,7 @@ public function currentUrlProvider() */ public function testUuid4() { - $method = new ReflectionMethod('\\Raven\\Client', 'uuid4'); + $method = new \ReflectionMethod('\\Raven\\Client', 'uuid4'); $method->setAccessible(true); for ($i = 0; $i < 1000; $i++) { $this->assertRegExp('/^[0-9a-z-]+$/', $method->invoke(null)); @@ -1449,7 +1451,7 @@ public function testUuid4() public function testGettersAndSetters() { $client = new Dummy_Raven_Client(); - $property_method__convert_path = new ReflectionMethod('\\Raven\\Client', '_convertPath'); + $property_method__convert_path = new \ReflectionMethod('\\Raven\\Client', '_convertPath'); $property_method__convert_path->setAccessible(true); $callable = array($this, 'stabClosureVoid'); @@ -1508,7 +1510,7 @@ private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) $method_get_name = 'get'.$function_name; $method_set_name = 'set'.$function_name; - $property = new ReflectionProperty('\\Raven\\Client', $property_name); + $property = new \ReflectionProperty('\\Raven\\Client', $property_name); $property->setAccessible(true); if (method_exists($client, $method_set_name)) { @@ -1523,7 +1525,7 @@ private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) if (method_exists($client, $method_get_name)) { $property->setValue($client, $value_out); - $reflection = new ReflectionMethod('\\Raven\Client', $method_get_name); + $reflection = new \ReflectionMethod('\\Raven\Client', $method_get_name); if ($reflection->isPublic()) { $actual_value = $client->$method_get_name(); $this->assertMixedValueAndArray($value_out, $actual_value); @@ -1558,7 +1560,7 @@ private function assertMixedValueAndArray($expected_value, $actual_value) */ public function test_convertPath() { - $property = new ReflectionMethod('\\Raven\Client', '_convertPath'); + $property = new \ReflectionMethod('\\Raven\Client', '_convertPath'); $property->setAccessible(true); $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar')); @@ -1577,7 +1579,7 @@ public function testGetDefaultProcessors() foreach (\Raven\Client::getDefaultProcessors() as $class_name) { $this->assertInternalType('string', $class_name); $this->assertTrue(class_exists($class_name)); - $reflection = new ReflectionClass($class_name); + $reflection = new \ReflectionClass($class_name); $this->assertTrue($reflection->isSubclassOf('\\Raven\\Processor')); $this->assertFalse($reflection->isAbstract()); } @@ -1588,7 +1590,7 @@ public function testGetDefaultProcessors() */ public function testGet_default_ca_cert() { - $reflection = new ReflectionMethod('\\Raven\Client', 'get_default_ca_cert'); + $reflection = new \ReflectionMethod('\\Raven\Client', 'get_default_ca_cert'); $reflection->setAccessible(true); $this->assertFileExists($reflection->invoke(null)); } @@ -1599,7 +1601,7 @@ public function testGet_default_ca_cert() */ public function testTranslateSeverity() { - $reflection = new ReflectionProperty('\\Raven\Client', 'severity_map'); + $reflection = new \ReflectionProperty('\\Raven\Client', 'severity_map'); $reflection->setAccessible(true); $client = new Dummy_Raven_Client(); @@ -1656,7 +1658,7 @@ public function testGetUserAgent() public function testCaptureExceptionWithLogger() { $client = new Dummy_Raven_Client(); - $client->captureException(new Exception(), null, 'foobar'); + $client->captureException(new \Exception(), null, 'foobar'); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); @@ -1714,7 +1716,7 @@ public function testCurl_method_async() $this->assertInternalType('object', $object); $this->assertEquals('Raven\\CurlHandler', get_class($object)); - $reflection = new ReflectionProperty('Raven\\CurlHandler', 'options'); + $reflection = new \ReflectionProperty('Raven\\CurlHandler', 'options'); $reflection->setAccessible(true); $this->assertEquals($reflection->getValue($object), $client->get_curl_options()); @@ -1747,7 +1749,7 @@ public function testConstructWithServerDSN() */ public function test_server_variable() { - $method = new ReflectionMethod('\\Raven\Client', '_server_variable'); + $method = new \ReflectionMethod('\\Raven\Client', '_server_variable'); $method->setAccessible(true); foreach ($_SERVER as $key => $value) { $actual = $method->invoke(null, $key); @@ -2218,7 +2220,7 @@ public function testSend_http_asynchronous_curl_exec() public function testClose_curl_resource() { $raven = new Dummy_Raven_Client(); - $reflection = new ReflectionProperty('\\Raven\Client', '_curl_instance'); + $reflection = new \ReflectionProperty('\\Raven\Client', '_curl_instance'); $reflection->setAccessible(true); $ch = curl_init(); $reflection->setValue($raven, $ch); diff --git a/tests/Raven/Tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php similarity index 95% rename from tests/Raven/Tests/ErrorHandlerTest.php rename to tests/ErrorHandlerTest.php index 937a0d3cb..8a407618f 100644 --- a/tests/Raven/Tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -9,7 +9,9 @@ * file that was distributed with this source code. */ -class Raven_Tests_ErrorHandlerTest extends PHPUnit_Framework_TestCase +namespace Raven\Tests; + +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase { private $errorLevel; private $errorHandlerCalled; @@ -62,7 +64,7 @@ public function testExceptionsAreLogged() ->method('captureException') ->with($this->isInstanceOf('ErrorException')); - $e = new ErrorException('message', 0, E_WARNING, '', 0); + $e = new \ErrorException('message', 0, E_WARNING, '', 0); $handler = new \Raven\ErrorHandler($client); $handler->handleException($e); @@ -109,17 +111,17 @@ public function testExceptionHandlerPropagatesToNative() ->method('captureException') ->with($this->isInstanceOf('Exception')); - $handler = new Raven\ErrorHandler($client); + $handler = new \Raven\ErrorHandler($client); set_exception_handler(null); $handler->registerExceptionHandler(false); - $testException = new Exception('Test exception'); + $testException = new \Exception('Test exception'); $didRethrow = false; try { $handler->handleException($testException); - } catch (Exception $e) { + } catch (\Exception $e) { $didRethrow = true; } @@ -132,7 +134,7 @@ public function testExceptionHandlerPropagatesToNative() $rethrownException = null; try { $handler->handleException($testException); - } catch (Exception $e) { + } catch (\Exception $e) { $didRethrow = true; $rethrownException = $e; } diff --git a/tests/Raven/Tests/IntegrationTest.php b/tests/IntegrationTest.php similarity index 81% rename from tests/Raven/Tests/IntegrationTest.php rename to tests/IntegrationTest.php index 2b6ea2161..167ffbd2d 100644 --- a/tests/Raven/Tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -8,6 +8,8 @@ * file that was distributed with this source code. */ +namespace Raven\Tests; + class DummyIntegration_Raven_Client extends \Raven\Client { private $__sent_events = array(); @@ -34,16 +36,16 @@ public function registerDefaultBreadcrumbHandlers() } } -class Raven_Tests_IntegrationTest extends PHPUnit_Framework_TestCase +class Raven_Tests_IntegrationTest extends \PHPUnit_Framework_TestCase { private function create_chained_exception() { try { - throw new Exception('Foo bar'); - } catch (Exception $ex) { + throw new \Exception('Foo bar'); + } catch (\Exception $ex) { try { - throw new Exception('Child exc', 0, $ex); - } catch (Exception $ex2) { + throw new \Exception('Child exc', 0, $ex); + } catch (\Exception $ex2) { return $ex2; } } @@ -64,6 +66,6 @@ public function testCaptureSimpleError() $this->assertEquals($exc['value'], 'mkdir(): No such file or directory'); $stack = $exc['stacktrace']['frames']; $lastFrame = $stack[count($stack) - 1]; - $this->assertEquals(@$lastFrame['filename'], 'tests/Raven/Tests/IntegrationTest.php'); + $this->assertEquals(@$lastFrame['filename'], 'tests/IntegrationTest.php'); } } diff --git a/tests/Raven/Tests/Processor/RemoveCookiesProcessorTest.php b/tests/Processor/RemoveCookiesProcessorTest.php similarity index 89% rename from tests/Raven/Tests/Processor/RemoveCookiesProcessorTest.php rename to tests/Processor/RemoveCookiesProcessorTest.php index 6660def54..f3cf0d108 100644 --- a/tests/Raven/Tests/Processor/RemoveCookiesProcessorTest.php +++ b/tests/Processor/RemoveCookiesProcessorTest.php @@ -9,9 +9,12 @@ * file that was distributed with this source code. */ +namespace Raven\Tests; + +use Raven\Client; use Raven\Processor\RemoveCookiesProcessor; -class Raven_Tests_RemoveCookiesProcessorTest extends \PHPUnit_Framework_TestCase +class RemoveCookiesProcessorTest extends \PHPUnit_Framework_TestCase { /** * @var RemoveCookiesProcessor|\PHPUnit_Framework_MockObject_MockObject @@ -20,8 +23,8 @@ class Raven_Tests_RemoveCookiesProcessorTest extends \PHPUnit_Framework_TestCase protected function setUp() { - /** @var \Raven\Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder('Raven\Client') + /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(Client::class) ->disableOriginalConstructor() ->getMock(); diff --git a/tests/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php b/tests/Processor/RemoveHttpBodyProcessorTest.php similarity index 93% rename from tests/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php rename to tests/Processor/RemoveHttpBodyProcessorTest.php index a9e44f728..22ebafa73 100644 --- a/tests/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php +++ b/tests/Processor/RemoveHttpBodyProcessorTest.php @@ -9,9 +9,12 @@ * file that was distributed with this source code. */ +namespace Raven\Tests; + +use Raven\Client; use Raven\Processor\RemoveHttpBodyProcessor; -class Raven_Tests_RemoveHttpBodyProcessorTest extends \PHPUnit_Framework_TestCase +class RemoveHttpBodyProcessorTest extends \PHPUnit_Framework_TestCase { /** * @var RemoveHttpBodyProcessor|\PHPUnit_Framework_MockObject_MockObject @@ -20,8 +23,8 @@ class Raven_Tests_RemoveHttpBodyProcessorTest extends \PHPUnit_Framework_TestCas protected function setUp() { - /** @var \Raven\Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder('Raven\Client') + /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(Client::class) ->disableOriginalConstructor() ->getMock(); diff --git a/tests/Raven/Tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php similarity index 71% rename from tests/Raven/Tests/Processor/SanitizeDataProcessorTest.php rename to tests/Processor/SanitizeDataProcessorTest.php index be788bb51..0f792fc63 100644 --- a/tests/Raven/Tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -9,7 +9,11 @@ * file that was distributed with this source code. */ -class Raven_Tests_SanitizeDataProcessorTest extends PHPUnit_Framework_TestCase +namespace Raven\Tests; + +use Raven\Processor\SanitizeDataProcessor; + +class SanitizeDataProcessorTest extends \PHPUnit_Framework_TestCase { public function testDoesFilterHttpData() { @@ -33,20 +37,20 @@ public function testDoesFilterHttpData() ); $client = new Dummy_Raven_Client(); - $processor = new Raven\Processor\SanitizeDataProcessor($client); + $processor = new SanitizeDataProcessor($client); $processor->process($data); $vars = $data['request']['data']; $this->assertEquals($vars['foo'], 'bar'); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['password']); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['the_secret']); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['a_password_here']); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['mypasswd']); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['authorization']); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['password']); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['the_secret']); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['a_password_here']); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['mypasswd']); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['authorization']); $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0']); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0']); } public function testDoesFilterSessionId() @@ -60,11 +64,11 @@ public function testDoesFilterSessionId() ); $client = new Dummy_Raven_Client(); - $processor = new Raven\Processor\SanitizeDataProcessor($client); + $processor = new SanitizeDataProcessor($client); $processor->process($data); $cookies = $data['request']['cookies']; - $this->assertEquals($cookies[ini_get('session.name')], Raven\Processor\SanitizeDataProcessor::STRING_MASK); + $this->assertEquals($cookies[ini_get('session.name')], SanitizeDataProcessor::STRING_MASK); } public function testDoesFilterCreditCard() @@ -76,16 +80,16 @@ public function testDoesFilterCreditCard() ); $client = new Dummy_Raven_Client(); - $processor = new Raven\Processor\SanitizeDataProcessor($client); + $processor = new SanitizeDataProcessor($client); $processor->process($data); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $data['extra']['ccnumba']); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $data['extra']['ccnumba']); } public function testSettingProcessorOptions() { $client = new Dummy_Raven_Client(); - $processor = new Raven\Processor\SanitizeDataProcessor($client); + $processor = new SanitizeDataProcessor($client); $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); @@ -112,13 +116,13 @@ public function testOverrideOptions($processorOptions, $client_options, $dsn) { $client = new Dummy_Raven_Client($dsn, $client_options); /** - * @var Raven\Processor\SanitizeDataProcessor $processor + * @var SanitizeDataProcessor $processor */ $processor = $client->processors[0]; - $this->assertInstanceOf('Raven\Processor\SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven\Processor\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven\Processor\SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf(SanitizeDataProcessor::class, $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions[SanitizeDataProcessor::class]['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions[SanitizeDataProcessor::class]['values_re'], 'overwrote values'); } /** @@ -151,13 +155,13 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $client = new Dummy_Raven_Client($dsn, $client_options); /** - * @var Raven\Processor\SanitizeDataProcessor $processor + * @var SanitizeDataProcessor $processor */ $processor = $client->processors[0]; - $this->assertInstanceOf('Raven\Processor\SanitizeDataProcessor', $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven\Processor\SanitizeDataProcessor']['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven\Processor\SanitizeDataProcessor']['values_re'], 'overwrote values'); + $this->assertInstanceOf(SanitizeDataProcessor::class, $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions[SanitizeDataProcessor::class]['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions[SanitizeDataProcessor::class]['values_re'], 'overwrote values'); $processor->process($data); @@ -168,9 +172,9 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $this->assertEquals($vars['a_password_here'], 'hello', 'did not alter a_password_here'); $this->assertEquals($vars['mypasswd'], 'hello', 'did not alter mypasswd'); $this->assertEquals($vars['authorization'], 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', 'did not alter authorization'); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['api_token'], 'masked api_token'); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['api_token'], 'masked api_token'); - $this->assertEquals(Raven\Processor\SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0'], 'masked card_number[0]'); + $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0'], 'masked card_number[0]'); $this->assertEquals($vars['card_number']['1'], $vars['card_number']['1'], 'did not alter card_number[1]'); } @@ -182,14 +186,14 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) public static function overrideDataProvider() { $processorOptions = array( - 'Raven\Processor\SanitizeDataProcessor' => array( + SanitizeDataProcessor::class => array( 'fields_re' => '/(api_token)/i', 'values_re' => '/^(?:\d[ -]*?){15,16}$/' ) ); $client_options = array( - 'processors' => array('Raven\Processor\SanitizeDataProcessor'), + 'processors' => array(SanitizeDataProcessor::class), 'processorOptions' => $processorOptions ); diff --git a/tests/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php b/tests/Processor/SanitizeHttpHeadersProcessorTest.php similarity index 90% rename from tests/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php rename to tests/Processor/SanitizeHttpHeadersProcessorTest.php index 77121efe4..3d04345fb 100644 --- a/tests/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php +++ b/tests/Processor/SanitizeHttpHeadersProcessorTest.php @@ -9,9 +9,12 @@ * file that was distributed with this source code. */ +namespace Raven\Tests; + +use Raven\Client; use Raven\Processor\SanitizeHttpHeadersProcessor; -class Raven_SanitizeHttpHeadersProcessorTest extends \PHPUnit_Framework_TestCase +class SanitizeHttpHeadersProcessorTest extends \PHPUnit_Framework_TestCase { /** * @var SanitizeHttpHeadersProcessor|\PHPUnit_Framework_MockObject_MockObject @@ -20,8 +23,8 @@ class Raven_SanitizeHttpHeadersProcessorTest extends \PHPUnit_Framework_TestCase protected function setUp() { - /** @var \Raven\Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder('Raven\Client') + /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(Client::class) ->disableOriginalConstructor() ->getMock(); diff --git a/tests/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php similarity index 87% rename from tests/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php rename to tests/Processor/SanitizeStacktraceProcessorTest.php index 3e57263c7..7d17066fc 100644 --- a/tests/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -9,12 +9,15 @@ * file that was distributed with this source code. */ +namespace Raven\Tests; + +use Raven\Client; use Raven\Processor\SanitizeStacktraceProcessor; -class Raven_Tests_SanitizeStacktraceProcessorTest extends PHPUnit_Framework_TestCase +class SanitizeStacktraceProcessorTest extends \PHPUnit_Framework_TestCase { /** - * @var \Raven\Client|PHPUnit_Framework_MockObject_MockObject + * @var Client|\PHPUnit_Framework_MockObject_MockObject */ protected $client; @@ -25,8 +28,8 @@ class Raven_Tests_SanitizeStacktraceProcessorTest extends PHPUnit_Framework_Test protected function setUp() { - $this->client = $this->getMockBuilder('Raven\Client') - ->setMethods(array_diff($this->getClassMethods('Raven\Client'), array('captureException', 'capture', 'get_default_data', 'get_http_data', 'get_user_data', 'get_extra_data'))) + $this->client = $this->getMockBuilder(Client::class) + ->setMethods(array_diff($this->getClassMethods(Client::class), array('captureException', 'capture', 'get_default_data', 'get_http_data', 'get_user_data', 'get_extra_data'))) ->getMock(); $this->client->store_errors_for_bulk_send = true; @@ -101,7 +104,7 @@ public function testProcessWithPreviousException() */ private function getClassMethods($className) { - $class = new ReflectionClass($className); + $class = new \ReflectionClass($className); $methods = array(); foreach ($class->getMethods() as $method) { diff --git a/tests/Raven/Tests/ReprSerializerTest.php b/tests/ReprSerializerTest.php similarity index 93% rename from tests/Raven/Tests/ReprSerializerTest.php rename to tests/ReprSerializerTest.php index 2db3ca8f9..19818d5ed 100644 --- a/tests/Raven/Tests/ReprSerializerTest.php +++ b/tests/ReprSerializerTest.php @@ -9,7 +9,9 @@ * file that was distributed with this source code. */ -class Raven_Tests_ReprSerializerTest extends PHPUnit_Framework_TestCase +namespace Raven\Tests; + +class ReprSerializerTest extends \PHPUnit_Framework_TestCase { public function testArraysAreArrays() { @@ -22,9 +24,9 @@ public function testArraysAreArrays() public function testObjectsAreStrings() { $serializer = new \Raven\ReprSerializer(); - $input = new Raven_StacktraceTestObject(); + $input = new \Raven\Tests\StacktraceTestObject(); $result = $serializer->serialize($input); - $this->assertEquals('Object Raven_StacktraceTestObject', $result); + $this->assertEquals('Object Raven\Tests\StacktraceTestObject', $result); } public function testIntsAreInts() diff --git a/tests/Raven/Tests/SerializerTest.php b/tests/SerializerTest.php similarity index 93% rename from tests/Raven/Tests/SerializerTest.php rename to tests/SerializerTest.php index 29604b4f8..c2e4632fc 100644 --- a/tests/Raven/Tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -9,12 +9,14 @@ * file that was distributed with this source code. */ -class Raven_SerializerTestObject +namespace Raven\Tests; + +class SerializerTestObject { private $foo = 'bar'; } -class Raven_Tests_SerializerTest extends PHPUnit_Framework_TestCase +class Raven_Tests_SerializerTest extends \PHPUnit_Framework_TestCase { public function testArraysAreArrays() { @@ -27,7 +29,7 @@ public function testArraysAreArrays() public function testStdClassAreArrays() { $serializer = new \Raven\Serializer(); - $input = new stdClass(); + $input = new \stdClass(); $input->foo = 'BAR'; $result = $serializer->serialize($input); $this->assertEquals(array('foo' => 'BAR'), $result); @@ -36,9 +38,9 @@ public function testStdClassAreArrays() public function testObjectsAreStrings() { $serializer = new \Raven\Serializer(); - $input = new Raven_SerializerTestObject(); + $input = new \Raven\Tests\SerializerTestObject(); $result = $serializer->serialize($input); - $this->assertEquals('Object Raven_SerializerTestObject', $result); + $this->assertEquals('Object Raven\Tests\SerializerTestObject', $result); } public function testIntsAreInts() diff --git a/tests/Raven/Tests/StacktraceTest.php b/tests/StacktraceTest.php similarity index 90% rename from tests/Raven/Tests/StacktraceTest.php rename to tests/StacktraceTest.php index 1aef2d680..22d327d2b 100644 --- a/tests/Raven/Tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -9,11 +9,13 @@ * file that was distributed with this source code. */ +namespace Raven\Tests; + function raven_test_recurse($times, $callback) { $times -= 1; if ($times > 0) { - return call_user_func('raven_test_recurse', $times, $callback); + return call_user_func('\Raven\Tests\raven_test_recurse', $times, $callback); } return call_user_func($callback); @@ -24,7 +26,7 @@ function raven_test_create_stacktrace($args=null, $times=3) return raven_test_recurse($times, 'debug_backtrace'); } -class Raven_Tests_StacktraceTest extends PHPUnit_Framework_TestCase +class Raven_Tests_StacktraceTest extends \PHPUnit_Framework_TestCase { public function testCanTraceParamContext() { @@ -137,24 +139,24 @@ public function testDoesFixFrameInfo() if ($skip_call_user_func_fix) { $frame = $frames[3]; - $this->assertEquals('raven_test_create_stacktrace', $frame['function']); + $this->assertEquals('Raven\Tests\raven_test_create_stacktrace', $frame['function']); $frame = $frames[4]; - $this->assertEquals('raven_test_recurse', $frame['function']); + $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); $frame = $frames[5]; - $this->assertEquals('raven_test_recurse', $frame['function']); + $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); } else { $frame = $frames[0]; - $this->assertEquals('raven_test_create_stacktrace', $frame['function']); + $this->assertEquals('Raven\Tests\raven_test_create_stacktrace', $frame['function']); $frame = $frames[1]; - $this->assertEquals('raven_test_recurse', $frame['function']); + $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); $frame = $frames[2]; $this->assertEquals('call_user_func', $frame['function']); $frame = $frames[3]; - $this->assertEquals('raven_test_recurse', $frame['function']); + $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); $frame = $frames[4]; $this->assertEquals('call_user_func', $frame['function']); $frame = $frames[5]; - $this->assertEquals('raven_test_recurse', $frame['function']); + $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); } } @@ -236,7 +238,7 @@ public function testWithEvaldCode() { try { eval("throw new Exception('foobar');"); - } catch (Exception $ex) { + } catch (\Exception $ex) { $trace = $ex->getTrace(); $frames = \Raven\Stacktrace::get_stack_info($trace); } diff --git a/tests/Raven/Tests/TransactionStackTest.php b/tests/TransactionStackTest.php similarity index 91% rename from tests/Raven/Tests/TransactionStackTest.php rename to tests/TransactionStackTest.php index f706fd95f..08563699a 100644 --- a/tests/Raven/Tests/TransactionStackTest.php +++ b/tests/TransactionStackTest.php @@ -9,8 +9,9 @@ * file that was distributed with this source code. */ +namespace Raven\Tests; -class Raven_Tests_TransactionStackTest extends PHPUnit_Framework_TestCase +class Raven_Tests_TransactionStackTest extends \PHPUnit_Framework_TestCase { public function testSimple() { diff --git a/tests/Raven/Tests/UtilTest.php b/tests/UtilTest.php similarity index 85% rename from tests/Raven/Tests/UtilTest.php rename to tests/UtilTest.php index 885c8bda3..77d25067b 100644 --- a/tests/Raven/Tests/UtilTest.php +++ b/tests/UtilTest.php @@ -9,12 +9,14 @@ * file that was distributed with this source code. */ -class Raven_StacktraceTestObject +namespace Raven\Tests; + +class StacktraceTestObject { private $foo = 'bar'; } -class Raven_Tests_UtilTest extends PHPUnit_Framework_TestCase +class Raven_Tests_UtilTest extends \PHPUnit_Framework_TestCase { public function testGetReturnsDefaultOnMissing() { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 853dd6be9..462dba31a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -13,5 +13,4 @@ session_start(); -require_once __DIR__.'/../lib/Raven/Autoloader.php'; -\Raven\Autoloader::register(); +require_once __DIR__.'/../vendor/autoload.php'; diff --git a/tests/Raven/Tests/resources/a.php b/tests/resources/a.php similarity index 100% rename from tests/Raven/Tests/resources/a.php rename to tests/resources/a.php diff --git a/tests/Raven/Tests/resources/b.php b/tests/resources/b.php similarity index 100% rename from tests/Raven/Tests/resources/b.php rename to tests/resources/b.php diff --git a/tests/Raven/Tests/resources/captureExceptionInLatin1File.php b/tests/resources/captureExceptionInLatin1File.php similarity index 100% rename from tests/Raven/Tests/resources/captureExceptionInLatin1File.php rename to tests/resources/captureExceptionInLatin1File.php From d623812c31296af83e5ee8171190cd1ecdeea521 Mon Sep 17 00:00:00 2001 From: Edward Slipszenko Date: Wed, 12 Apr 2017 17:34:42 +0200 Subject: [PATCH 0285/1161] Missing closing parenthesis on usage filtering doc --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index 83cbd43f8..2fd1204f5 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -263,7 +263,7 @@ Its common that you might want to prevent automatic capture of certain areas. Id $sentryClient->setSendCallback(function($data) { $ignore_types = array('Symfony\Component\HttpKernel\Exception\NotFoundHttpException'); - if (isset($data['exception'] && in_array($data['exception']['values'][0]['type'], $ignore_types) + if (isset($data['exception']) && in_array($data['exception']['values'][0]['type'], $ignore_types)) { return false; } From adc36eb140fe7bf47478e933c415073969e9f32c Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Tue, 18 Apr 2017 16:54:54 +0300 Subject: [PATCH 0286/1161] Serializing all objects (their public properties) --- lib/Raven/Client.php | 8 + lib/Raven/Serializer.php | 24 ++- lib/Raven/TransactionStack.php | 5 + tests/ClientTest.php | 64 +++++++ tests/ReprSerializerTest.php | 88 +++++---- tests/SerializerAbstractTest.php | 316 +++++++++++++++++++++++++++++++ tests/SerializerTest.php | 141 ++------------ 7 files changed, 481 insertions(+), 165 deletions(-) create mode 100644 tests/SerializerAbstractTest.php diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 9ebd2bc06..d3c7b5ba2 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -123,6 +123,10 @@ class Client * @var bool */ protected $_shutdown_function_has_been_set; + /** + * @var \Raven\TransactionStack + */ + public $transaction; public function __construct($options_or_dsn = null, $options = array()) { @@ -198,6 +202,10 @@ public function __construct($options_or_dsn = null, $options = array()) )); $this->serializer = new \Raven\Serializer($this->mb_detect_order); $this->reprSerializer = new \Raven\ReprSerializer($this->mb_detect_order); + if (\Raven\Util::get($options, 'serialize_all_object', false)) { + $this->serializer->setAllObjectSerialize(true); + $this->reprSerializer->setAllObjectSerialize(true); + } if ($this->curl_method == 'async') { $this->_curl_handler = new \Raven\CurlHandler($this->get_curl_options()); diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index ce8269a74..e4b24101a 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -47,6 +47,11 @@ class Serializer */ protected $mb_detect_order = self::DEFAULT_MB_DETECT_ORDER; + /** + * @var boolean $_all_object_serialize + */ + protected $_all_object_serialize = false; + /** * @param null|string $mb_detect_order */ @@ -69,7 +74,8 @@ public function __construct($mb_detect_order = null) public function serialize($value, $max_depth = 3, $_depth = 0) { $className = is_object($value) ? get_class($value) : null; - $toArray = is_array($value) || $className === 'stdClass'; + $toArray = (is_array($value) or ($className == 'stdClass') or + (is_object($value) and $this->_all_object_serialize)); if ($toArray && $_depth < $max_depth) { $new = array(); foreach ($value as $k => $v) { @@ -143,4 +149,20 @@ public function setMbDetectOrder($mb_detect_order) return $this; } + + /** + * @param boolean $value + */ + public function setAllObjectSerialize($value) + { + $this->_all_object_serialize = $value; + } + + /** + * @return boolean + */ + public function getAllObjectSerialize() + { + return $this->_all_object_serialize; + } } diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index 2b8d5e733..711d68323 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -11,6 +11,11 @@ class TransactionStack { + /** + * @var array $stack + */ + public $stack; + public function __construct() { $this->stack = array(); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f3a051cb5..3c3f7ea48 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1176,6 +1176,70 @@ public function testSanitizeExtra() )), $data); } + /** + * @covers \Raven\Client::sanitize + */ + public function testSanitizeObjects() + { + $client = new Dummy_Raven_Client( + null, [ + 'serialize_all_object' => true, + ] + ); + $clone = new Dummy_Raven_Client(); + $data = array( + 'extra' => array( + 'object' => $clone, + ), + ); + + $reflection = new \ReflectionClass($clone); + $expected = []; + foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $value = $property->getValue($clone); + if (is_array($value)) { + $property->setValue($clone, []); + $expected[$property->getName()] = []; + continue; + } + if (!is_object($value)) { + $expected[$property->getName()] = $value; + continue; + } + + $new_value = []; + $reflection2 = new \ReflectionClass($value); + foreach ($reflection2->getProperties(\ReflectionProperty::IS_PUBLIC) as $property2) { + $sub_value = $property2->getValue($value); + if (is_array($sub_value)) { + $new_value[$property2->getName()] = 'Array of length '.count($sub_value); + continue; + } + if (is_object($sub_value)) { + $sub_value = null; + $property2->setValue($value, null); + } + $new_value[$property2->getName()] = $sub_value; + } + + ksort($new_value); + $expected[$property->getName()] = $new_value; + unset($reflection2, $property2, $sub_value, $new_value); + } + unset($reflection, $property, $value, $reflection, $clone); + ksort($expected); + + $client->sanitize($data); + ksort($data['extra']['object']); + foreach ($data['extra']['object'] as $key => &$value) { + if (is_array($value)) { + ksort($value); + } + } + + $this->assertEquals(array('extra' => array('object' => $expected)), $data); + } + /** * @covers \Raven\Client::sanitize */ diff --git a/tests/ReprSerializerTest.php b/tests/ReprSerializerTest.php index 19818d5ed..8c38f5988 100644 --- a/tests/ReprSerializerTest.php +++ b/tests/ReprSerializerTest.php @@ -11,45 +11,60 @@ namespace Raven\Tests; -class ReprSerializerTest extends \PHPUnit_Framework_TestCase -{ - public function testArraysAreArrays() - { - $serializer = new \Raven\ReprSerializer(); - $input = array(1, 2, 3); - $result = $serializer->serialize($input); - $this->assertEquals(array('1', '2', '3'), $result); - } +require_once 'SerializerAbstractTest.php'; - public function testObjectsAreStrings() +class ReprSerializerTest extends \Raven\Tests\Raven_Tests_SerializerAbstractTest +{ + /** + * @return string + */ + protected static function get_test_class() { - $serializer = new \Raven\ReprSerializer(); - $input = new \Raven\Tests\StacktraceTestObject(); - $result = $serializer->serialize($input); - $this->assertEquals('Object Raven\Tests\StacktraceTestObject', $result); + return '\\Raven\\ReprSerializer'; } - public function testIntsAreInts() + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testIntsAreInts($serialize_all_objects) { $serializer = new \Raven\ReprSerializer(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } $input = 1; $result = $serializer->serialize($input); $this->assertInternalType('string', $result); $this->assertEquals(1, $result); } - public function testFloats() + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testFloats($serialize_all_objects) { $serializer = new \Raven\ReprSerializer(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } $input = 1.5; $result = $serializer->serialize($input); $this->assertInternalType('string', $result); $this->assertEquals('1.5', $result); } - public function testBooleans() + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testBooleans($serialize_all_objects) { $serializer = new \Raven\ReprSerializer(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } $input = true; $result = $serializer->serialize($input); $this->assertEquals('true', $result); @@ -60,44 +75,33 @@ public function testBooleans() $this->assertEquals('false', $result); } - public function testNull() + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testNull($serialize_all_objects) { $serializer = new \Raven\ReprSerializer(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } $input = null; $result = $serializer->serialize($input); $this->assertInternalType('string', $result); $this->assertEquals('null', $result); } - public function testRecursionMaxDepth() - { - $serializer = new \Raven\ReprSerializer(); - $input = array(); - $input[] = &$input; - $result = $serializer->serialize($input, 3); - $this->assertEquals(array(array(array('Array of length 1'))), $result); - } - - /** - * @covers \Raven\ReprSerializer::serializeValue - */ - public function testSerializeValueResource() - { - $serializer = new \Raven\ReprSerializer(); - $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); - $fo = fopen($filename, 'wb'); - - $result = $serializer->serialize($fo); - $this->assertInternalType('string', $result); - $this->assertEquals('Resource stream', $result); - } - /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam * @covers \Raven\ReprSerializer::serializeValue */ - public function testSerializeRoundedFloat() + public function testSerializeRoundedFloat($serialize_all_objects) { $serializer = new \Raven\ReprSerializer(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } $result = $serializer->serialize((double)1); $this->assertInternalType('string', $result); diff --git a/tests/SerializerAbstractTest.php b/tests/SerializerAbstractTest.php new file mode 100644 index 000000000..20aaf2332 --- /dev/null +++ b/tests/SerializerAbstractTest.php @@ -0,0 +1,316 @@ + false], + ['serialize_all_objects' => true], + ]; + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testArraysAreArrays($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer * */ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + $input = array(1, 2, 3); + $result = $serializer->serialize($input); + $this->assertEquals(array('1', '2', '3'), $result); + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testStdClassAreArrays($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + $input = new \stdClass(); + $input->foo = 'BAR'; + $result = $serializer->serialize($input); + $this->assertEquals(array('foo' => 'BAR'), $result); + } + + public function testObjectsAreStrings() + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer * */ + $serializer = new $class_name(); + $input = new \Raven\Tests\SerializerTestObject(); + $result = $serializer->serialize($input); + $this->assertEquals('Object Raven\Tests\SerializerTestObject', $result); + } + + public function testObjectsAreNotStrings() + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer * */ + $serializer = new $class_name(); + $serializer->setAllObjectSerialize(true); + $input = new \Raven\Tests\SerializerTestObject(); + $result = $serializer->serialize($input); + $this->assertEquals(array('key' => 'value'), $result); + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testIntsAreInts($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + $input = 1; + $result = $serializer->serialize($input); + $this->assertInternalType('integer', $result); + $this->assertEquals(1, $result); + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testFloats($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + $input = 1.5; + $result = $serializer->serialize($input); + $this->assertInternalType('double', $result); + $this->assertEquals(1.5, $result); + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testBooleans($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + $input = true; + $result = $serializer->serialize($input); + $this->assertTrue($result); + + $input = false; + $result = $serializer->serialize($input); + $this->assertFalse($result); + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testNull($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + $input = null; + $result = $serializer->serialize($input); + $this->assertNull($result); + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testRecursionMaxDepth($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + $input = array(); + $input[] = &$input; + $result = $serializer->serialize($input, 3); + $this->assertEquals(array(array(array('Array of length 1'))), $result); + + $result = $serializer->serialize([], 3); + $this->assertEquals([], $result); + + $result = $serializer->serialize([[]], 3); + $this->assertEquals([[]], $result); + + $result = $serializer->serialize([[[]]], 3); + $this->assertEquals([[[]]], $result); + + $result = $serializer->serialize([[[[]]]], 3); + $this->assertEquals([[['Array of length 0']]], $result); + } + + public function testRecursionMaxDepthForObject() + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + $serializer->setAllObjectSerialize(true); + + $result = $serializer->serialize((object)['key' => (object)['key' => 12345]], 3); + $this->assertEquals(['key' => ['key' => 12345]], $result); + + $result = $serializer->serialize((object)['key' => (object)['key' => (object)['key' => 12345]]], 3); + $this->assertEquals(['key' => ['key' => ['key' => 12345]]], $result); + + $result = $serializer->serialize( + (object)['key' => (object)['key' => (object)['key' => (object)['key' => 12345]]]], 3 + ); + $this->assertEquals(['key' => ['key' => ['key' => 'Object stdClass']]], $result); + } + + public function testObjectInArray() + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + $input = array('foo' => new \Raven\Tests\SerializerTestObject()); + $result = $serializer->serialize($input); + $this->assertEquals(array('foo' => 'Object Raven\\Tests\\SerializerTestObject'), $result); + } + + public function testObjectInArraySerializeAll() + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + $serializer->setAllObjectSerialize(true); + $input = array('foo' => new \Raven\Tests\SerializerTestObject()); + $result = $serializer->serialize($input); + $this->assertEquals(array('foo' => array('key' => 'value')), $result); + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testBrokenEncoding($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + foreach (array('7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90') as $key) { + $input = pack('H*', $key); + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + if (function_exists('mb_detect_encoding')) { + $this->assertContains(mb_detect_encoding($result), array('ASCII', 'UTF-8')); + } + } + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testLongString($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + for ($i = 0; $i < 100; $i++) { + foreach (array(100, 1000, 1010, 1024, 1050, 1100, 10000) as $length) { + $input = ''; + for ($i = 0; $i < $length; $i++) { + $input .= chr(mt_rand(0, 255)); + } + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(1024, strlen($result)); + } + } + } + + /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam + */ + public function testSerializeValueResource($serialize_all_objects) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + if ($serialize_all_objects) { + $serializer->setAllObjectSerialize(true); + } + $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); + $fo = fopen($filename, 'wb'); + + $result = $serializer->serialize($fo); + $this->assertInternalType('string', $result); + $this->assertEquals('Resource stream', $result); + } + + public function testSetAllObjectSerialize() + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + $serializer->setAllObjectSerialize(true); + $this->assertTrue($serializer->getAllObjectSerialize()); + $serializer->setAllObjectSerialize(false); + $this->assertFalse($serializer->getAllObjectSerialize()); + } +} diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index c2e4632fc..cfd51b4eb 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -1,149 +1,46 @@ serialize($input); - $this->assertEquals(array('1', '2', '3'), $result); - } - - public function testStdClassAreArrays() - { - $serializer = new \Raven\Serializer(); - $input = new \stdClass(); - $input->foo = 'BAR'; - $result = $serializer->serialize($input); - $this->assertEquals(array('foo' => 'BAR'), $result); - } - - public function testObjectsAreStrings() - { - $serializer = new \Raven\Serializer(); - $input = new \Raven\Tests\SerializerTestObject(); - $result = $serializer->serialize($input); - $this->assertEquals('Object Raven\Tests\SerializerTestObject', $result); - } - - public function testIntsAreInts() - { - $serializer = new \Raven\Serializer(); - $input = 1; - $result = $serializer->serialize($input); - $this->assertInternalType('integer', $result); - $this->assertEquals(1, $result); - } - - public function testFloats() - { - $serializer = new \Raven\Serializer(); - $input = 1.5; - $result = $serializer->serialize($input); - $this->assertInternalType('double', $result); - $this->assertEquals(1.5, $result); - } - - public function testBooleans() - { - $serializer = new \Raven\Serializer(); - $input = true; - $result = $serializer->serialize($input); - $this->assertTrue($result); - - $input = false; - $result = $serializer->serialize($input); - $this->assertFalse($result); - } - - public function testNull() - { - $serializer = new \Raven\Serializer(); - $input = null; - $result = $serializer->serialize($input); - $this->assertNull($result); - } - - public function testRecursionMaxDepth() - { - $serializer = new \Raven\Serializer(); - $input = array(); - $input[] = &$input; - $result = $serializer->serialize($input, 3); - $this->assertEquals(array(array(array('Array of length 1'))), $result); - } - - public function testObjectInArray() + /** + * @return string + */ + protected static function get_test_class() { - $serializer = new \Raven\Serializer(); - $input = array('foo' => new \Raven\Serializer()); - $result = $serializer->serialize($input); - $this->assertEquals(array('foo' => 'Object Raven\\Serializer'), $result); + return '\\Raven\\Serializer'; } /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam * @covers \Raven\Serializer::serializeString */ - public function testBrokenEncoding() + public function testBrokenEncoding($serialize_all_objects) { - $serializer = new \Raven\Serializer(); - foreach (array('7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90') as $key) { - $input = pack('H*', $key); - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - if (function_exists('mb_detect_encoding')) { - $this->assertContains(mb_detect_encoding($result), array('ASCII', 'UTF-8')); - } - } + parent::testBrokenEncoding($serialize_all_objects); } /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam * @covers \Raven\Serializer::serializeString */ - public function testLongString() + public function testLongString($serialize_all_objects) { - $serializer = new \Raven\Serializer(); - for ($i = 0; $i < 100; $i++) { - foreach (array(100, 1000, 1010, 1024, 1050, 1100, 10000) as $length) { - $input = ''; - for ($i = 0; $i < $length; $i++) { - $input .= chr(mt_rand(0, 255)); - } - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(1024, strlen($result)); - } - } + parent::testLongString($serialize_all_objects); } /** + * @param boolean $serialize_all_objects + * @dataProvider dataGetBaseParam * @covers \Raven\Serializer::serializeValue */ - public function testSerializeValueResource() + public function testSerializeValueResource($serialize_all_objects) { - $serializer = new \Raven\Serializer(); - $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); - $fo = fopen($filename, 'wb'); - - $result = $serializer->serialize($fo); - $this->assertInternalType('string', $result); - $this->assertEquals('Resource stream', $result); + parent::testSerializeValueResource($serialize_all_objects); } } From 0ea8c31694309497a5e17d9dd3c681f03ad1cd6c Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Wed, 19 Apr 2017 20:22:22 +0300 Subject: [PATCH 0287/1161] Suppress object serializing via method in \Raven\Client Suppress cyclic reference --- lib/Raven/Client.php | 9 ++- lib/Raven/Serializer.php | 48 +++++++++++--- tests/ClientTest.php | 31 +++++++++ tests/SerializerAbstractTest.php | 107 +++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 10 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index d3c7b5ba2..ff2035faa 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -203,8 +203,7 @@ public function __construct($options_or_dsn = null, $options = array()) $this->serializer = new \Raven\Serializer($this->mb_detect_order); $this->reprSerializer = new \Raven\ReprSerializer($this->mb_detect_order); if (\Raven\Util::get($options, 'serialize_all_object', false)) { - $this->serializer->setAllObjectSerialize(true); - $this->reprSerializer->setAllObjectSerialize(true); + $this->setAllObjectSerialize(true); } if ($this->curl_method == 'async') { @@ -1447,4 +1446,10 @@ public function close_curl_resource() $this->_curl_instance = null; } } + + public function setAllObjectSerialize($value) + { + $this->serializer->setAllObjectSerialize($value); + $this->reprSerializer->setAllObjectSerialize($value); + } } diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index e4b24101a..db3c3f830 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -73,20 +73,52 @@ public function __construct($mb_detect_order = null) */ public function serialize($value, $max_depth = 3, $_depth = 0) { - $className = is_object($value) ? get_class($value) : null; - $toArray = (is_array($value) or ($className == 'stdClass') or - (is_object($value) and $this->_all_object_serialize)); - if ($toArray && $_depth < $max_depth) { - $new = array(); - foreach ($value as $k => $v) { - $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); + if ($_depth < $max_depth) { + if (is_array($value)) { + $new = array(); + foreach ($value as $k => $v) { + $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); + } + + return $new; } - return $new; + if (is_object($value)) { + if ((get_class($value) == 'stdClass') or $this->_all_object_serialize) { + return $this->serializeObject($value, $max_depth, $_depth, []); + } + } } return $this->serializeValue($value); } + /** + * @param object $object + * @param integer $max_depth + * @param integer $_depth + * @param string[] $hashes + * + * @return array|string + */ + public function serializeObject($object, $max_depth = 3, $_depth = 0, $hashes = []) + { + if (($_depth >= $max_depth) or in_array(spl_object_hash($object), $hashes)) { + return $this->serializeValue($object); + } + $hashes[] = spl_object_hash($object); + $return = []; + foreach ($object as $key => &$value) { + if (is_object($value)) { + $new_value = $this->serializeObject($value, $max_depth, $_depth + 1, $hashes); + } else { + $new_value = $this->serialize($value, $max_depth, $_depth + 1); + } + $return[$key] = $new_value; + } + + return $return; + } + protected function serializeString($value) { $value = (string) $value; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 3c3f7ea48..c3b28c00b 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,6 +10,9 @@ namespace Raven\Tests; +use Raven\Client; +use Raven\Serializer; + function simple_function($a = null, $b = null, $c = null) { assert(0); @@ -2356,4 +2359,32 @@ public function testSampleRatePrc() $this->fail('sample_rate=0.5 can not produce fails and successes at the same time'); } + + /** + * @covers \Raven\Client::setAllObjectSerialize + */ + public function testSetAllObjectSerialize() + { + $client = new \Raven\Client; + + $ref1 = new \ReflectionProperty($client, 'serializer'); + $ref1->setAccessible(true); + $ref2 = new \ReflectionProperty($client, 'reprSerializer'); + $ref2->setAccessible(true); + + /** + * @var \Raven\Serializer $o1 + * @var \Raven\Serializer $o2 + */ + $o1 = $ref1->getValue($client); + $o2 = $ref2->getValue($client); + + $client->setAllObjectSerialize(true); + $this->assertTrue($o1->getAllObjectSerialize()); + $this->assertTrue($o2->getAllObjectSerialize()); + + $client->setAllObjectSerialize(false); + $this->assertFalse($o1->getAllObjectSerialize()); + $this->assertFalse($o2->getAllObjectSerialize()); + } } diff --git a/tests/SerializerAbstractTest.php b/tests/SerializerAbstractTest.php index 20aaf2332..34970ab1c 100644 --- a/tests/SerializerAbstractTest.php +++ b/tests/SerializerAbstractTest.php @@ -11,6 +11,12 @@ namespace Raven\Tests; +/** + * Class SerializerTestObject + * + * @package Raven\Tests + * @property mixed $keys + */ class SerializerTestObject { private $foo = 'bar'; @@ -196,6 +202,107 @@ public function testRecursionMaxDepth($serialize_all_objects) $this->assertEquals([[['Array of length 0']]], $result); } + public function dataRecursionInObjects() + { + $data = []; + // case 1 + $object = new SerializerTestObject; + $object->key = $object; + $data[] = [ + 'object' => $object, + 'result_serialize' => ['key' => 'Object Raven\Tests\SerializerTestObject'], + ]; + + // case 2 + $object = new SerializerTestObject; + $object2 = new SerializerTestObject; + $object2->key = $object; + $object->key = $object2; + $data[] = [ + 'object' => $object, + 'result_serialize' => ['key' => ['key' => 'Object Raven\Tests\SerializerTestObject']], + ]; + + // case 3 + $object = new SerializerTestObject; + $object2 = new SerializerTestObject; + $object2->key = 'foobar'; + $object->key = $object2; + $data[] = [ + 'object' => $object, + 'result_serialize' => ['key' => ['key' => 'foobar']], + ]; + + // case 4 + $object3 = new SerializerTestObject; + $object3->key = 'foobar'; + $object2 = new SerializerTestObject; + $object2->key = $object3; + $object = new SerializerTestObject; + $object->key = $object2; + $data[] = [ + 'object' => $object, + 'result_serialize' => ['key' => ['key' => ['key' => 'foobar']]], + ]; + + // case 5 + $object4 = new SerializerTestObject; + $object4->key = 'foobar'; + $object3 = new SerializerTestObject; + $object3->key = $object4; + $object2 = new SerializerTestObject; + $object2->key = $object3; + $object = new SerializerTestObject; + $object->key = $object2; + $data[] = [ + 'object' => $object, + 'result_serialize' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject']]], + ]; + + // case 6 + $object3 = new SerializerTestObject; + $object2 = new SerializerTestObject; + $object2->key = $object3; + $object2->keys = 'keys'; + $object = new SerializerTestObject; + $object->key = $object2; + $object3->key = $object2; + $data[] = [ + 'object' => $object, + 'result_serialize' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject'], + 'keys' => 'keys']], + ]; + + // + foreach ($data as &$datum) { + if (!isset($datum['result_serialize_object'])) { + $datum['result_serialize_object'] = $datum['result_serialize']; + } + } + + return $data; + } + + /** + * @param object $object + * @param array $result_serialize + * @param array $result_serialize_object + * + * @dataProvider dataRecursionInObjects + */ + public function testRecursionInObjects($object, $result_serialize, $result_serialize_object) + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer **/ + $serializer = new $class_name(); + $serializer->setAllObjectSerialize(true); + + $result1 = $serializer->serialize($object, 3); + $result2 = $serializer->serializeObject($object, 3); + $this->assertEquals($result_serialize, $result1); + $this->assertEquals($result_serialize_object, $result2); + } + public function testRecursionMaxDepthForObject() { $class_name = static::get_test_class(); From 904bb7f61b7c53e9c6fa56e4b629af9eed658d56 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 21 Apr 2017 10:47:01 -0700 Subject: [PATCH 0288/1161] Expand laravel documentation --- docs/integrations/laravel.rst | 77 +++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 91c62efc5..da152473e 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -197,8 +197,68 @@ Create the Sentry configuration file (``config/sentry.php``): // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), ); -Available Settings ------------------- +Testing with Artisan +-------------------- + +You can test your configuration using the provided ``artisan`` command: + +```bash +$ php artisan sentry:test +[sentry] Client configuration: +-> server: https://app.getsentry.com/api/3235/store/ +-> project: 3235 +-> public_key: e9ebbd88548a441288393c457ec90441 +-> secret_key: 399aaee02d454e2ca91351f29bdc3a07 +[sentry] Generating test event +[sentry] Sending test event with ID: 5256614438cf4e0798dc9688e9545d94 +``` + +Adding Context +-------------- + +The mechanism to add context will vary depending on which version of Laravel you're using, but the general approach is the same. Find a good entry point to your application in which the context you want to add is available, ideally early in the process. + +In the following example, we'll use a middleware: + +```php +namespace App\Http\Middleware; + +use Closure; + +class SentryContext +{ + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * + * @return mixed + */ + public function handle($request, Closure $next) + { + if (app()->bound('sentry')) { + /** @var \Raven_Client $sentry */ + $sentry = app('sentry'); + + // Add user context + if (auth()->check()) { + $sentry->user_context([...]); + } else { + $sentry->user_context(['id' => null]); + } + + // Add tags context + $sentry->tags_context([...]); + } + + return $next($request); + } +} +``` + +Configuration +------------- The following settings are available for the client: @@ -221,7 +281,7 @@ The following settings are available for the client: .. describe:: breadcrumbs.sql_bindings - Capturing bindings on SQL queries. + Capture bindings on SQL queries. Defaults to ``true``. @@ -229,3 +289,14 @@ The following settings are available for the client: 'breadcrumbs.sql_bindings' => false, + +.. describe:: user_context + + Capture user_context automatically. + + Defaults to ``true``. + + .. code-block:: php + + 'user_context' => false, + From 9251b0ddf066b8c41c55409d117a6766aa44f71b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 21 Apr 2017 10:47:32 -0700 Subject: [PATCH 0289/1161] Correct rest syntax --- docs/integrations/laravel.rst | 82 +++++++++++++++++------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index da152473e..7fa5b1274 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -202,16 +202,16 @@ Testing with Artisan You can test your configuration using the provided ``artisan`` command: -```bash -$ php artisan sentry:test -[sentry] Client configuration: --> server: https://app.getsentry.com/api/3235/store/ --> project: 3235 --> public_key: e9ebbd88548a441288393c457ec90441 --> secret_key: 399aaee02d454e2ca91351f29bdc3a07 -[sentry] Generating test event -[sentry] Sending test event with ID: 5256614438cf4e0798dc9688e9545d94 -``` +.. code-block:: bash + + $ php artisan sentry:test + [sentry] Client configuration: + -> server: https://app.getsentry.com/api/3235/store/ + -> project: 3235 + -> public_key: e9ebbd88548a441288393c457ec90441 + -> secret_key: 399aaee02d454e2ca91351f29bdc3a07 + [sentry] Generating test event + [sentry] Sending test event with ID: 5256614438cf4e0798dc9688e9545d94 Adding Context -------------- @@ -220,42 +220,42 @@ The mechanism to add context will vary depending on which version of Laravel you In the following example, we'll use a middleware: -```php -namespace App\Http\Middleware; - -use Closure; - -class SentryContext -{ - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * - * @return mixed - */ - public function handle($request, Closure $next) +.. code-block:: php + + namespace App\Http\Middleware; + + use Closure; + + class SentryContext { - if (app()->bound('sentry')) { - /** @var \Raven_Client $sentry */ - $sentry = app('sentry'); - - // Add user context - if (auth()->check()) { - $sentry->user_context([...]); - } else { - $sentry->user_context(['id' => null]); + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * + * @return mixed + */ + public function handle($request, Closure $next) + { + if (app()->bound('sentry')) { + /** @var \Raven_Client $sentry */ + $sentry = app('sentry'); + + // Add user context + if (auth()->check()) { + $sentry->user_context([...]); + } else { + $sentry->user_context(['id' => null]); + } + + // Add tags context + $sentry->tags_context([...]); } - // Add tags context - $sentry->tags_context([...]); + return $next($request); } - - return $next($request); } -} -``` Configuration ------------- From 72a43fbb0924df897a9bb433b460290e23ac2206 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 19 Apr 2017 00:39:27 +0200 Subject: [PATCH 0290/1161] Refactor and improve the class that generates a stacktrace for an event --- bin/sentry | 5 +- lib/Raven/Client.php | 30 +- lib/Raven/Stacktrace.php | 512 +++++++++++------- tests/ClientTest.php | 4 +- .../Fixtures/backtraces/anonymous_frame.json | 15 + tests/Fixtures/backtraces/exception.json | 18 + .../code/Latin1File.php} | 0 tests/Fixtures/code/LongFile.php | 13 + tests/Fixtures/code/ShortFile.php | 4 + tests/Fixtures/frames/eval.json | 5 + tests/Fixtures/frames/function.json | 6 + tests/Fixtures/frames/runtime_created.json | 5 + .../SanitizeStacktraceProcessorTest.php | 23 +- tests/StacktraceTest.php | 400 ++++++++------ 14 files changed, 622 insertions(+), 418 deletions(-) create mode 100644 tests/Fixtures/backtraces/anonymous_frame.json create mode 100644 tests/Fixtures/backtraces/exception.json rename tests/{resources/captureExceptionInLatin1File.php => Fixtures/code/Latin1File.php} (100%) create mode 100644 tests/Fixtures/code/LongFile.php create mode 100644 tests/Fixtures/code/ShortFile.php create mode 100644 tests/Fixtures/frames/eval.json create mode 100644 tests/Fixtures/frames/function.json create mode 100644 tests/Fixtures/frames/runtime_created.json diff --git a/bin/sentry b/bin/sentry index e55634d1b..d5cf11209 100755 --- a/bin/sentry +++ b/bin/sentry @@ -4,10 +4,7 @@ // Maximize error reporting error_reporting(E_ALL | E_STRICT); -// TODO: if we could get rid of this and have composer figure things out it'd make it -// a bit more sane -require(__DIR__ . '/../lib/Raven/Autoloader.php'); -\Raven\Autoloader::register(); +require(__DIR__ . '/../vendor/autoload.php'); function raven_cli_test($command, $args) { diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 9ebd2bc06..52d759330 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -235,6 +235,26 @@ public function close_all_children_link() $this->processors = array(); } + /** + * Gets the representation serialier. + * + * @return ReprSerializer + */ + public function getReprSerializer() + { + return $this->reprSerializer; + } + + /** + * Gets the serializer. + * + * @return Serializer + */ + public function getSerializer() + { + return $this->serializer; + } + /** * Installs any available automated hooks (such as error_reporting). */ @@ -596,10 +616,7 @@ public function captureException($exception, $data = null, $logger = null, $vars } $exc_data['stacktrace'] = array( - 'frames' => \Raven\Stacktrace::get_stack_info( - $trace, $this->trace, $vars, $this->message_limit, $this->prefixes, - $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer - ), + 'frames' => Stacktrace::fromBacktrace($this, $exception->getTrace(), $exception->getFile(), $exception->getLine())->getFrames(), ); $exceptions[] = $exc_data; @@ -858,10 +875,7 @@ public function capture($data, $stack = null, $vars = null) if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = array( - 'frames' => \Raven\Stacktrace::get_stack_info( - $stack, $this->trace, $vars, $this->message_limit, $this->prefixes, - $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer - ), + 'frames' => Stacktrace::fromBacktrace($this, $stack, isset($stack['file']) ? $stack['file'] : __FILE__, isset($stack['line']) ? $stack['line'] : __LINE__)->getFrames(), ); } } diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index cb436dd46..944c95648 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -1,166 +1,344 @@ */ -class Stacktrace +class Stacktrace implements \JsonSerializable { - public static $statements = array( + /** + * This constant defines the default number of lines of code to include. + */ + const CONTEXT_NUM_LINES = 5; + + /** + * @var Client The Raven client + */ + protected $client; + + /** + * @var Serializer The serializer + */ + protected $serializer; + + /** + * @var ReprSerializer The representation serializer + */ + protected $reprSerializer; + + /** + * @var array The frames that compose the stacktrace + */ + protected $frames = []; + + /** + * @var array The list of functions to import a file + */ + protected static $importStatements = [ 'include', 'include_once', 'require', 'require_once', - ); - - public static function get_stack_info($frames, - $trace = false, - $errcontext = null, - $frame_var_limit = \Raven\Client::MESSAGE_LIMIT, - $strip_prefixes = null, - $app_path = null, - $excluded_app_paths = null, - \Raven\Serializer $serializer = null, - \Raven\ReprSerializer $reprSerializer = null) + ]; + + /** + * Constructor. + * + * @param Client $client The Raven client + */ + public function __construct(Client $client) { - $serializer = $serializer ?: new \Raven\Serializer(); - $reprSerializer = $reprSerializer ?: new \Raven\ReprSerializer(); - - /** - * PHP stores calls in the stacktrace, rather than executing context. Sentry - * wants to know "when Im calling this code, where am I", and PHP says "I'm - * calling this function" not "I'm in this function". Due to that, we shift - * the context for a frame up one, meaning the variables (which are the calling - * args) come from the previous frame. - */ - $result = array(); - for ($i = 0; $i < count($frames); $i++) { - $frame = isset($frames[$i]) ? $frames[$i] : null; - $nextframe = isset($frames[$i + 1]) ? $frames[$i + 1] : null; - - if (!array_key_exists('file', $frame)) { - if (!empty($frame['class'])) { - $context['line'] = sprintf('%s%s%s', - $frame['class'], $frame['type'], $frame['function']); - } else { - $context['line'] = sprintf('%s(anonymous)', $frame['function']); + $this->client = $client; + $this->serializer = $client->getSerializer(); + $this->reprSerializer = $client->getReprSerializer(); + } + + /** + * Creates a new instance of this class from the given backtrace. + * + * @param Client $client The Raven client + * @param array $backtrace The backtrace + * @param string $file The file that originated the backtrace + * @param string $line The line at which the backtrace originated + * + * @return static + */ + public static function fromBacktrace(Client $client, array $backtrace, $file, $line) + { + $stacktrace = new static($client); + + foreach ($backtrace as $frame) { + $stacktrace->addFrame($file, $line, $frame); + + $file = isset($frame['file']) ? $frame['file'] : '[internal]'; + $line = isset($frame['line']) ? $frame['line'] : 0; + } + + // Add a final stackframe for the first method ever of this stacktrace + $stacktrace->addFrame($file, $line, []); + + return $stacktrace; + } + + /** + * Gets the stacktrace frames. + * + * @return array[] + */ + public function getFrames() + { + return $this->frames; + } + + /** + * Adds a new frame to the stacktrace. + * + * @param string $file The file where the frame originated + * @param int $line The line at which the frame originated + * @param array $backtraceFrame The data of the frame to add + */ + public function addFrame($file, $line, array $backtraceFrame) + { + // The $file argument can be any of these formats: + // + // () : eval()'d code + // () : runtime-created function + if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d code|runtime-created function)$/', $file, $matches)) { + $file = $matches[1]; + $line = $matches[2]; + } + + $frame = array_merge( + [ + 'filename' => $this->stripPrefixFromFilePath($file), + 'lineno' => (int) $line, + 'function' => isset($backtraceFrame['class']) ? sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']) : (isset($backtraceFrame['function']) ? $backtraceFrame['function'] : null), + ], + self::getSourceCodeExcerpt($file, $line, self::CONTEXT_NUM_LINES) + ); + + if (null !== $this->client->getAppPath()) { + $excludedAppPaths = $this->client->getExcludedAppPaths(); + $absoluteFilePath = @realpath($file) ?: $file; + $isApplicationFile = 0 === strpos($absoluteFilePath, $this->client->getAppPath()); + + if ($isApplicationFile && !empty($excludedAppPaths)) { + foreach ($excludedAppPaths as $path) { + if (0 === strpos($absoluteFilePath, $path)) { + $frame['in_app'] = $isApplicationFile; + + break; + } } - $abs_path = ''; - $context['prefix'] = ''; - $context['suffix'] = ''; - $context['filename'] = $filename = '[Anonymous function]'; - $context['lineno'] = 0; - } else { - $context = self::read_source_file($frame['file'], $frame['line']); - $abs_path = $frame['file']; } + } - // strip base path if present - $context['filename'] = self::strip_prefixes($context['filename'], $strip_prefixes); - if ($i === 0 && isset($errcontext)) { - // If we've been given an error context that can be used as the vars for the first frame. - $vars = $errcontext; - } else { - if ($trace) { - $vars = self::get_frame_context($nextframe, $frame_var_limit); + $frameArguments = self::getFrameArguments($backtraceFrame); + + if (!empty($frameArguments)) { + foreach ($frameArguments as $argumentName => $argumentValue) { + $argumentValue = $this->reprSerializer->serialize($argumentValue); + + if (is_string($argumentValue) || is_numeric($argumentValue)) { + $frameArguments[(string) $argumentName] = substr($argumentValue, 0, Client::MESSAGE_LIMIT); } else { - $vars = array(); + $frameArguments[(string) $argumentName] = $argumentValue; } } - $data = array( - 'filename' => $context['filename'], - 'lineno' => (int) $context['lineno'], - 'function' => isset($nextframe['function']) ? $nextframe['function'] : null, - 'pre_context' => $serializer->serialize($context['prefix']), - 'context_line' => $serializer->serialize($context['line']), - 'post_context' => $serializer->serialize($context['suffix']), - ); - - // detect in_app based on app path - if ($app_path) { - $norm_abs_path = @realpath($abs_path) ?: $abs_path; - $in_app = (bool)(substr($norm_abs_path, 0, strlen($app_path)) === $app_path); - if ($in_app && $excluded_app_paths) { - foreach ($excluded_app_paths as $path) { - if (substr($norm_abs_path, 0, strlen($path)) === $path) { - $in_app = false; - break; - } - } + $frame['vars'] = $frameArguments; + } + + array_unshift($this->frames, $frame); + } + + /** + * Removes the frame at the given index from the stacktrace. + * + * @param int $index The index of the frame + * + * @throws \OutOfBoundsException If the index is out of range + */ + public function removeFrame($index) + { + if (!isset($this->frames[$index])) { + throw new \OutOfBoundsException('Invalid frame index to remove.'); + } + + array_splice($this->frames, $index, 1); + } + + /** + * Gets the stacktrace frames (this is the same as calling the getFrames + * method). + * + * @return array + */ + public function toArray() + { + return $this->frames; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Gets an excerpt of the source code around a given line. + * + * @param string $path The file path + * @param int $lineNumber The line to centre about + * @param int $linesNum The number of lines to fetch + * + * @return array + */ + protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) + { + $frame = [ + 'pre_context' => [], + 'context_line' => '', + 'post_context' => [], + ]; + + if (!is_file($path) || !is_readable($path)) { + return []; + } + + $target = max(0, ($lineNumber - ($linesNum + 1))); + $currentLineNumber = $target + 1; + + try { + $file = new \SplFileObject($path); + $file->seek($target); + + while (!$file->eof()) { + $line = rtrim($file->current(), "\r\n"); + + if ($currentLineNumber == $lineNumber) { + $frame['context_line'] = $line; + } elseif ($currentLineNumber < $lineNumber) { + $frame['pre_context'][] = $line; + } elseif ($currentLineNumber > $lineNumber) { + $frame['post_context'][] = $line; } - $data['in_app'] = $in_app; - } - // dont set this as an empty array as PHP will treat it as a numeric array - // instead of a mapping which goes against the defined Sentry spec - if (!empty($vars)) { - $cleanVars = array(); - foreach ($vars as $key => $value) { - $value = $reprSerializer->serialize($value); - if (is_string($value) || is_numeric($value)) { - $cleanVars[(string)$key] = substr($value, 0, $frame_var_limit); - } else { - $cleanVars[(string)$key] = $value; - } + ++$currentLineNumber; + + if ($currentLineNumber > $lineNumber + $linesNum) { + break; } - $data['vars'] = $cleanVars; + + $file->next(); } + } catch (\Exception $ex) { + } + + $frame['pre_context'] = $this->serializer->serialize($frame['pre_context']); + $frame['context_line'] = $this->serializer->serialize($frame['context_line']); + $frame['post_context'] = $this->serializer->serialize($frame['post_context']); + + return $frame; + } - $result[] = $data; + /** + * Removes from the given file path the specified prefixes. + * + * @param string $filePath The path to the file + * + * @return string + */ + protected function stripPrefixFromFilePath($filePath) + { + foreach ($this->client->getPrefixes() as $prefix) { + if (0 === strpos($filePath, $prefix)) { + return substr($filePath, strlen($prefix)); + } } - return array_reverse($result); + return $filePath; } - public static function get_default_context($frame, $frame_arg_limit = \Raven\Client::MESSAGE_LIMIT) + /** + * Gets the values of the arguments of the given stackframe. + * + * @param array $frame The frame from where arguments are retrieved + * @param int $maxValueLength The maximum string length to get from the arguments values + * + * @return array + */ + protected static function getFrameArgumentsValues($frame, $maxValueLength = Client::MESSAGE_LIMIT) { if (!isset($frame['args'])) { - return array(); + return []; } - $i = 1; - $args = array(); - foreach ($frame['args'] as $arg) { - $args['param'.$i] = self::serialize_argument($arg, $frame_arg_limit); - $i++; + $result = []; + + for ($i = 0; $i < count($frame['args']); ++$i) { + $result['param' . ($i + 1)] = self::serializeArgument($frame['args'][$i], $maxValueLength); } - return $args; + + return $result; } - public static function get_frame_context($frame, $frame_arg_limit = \Raven\Client::MESSAGE_LIMIT) + /** + * Gets the arguments of the given stackframe. + * + * @param array $frame The frame from where arguments are retrieved + * @param int $maxValueLength The maximum string length to get from the arguments values + * + * @return array + */ + public static function getFrameArguments($frame, $maxValueLength = Client::MESSAGE_LIMIT) { if (!isset($frame['args'])) { - return array(); + return []; } // The Reflection API seems more appropriate if we associate it with the frame // where the function is actually called (since we're treating them as function context) if (!isset($frame['function'])) { - return self::get_default_context($frame, $frame_arg_limit); + return self::getFrameArgumentsValues($frame, $maxValueLength); } - if (strpos($frame['function'], '__lambda_func') !== false) { - return self::get_default_context($frame, $frame_arg_limit); + + if (false !== strpos($frame['function'], '__lambda_func')) { + return self::getFrameArgumentsValues($frame, $maxValueLength); } - if (isset($frame['class']) && $frame['class'] == 'Closure') { - return self::get_default_context($frame, $frame_arg_limit); + + if (false !== strpos($frame['function'], '{closure}')) { + return self::getFrameArgumentsValues($frame, $maxValueLength); } - if (strpos($frame['function'], '{closure}') !== false) { - return self::get_default_context($frame, $frame_arg_limit); + + if (isset($frame['class']) && 'Closure' === $frame['class']) { + return self::getFrameArgumentsValues($frame, $maxValueLength); } - if (in_array($frame['function'], self::$statements)) { + + if (in_array($frame['function'], static::$importStatements, true)) { if (empty($frame['args'])) { - // No arguments - return array(); - } else { - // Sanitize the file path - return array( - 'param1' => self::serialize_argument($frame['args'][0], $frame_arg_limit), - ); + return []; } + + return [ + 'param1' => self::serializeArgument($frame['args'][0], $maxValueLength), + ]; } + try { if (isset($frame['class'])) { if (method_exists($frame['class'], $frame['function'])) { @@ -173,117 +351,45 @@ public static function get_frame_context($frame, $frame_arg_limit = \Raven\Clien } else { $reflection = new \ReflectionFunction($frame['function']); } - } catch (\ReflectionException $e) { - return self::get_default_context($frame, $frame_arg_limit); + } catch (\ReflectionException $ex) { + return self::getFrameArgumentsValues($frame, $maxValueLength); } $params = $reflection->getParameters(); + $args = []; + + foreach ($frame['args'] as $index => $arg) { + $arg = self::serializeArgument($arg, $maxValueLength); - $args = array(); - foreach ($frame['args'] as $i => $arg) { - $arg = self::serialize_argument($arg, $frame_arg_limit); - if (isset($params[$i])) { + if (isset($params[$index])) { // Assign the argument by the parameter name - $args[$params[$i]->name] = $arg; + $args[$params[$index]->name] = $arg; } else { - $args['param'.$i] = $arg; + $args['param' . $index] = $arg; } } return $args; } - private static function serialize_argument($arg, $frame_arg_limit) + protected static function serializeArgument($arg, $maxValueLength) { if (is_array($arg)) { - $_arg = array(); + $result = []; + foreach ($arg as $key => $value) { if (is_string($value) || is_numeric($value)) { - $_arg[$key] = substr($value, 0, $frame_arg_limit); + $result[$key] = substr($value, 0, $maxValueLength); } else { - $_arg[$key] = $value; + $result[$key] = $value; } } - return $_arg; + + return $result; } elseif (is_string($arg) || is_numeric($arg)) { - return substr($arg, 0, $frame_arg_limit); + return substr($arg, 0, $maxValueLength); } else { return $arg; } } - - private static function strip_prefixes($filename, $prefixes) - { - if ($prefixes === null) { - return $filename; - } - foreach ($prefixes as $prefix) { - if (substr($filename, 0, strlen($prefix)) === $prefix) { - return substr($filename, strlen($prefix)); - } - } - return $filename; - } - - private static function read_source_file($filename, $lineno, $context_lines = 5) - { - $frame = array( - 'prefix' => array(), - 'line' => '', - 'suffix' => array(), - 'filename' => $filename, - 'lineno' => $lineno, - ); - - if ($filename === null || $lineno === null) { - return $frame; - } - - // Code which is eval'ed have a modified filename.. Extract the - // correct filename + linenumber from the string. - $matches = array(); - $matched = preg_match("/^(.*?)\\((\\d+)\\) : eval\\(\\)'d code$/", - $filename, $matches); - if ($matched) { - $frame['filename'] = $filename = $matches[1]; - $frame['lineno'] = $lineno = $matches[2]; - } - - // In the case of an anonymous function, the filename is sent as: - // "() : runtime-created function" - // Extract the correct filename + linenumber from the string. - $matches = array(); - $matched = preg_match("/^(.*?)\\((\\d+)\\) : runtime-created function$/", - $filename, $matches); - if ($matched) { - $frame['filename'] = $filename = $matches[1]; - $frame['lineno'] = $lineno = $matches[2]; - } - - try { - $file = new \SplFileObject($filename); - $target = max(0, ($lineno - ($context_lines + 1))); - $file->seek($target); - $cur_lineno = $target+1; - while (!$file->eof()) { - $line = rtrim($file->current(), "\r\n"); - if ($cur_lineno == $lineno) { - $frame['line'] = $line; - } elseif ($cur_lineno < $lineno) { - $frame['prefix'][] = $line; - } elseif ($cur_lineno > $lineno) { - $frame['suffix'][] = $line; - } - $cur_lineno++; - if ($cur_lineno > $lineno + $context_lines) { - break; - } - $file->next(); - } - } catch (\RuntimeException $exc) { - return $frame; - } - - return $frame; - } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f3a051cb5..0d31ca54e 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -576,7 +576,7 @@ public function testCaptureExceptionSetsInterfaces() $frames = $exc['values'][0]['stacktrace']['frames']; $frame = $frames[count($frames) - 1]; $this->assertTrue($frame['lineno'] > 0); - $this->assertEquals('create_exception', $frame['function']); + $this->assertEquals('Raven\Tests\Raven_Tests_ClientTest::create_exception', $frame['function']); $this->assertFalse(isset($frame['vars'])); $this->assertEquals(' throw new \Exception(\'Foo bar\');', $frame['context_line']); $this->assertFalse(empty($frame['pre_context'])); @@ -1005,7 +1005,7 @@ public function testCaptureExceptionInLatin1File() $client = new Dummy_Raven_Client($options); - require_once(__DIR__.'/resources/captureExceptionInLatin1File.php'); + require_once(__DIR__ . '/Fixtures/code/Latin1File.php'); $events = $client->getSentEvents(); $event = array_pop($events); diff --git a/tests/Fixtures/backtraces/anonymous_frame.json b/tests/Fixtures/backtraces/anonymous_frame.json new file mode 100644 index 000000000..84eaa9898 --- /dev/null +++ b/tests/Fixtures/backtraces/anonymous_frame.json @@ -0,0 +1,15 @@ +{ + "file": "path/to/file", + "line": 12, + "backtrace": [ + { + "function": "triggerError", + "class": "TestClass" + }, + { + "file": "path/to/file", + "line": 7, + "function": "call_user_func" + } + ] +} \ No newline at end of file diff --git a/tests/Fixtures/backtraces/exception.json b/tests/Fixtures/backtraces/exception.json new file mode 100644 index 000000000..543157330 --- /dev/null +++ b/tests/Fixtures/backtraces/exception.json @@ -0,0 +1,18 @@ +{ + "file": "path/to/file", + "line": 12, + "backtrace": [ + { + "file": "path/to/file", + "function": "triggerError", + "line": 7, + "class": "TestClass" + }, + { + "file": "path/to/file", + "line": 16, + "class": "TestClass", + "function": "crashyFunction" + } + ] +} \ No newline at end of file diff --git a/tests/resources/captureExceptionInLatin1File.php b/tests/Fixtures/code/Latin1File.php similarity index 100% rename from tests/resources/captureExceptionInLatin1File.php rename to tests/Fixtures/code/Latin1File.php diff --git a/tests/Fixtures/code/LongFile.php b/tests/Fixtures/code/LongFile.php new file mode 100644 index 000000000..9821d143c --- /dev/null +++ b/tests/Fixtures/code/LongFile.php @@ -0,0 +1,13 @@ +client = $this->getMockBuilder(Client::class) - ->setMethods(array_diff($this->getClassMethods(Client::class), array('captureException', 'capture', 'get_default_data', 'get_http_data', 'get_user_data', 'get_extra_data'))) - ->getMock(); - + $this->client = new Client(); $this->client->store_errors_for_bulk_send = true; $this->processor = new SanitizeStacktraceProcessor($this->client); @@ -45,14 +42,6 @@ public function testProcess() $this->client->captureException($exception); } - foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { - foreach ($exceptionValue['stacktrace']['frames'] as $frame) { - $this->assertArrayHasKey('pre_context', $frame); - $this->assertArrayHasKey('context_line', $frame); - $this->assertArrayHasKey('post_context', $frame); - } - } - $this->processor->process($this->client->_pending_events[0]); foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { @@ -76,14 +65,6 @@ public function testProcessWithPreviousException() $this->client->captureException($exception); } - foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { - foreach ($exceptionValue['stacktrace']['frames'] as $frame) { - $this->assertArrayHasKey('pre_context', $frame); - $this->assertArrayHasKey('context_line', $frame); - $this->assertArrayHasKey('post_context', $frame); - } - } - $this->processor->process($this->client->_pending_events[0]); foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 22d327d2b..623739ca6 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -11,74 +11,232 @@ namespace Raven\Tests; -function raven_test_recurse($times, $callback) +use Raven\Client; +use Raven\Stacktrace; + +class StacktraceTest extends \PHPUnit_Framework_TestCase { - $times -= 1; - if ($times > 0) { - return call_user_func('\Raven\Tests\raven_test_recurse', $times, $callback); + /** + * @var Client + */ + protected $client; + + protected function setUp() + { + $this->client = new Client(); } - return call_user_func($callback); -} + public function testGetFramesAndToArray() + { + $stacktrace = new Stacktrace($this->client); -function raven_test_create_stacktrace($args=null, $times=3) -{ - return raven_test_recurse($times, 'debug_backtrace'); -} + $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); + $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); -class Raven_Tests_StacktraceTest extends \PHPUnit_Framework_TestCase -{ - public function testCanTraceParamContext() + $frames = $stacktrace->getFrames(); + + $this->assertCount(2, $frames); + $this->assertEquals($frames, $stacktrace->toArray()); + $this->assertFrameEquals($frames[0], 'TestClass::test_function', 'path/to/file', 2); + $this->assertFrameEquals($frames[1], 'test_function', 'path/to/file', 1); + } + + public function testStacktraceJsonSerialization() { - $stack = raven_test_create_stacktrace(array('biz', 'baz'), 0); + $stacktrace = new Stacktrace($this->client); + + $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); + $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); + + $frames = $stacktrace->getFrames(); - if (isset($stack[0]['function']) and ($stack[0]['function'] == 'call_user_func')) { - $offset = 2; - } else { - $offset = 1; + $this->assertJsonStringEqualsJsonString(json_encode($frames), json_encode($stacktrace)); + } + + public function testAddFrame() + { + $stacktrace = new Stacktrace($this->client); + $frames = [ + $this->getJsonFixture('frames/eval.json'), + $this->getJsonFixture('frames/runtime_created.json'), + $this->getJsonFixture('frames/function.json'), + ]; + + foreach ($frames as $frame) { + $stacktrace->addFrame($frame['file'], $frame['line'], $frame); } - $frame = $stack[$offset]; - $params = \Raven\Stacktrace::get_frame_context($frame); - $this->assertEquals($params['args'], array('biz', 'baz')); - $this->assertEquals($params['times'], 0); + + $frames = $stacktrace->getFrames(); + + $this->assertCount(3, $frames); + $this->assertFrameEquals($frames[0], 'TestClass::test_function', 'path/to/file', 12); + $this->assertFrameEquals($frames[1], 'test_function', 'path/to/file', 12); + $this->assertFrameEquals($frames[2], 'test_function', 'path/to/file', 12); } - public function testSimpleTrace() + public function testAddFrameSerializesMethodArguments() { - $stack = array( - array( - 'file' => dirname(__FILE__).'/resources/a.php', - 'line' => 9, - 'function' => 'a_test', - 'args' => array('friend'), - ), - array( - 'file' => dirname(__FILE__).'/resources/b.php', - 'line' => 2, - 'args' => array( - dirname(__FILE__).'/resources/a.php', - ), - 'function' => 'include_once', - ) - ); + $stacktrace = new Stacktrace($this->client); + $stacktrace->addFrame('path/to/file', 12, [ + 'file' => 'path/to/file', + 'line' => 12, + 'function' => 'test_function', + 'args' => [1, 'foo'] + ]); + + $frames = $stacktrace->getFrames(); + + $this->assertCount(1, $frames); + $this->assertFrameEquals($frames[0], 'test_function', 'path/to/file', 12); + $this->assertEquals(['param1' => 1, 'param2' => 'foo'], $frames[0]['vars']); + } + + public function testAddFrameStripsPath() + { + $this->client->setPrefixes(['path/to/', 'path/to/app']); + + $stacktrace = new Stacktrace($this->client); + + $stacktrace->addFrame('path/to/app/file', 12, ['function' => 'test_function_parent_parent_parent']); + $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function_parent_parent']); + $stacktrace->addFrame('path/not/of/app/path/to/file', 12, ['function' => 'test_function_parent']); + $stacktrace->addFrame('path/not/of/app/to/file', 12, ['function' => 'test_function']); + + $frames = $stacktrace->getFrames(); + + $this->assertFrameEquals($frames[0], 'test_function', 'path/not/of/app/to/file', 12); + $this->assertFrameEquals($frames[1], 'test_function_parent', 'path/not/of/app/path/to/file', 12); + $this->assertFrameEquals($frames[2], 'test_function_parent_parent', 'file', 12); + $this->assertFrameEquals($frames[3], 'test_function_parent_parent_parent', 'app/file', 12); + } + + public function testAddFrameMarksAsInApp() + { + $this->client->setAppPath('path/to'); + $this->client->setExcludedAppPaths(['path/to/excluded/path']); + + $stacktrace = new Stacktrace($this->client); + + $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function']); + $stacktrace->addFrame('path/to/excluded/path/to/file', 12, ['function' => 'test_function']); + + $frames = $stacktrace->getFrames(); + + $this->assertArrayHasKey('in_app', $frames[0]); + $this->assertTrue($frames[0]['in_app']); + $this->assertArrayNotHasKey('in_app', $frames[1]); + } + + public function testAddFrameReadsCodeFromShortFile() + { + $fileContent = explode("\n", $this->getFixture('code/ShortFile.php')); + $stacktrace = new Stacktrace($this->client); + + $stacktrace->addFrame($this->getFixturePath('code/ShortFile.php'), 3, ['function' => '[unknown]']); + + $frames = $stacktrace->getFrames(); + + $this->assertCount(1, $frames); + $this->assertCount(2, $frames[0]['pre_context']); + $this->assertCount(2, $frames[0]['post_context']); + + for ($i = 0; $i < 2; ++$i) { + $this->assertEquals(rtrim($fileContent[$i]), $frames[0]['pre_context'][$i]); + } + + $this->assertEquals(rtrim($fileContent[2]), $frames[0]['context_line']); + + for ($i = 0; $i < 2; ++$i) { + $this->assertEquals(rtrim($fileContent[$i + 3]), $frames[0]['post_context'][$i]); + } + } + + public function testAddFrameReadsCodeFromLongFile() + { + $fileContent = explode("\n", $this->getFixture('code/LongFile.php')); + $stacktrace = new Stacktrace($this->client); + + $stacktrace->addFrame($this->getFixturePath('code/LongFile.php'), 8, [ + 'function' => '[unknown]', + ]); + + $frames = $stacktrace->getFrames(); + + $this->assertCount(1, $frames); + $this->assertCount(5, $frames[0]['pre_context']); + $this->assertCount(5, $frames[0]['post_context']); + + for ($i = 0; $i < 5; ++$i) { + $this->assertEquals(rtrim($fileContent[$i + 2]), $frames[0]['pre_context'][$i]); + } + + $this->assertEquals(rtrim($fileContent[7]), $frames[0]['context_line']); + + for ($i = 0; $i < 5; ++$i) { + $this->assertEquals(rtrim($fileContent[$i + 8]), $frames[0]['post_context'][$i]); + } + } + + /** + * @dataProvider removeFrameDataProvider + */ + public function testRemoveFrame($index, $throwException) + { + if ($throwException) { + $this->setExpectedException(\OutOfBoundsException::class, 'Invalid frame index to remove.'); + } + + $stacktrace = new Stacktrace($this->client); + + $stacktrace->addFrame('path/to/file', 12, [ + 'function' => 'test_function_parent', + ]); + + $stacktrace->addFrame('path/to/file', 12, [ + 'function' => 'test_function', + ]); + + $this->assertCount(2, $stacktrace->getFrames()); + + $stacktrace->removeFrame($index); - $frames = \Raven\Stacktrace::get_stack_info($stack, true); + $frames = $stacktrace->getFrames(); - $frame = $frames[0]; - $this->assertEquals(2, $frame['lineno']); - $this->assertNull($frame['function']); - $this->assertEquals("include_once 'a.php';", $frame['context_line']); - $this->assertFalse(isset($frame['vars'])); - $frame = $frames[1]; - $this->assertEquals(9, $frame['lineno']); - $this->assertEquals('include_once', $frame['function']); - $this->assertEquals('a_test($foo);', $frame['context_line']); - $this->assertEquals(dirname(__FILE__) . '/resources/a.php', $frame['vars']['param1']); + $this->assertCount(1, $frames); + $this->assertFrameEquals($frames[0], 'test_function_parent', 'path/to/file', 12); } - public function testDoesNotModifyCaptureVars() + public function removeFrameDataProvider() { + return [ + [-1, true], + [2, true], + [0, false], + ]; + } + + public function testFromBacktrace() + { + $fixture = $this->getJsonFixture('backtraces/exception.json'); + $frames = Stacktrace::fromBacktrace($this->client, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + + $this->assertFrameEquals($frames[0], null, 'path/to/file', 16); + $this->assertFrameEquals($frames[1], 'TestClass::crashyFunction', 'path/to/file', 7); + $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); + } + + public function testFromBacktraceWithAnonymousFrame() + { + $fixture = $this->getJsonFixture('backtraces/anonymous_frame.json'); + $frames = Stacktrace::fromBacktrace($this->client, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + $this->assertFrameEquals($frames[0], null, 'path/to/file', 7); + $this->assertFrameEquals($frames[1], 'call_user_func', '[internal]', 0); + $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); + } + + public function testGetFrameArgumentsDoesNotModifyCapturedArgs() + { // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. // Modification of these would be really bad, since if control is returned (non-fatal error) we'll have altered the state of things! $originalFoo = 'bloopblarp'; @@ -97,7 +255,7 @@ public function testDoesNotModifyCaptureVars() "function" => "a_test", ); - $result = \Raven\Stacktrace::get_frame_context($frame, 5); + $result = Stacktrace::getFrameArguments($frame, 5); // Check we haven't modified our vars. $this->assertEquals($originalFoo, 'bloopblarp'); @@ -108,143 +266,25 @@ public function testDoesNotModifyCaptureVars() $this->assertEquals($result['param2']['key'], 'xxxxx'); } - public function testDoesFixFrameInfo() - { - if (isset($_ENV['HHVM']) and ($_ENV['HHVM'] == 1)) { - $this->markTestSkipped('HHVM stacktrace behaviour'); - return; - } - - /** - * PHP's way of storing backstacks seems bass-ackwards to me - * 'function' is not the function you're in; it's any function being - * called, so we have to shift 'function' down by 1. Ugh. - */ - $stack = raven_test_create_stacktrace(); - - $frames = \Raven\Stacktrace::get_stack_info($stack, true); - // just grab the last few frames - $frames = array_slice($frames, -6); - $skip_call_user_func_fix = false; - if (version_compare(PHP_VERSION, '7.0', '>=')) { - $skip_call_user_func_fix = true; - foreach ($frames as &$frame) { - if (isset($frame['function']) and ($frame['function'] == 'call_user_func')) { - $skip_call_user_func_fix = false; - break; - } - } - unset($frame); - } - - if ($skip_call_user_func_fix) { - $frame = $frames[3]; - $this->assertEquals('Raven\Tests\raven_test_create_stacktrace', $frame['function']); - $frame = $frames[4]; - $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); - $frame = $frames[5]; - $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); - } else { - $frame = $frames[0]; - $this->assertEquals('Raven\Tests\raven_test_create_stacktrace', $frame['function']); - $frame = $frames[1]; - $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); - $frame = $frames[2]; - $this->assertEquals('call_user_func', $frame['function']); - $frame = $frames[3]; - $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); - $frame = $frames[4]; - $this->assertEquals('call_user_func', $frame['function']); - $frame = $frames[5]; - $this->assertEquals('Raven\Tests\raven_test_recurse', $frame['function']); - } - } - - public function testInApp() - { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - ), - array( - "file" => dirname(__FILE__) . "/resources/b.php", - "line" => 3, - "function" => "include_once", - ), - ); - - $frames = \Raven\Stacktrace::get_stack_info($stack, true, null, 0, null, dirname(__FILE__)); - - $this->assertEquals($frames[0]['in_app'], true); - $this->assertEquals($frames[1]['in_app'], true); - } - - public function testInAppWithExclusion() + protected function getFixturePath($file) { - $stack = array( - array( - "file" => dirname(__FILE__) . '/resources/foo/a.php', - "line" => 11, - "function" => "a_test", - ), - array( - "file" => dirname(__FILE__) . '/resources/bar/b.php', - "line" => 3, - "function" => "include_once", - ), - ); - - $frames = \Raven\Stacktrace::get_stack_info( - $stack, true, null, 0, null, dirname(__FILE__) . '/', - array(dirname(__FILE__) . '/resources/bar/')); - - // stack gets reversed - $this->assertEquals($frames[0]['in_app'], false); - $this->assertEquals($frames[1]['in_app'], true); + return realpath(__DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . $file); } - public function testBasePath() + protected function getFixture($file) { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - ), - ); - - $frames = \Raven\Stacktrace::get_stack_info($stack, true, null, 0, array(dirname(__FILE__) . '/')); - - $this->assertEquals($frames[0]['filename'], 'resources/a.php'); + return file_get_contents($this->getFixturePath($file)); } - public function testNoBasePath() + protected function getJsonFixture($file) { - $stack = array( - array( - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 11, - "function" => "a_test", - ), - ); - - $frames = \Raven\Stacktrace::get_stack_info($stack); - $this->assertEquals($frames[0]['filename'], dirname(__FILE__) . '/resources/a.php'); + return json_decode($this->getFixture($file), true); } - public function testWithEvaldCode() + protected function assertFrameEquals($frame, $method, $file, $line) { - try { - eval("throw new Exception('foobar');"); - } catch (\Exception $ex) { - $trace = $ex->getTrace(); - $frames = \Raven\Stacktrace::get_stack_info($trace); - } - /** - * @var array $frames - */ - $this->assertEquals($frames[count($frames) -1]['filename'], __FILE__); + $this->assertSame($method, $frame['function']); + $this->assertSame($file, $frame['filename']); + $this->assertSame($line, $frame['lineno']); } } From a316303c3a0218a9da73790b2334f52240c71fbd Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Wed, 26 Apr 2017 15:00:05 +0300 Subject: [PATCH 0291/1161] Scrutinizer support --- .gitignore | 1 + .scrutinizer.yml | 23 +++++++++++++++++++++++ README.md | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 .scrutinizer.yml diff --git a/.gitignore b/.gitignore index 25fe3e9ad..eebb99ae4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ package.xml .idea .php_cs.cache docs/_build +test/clover.xml diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 000000000..daa6b32f6 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,23 @@ +tools: + php_sim: false + php_pdepend: true + php_analyzer: true + +build: + environment: + php: + version: 5.6.0 + tests: + override: + - + command: 'vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-clover test/clover.xml' + coverage: + file: 'test/clover.xml' + format: 'clover' + environment: + redis: false + postgresql: false + mongodb: false + +filter: + excluded_paths: [vendor/*, test/*, bin/*, docs/*, examples/*, test/*] diff --git a/README.md b/README.md index db0dcdf0e..68181b0f8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ [![Downloads per month](https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) [![Latest stable version](https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) [![License](http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/getsentry/sentry-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) +[![Code Coverage](https://scrutinizer-ci.com/g/getsentry/sentry-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) The Sentry PHP error reporter tracks errors and exceptions that happen during the execution of your application and provides instant notification with detailed From 153949d57e8807fe8f0ccb7d173352ee10064297 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Thu, 27 Apr 2017 14:57:53 +0300 Subject: [PATCH 0292/1161] Scrutinizer: external code coverage via Travis Badges and duplicate folder --- .scrutinizer.yml | 14 +++++--------- .travis.yml | 10 +++++++++- README.md | 4 ++-- composer.json | 3 +++ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index daa6b32f6..7059f7248 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -2,22 +2,18 @@ tools: php_sim: false php_pdepend: true php_analyzer: true + php_code_coverage: true + external_code_coverage: + timeout: 2400 # There can be another pull request in progress + runs: 6 # PHP 5.3 + PHP 5.4 + PHP 5.5 + PHP 5.6 + PHP 7.0 * 2 build: environment: php: version: 5.6.0 - tests: - override: - - - command: 'vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-clover test/clover.xml' - coverage: - file: 'test/clover.xml' - format: 'clover' - environment: redis: false postgresql: false mongodb: false filter: - excluded_paths: [vendor/*, test/*, bin/*, docs/*, examples/*, test/*] + excluded_paths: [vendor/*, test/*, bin/*, docs/*, examples/*] diff --git a/.travis.yml b/.travis.yml index 89472652d..3bcc2d4ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,4 +59,12 @@ install: travis_retry composer install --no-interaction --prefer-source script: - composer phpcs - - composer tests + - composer tests-travis + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - if [ $(phpenv version-name) = "5.3" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "5.4" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "5.5" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "5.6" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "7.0" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi diff --git a/README.md b/README.md index 68181b0f8..08c7e347a 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ [![Downloads per month](https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) [![Latest stable version](https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) [![License](http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/getsentry/sentry-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) -[![Code Coverage](https://scrutinizer-ci.com/g/getsentry/sentry-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) +[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) The Sentry PHP error reporter tracks errors and exceptions that happen during the execution of your application and provides instant notification with detailed diff --git a/composer.json b/composer.json index 930e24f3b..7704b8eca 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,9 @@ "tests": [ "vendor/bin/phpunit --verbose" ], + "tests-travis": [ + "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-clover test/clover.xml" + ], "tests-report": [ "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-html test/html-report" ], From 666fb9211bf5b61df97d153d089afe2a0c8b140b Mon Sep 17 00:00:00 2001 From: marcelwinkes Date: Fri, 28 Apr 2017 11:41:53 +0200 Subject: [PATCH 0293/1161] Fixed typo in doc block comment --- lib/Raven/Processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Processor.php b/lib/Raven/Processor.php index e7e239ba5..dabf25b41 100644 --- a/lib/Raven/Processor.php +++ b/lib/Raven/Processor.php @@ -8,7 +8,7 @@ abstract class Raven_Processor { /** - * This constant defines the mask string used to strip sensitive informations. + * This constant defines the mask string used to strip sensitive information. */ const STRING_MASK = '********'; From 152d4656e7d452b47adaa7fd0531313f937fce19 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Sun, 30 Apr 2017 01:50:21 +0200 Subject: [PATCH 0294/1161] Refactor the initialization of the options in the constructor of the Client class --- lib/Raven/Client.php | 231 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 182 insertions(+), 49 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 82d359ea7..fcefbf6de 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -14,11 +14,12 @@ * Raven PHP Client * * @package raven + * @doc https://docs.sentry.io/clients/php/config/ */ class Client { - const VERSION = '1.7.x-dev'; + const VERSION = '2.0.x-dev'; const PROTOCOL = '6'; @@ -29,74 +30,176 @@ class Client const ERROR = 'error'; const FATAL = 'fatal'; + /** + * Default message limit + */ const MESSAGE_LIMIT = 1024; + /** + * @var \Raven\Breadcrumbs + */ public $breadcrumbs; /** * @var \Raven\Context */ public $context; + /** + * @var \Raven\TransactionStack + */ + public $transaction; + /** + * @var array $extra_data + */ public $extra_data; /** - * @var array|null + * @var string[]|null */ public $severity_map; public $store_errors_for_bulk_send = false; + /** + * @var \Raven\ErrorHandler $error_handler + */ protected $error_handler; + /** + * @var integer|null bit mask for error_reporting used in ErrorHandler::handleError + */ protected $error_types; + /** + * @var \Raven\Serializer $serializer + */ protected $serializer; + /** + * @var \Raven\Serializer $serializer + */ protected $reprSerializer; /** - * @var string + * @var string $app_path The root path to your application code */ protected $app_path; /** - * @var string[] + * @var string[] $prefixes Prefixes which should be stripped from filenames to create relative paths */ protected $prefixes; /** - * @var string[]|null + * @var string[]|null Paths to exclude from app_path detection */ protected $excluded_app_paths; /** - * @var Callable + * @var Callable Set a custom transport to override how Sentry events are sent upstream */ protected $transport; + /** + * @var string $logger Adjust the default logger name for messages + */ public $logger; /** - * @var string Full URL to Sentry + * @var string Full URL to Sentry (not a DSN) + * @doc https://docs.sentry.io/quickstart/ */ public $server; + /** + * @var string $secret_key Password in Sentry Server + */ public $secret_key; + /** + * @var string $public_key Password in Sentry Server + */ public $public_key; + /** + * @var integer $project This project ID in Sentry Server + */ public $project; + /** + * @var boolean $auto_log_stacks Fill stacktrace by debug_backtrace() + */ public $auto_log_stacks; + /** + * @var string $name Override the default value for the server’s hostname + */ public $name; + /** + * @var string $site SERVER_NAME (not a HTTP_HOST) + */ public $site; + /** + * @var array $tags An array of tags to apply to events in this context + */ public $tags; + /** + * @var mixed $release The version of your application (e.g. git SHA) + */ public $release; + /** + * @var string $environment The environment your application is running in + */ public $environment; + /** + * @var double The sampling factor to apply to events. A value of 0.00 will deny sending + * any events, and a value of 1.00 will send 100% of events + */ public $sample_rate; + /** + * @var boolean $trace Set this to false to disable reflection tracing + * (function calling arguments) in stacktraces + */ public $trace; + /** + * @var double $timeout Timeout for sending data + */ public $timeout; + /** + * @var string $message_limit This value is used to truncate message and frame variables. + * However it is not guarantee that length of whole message will be restricted by this value + */ public $message_limit; + /** + * @var string[] $exclude Excluded exceptions classes + */ public $exclude; public $http_proxy; + /** + * @var Callable $send_callback A function which will be called whenever data is ready to be sent. + * Within the function you can mutate the data, or alternatively return false to instruct the SDK + * to not send the event + */ protected $send_callback; + /** + * @var string $curl_method + * sync (default): send requests immediately when they’re made + * async: uses a curl_multi handler for best-effort asynchronous submissions + * exec: asynchronously send events by forking a curl process for each item + */ public $curl_method; + /** + * @var string $curl_path Specify the path to the curl binary to be used with the ‘exec’ curl method + */ public $curl_path; + /** + * @var boolean $curl_ipv4 Resolve domain only with IPv4 + * @todo change to $curl_ipresolve, http://php.net/manual/ru/function.curl-setopt.php + */ public $curl_ipv4; + /** + * @var string $ca_cert The path to the CA certificate bundle + */ public $ca_cert; + /** + * @var boolean $verify_ssl + */ public $verify_ssl; + /** + * @var mixed The SSL version (2 or 3) to use. By default PHP will try to determine this itself, + * although in some cases this must be set manually + */ public $curl_ssl_version; public $trust_x_forwarded_proto; public $mb_detect_order; /** - * @var \Raven\Processor[] + * @var \Raven\Processor[] $processors An array of classes to use to process data before it is sent to Sentry */ public $processors; /** @@ -109,7 +212,13 @@ class Client protected $_last_sentry_error; public $_last_event_id; public $_user; + /** + * @var array[] $_pending_events + */ public $_pending_events; + /** + * @var array User Agent showed in Sentry + */ public $sdk; /** * @var \Raven\CurlHandler @@ -123,10 +232,6 @@ class Client * @var bool */ protected $_shutdown_function_has_been_set; - /** - * @var \Raven\TransactionStack - */ - public $transaction; public function __construct($options_or_dsn = null, $options = array()) { @@ -147,37 +252,73 @@ public function __construct($options_or_dsn = null, $options = array()) if (!empty($dsn)) { $options = array_merge($options, self::parseDSN($dsn)); } + unset($dsn); + $this->init_with_options($options); + + if (\Raven\Util::get($options, 'install_default_breadcrumb_handlers', true)) { + $this->registerDefaultBreadcrumbHandlers(); + } + + if (\Raven\Util::get($options, 'install_shutdown_handler', true)) { + $this->registerShutdownFunction(); + } + } + + /** + * @param array $options + */ + public function init_with_options($options) + { + foreach ( + [ + ['logger', 'php',], + ['server',], + ['secret_key',], + ['public_key',], + ['project',1,], + ['auto_log_stacks', false,], + ['name', gethostname()], + ['site', self::_server_variable('SERVER_NAME')], + ['tags', []], + ['release', []], + ['environment'], + ['sample_rate', 1], + ['trace', true], + ['timeout', 2], + ['message_limit', self::MESSAGE_LIMIT], + ['exclude', []], + ['http_proxy'], + ['extra_data', [], 'extra'], + ['send_callback'], + + ['curl_method', 'sync'], + ['curl_path', 'curl'], + ['curl_ipv4', true], + ['ca_cert', static::get_default_ca_cert()], + ['verify_ssl', true], + ['curl_ssl_version'], + ['trust_x_forwarded_proto'], + ['transport'], + ['mb_detect_order'], + ['error_types'], + ] as &$set + ) { + if (count($set) == 1) { + $set = [$set[0], null, null]; + } elseif (count($set) == 2) { + $set[] = null; + } + + list($object_key, $default_value, $array_key) = $set; + if (is_null($array_key)) { + $array_key = $object_key; + } - $this->logger = \Raven\Util::get($options, 'logger', 'php'); - $this->server = \Raven\Util::get($options, 'server'); - $this->secret_key = \Raven\Util::get($options, 'secret_key'); - $this->public_key = \Raven\Util::get($options, 'public_key'); - $this->project = \Raven\Util::get($options, 'project', 1); - $this->auto_log_stacks = (bool) \Raven\Util::get($options, 'auto_log_stacks', false); - $this->name = \Raven\Util::get($options, 'name', gethostname()); - $this->site = \Raven\Util::get($options, 'site', self::_server_variable('SERVER_NAME')); - $this->tags = \Raven\Util::get($options, 'tags', array()); - $this->release = \Raven\Util::get($options, 'release', null); - $this->environment = \Raven\Util::get($options, 'environment', null); - $this->sample_rate = \Raven\Util::get($options, 'sample_rate', 1); - $this->trace = (bool) \Raven\Util::get($options, 'trace', true); - $this->timeout = \Raven\Util::get($options, 'timeout', 2); - $this->message_limit = \Raven\Util::get($options, 'message_limit', self::MESSAGE_LIMIT); - $this->exclude = \Raven\Util::get($options, 'exclude', array()); + // @todo It should be isset or array_key_exists? + $this->{$object_key} = isset($options[$array_key]) ? $options[$array_key] : $default_value; + } + $this->auto_log_stacks = (boolean)$this->auto_log_stacks; $this->severity_map = null; - $this->http_proxy = \Raven\Util::get($options, 'http_proxy'); - $this->extra_data = \Raven\Util::get($options, 'extra', array()); - $this->send_callback = \Raven\Util::get($options, 'send_callback', null); - $this->curl_method = \Raven\Util::get($options, 'curl_method', 'sync'); - $this->curl_path = \Raven\Util::get($options, 'curl_path', 'curl'); - $this->curl_ipv4 = \Raven\Util::get($options, 'curl_ipv4', true); - $this->ca_cert = \Raven\Util::get($options, 'ca_cert', static::get_default_ca_cert()); - $this->verify_ssl = \Raven\Util::get($options, 'verify_ssl', true); - $this->curl_ssl_version = \Raven\Util::get($options, 'curl_ssl_version'); - $this->trust_x_forwarded_proto = \Raven\Util::get($options, 'trust_x_forwarded_proto'); - $this->transport = \Raven\Util::get($options, 'transport', null); - $this->mb_detect_order = \Raven\Util::get($options, 'mb_detect_order', null); - $this->error_types = \Raven\Util::get($options, 'error_types', null); // app path is used to determine if code is part of your application $this->setAppPath(\Raven\Util::get($options, 'app_path', null)); @@ -216,14 +357,6 @@ public function __construct($options_or_dsn = null, $options = array()) $this->transaction->push($_SERVER['PATH_INFO']); // @codeCoverageIgnoreEnd } - - if (\Raven\Util::get($options, 'install_default_breadcrumb_handlers', true)) { - $this->registerDefaultBreadcrumbHandlers(); - } - - if (\Raven\Util::get($options, 'install_shutdown_handler', true)) { - $this->registerShutdownFunction(); - } } public function __destruct() From 7b48e31be0f9bef4e8f06629c0c3bf3b67404030 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Sun, 30 Apr 2017 01:50:36 +0200 Subject: [PATCH 0295/1161] Applied code linting to all files (short-array syntax and PSR-2 rules) --- .php_cs | 1 + .travis.yml | 1 - bin/sentry | 2 +- composer.json | 2 +- examples/vanilla/index.php | 2 +- lib/Raven/Breadcrumbs.php | 8 +- lib/Raven/Breadcrumbs/ErrorHandler.php | 12 +- lib/Raven/Breadcrumbs/MonologHandler.php | 26 +- lib/Raven/Client.php | 144 ++-- lib/Raven/Context.php | 4 +- lib/Raven/CurlHandler.php | 8 +- lib/Raven/ErrorHandler.php | 14 +- .../Processor/RemoveHttpBodyProcessor.php | 4 +- lib/Raven/Processor/SanitizeDataProcessor.php | 8 +- .../SanitizeHttpHeadersProcessor.php | 9 +- lib/Raven/Serializer.php | 2 +- lib/Raven/TransactionStack.php | 4 +- tests/Breadcrumbs/ErrorHandlerTest.php | 4 +- tests/Breadcrumbs/MonologTest.php | 8 +- tests/BreadcrumbsTest.php | 4 +- tests/ClientTest.php | 634 +++++++++--------- tests/ErrorHandlerTest.php | 26 +- tests/IntegrationTest.php | 4 +- .../Processor/RemoveCookiesProcessorTest.php | 57 +- .../Processor/RemoveHttpBodyProcessorTest.php | 120 ++-- tests/Processor/SanitizeDataProcessorTest.php | 78 +-- .../SanitizeHttpHeadersProcessorTest.php | 63 +- .../SanitizeStacktraceProcessorTest.php | 21 - tests/SerializerAbstractTest.php | 26 +- tests/StacktraceTest.php | 12 +- tests/UtilTest.php | 4 +- 31 files changed, 645 insertions(+), 667 deletions(-) diff --git a/.php_cs b/.php_cs index 477a2547f..a3c53c2b8 100644 --- a/.php_cs +++ b/.php_cs @@ -3,6 +3,7 @@ return PhpCsFixer\Config::create() ->setRules([ '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/.travis.yml b/.travis.yml index 78af94e6d..1fe853dd8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ php: matrix: allow_failures: - - php: 7.1 - php: hhvm-3.12 - php: nightly fast_finish: true diff --git a/bin/sentry b/bin/sentry index d5cf11209..d6b68e88f 100755 --- a/bin/sentry +++ b/bin/sentry @@ -55,7 +55,7 @@ function cmd_test($dsn) echo "Sending a test event:\n"; - $ex = raven_cli_test("command name", array("foo" => "bar")); + $ex = raven_cli_test("command name", ["foo" => "bar"]); $event_id = $client->captureException($ex); echo "-> event ID: $event_id\n"; diff --git a/composer.json b/composer.json index 22d3d64de..6e9e1f570 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "vendor/bin/phpunit --verbose" ], "tests-report": [ - "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-html tests/html-report" + "vendor/bin/phpunit --verbose --configuration phpunit.xml.dist --coverage-html tests/html-report" ], "phpcs": [ "vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run" diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php index 191eddd2d..25e6c342b 100644 --- a/examples/vanilla/index.php +++ b/examples/vanilla/index.php @@ -12,7 +12,7 @@ function setupSentry() $object = new \Raven\Client(SENTRY_DSN); $object->setAppPath(__DIR__) ->setRelease(\Raven\Client::VERSION) - ->setPrefixes(array(__DIR__)) + ->setPrefixes([__DIR__]) ->install(); } diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php index 766e68b33..16eab3610 100644 --- a/lib/Raven/Breadcrumbs.php +++ b/lib/Raven/Breadcrumbs.php @@ -36,7 +36,7 @@ public function reset() { $this->count = 0; $this->pos = 0; - $this->buffer = array(); + $this->buffer = []; } public function record($crumb) @@ -54,7 +54,7 @@ public function record($crumb) */ public function fetch() { - $results = array(); + $results = []; for ($i = 0; $i <= ($this->size - 1); $i++) { $idx = ($this->pos + $i) % $this->size; if (isset($this->buffer[$idx])) { @@ -71,8 +71,8 @@ public function is_empty() public function to_json() { - return array( + return [ 'values' => $this->fetch(), - ); + ]; } } diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index 5c4bda141..411128075 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -19,18 +19,18 @@ public function __construct(\Raven\Client $ravenClient) $this->ravenClient = $ravenClient; } - public function handleError($code, $message, $file = '', $line = 0, $context = array()) + public function handleError($code, $message, $file = '', $line = 0, $context = []) { - $this->ravenClient->breadcrumbs->record(array( + $this->ravenClient->breadcrumbs->record([ 'category' => 'error_reporting', 'message' => $message, 'level' => $this->ravenClient->translateSeverity($code), - 'data' => array( + 'data' => [ 'code' => $code, 'line' => $line, 'file' => $file, - ), - )); + ], + ]); if ($this->existingHandler !== null) { return call_user_func($this->existingHandler, $code, $message, $file, $line, $context); @@ -41,7 +41,7 @@ public function handleError($code, $message, $file = '', $line = 0, $context = a public function install() { - $this->existingHandler = set_error_handler(array($this, 'handleError'), E_ALL); + $this->existingHandler = set_error_handler([$this, 'handleError'], E_ALL); return $this; } } diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index d54c35ceb..26b83d66b 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -9,7 +9,7 @@ class MonologHandler extends \Monolog\Handler\AbstractProcessingHandler /** * Translates Monolog log levels to Raven log levels. */ - protected $logLevels = array( + protected $logLevels = [ Logger::DEBUG => \Raven\Client::DEBUG, Logger::INFO => \Raven\Client::INFO, Logger::NOTICE => \Raven\Client::INFO, @@ -18,7 +18,7 @@ class MonologHandler extends \Monolog\Handler\AbstractProcessingHandler Logger::CRITICAL => \Raven\Client::FATAL, Logger::ALERT => \Raven\Client::FATAL, Logger::EMERGENCY => \Raven\Client::FATAL, - ); + ]; protected $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; @@ -46,7 +46,7 @@ public function __construct(\Raven\Client $ravenClient, $level = Logger::DEBUG, protected function parseException($message) { if (preg_match($this->excMatch, $message, $matches)) { - return array($matches[1], $matches[2]); + return [$matches[1], $matches[2]]; } return null; @@ -67,33 +67,33 @@ protected function write(array $record) * @var \Exception $exc */ $exc = $record['context']['exception']; - $crumb = array( + $crumb = [ 'type' => 'error', 'level' => $this->logLevels[$record['level']], 'category' => $record['channel'], - 'data' => array( + 'data' => [ 'type' => get_class($exc), 'value' => $exc->getMessage(), - ), - ); + ], + ]; } else { // TODO(dcramer): parse exceptions out of messages and format as above if ($error = $this->parseException($record['message'])) { - $crumb = array( + $crumb = [ 'type' => 'error', 'level' => $this->logLevels[$record['level']], 'category' => $record['channel'], - 'data' => array( + 'data' => [ 'type' => $error[0], 'value' => $error[1], - ), - ); + ], + ]; } else { - $crumb = array( + $crumb = [ 'level' => $this->logLevels[$record['level']], 'category' => $record['channel'], 'message' => $record['message'], - ); + ]; } } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index fcefbf6de..c86fb0fcc 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -233,7 +233,7 @@ class Client */ protected $_shutdown_function_has_been_set; - public function __construct($options_or_dsn = null, $options = array()) + public function __construct($options_or_dsn = null, $options = []) { if (is_array($options_or_dsn)) { $options = array_merge($options_or_dsn, $options); @@ -332,15 +332,15 @@ public function init_with_options($options) $this->_curl_instance = null; $this->_last_event_id = null; $this->_user = null; - $this->_pending_events = array(); + $this->_pending_events = []; $this->context = new \Raven\Context(); $this->breadcrumbs = new \Raven\Breadcrumbs(); $this->_shutdown_function_has_been_set = false; - $this->sdk = \Raven\Util::get($options, 'sdk', array( + $this->sdk = \Raven\Util::get($options, 'sdk', [ 'name' => 'sentry-php', 'version' => self::VERSION, - )); + ]); $this->serializer = new \Raven\Serializer($this->mb_detect_order); $this->reprSerializer = new \Raven\ReprSerializer($this->mb_detect_order); if (\Raven\Util::get($options, 'serialize_all_object', false)) { @@ -372,7 +372,7 @@ public function __destruct() */ public function close_all_children_link() { - $this->processors = array(); + $this->processors = []; } /** @@ -475,7 +475,7 @@ public function getExcludedAppPaths() public function setExcludedAppPaths($value) { - $this->excluded_app_paths = $value ? array_map(array($this, '_convertPath'), $value) : null; + $this->excluded_app_paths = $value ? array_map([$this, '_convertPath'], $value) : null; return $this; } @@ -490,7 +490,7 @@ public function getPrefixes() */ public function setPrefixes($value) { - $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value; + $this->prefixes = $value ? array_map([$this, '_convertPath'], $value) : $value; return $this; } @@ -541,9 +541,9 @@ public function setTransport($value) */ public static function getDefaultProcessors() { - return array( + return [ '\\Raven\\Processor\\SanitizeDataProcessor', - ); + ]; } /** @@ -555,7 +555,7 @@ public static function getDefaultProcessors() */ public function setProcessorsFromOptions($options) { - $processors = array(); + $processors = []; foreach (\Raven\util::get($options, 'processors', static::getDefaultProcessors()) as $processor) { /** * @var \Raven\Processor $new_processor @@ -587,10 +587,10 @@ public static function parseDSN($dsn) { $url = parse_url($dsn); $scheme = (isset($url['scheme']) ? $url['scheme'] : ''); - if (!in_array($scheme, array('http', 'https'))) { + if (!in_array($scheme, ['http', 'https'])) { throw new \InvalidArgumentException( 'Unsupported Sentry DSN scheme: '. - (!empty($scheme) ? $scheme : '') + (!empty($scheme) ? $scheme : /** @lang text */'') ); } $netloc = (isset($url['host']) ? $url['host'] : null); @@ -615,12 +615,12 @@ public static function parseDSN($dsn) throw new \InvalidArgumentException('Invalid Sentry DSN: ' . $dsn); } - return array( + return [ 'server' => sprintf('%s://%s%s/api/%s/store/', $scheme, $netloc, $path, $project), 'project' => $project, 'public_key' => $username, 'secret_key' => $password, - ); + ]; } public function getLastError() @@ -651,7 +651,7 @@ public function getIdent($ident) * @deprecated * @codeCoverageIgnore */ - public function message($message, $params = array(), $level = self::INFO, + public function message($message, $params = [], $level = self::INFO, $stack = false, $vars = null) { return $this->captureMessage($message, $params, $level, $stack, $vars); @@ -678,7 +678,7 @@ public function exception($exception) * @param mixed $vars * @return string|null */ - public function captureMessage($message, $params = array(), $data = array(), + public function captureMessage($message, $params = [], $data = [], $stack = false, $vars = null) { // Gracefully handle messages which contain formatting characters, but were not @@ -690,20 +690,20 @@ public function captureMessage($message, $params = array(), $data = array(), } if ($data === null) { - $data = array(); + $data = []; // support legacy method of passing in a level name as the third arg } elseif (!is_array($data)) { - $data = array( + $data = [ 'level' => $data, - ); + ]; } $data['message'] = $formatted_message; - $data['sentry.interfaces.Message'] = array( + $data['sentry.interfaces.Message'] = [ 'message' => $message, 'params' => $params, 'formatted' => $formatted_message, - ); + ]; return $this->capture($data, $stack, $vars); } @@ -726,25 +726,25 @@ public function captureException($exception, $data = null, $logger = null, $vars } if ($data === null) { - $data = array(); + $data = []; } $exc = $exception; do { - $exc_data = array( + $exc_data = [ 'value' => $this->serializer->serialize($exc->getMessage()), 'type' => get_class($exc), - ); + ]; /**'exception' * Exception::getTrace doesn't store the point at where the exception * was thrown, so we have to stuff it in ourselves. Ugh. */ $trace = $exc->getTrace(); - $frame_where_exception_thrown = array( + $frame_where_exception_thrown = [ 'file' => $exc->getFile(), 'line' => $exc->getLine(), - ); + ]; array_unshift($trace, $frame_where_exception_thrown); @@ -755,16 +755,18 @@ public function captureException($exception, $data = null, $logger = null, $vars // @codeCoverageIgnoreEnd } - $exc_data['stacktrace'] = array( - 'frames' => Stacktrace::fromBacktrace($this, $exception->getTrace(), $exception->getFile(), $exception->getLine())->getFrames(), - ); + $exc_data['stacktrace'] = [ + 'frames' => Stacktrace::fromBacktrace( + $this, $exception->getTrace(), $exception->getFile(), $exception->getLine() + )->getFrames(), + ]; $exceptions[] = $exc_data; } while ($has_chained_exceptions && $exc = $exc->getPrevious()); - $data['exception'] = array( + $data['exception'] = [ 'values' => array_reverse($exceptions), - ); + ]; if ($logger !== null) { $data['logger'] = $logger; } @@ -810,13 +812,13 @@ public function captureLastError() */ public function captureQuery($query, $level = self::INFO, $engine = '') { - $data = array( + $data = [ 'message' => $query, 'level' => $level, - 'sentry.interfaces.Query' => array( + 'sentry.interfaces.Query' => [ 'query' => $query - ) - ); + ] + ]; if ($engine !== '') { $data['sentry.interfaces.Query']['engine'] = $engine; @@ -842,7 +844,7 @@ protected function registerShutdownFunction() { if (!$this->_shutdown_function_has_been_set) { $this->_shutdown_function_has_been_set = true; - register_shutdown_function(array($this, 'onShutdown')); + register_shutdown_function([$this, 'onShutdown']); } } @@ -857,24 +859,24 @@ protected static function is_http_request() protected function get_http_data() { - $headers = array(); + $headers = []; foreach ($_SERVER as $key => $value) { if (0 === strpos($key, 'HTTP_')) { $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); $headers[$header_key] = $value; - } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { + } elseif (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH']) && $value !== '') { $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); $headers[$header_key] = $value; } } - $result = array( + $result = [ 'method' => self::_server_variable('REQUEST_METHOD'), 'url' => $this->get_current_url(), 'query_string' => self::_server_variable('QUERY_STRING'), - ); + ]; // dont set this as an empty array as PHP will treat it as a numeric array // instead of a mapping which goes against the defined Sentry spec @@ -888,9 +890,9 @@ protected function get_http_data() $result['headers'] = $headers; } - return array( + return [ 'request' => $result, - ); + ]; } protected function get_user_data() @@ -898,11 +900,11 @@ protected function get_user_data() $user = $this->context->user; if ($user === null) { if (!function_exists('session_id') || !session_id()) { - return array(); + return []; } - $user = array( + $user = [ 'id' => session_id(), - ); + ]; if (!empty($_SERVER['REMOTE_ADDR'])) { $user['ip_address'] = $_SERVER['REMOTE_ADDR']; } @@ -910,9 +912,9 @@ protected function get_user_data() $user['data'] = $_SESSION; } } - return array( + return [ 'user' => $user, - ); + ]; } protected function get_extra_data() @@ -922,7 +924,7 @@ protected function get_extra_data() public function get_default_data() { - return array( + return [ 'server_name' => $this->name, 'project' => $this->project, 'site' => $this->site, @@ -931,7 +933,7 @@ public function get_default_data() 'platform' => 'php', 'sdk' => $this->sdk, 'culprit' => $this->transaction->peek(), - ); + ]; } public function capture($data, $stack = null, $vars = null) @@ -943,10 +945,10 @@ public function capture($data, $stack = null, $vars = null) $data['level'] = self::ERROR; } if (!isset($data['tags'])) { - $data['tags'] = array(); + $data['tags'] = []; } if (!isset($data['extra'])) { - $data['extra'] = array(); + $data['extra'] = []; } if (!isset($data['event_id'])) { $data['event_id'] = static::uuid4(); @@ -1014,9 +1016,12 @@ public function capture($data, $stack = null, $vars = null) } if (!isset($data['stacktrace']) && !isset($data['exception'])) { - $data['stacktrace'] = array( - 'frames' => Stacktrace::fromBacktrace($this, $stack, isset($stack['file']) ? $stack['file'] : __FILE__, isset($stack['line']) ? $stack['line'] : __LINE__)->getFrames(), - ); + $data['stacktrace'] = [ + 'frames' => Stacktrace::fromBacktrace( + $this, $stack, isset($stack['file']) ? $stack['file'] : __FILE__, + isset($stack['line']) ? $stack['line'] : __LINE__ - 2 + )->getFrames(), + ]; } } @@ -1073,7 +1078,7 @@ public function sendUnsentErrors() foreach ($this->_pending_events as $data) { $this->send($data); } - $this->_pending_events = array(); + $this->_pending_events = []; if ($this->store_errors_for_bulk_send) { //in case an error occurs after this is called, on shutdown, send any new errors. $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED'); @@ -1116,7 +1121,7 @@ public function encode(&$data) public function send(&$data) { if (is_callable($this->send_callback) - && call_user_func_array($this->send_callback, array(&$data)) === false + && call_user_func_array($this->send_callback, [&$data]) === false ) { // if send_callback returns false, end native send return; @@ -1138,11 +1143,11 @@ public function send(&$data) $message = $this->encode($data); - $headers = array( + $headers = [ 'User-Agent' => static::getUserAgent(), 'X-Sentry-Auth' => $this->getAuthHeader(), 'Content-Type' => 'application/octet-stream' - ); + ]; $this->send_remote($this->server, $message, $headers); } @@ -1154,7 +1159,7 @@ public function send(&$data) * @param array|string $data Associative array of data to log * @param array $headers Associative array of headers */ - protected function send_remote($url, $data, $headers = array()) + protected function send_remote($url, $data, $headers = []) { $parts = parse_url($url); $parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null); @@ -1169,16 +1174,17 @@ protected static function get_default_ca_cert() /** * @return array * @doc http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006 + * @doc https://3v4l.org/4I7F5 */ protected function get_curl_options() { - $options = array( + $options = [ CURLOPT_VERBOSE => false, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_SSL_VERIFYPEER => $this->verify_ssl, CURLOPT_CAINFO => $this->ca_cert, CURLOPT_USERAGENT => 'sentry-php/' . self::VERSION, - ); + ]; if ($this->http_proxy) { $options[CURLOPT_PROXY] = $this->http_proxy; } @@ -1192,7 +1198,7 @@ protected function get_curl_options() // MS is available in curl >= 7.16.2 $timeout = max(1, ceil(1000 * $this->timeout)); - // some versions of PHP 5.3 don't have this defined correctly + // None of the versions of PHP contains this constant if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) { //see stackoverflow link in the phpdoc define('CURLOPT_CONNECTTIMEOUT_MS', 156); @@ -1216,7 +1222,7 @@ protected function get_curl_options() * @param array|string $data Associative array of data to log * @param array $headers Associative array of headers */ - protected function send_http($url, $data, $headers = array()) + protected function send_http($url, $data, $headers = []) { if ($this->curl_method == 'async') { $this->_curl_handler->enqueue($url, $data, $headers); @@ -1269,7 +1275,7 @@ protected function send_http_asynchronous_curl_exec($url, $data, $headers) */ protected function send_http_synchronous($url, $data, $headers) { - $new_headers = array(); + $new_headers = []; foreach ($headers as $key => $value) { array_push($new_headers, $key .': '. $value); } @@ -1332,11 +1338,11 @@ protected function send_http_synchronous($url, $data, $headers) */ protected static function get_auth_header($timestamp, $client, $api_key, $secret_key) { - $header = array( + $header = [ sprintf('sentry_timestamp=%F', $timestamp), "sentry_client={$client}", sprintf('sentry_version=%s', self::PROTOCOL), - ); + ]; if ($api_key) { $header[] = "sentry_key={$api_key}"; @@ -1487,7 +1493,7 @@ public function translateSeverity($severity) * Provide a map of PHP Error constants to Sentry logging groups to use instead * of the defaults in translateSeverity() * - * @param array $map + * @param string[] $map */ public function registerSeverityMap($map) { @@ -1503,9 +1509,9 @@ public function registerSeverityMap($map) * @param array $data Additional user data * @codeCoverageIgnore */ - public function set_user_data($id, $email = null, $data = array()) + public function set_user_data($id, $email = null, $data = []) { - $user = array('id' => $id); + $user = ['id' => $id]; if (isset($email)) { $user['email'] = $email; } diff --git a/lib/Raven/Context.php b/lib/Raven/Context.php index de79166ec..bbb5ea44e 100644 --- a/lib/Raven/Context.php +++ b/lib/Raven/Context.php @@ -32,8 +32,8 @@ public function __construct() */ public function clear() { - $this->tags = array(); - $this->extra = array(); + $this->tags = []; + $this->extra = []; $this->user = null; } } diff --git a/lib/Raven/CurlHandler.php b/lib/Raven/CurlHandler.php index 7bad9581d..43986e104 100644 --- a/lib/Raven/CurlHandler.php +++ b/lib/Raven/CurlHandler.php @@ -28,10 +28,10 @@ public function __construct($options, $join_timeout = 5) { $this->options = $options; $this->multi_handle = curl_multi_init(); - $this->requests = array(); + $this->requests = []; $this->join_timeout = 5; - register_shutdown_function(array($this, 'join')); + register_shutdown_function([$this, 'join']); } public function __destruct() @@ -39,11 +39,11 @@ public function __destruct() $this->join(); } - public function enqueue($url, $data = null, $headers = array()) + public function enqueue($url, $data = null, $headers = []) { $ch = curl_init(); - $new_headers = array(); + $new_headers = []; foreach ($headers as $key => $value) { array_push($new_headers, $key .': '. $value); } diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index eb19d7f8d..868693883 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -36,7 +36,7 @@ class ErrorHandler /** @var \Raven\Client */ protected $client; protected $send_errors_last = false; - protected $fatal_error_types = array( + protected $fatal_error_types = [ E_ERROR, E_PARSE, E_CORE_ERROR, @@ -44,7 +44,7 @@ class ErrorHandler E_COMPILE_ERROR, E_COMPILE_WARNING, E_STRICT, - ); + ]; /** * @var array @@ -63,7 +63,7 @@ public function __construct($client, $send_errors_last = false, $error_types = n $this->client = $client; $this->error_types = $error_types; - $this->fatal_error_types = array_reduce($this->fatal_error_types, array($this, 'bitwiseOr')); + $this->fatal_error_types = array_reduce($this->fatal_error_types, [$this, 'bitwiseOr']); if ($send_errors_last) { $this->send_errors_last = true; $this->client->store_errors_for_bulk_send = true; @@ -88,7 +88,7 @@ public function handleException($e, $isError = false, $vars = null) } } - public function handleError($type, $message, $file = '', $line = 0, $context = array()) + public function handleError($type, $message, $file = '', $line = 0, $context = []) { // http://php.net/set_error_handler // The following error types cannot be handled with a user defined function: E_ERROR, @@ -155,7 +155,7 @@ public function shouldCaptureFatalError($type) */ public function registerExceptionHandler($call_existing = true) { - $this->old_exception_handler = set_exception_handler(array($this, 'handleException')); + $this->old_exception_handler = set_exception_handler([$this, 'handleException']); $this->call_existing_exception_handler = $call_existing; return $this; } @@ -174,7 +174,7 @@ public function registerErrorHandler($call_existing = true, $error_types = null) if ($error_types !== null) { $this->error_types = $error_types; } - $this->old_error_handler = set_error_handler(array($this, 'handleError'), E_ALL); + $this->old_error_handler = set_error_handler([$this, 'handleError'], E_ALL); $this->call_existing_error_handler = $call_existing; return $this; } @@ -189,7 +189,7 @@ public function registerErrorHandler($call_existing = true, $error_types = null) */ public function registerShutdownFunction($reservedMemorySize = 10) { - register_shutdown_function(array($this, 'handleFatalError')); + register_shutdown_function([$this, 'handleFatalError']); $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); return $this; diff --git a/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/lib/Raven/Processor/RemoveHttpBodyProcessor.php index 09161b250..9421f894e 100644 --- a/lib/Raven/Processor/RemoveHttpBodyProcessor.php +++ b/lib/Raven/Processor/RemoveHttpBodyProcessor.php @@ -27,7 +27,9 @@ final class RemoveHttpBodyProcessor extends Processor */ public function process(&$data) { - if (isset($data['request'], $data['request']['method']) && in_array(strtoupper($data['request']['method']), array('POST', 'PUT', 'PATCH', 'DELETE'))) { + if (isset($data['request'], $data['request']['method']) + && in_array(strtoupper($data['request']['method']), ['POST', 'PUT', 'PATCH', 'DELETE']) + ) { $data['request']['data'] = self::STRING_MASK; } } diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php index 24668e0bf..7e50de5a2 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -84,7 +84,7 @@ public function sanitize(&$item, $key) public function sanitizeException(&$data) { foreach ($data['exception']['values'] as &$value) { - return $this->sanitizeStacktrace($value['stacktrace']); + $this->sanitizeStacktrace($value['stacktrace']); } } @@ -98,7 +98,7 @@ public function sanitizeHttp(&$data) } } if (!empty($http['data']) && is_array($http['data'])) { - array_walk_recursive($http['data'], array($this, 'sanitize')); + array_walk_recursive($http['data'], [$this, 'sanitize']); } } @@ -108,7 +108,7 @@ public function sanitizeStacktrace(&$data) if (empty($frame['vars'])) { continue; } - array_walk_recursive($frame['vars'], array($this, 'sanitize')); + array_walk_recursive($frame['vars'], [$this, 'sanitize']); } } @@ -127,7 +127,7 @@ public function process(&$data) $this->sanitizeHttp($data); } if (!empty($data['extra'])) { - array_walk_recursive($data['extra'], array($this, 'sanitize')); + array_walk_recursive($data['extra'], [$this, 'sanitize']); } } diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php index 526923635..3e712d689 100644 --- a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php +++ b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -25,7 +25,7 @@ final class SanitizeHttpHeadersProcessor extends Processor /** * @var string[] $httpHeadersToSanitize The list of HTTP headers to sanitize */ - private $httpHeadersToSanitize = array(); + private $httpHeadersToSanitize = []; /** * {@inheritdoc} @@ -40,7 +40,10 @@ public function __construct(Client $client) */ public function setProcessorOptions(array $options) { - $this->httpHeadersToSanitize = array_merge($this->getDefaultHeaders(), isset($options['sanitize_http_headers']) ? $options['sanitize_http_headers'] : array()); + $this->httpHeadersToSanitize = array_merge( + $this->getDefaultHeaders(), + isset($options['sanitize_http_headers']) ? $options['sanitize_http_headers'] : [] + ); } /** @@ -64,6 +67,6 @@ public function process(&$data) */ private function getDefaultHeaders() { - return array('Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN'); + return ['Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN']; } } diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index db3c3f830..90527e3c8 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -75,7 +75,7 @@ public function serialize($value, $max_depth = 3, $_depth = 0) { if ($_depth < $max_depth) { if (is_array($value)) { - $new = array(); + $new = []; foreach ($value as $k => $v) { $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); } diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index 711d68323..b50d387d8 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -18,12 +18,12 @@ class TransactionStack public function __construct() { - $this->stack = array(); + $this->stack = []; } public function clear() { - $this->stack = array(); + $this->stack = []; } public function peek() diff --git a/tests/Breadcrumbs/ErrorHandlerTest.php b/tests/Breadcrumbs/ErrorHandlerTest.php index f01b9c593..8909fe3b8 100644 --- a/tests/Breadcrumbs/ErrorHandlerTest.php +++ b/tests/Breadcrumbs/ErrorHandlerTest.php @@ -13,9 +13,9 @@ class Raven_Tests_ErrorHandlerBreadcrumbHandlerTest extends PHPUnit_Framework_Te { public function testSimple() { - $client = new \Raven\Client(array( + $client = new \Raven\Client([ 'install_default_breadcrumb_handlers' => false, - )); + ]); $handler = new \Raven\Breadcrumbs\ErrorHandler($client); $handler->handleError(E_WARNING, 'message'); diff --git a/tests/Breadcrumbs/MonologTest.php b/tests/Breadcrumbs/MonologTest.php index 0700a0070..6165324d1 100644 --- a/tests/Breadcrumbs/MonologTest.php +++ b/tests/Breadcrumbs/MonologTest.php @@ -37,9 +37,9 @@ protected function getSampleErrorMessage() public function testSimple() { - $client = new \Raven\Client(array( + $client = new \Raven\Client([ 'install_default_breadcrumb_handlers' => false, - )); + ]); $handler = new \Raven\Breadcrumbs\MonologHandler($client); $logger = new \Monolog\Logger('sentry'); @@ -55,9 +55,9 @@ public function testSimple() public function testErrorInMessage() { - $client = new \Raven\Client(array( + $client = new \Raven\Client([ 'install_default_breadcrumb_handlers' => false, - )); + ]); $handler = new \Raven\Breadcrumbs\MonologHandler($client); $logger = new \Monolog\Logger('sentry'); diff --git a/tests/BreadcrumbsTest.php b/tests/BreadcrumbsTest.php index 95724a8bf..f746856ff 100644 --- a/tests/BreadcrumbsTest.php +++ b/tests/BreadcrumbsTest.php @@ -15,7 +15,7 @@ public function testBuffer() { $breadcrumbs = new \Raven\Breadcrumbs(10); for ($i = 0; $i <= 10; $i++) { - $breadcrumbs->record(array('message' => $i)); + $breadcrumbs->record(['message' => $i]); } $results = $breadcrumbs->fetch(); @@ -29,7 +29,7 @@ public function testBuffer() public function testJson() { $breadcrumbs = new \Raven\Breadcrumbs(1); - $breadcrumbs->record(array('message' => 'test')); + $breadcrumbs->record(['message' => 'test']); $json = $breadcrumbs->to_json(); $this->assertEquals(count($json['values']), 1); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index d79699b20..d16b10c40 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -29,7 +29,7 @@ function invalid_encoding() // XXX: Is there a better way to stub the client? class Dummy_Raven_Client extends \Raven\Client { - private $__sent_events = array(); + private $__sent_events = []; public $dummy_breadcrumbs_handlers_has_set = false; public $dummy_shutdown_handlers_has_set = false; @@ -40,7 +40,7 @@ public function getSentEvents() public function send(&$data) { - if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { + if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, [&$data]) === false) { // if send_callback returns falsely, end native send return; } @@ -189,12 +189,12 @@ class Dummy_Raven_CurlHandler extends \Raven\CurlHandler public $_enqueue_called = false; public $_join_called = false; - public function __construct($options = array(), $join_timeout = 5) + public function __construct($options = [], $join_timeout = 5) { parent::__construct($options, $join_timeout); } - public function enqueue($url, $data = null, $headers = array()) + public function enqueue($url, $data = null, $headers = []) { $this->_enqueue_called = true; $this->_set_url = $url; @@ -345,9 +345,9 @@ public function testDsnFirstArgument() */ public function testDsnFirstArgumentWithOptions() { - $client = new Dummy_Raven_Client('http://public:secret@example.com/1', array( + $client = new Dummy_Raven_Client('http://public:secret@example.com/1', [ 'site' => 'foo', - )); + ]); $this->assertEquals(1, $client->project); $this->assertEquals('http://example.com/api/1/store/', $client->server); @@ -361,10 +361,10 @@ public function testDsnFirstArgumentWithOptions() */ public function testOptionsFirstArgument() { - $client = new Dummy_Raven_Client(array( + $client = new Dummy_Raven_Client([ 'server' => 'http://example.com/api/1/store/', 'project' => 1, - )); + ]); $this->assertEquals('http://example.com/api/1/store/', $client->server); } @@ -374,9 +374,9 @@ public function testOptionsFirstArgument() */ public function testDsnInOptionsFirstArg() { - $client = new Dummy_Raven_Client(array( + $client = new Dummy_Raven_Client([ 'dsn' => 'http://public:secret@example.com/1', - )); + ]); $this->assertEquals(1, $client->project); $this->assertEquals('http://example.com/api/1/store/', $client->server); @@ -389,9 +389,9 @@ public function testDsnInOptionsFirstArg() */ public function testDsnInOptionsSecondArg() { - $client = new Dummy_Raven_Client(null, array( + $client = new Dummy_Raven_Client(null, [ 'dsn' => 'http://public:secret@example.com/1', - )); + ]); $this->assertEquals(1, $client->project); $this->assertEquals('http://example.com/api/1/store/', $client->server); @@ -404,12 +404,12 @@ public function testDsnInOptionsSecondArg() */ public function testOptionsFirstArgumentWithOptions() { - $client = new Dummy_Raven_Client(array( + $client = new Dummy_Raven_Client([ 'server' => 'http://example.com/api/1/store/', 'project' => 1, - ), array( + ], [ 'site' => 'foo', - )); + ]); $this->assertEquals('http://example.com/api/1/store/', $client->server); $this->assertEquals('foo', $client->site); @@ -420,9 +420,9 @@ public function testOptionsFirstArgumentWithOptions() */ public function testOptionsExtraData() { - $client = new Dummy_Raven_Client(array('extra' => array('foo' => 'bar'))); + $client = new Dummy_Raven_Client(['extra' => ['foo' => 'bar']]); - $client->captureMessage('Test Message %s', array('foo')); + $client->captureMessage('Test Message %s', ['foo']); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -434,9 +434,9 @@ public function testOptionsExtraData() */ public function testOptionsExtraDataWithNull() { - $client = new Dummy_Raven_Client(array('extra' => array('foo' => 'bar'))); + $client = new Dummy_Raven_Client(['extra' => ['foo' => 'bar']]); - $client->captureMessage('Test Message %s', array('foo'), null); + $client->captureMessage('Test Message %s', ['foo'], null); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -448,9 +448,9 @@ public function testOptionsExtraDataWithNull() */ public function testEmptyExtraData() { - $client = new Dummy_Raven_Client(array('extra' => array())); + $client = new Dummy_Raven_Client(['extra' => []]); - $client->captureMessage('Test Message %s', array('foo')); + $client->captureMessage('Test Message %s', ['foo']); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -478,7 +478,7 @@ public function testCaptureMessageDoesHandleInterpolatedMessage() { $client = new Dummy_Raven_Client(); - $client->captureMessage('Test Message %s', array('foo')); + $client->captureMessage('Test Message %s', ['foo']); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -495,7 +495,7 @@ public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() $this->assertEquals(20160909144742, $client->getRelease()); - $client->captureMessage('Test Message %s', array('foo')); + $client->captureMessage('Test Message %s', ['foo']); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -510,15 +510,15 @@ public function testCaptureMessageSetsInterface() { $client = new Dummy_Raven_Client(); - $client->captureMessage('Test Message %s', array('foo')); + $client->captureMessage('Test Message %s', ['foo']); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals(array( + $this->assertEquals([ 'message' => 'Test Message %s', - 'params' => array('foo'), + 'params' => ['foo'], 'formatted' => 'Test Message foo', - ), $event['sentry.interfaces.Message']); + ], $event['sentry.interfaces.Message']); $this->assertEquals('Test Message foo', $event['message']); } @@ -529,10 +529,10 @@ public function testCaptureMessageHandlesOptionsAsThirdArg() { $client = new Dummy_Raven_Client(); - $client->captureMessage('Test Message %s', array('foo'), array( + $client->captureMessage('Test Message %s', ['foo'], [ 'level' => Dummy_Raven_Client::WARNING, - 'extra' => array('foo' => 'bar') - )); + 'extra' => ['foo' => 'bar'] + ]); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -548,7 +548,7 @@ public function testCaptureMessageHandlesLevelAsThirdArg() { $client = new Dummy_Raven_Client(); - $client->captureMessage('Test Message %s', array('foo'), Dummy_Raven_Client::WARNING); + $client->captureMessage('Test Message %s', ['foo'], Dummy_Raven_Client::WARNING); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -646,7 +646,7 @@ public function testCaptureExceptionHandlesOptionsAsSecondArg() { $client = new Dummy_Raven_Client(); $ex = $this->create_exception(); - $client->captureException($ex, array('culprit' => 'test')); + $client->captureException($ex, ['culprit' => 'test']); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -658,9 +658,9 @@ public function testCaptureExceptionHandlesOptionsAsSecondArg() */ public function testCaptureExceptionHandlesExcludeOption() { - $client = new Dummy_Raven_Client(array( - 'exclude' => array('Exception'), - )); + $client = new Dummy_Raven_Client([ + 'exclude' => ['Exception'], + ]); $ex = $this->create_exception(); $client->captureException($ex, 'test'); $events = $client->getSentEvents(); @@ -694,9 +694,9 @@ public function testCaptureExceptionInvalidUTF8() */ public function testDoesRegisterProcessors() { - $client = new Dummy_Raven_Client(array( - 'processors' => array('\\Raven\\Processor\\SanitizeDataProcessor'), - )); + $client = new Dummy_Raven_Client([ + 'processors' => ['\\Raven\\Processor\\SanitizeDataProcessor'], + ]); $this->assertEquals(1, count($client->processors)); $this->assertInstanceOf('\\Raven\\Processor\\SanitizeDataProcessor', $client->processors[0]); @@ -704,10 +704,10 @@ public function testDoesRegisterProcessors() public function testProcessDoesCallProcessors() { - $data = array("key"=>"value"); + $data = ["key"=>"value"]; $processor = $this->getMockBuilder('Processor') - ->setMethods(array('process')) + ->setMethods(['process']) ->getMock(); $processor->expects($this->once()) ->method('process') @@ -746,19 +746,19 @@ public function testGetDefaultData() { $client = new Dummy_Raven_Client(); $client->transaction->push('test'); - $expected = array( + $expected = [ 'platform' => 'php', 'project' => $client->project, 'server_name' => $client->name, 'site' => $client->site, 'logger' => $client->logger, 'tags' => $client->tags, - 'sdk' => array( + 'sdk' => [ 'name' => 'sentry-php', 'version' => $client::VERSION, - ), + ], 'culprit' => 'test', - ); + ]; $this->assertEquals($expected, $client->get_default_data()); } @@ -768,7 +768,7 @@ public function testGetDefaultData() */ public function testGetHttpData() { - $_SERVER = array( + $_SERVER = [ 'REDIRECT_STATUS' => '200', 'CONTENT_TYPE' => 'text/xml', 'CONTENT_LENGTH' => '99', @@ -782,35 +782,35 @@ public function testGetHttpData() 'QUERY_STRING' => 'q=bitch&l=en', 'REQUEST_URI' => '/welcome/', 'SCRIPT_NAME' => '/index.php', - ); - $_POST = array( + ]; + $_POST = [ 'stamp' => '1c', - ); - $_COOKIE = array( + ]; + $_COOKIE = [ 'donut' => 'chocolat', - ); + ]; - $expected = array( - 'request' => array( + $expected = [ + 'request' => [ 'method' => 'PATCH', 'url' => 'https://getsentry.com/welcome/', 'query_string' => 'q=bitch&l=en', - 'data' => array( + 'data' => [ 'stamp' => '1c', - ), - 'cookies' => array( + ], + 'cookies' => [ 'donut' => 'chocolat', - ), - 'headers' => array( + ], + 'headers' => [ 'Host' => 'getsentry.com', 'Accept' => 'text/html', 'Accept-Charset' => 'utf-8', 'Cookie' => 'cupcake: strawberry', 'Content-Type' => 'text/xml', 'Content-Length' => '99', - ), - ) - ); + ], + ] + ]; $client = new Dummy_Raven_Client(); $this->assertEquals($expected, $client->get_http_data()); @@ -826,15 +826,15 @@ public function testGetUserDataWithSetUser() $id = 'unique_id'; $email = 'foo@example.com'; - $client->user_context(array('id' => $id, 'email' => $email, 'username' => 'my_user', )); + $client->user_context(['id' => $id, 'email' => $email, 'username' => 'my_user', ]); - $expected = array( - 'user' => array( + $expected = [ + 'user' => [ 'id' => $id, 'username' => 'my_user', 'email' => $email, - ) - ); + ] + ]; $this->assertEquals($expected, $client->get_user_data()); } @@ -846,11 +846,11 @@ public function testGetUserDataWithNoUser() { $client = new Dummy_Raven_Client(); - $expected = array( - 'user' => array( + $expected = [ + 'user' => [ 'id' => session_id(), - ) - ); + ] + ]; $this->assertEquals($expected, $client->get_user_data()); } @@ -893,15 +893,15 @@ public function testCaptureMessageWithUserContext() { $client = new Dummy_Raven_Client(); - $client->user_context(array('email' => 'foo@example.com')); + $client->user_context(['email' => 'foo@example.com']); $client->captureMessage('test'); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals(array( + $this->assertEquals([ 'email' => 'foo@example.com', - ), $event['user']); + ], $event['user']); } /** @@ -911,12 +911,12 @@ public function testCaptureMessageWithUnserializableUserData() { $client = new Dummy_Raven_Client(); - $client->user_context(array( + $client->user_context([ 'email' => 'foo@example.com', - 'data' => array( + 'data' => [ 'error' => new \Exception('test'), - ) - )); + ] + ]); $client->captureMessage('test'); $events = $client->getSentEvents(); @@ -933,18 +933,18 @@ public function testCaptureMessageWithTagsContext() { $client = new Dummy_Raven_Client(); - $client->tags_context(array('foo' => 'bar')); - $client->tags_context(array('biz' => 'boz')); - $client->tags_context(array('biz' => 'baz')); + $client->tags_context(['foo' => 'bar']); + $client->tags_context(['biz' => 'boz']); + $client->tags_context(['biz' => 'baz']); $client->captureMessage('test'); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals(array( + $this->assertEquals([ 'foo' => 'bar', 'biz' => 'baz', - ), $event['tags']); + ], $event['tags']); } /** @@ -955,18 +955,18 @@ public function testCaptureMessageWithExtraContext() { $client = new Dummy_Raven_Client(); - $client->extra_context(array('foo' => 'bar')); - $client->extra_context(array('biz' => 'boz')); - $client->extra_context(array('biz' => 'baz')); + $client->extra_context(['foo' => 'bar']); + $client->extra_context(['biz' => 'boz']); + $client->extra_context(['biz' => 'baz']); $client->captureMessage('test'); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); - $this->assertEquals(array( + $this->assertEquals([ 'foo' => 'bar', 'biz' => 'baz', - ), $event['extra']); + ], $event['extra']); } /** @@ -975,11 +975,11 @@ public function testCaptureMessageWithExtraContext() public function testCaptureExceptionContainingLatin1() { // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order - $options = array( - 'mb_detect_order' => array( + $options = [ + 'mb_detect_order' => [ 'ISO-8859-1', 'ASCII', 'UTF-8' - ) - ); + ] + ]; $client = new Dummy_Raven_Client($options); @@ -1000,11 +1000,11 @@ public function testCaptureExceptionContainingLatin1() public function testCaptureExceptionInLatin1File() { // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order - $options = array( - 'mb_detect_order' => array( + $options = [ + 'mb_detect_order' => [ 'ISO-8859-1', 'ASCII', 'UTF-8' - ) - ); + ] + ]; $client = new Dummy_Raven_Client($options); @@ -1039,6 +1039,8 @@ public function testCaptureLastError() $this->assertNull($client->captureLastError()); $this->assertEquals(0, count($client->getSentEvents())); + /** @var $undefined */ + /** @noinspection PhpExpressionResultUnusedInspection */ @$undefined; $client->captureLastError(); @@ -1054,7 +1056,7 @@ public function testCaptureLastError() public function testGetLastEventID() { $client = new Dummy_Raven_Client(); - $client->capture(array('message' => 'test', 'event_id' => 'abc')); + $client->capture(['message' => 'test', 'event_id' => 'abc']); $this->assertEquals('abc', $client->getLastEventID()); } @@ -1063,16 +1065,16 @@ public function testGetLastEventID() */ public function testCustomTransport() { - $events = array(); + $events = []; // transport test requires default client - $client = new \Raven\Client('https://public:secret@sentry.example.com/1', array( + $client = new \Raven\Client('https://public:secret@sentry.example.com/1', [ 'install_default_breadcrumb_handlers' => false, - )); + ]); $client->setTransport(function ($client, $data) use (&$events) { $events[] = $data; }); - $client->capture(array('message' => 'test', 'event_id' => 'abc')); + $client->capture(['message' => 'test', 'event_id' => 'abc']); $this->assertEquals(1, count($events)); } @@ -1136,17 +1138,17 @@ public function cb3(&$data) */ public function testSendCallback() { - $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb1'))); + $client = new Dummy_Raven_Client(['send_callback' => [$this, 'cb1']]); $client->captureMessage('test'); $events = $client->getSentEvents(); $this->assertEquals(0, count($events)); - $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb2'))); + $client = new Dummy_Raven_Client(['send_callback' => [$this, 'cb2']]); $client->captureMessage('test'); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); - $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb3'))); + $client = new Dummy_Raven_Client(['send_callback' => [$this, 'cb3']]); $client->captureMessage('test'); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); @@ -1159,24 +1161,24 @@ public function testSendCallback() public function testSanitizeExtra() { $client = new Dummy_Raven_Client(); - $data = array('extra' => array( - 'context' => array( + $data = ['extra' => [ + 'context' => [ 'line' => 1216, - 'stack' => array( - 1, array(2), 3 - ), - ), - )); + 'stack' => [ + 1, [2], 3 + ], + ], + ]]; $client->sanitize($data); - $this->assertEquals(array('extra' => array( - 'context' => array( + $this->assertEquals(['extra' => [ + 'context' => [ 'line' => 1216, - 'stack' => array( + 'stack' => [ 1, 'Array of length 1', 3 - ), - ), - )), $data); + ], + ], + ]], $data); } /** @@ -1190,11 +1192,11 @@ public function testSanitizeObjects() ] ); $clone = new Dummy_Raven_Client(); - $data = array( - 'extra' => array( + $data = [ + 'extra' => [ 'object' => $clone, - ), - ); + ], + ]; $reflection = new \ReflectionClass($clone); $expected = []; @@ -1240,7 +1242,7 @@ public function testSanitizeObjects() } } - $this->assertEquals(array('extra' => array('object' => $expected)), $data); + $this->assertEquals(['extra' => ['object' => $expected]], $data); } /** @@ -1249,16 +1251,16 @@ public function testSanitizeObjects() public function testSanitizeTags() { $client = new Dummy_Raven_Client(); - $data = array('tags' => array( + $data = ['tags' => [ 'foo' => 'bar', - 'baz' => array('biz'), - )); + 'baz' => ['biz'], + ]]; $client->sanitize($data); - $this->assertEquals(array('tags' => array( + $this->assertEquals(['tags' => [ 'foo' => 'bar', 'baz' => 'Array', - )), $data); + ]], $data); } /** @@ -1267,14 +1269,14 @@ public function testSanitizeTags() public function testSanitizeUser() { $client = new Dummy_Raven_Client(); - $data = array('user' => array( + $data = ['user' => [ 'email' => 'foo@example.com', - )); + ]]; $client->sanitize($data); - $this->assertEquals(array('user' => array( + $this->assertEquals(['user' => [ 'email' => 'foo@example.com', - )), $data); + ]], $data); } /** @@ -1283,24 +1285,24 @@ public function testSanitizeUser() public function testSanitizeRequest() { $client = new Dummy_Raven_Client(); - $data = array('request' => array( - 'context' => array( + $data = ['request' => [ + 'context' => [ 'line' => 1216, - 'stack' => array( - 1, array(2), 3 - ), - ), - )); + 'stack' => [ + 1, [2], 3 + ], + ], + ]]; $client->sanitize($data); - $this->assertEquals(array('request' => array( - 'context' => array( + $this->assertEquals(['request' => [ + 'context' => [ 'line' => 1216, - 'stack' => array( + 'stack' => [ 1, 'Array of length 1', 3 - ), - ), - )), $data); + ], + ], + ]], $data); } /** @@ -1309,30 +1311,30 @@ public function testSanitizeRequest() public function testSanitizeContexts() { $client = new Dummy_Raven_Client(); - $data = array('contexts' => array( - 'context' => array( + $data = ['contexts' => [ + 'context' => [ 'line' => 1216, - 'stack' => array( - 1, array( + 'stack' => [ + 1, [ 'foo' => 'bar', - 'level4' => array(array('level5', 'level5 a'), 2), - ), 3 - ), - ), - )); + 'level4' => [['level5', 'level5 a'], 2], + ], 3 + ], + ], + ]]; $client->sanitize($data); - $this->assertEquals(array('contexts' => array( - 'context' => array( + $this->assertEquals(['contexts' => [ + 'context' => [ 'line' => 1216, - 'stack' => array( - 1, array( + 'stack' => [ + 1, [ 'foo' => 'bar', - 'level4' => array('Array of length 2', 2), - ), 3 - ), - ), - )), $data); + 'level4' => ['Array of length 2', 2], + ], 3 + ], + ], + ]], $data); } /** @@ -1342,17 +1344,17 @@ public function testBuildCurlCommandEscapesInput() { $data = '{"foo": "\'; ls;"}'; $client = new Dummy_Raven_Client(); - $result = $client->buildCurlCommand('http://foo.com', $data, array()); + $result = $client->buildCurlCommand('http://foo.com', $data, []); $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); - $result = $client->buildCurlCommand('http://foo.com', $data, array('key' => 'value')); + $result = $client->buildCurlCommand('http://foo.com', $data, ['key' => 'value']); $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); $client->verify_ssl = false; - $result = $client->buildCurlCommand('http://foo.com', $data, array()); + $result = $client->buildCurlCommand('http://foo.com', $data, []); $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); - $result = $client->buildCurlCommand('http://foo.com', $data, array('key' => 'value')); + $result = $client->buildCurlCommand('http://foo.com', $data, ['key' => 'value']); $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); } @@ -1362,9 +1364,9 @@ public function testBuildCurlCommandEscapesInput() public function testUserContextWithoutMerge() { $client = new Dummy_Raven_Client(); - $client->user_context(array('foo' => 'bar'), false); - $client->user_context(array('baz' => 'bar'), false); - $this->assertEquals(array('baz' => 'bar'), $client->context->user); + $client->user_context(['foo' => 'bar'], false); + $client->user_context(['baz' => 'bar'], false); + $this->assertEquals(['baz' => 'bar'], $client->context->user); } /** @@ -1373,9 +1375,9 @@ public function testUserContextWithoutMerge() public function testUserContextWithMerge() { $client = new Dummy_Raven_Client(); - $client->user_context(array('foo' => 'bar'), true); - $client->user_context(array('baz' => 'bar'), true); - $this->assertEquals(array('foo' => 'bar', 'baz' => 'bar'), $client->context->user); + $client->user_context(['foo' => 'bar'], true); + $client->user_context(['baz' => 'bar'], true); + $this->assertEquals(['foo' => 'bar', 'baz' => 'bar'], $client->context->user); } /** @@ -1384,9 +1386,9 @@ public function testUserContextWithMerge() public function testUserContextWithMergeAndNull() { $client = new Dummy_Raven_Client(); - $client->user_context(array('foo' => 'bar'), true); + $client->user_context(['foo' => 'bar'], true); $client->user_context(null, true); - $this->assertEquals(array('foo' => 'bar'), $client->context->user); + $this->assertEquals(['foo' => 'bar'], $client->context->user); } /** @@ -1421,63 +1423,63 @@ public function testCurrentUrl($serverVars, $options, $expected, $message) */ public function currentUrlProvider() { - return array( - array( - array(), - array(), + return [ + [ + [], + [], null, 'No url expected for empty REQUEST_URI' - ), - array( - array( + ], + [ + [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', - ), - array(), + ], + [], 'http://example.com/', 'The url is expected to be http with the request uri' - ), - array( - array( + ], + [ + [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' - ), - array(), + ], + [], 'https://example.com/', 'The url is expected to be https because of HTTPS on' - ), - array( - array( + ], + [ + [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', 'SERVER_PORT' => '443' - ), - array(), + ], + [], 'https://example.com/', 'The url is expected to be https because of the server port' - ), - array( - array( + ], + [ + [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', 'X-FORWARDED-PROTO' => 'https' - ), - array(), + ], + [], 'http://example.com/', 'The url is expected to be http because the X-Forwarded header is ignored' - ), - array( - array( + ], + [ + [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', 'X-FORWARDED-PROTO' => 'https' - ), - array('trust_x_forwarded_proto' => true), + ], + ['trust_x_forwarded_proto' => true], 'https://example.com/', 'The url is expected to be https because the X-Forwarded header is trusted' - ) - ); + ] + ]; } /** @@ -1520,40 +1522,40 @@ public function testGettersAndSetters() $client = new Dummy_Raven_Client(); $property_method__convert_path = new \ReflectionMethod('\\Raven\\Client', '_convertPath'); $property_method__convert_path->setAccessible(true); - $callable = array($this, 'stabClosureVoid'); - - $data = array( - array('environment', null, 'value', ), - array('environment', null, null, ), - array('release', null, 'value', ), - array('release', null, null, ), - array('app_path', null, 'value', $property_method__convert_path->invoke($client, 'value')), - array('app_path', null, null, ), - array('app_path', null, false, null, ), - array('excluded_app_paths', null, array('value'), - array($property_method__convert_path->invoke($client, 'value'))), - array('excluded_app_paths', null, array(), null), - array('excluded_app_paths', null, null), - array('prefixes', null, array('value'), array($property_method__convert_path->invoke($client, 'value'))), - array('prefixes', null, array()), - array('send_callback', null, $callable), - array('send_callback', null, null), - array('transport', null, $callable), - array('transport', null, null), - array('server', 'ServerEndpoint', 'http://example.com/'), - array('server', 'ServerEndpoint', 'http://example.org/'), - array('_lasterror', null, null, ), - array('_lasterror', null, 'value', ), - array('_lasterror', null, mt_rand(100, 999), ), - array('_last_sentry_error', null, (object)array('error' => 'test',), ), - array('_last_event_id', null, mt_rand(100, 999), ), - array('_last_event_id', null, 'value', ), - array('extra_data', '_extra_data', array('key' => 'value'), ), - array('processors', 'processors', array(), ), - array('processors', 'processors', array('key' => 'value'), ), - array('_shutdown_function_has_been_set', null, true), - array('_shutdown_function_has_been_set', null, false), - ); + $callable = [$this, 'stabClosureVoid']; + + $data = [ + ['environment', null, 'value', ], + ['environment', null, null, ], + ['release', null, 'value', ], + ['release', null, null, ], + ['app_path', null, 'value', $property_method__convert_path->invoke($client, 'value')], + ['app_path', null, null,], + ['app_path', null, false, null,], + ['excluded_app_paths', null, ['value'], + [$property_method__convert_path->invoke($client, 'value')]], + ['excluded_app_paths', null, [], null], + ['excluded_app_paths', null, null], + ['prefixes', null, ['value'], [$property_method__convert_path->invoke($client, 'value')]], + ['prefixes', null, []], + ['send_callback', null, $callable], + ['send_callback', null, null], + ['transport', null, $callable], + ['transport', null, null], + ['server', 'ServerEndpoint', 'http://example.com/'], + ['server', 'ServerEndpoint', 'http://example.org/'], + ['_lasterror', null, null,], + ['_lasterror', null, 'value',], + ['_lasterror', null, mt_rand(100, 999),], + ['_last_sentry_error', null, (object)['error' => 'test',],], + ['_last_event_id', null, mt_rand(100, 999),], + ['_last_event_id', null, 'value',], + ['extra_data', '_extra_data', ['key' => 'value'],], + ['processors', 'processors', [],], + ['processors', 'processors', ['key' => 'value'],], + ['_shutdown_function_has_been_set', null, true], + ['_shutdown_function_has_been_set', null, false], + ]; foreach ($data as &$datum) { $this->subTestGettersAndSettersDatum($client, $datum); } @@ -1672,14 +1674,14 @@ public function testTranslateSeverity() $reflection->setAccessible(true); $client = new Dummy_Raven_Client(); - $predefined = array(E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, + $predefined = [E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR, E_USER_WARNING, - E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR, ); + E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR, ]; if (version_compare(PHP_VERSION, '5.3.0', '>=')) { $predefined[] = E_DEPRECATED; $predefined[] = E_USER_DEPRECATED; } - $predefined_values = array('debug', 'info', 'warning', 'warning', 'error', 'fatal', ); + $predefined_values = ['debug', 'info', 'warning', 'warning', 'error', 'fatal', ]; // step 1 foreach ($predefined as &$key) { @@ -1687,28 +1689,28 @@ public function testTranslateSeverity() } $this->assertEquals('error', $client->translateSeverity(123456)); // step 2 - $client->registerSeverityMap(array()); - $this->assertMixedValueAndArray(array(), $reflection->getValue($client)); + $client->registerSeverityMap([]); + $this->assertMixedValueAndArray([], $reflection->getValue($client)); foreach ($predefined as &$key) { $this->assertContains($client->translateSeverity($key), $predefined_values); } $this->assertEquals('error', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123456)); // step 3 - $client->registerSeverityMap(array(123456 => 'foo', )); - $this->assertMixedValueAndArray(array(123456 => 'foo'), $reflection->getValue($client)); + $client->registerSeverityMap([123456 => 'foo', ]); + $this->assertMixedValueAndArray([123456 => 'foo'], $reflection->getValue($client)); foreach ($predefined as &$key) { $this->assertContains($client->translateSeverity($key), $predefined_values); } $this->assertEquals('foo', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); // step 4 - $client->registerSeverityMap(array(E_USER_ERROR => 'bar', )); + $client->registerSeverityMap([E_USER_ERROR => 'bar', ]); $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); $this->assertEquals('error', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); // step 5 - $client->registerSeverityMap(array(E_USER_ERROR => 'bar', 123456 => 'foo', )); + $client->registerSeverityMap([E_USER_ERROR => 'bar', 123456 => 'foo', ]); $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); $this->assertEquals('foo', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); @@ -1743,10 +1745,10 @@ public function testCurl_method() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ) + ] ); $client->captureMessage('foobar'); $this->assertTrue($client->_send_http_synchronous); @@ -1754,10 +1756,10 @@ public function testCurl_method() // step 2 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'exec', 'install_default_breadcrumb_handlers' => false, - ) + ] ); $client->captureMessage('foobar'); $this->assertFalse($client->_send_http_synchronous); @@ -1774,10 +1776,10 @@ public function testCurl_method_async() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'async', 'install_default_breadcrumb_handlers' => false, - ) + ] ); $object = $client->get_curl_handler(); $this->assertInternalType('object', $object); @@ -1823,7 +1825,7 @@ public function test_server_variable() $this->assertNotNull($actual); $this->assertEquals($value, $actual); } - foreach (array('foo', 'bar', 'foobar', '123456', 'SomeLongNonExistedKey') as $key => $value) { + foreach (['foo', 'bar', 'foobar', '123456', 'SomeLongNonExistedKey'] as $key => $value) { if (!isset($_SERVER[$key])) { $actual = $method->invoke(null, $key); $this->assertNotNull($actual); @@ -1839,9 +1841,9 @@ public function test_server_variable() public function testEncodeTooDepth() { $client = new Dummy_Raven_Client(); - $data_broken = array(); + $data_broken = []; for ($i = 0; $i < 1024; $i++) { - $data_broken = array($data_broken); + $data_broken = [$data_broken]; } $value = $client->encode($data_broken); if (version_compare(PHP_VERSION, '5.5.0', '>=')) { @@ -1858,7 +1860,7 @@ public function testEncodeTooDepth() public function testEncode() { $client = new Dummy_Raven_Client(); - $data = array('some' => (object)array('value' => 'data'), 'foo' => array('bar', null, 123), false); + $data = ['some' => (object)['value' => 'data'], 'foo' => ['bar', null, 123], false]; $json_stringify = json_encode($data); $value = $client->encode($data); $this->assertNotFalse($value); @@ -1883,8 +1885,8 @@ public function testRegisterDefaultBreadcrumbHandlers() $this->markTestSkipped('HHVM stacktrace behaviour'); return; } - $previous = set_error_handler(array($this, 'stabClosureErrorHandler'), E_USER_NOTICE); - new \Raven\Client(null, array()); + $previous = set_error_handler([$this, 'stabClosureErrorHandler'], E_USER_NOTICE); + new \Raven\Client(null, []); $this->_closure_called = false; trigger_error('foobar', E_USER_NOTICE); $u = $this->_closure_called; @@ -1924,9 +1926,9 @@ public function stabClosureFalse() return false; } - private $_debug_backtrace = array(); + private $_debug_backtrace = []; - public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $context = array()) + public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $context = []) { $this->_closure_called = true; $this->_debug_backtrace = debug_backtrace(); @@ -1943,13 +1945,13 @@ public function testOnShutdown() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ) + ] ); $this->assertEquals(0, count($client->_pending_events)); - $client->_pending_events[] = array('foo' => 'bar'); + $client->_pending_events[] = ['foo' => 'bar']; $client->sendUnsentErrors(); $this->assertTrue($client->_send_http_synchronous); $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); @@ -1975,10 +1977,10 @@ public function testOnShutdown() // step 1 $client = null; $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'async', 'install_default_breadcrumb_handlers' => false, - ) + ] ); $ch = new Dummy_Raven_CurlHandler(); $client->set_curl_handler($ch); @@ -1995,15 +1997,15 @@ public function testNonWorkingSendSendCallback() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ) + ] ); $this->_closure_called = false; - $client->setSendCallback(array($this, 'stabClosureNull')); + $client->setSendCallback([$this, 'stabClosureNull']); $this->assertFalse($this->_closure_called); - $data = array('foo' => 'bar'); + $data = ['foo' => 'bar']; $client->send($data); $this->assertTrue($this->_closure_called); $this->assertTrue($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); @@ -2011,9 +2013,9 @@ public function testNonWorkingSendSendCallback() $this->_closure_called = false; $client->_send_http_synchronous = false; $client->_send_http_asynchronous_curl_exec_called = false; - $client->setSendCallback(array($this, 'stabClosureFalse')); + $client->setSendCallback([$this, 'stabClosureFalse']); $this->assertFalse($this->_closure_called); - $data = array('foo' => 'bar'); + $data = ['foo' => 'bar']; $client->send($data); $this->assertTrue($this->_closure_called); $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); @@ -2025,13 +2027,13 @@ public function testNonWorkingSendSendCallback() public function testNonWorkingSendDSNEmpty() { $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ) + ] ); $client->server = null; - $data = array('foo' => 'bar'); + $data = ['foo' => 'bar']; $client->send($data); $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); } @@ -2043,23 +2045,23 @@ public function testNonWorkingSendSetTransport() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ) + ] ); $this->_closure_called = false; - $client->setTransport(array($this, 'stabClosureNull')); + $client->setTransport([$this, 'stabClosureNull']); $this->assertFalse($this->_closure_called); - $data = array('foo' => 'bar'); + $data = ['foo' => 'bar']; $client->send($data); $this->assertTrue($this->_closure_called); $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); // step 2 $this->_closure_called = false; - $client->setSendCallback(array($this, 'stabClosureFalse')); + $client->setSendCallback([$this, 'stabClosureFalse']); $this->assertFalse($this->_closure_called); - $data = array('foo' => 'bar'); + $data = ['foo' => 'bar']; $client->send($data); $this->assertTrue($this->_closure_called); $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); @@ -2070,13 +2072,13 @@ public function testNonWorkingSendSetTransport() */ public function test__construct_handlers() { - foreach (array(true, false) as $u1) { - foreach (array(true, false) as $u2) { + foreach ([true, false] as $u1) { + foreach ([true, false] as $u2) { $client = new Dummy_Raven_Client( - null, array( + null, [ 'install_default_breadcrumb_handlers' => $u1, 'install_shutdown_handler' => $u2, - ) + ] ); $this->assertEquals($u1, $client->dummy_breadcrumbs_handlers_has_set); $this->assertEquals($u2, $client->dummy_shutdown_handlers_has_set); @@ -2091,10 +2093,10 @@ public function test__construct_handlers() public function test__destruct_calls_close_functions() { $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'install_default_breadcrumb_handlers' => false, 'install_shutdown_handler' => false, - ) + ] ); $client::$_close_curl_resource_called = false; $client->close_all_children_link(); @@ -2125,8 +2127,8 @@ public function testGet_user_data() // step 3 session_id($session_id); - @session_start(array('use_cookies' => false, )); - $_SESSION = array('foo' => 'bar'); + @session_start(['use_cookies' => false, ]); + $_SESSION = ['foo' => 'bar']; $output = $client->get_user_data(); $this->assertInternalType('array', $output); $this->assertArrayHasKey('user', $output); @@ -2144,13 +2146,13 @@ public function testGet_user_data() */ public function testCaptureLevel() { - foreach (array(\Raven\Client::MESSAGE_LIMIT * 3, 100) as $length) { + foreach ([\Raven\Client::MESSAGE_LIMIT * 3, 100] as $length) { $message = ''; for ($i = 0; $i < $length; $i++) { $message .= chr($i % 256); } $client = new Dummy_Raven_Client(); - $client->capture(array('message' => $message, )); + $client->capture(['message' => $message, ]); $events = $client->getSentEvents(); $this->assertEquals(1, count($events)); $event = array_pop($events); @@ -2162,7 +2164,7 @@ public function testCaptureLevel() } $client = new Dummy_Raven_Client(); - $client->capture(array('message' => 'foobar')); + $client->capture(['message' => 'foobar', ]); $events = $client->getSentEvents(); $event = array_pop($events); $input = $client->get_http_data(); @@ -2171,15 +2173,15 @@ public function testCaptureLevel() $this->assertArrayNotHasKey('environment', $event); $client = new Dummy_Raven_Client(); - $client->capture(array('message' => 'foobar', 'request' => array('foo' => 'bar'), )); + $client->capture(['message' => 'foobar', 'request' => ['foo' => 'bar'], ]); $events = $client->getSentEvents(); $event = array_pop($events); - $this->assertEquals(array('foo' => 'bar'), $event['request']); + $this->assertEquals(['foo' => 'bar'], $event['request']); $this->assertArrayNotHasKey('release', $event); $this->assertArrayNotHasKey('environment', $event); - foreach (array(false, true) as $u1) { - foreach (array(false, true) as $u2) { + foreach ([false, true] as $u1) { + foreach ([false, true] as $u2) { $client = new Dummy_Raven_Client(); if ($u1) { $client->setRelease('foo'); @@ -2187,7 +2189,7 @@ public function testCaptureLevel() if ($u2) { $client->setEnvironment('bar'); } - $client->capture(array('message' => 'foobar', )); + $client->capture(['message' => 'foobar', ]); $events = $client->getSentEvents(); $event = array_pop($events); if ($u1) { @@ -2209,13 +2211,13 @@ public function testCaptureLevel() */ public function testCaptureNoUserAndRequest() { - $client = new Dummy_Raven_Client_No_Http(null, array( + $client = new Dummy_Raven_Client_No_Http(null, [ 'install_default_breadcrumb_handlers' => false, - )); + ]); $session_id = session_id(); session_write_close(); session_id(''); - $client->capture(array('user' => '', 'request' => '')); + $client->capture(['user' => '', 'request' => '']); $events = $client->getSentEvents(); $event = array_pop($events); $this->assertArrayNotHasKey('user', $event); @@ -2223,7 +2225,7 @@ public function testCaptureNoUserAndRequest() // step 3 session_id($session_id); - @session_start(array('use_cookies' => false, )); + @session_start(['use_cookies' => false, ]); } /** @@ -2233,19 +2235,19 @@ public function testCaptureNonEmptyBreadcrumb() { $client = new Dummy_Raven_Client(); $ts1 = microtime(true); - $client->breadcrumbs->record(array('foo' => 'bar')); - $client->breadcrumbs->record(array('honey' => 'clover')); - $client->capture(array()); + $client->breadcrumbs->record(['foo' => 'bar']); + $client->breadcrumbs->record(['honey' => 'clover']); + $client->capture([]); $events = $client->getSentEvents(); $event = array_pop($events); foreach ($event['breadcrumbs'] as &$crumb) { $this->assertGreaterThanOrEqual($ts1, $crumb['timestamp']); unset($crumb['timestamp']); } - $this->assertEquals(array( - array('foo' => 'bar'), - array('honey' => 'clover'), - ), $event['breadcrumbs']); + $this->assertEquals([ + ['foo' => 'bar'], + ['honey' => 'clover'], + ], $event['breadcrumbs']); } @@ -2255,7 +2257,7 @@ public function testCaptureNonEmptyBreadcrumb() public function testCaptureAutoLogStacks() { $client = new Dummy_Raven_Client(); - $client->capture(array('auto_log_stacks' => true), true); + $client->capture(['auto_log_stacks' => true], true); $events = $client->getSentEvents(); $event = array_pop($events); $this->assertArrayHasKey('stacktrace', $event); @@ -2268,10 +2270,10 @@ public function testCaptureAutoLogStacks() public function testSend_http_asynchronous_curl_exec() { $client = new Dummy_Raven_Client_With_Sync_Override( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'exec', 'install_default_breadcrumb_handlers' => false, - ) + ] ); if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { unlink(Dummy_Raven_Client_With_Sync_Override::test_filename()); @@ -2305,11 +2307,11 @@ public function testSampleRateAbsolute() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, 'sample_rate' => 0, - ) + ] ); for ($i = 0; $i < 1000; $i++) { $client->captureMessage('foobar'); @@ -2318,11 +2320,11 @@ public function testSampleRateAbsolute() // step 2 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, 'sample_rate' => 1, - ) + ] ); for ($i = 0; $i < 1000; $i++) { $client->captureMessage('foobar'); @@ -2336,11 +2338,11 @@ public function testSampleRateAbsolute() public function testSampleRatePrc() { $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', array( + 'http://public:secret@example.com/1', [ 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, 'sample_rate' => 0.5, - ) + ] ); $u_true = false; $u_false = false; diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 8a407618f..2b40c6728 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -21,7 +21,7 @@ public function setUp() { $this->errorLevel = error_reporting(); $this->errorHandlerCalled = false; - $this->existingErrorHandler = set_error_handler(array($this, 'errorHandler'), -1); + $this->existingErrorHandler = set_error_handler([$this, 'errorHandler'], -1); // improves the reliability of tests if (function_exists('error_clear_last')) { error_clear_last(); @@ -38,14 +38,14 @@ public function tearDown() restore_exception_handler(); set_error_handler($this->existingErrorHandler); // // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level - // set_error_handler(array($this, 'errorHandler'), error_reporting()); + // set_error_handler([$this, 'errorHandler'], error_reporting()); error_reporting($this->errorLevel); } public function testErrorsAreLoggedAsExceptions() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException', 'sendUnsentErrors')) + ->setMethods(['captureException', 'sendUnsentErrors']) ->getMock(); $client->expects($this->once()) ->method('captureException') @@ -58,7 +58,7 @@ public function testErrorsAreLoggedAsExceptions() public function testExceptionsAreLogged() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $client->expects($this->once()) ->method('captureException') @@ -73,7 +73,7 @@ public function testExceptionsAreLogged() public function testErrorHandlerPassErrorReportingPass() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $client->expects($this->once()) ->method('captureException'); @@ -88,7 +88,7 @@ public function testErrorHandlerPassErrorReportingPass() public function testErrorHandlerPropagates() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $client->expects($this->never()) ->method('captureException'); @@ -105,7 +105,7 @@ public function testErrorHandlerPropagates() public function testExceptionHandlerPropagatesToNative() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $client->expects($this->exactly(2)) ->method('captureException') @@ -146,7 +146,7 @@ public function testExceptionHandlerPropagatesToNative() public function testErrorHandlerRespectsErrorReportingDefault() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $client->expects($this->once()) ->method('captureException'); @@ -167,7 +167,7 @@ public function testErrorHandlerRespectsErrorReportingDefault() public function testSilentErrorsAreNotReportedWithGlobal() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $client->expects($this->never()) ->method('captureException'); @@ -188,7 +188,7 @@ public function testSilentErrorsAreNotReportedWithGlobal() public function testSilentErrorsAreNotReportedWithLocal() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $client->expects($this->never()) ->method('captureException'); @@ -205,7 +205,7 @@ public function testSilentErrorsAreNotReportedWithLocal() public function testShouldCaptureFatalErrorBehavior() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $handler = new \Raven\ErrorHandler($client); @@ -217,7 +217,7 @@ public function testShouldCaptureFatalErrorBehavior() public function testErrorHandlerDefaultsErrorReporting() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $client->expects($this->never()) ->method('captureException'); @@ -233,7 +233,7 @@ public function testErrorHandlerDefaultsErrorReporting() public function testFluidInterface() { $client = $this->getMockBuilder('Client') - ->setMethods(array('captureException')) + ->setMethods(['captureException']) ->getMock(); $handler = new \Raven\ErrorHandler($client); $result = $handler->registerErrorHandler(); diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 167ffbd2d..d808e00e9 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -12,7 +12,7 @@ class DummyIntegration_Raven_Client extends \Raven\Client { - private $__sent_events = array(); + private $__sent_events = []; public function getSentEvents() { @@ -20,7 +20,7 @@ public function getSentEvents() } public function send(&$data) { - if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { + if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, [&$data]) === false) { // if send_callback returns falsely, end native send return; } diff --git a/tests/Processor/RemoveCookiesProcessorTest.php b/tests/Processor/RemoveCookiesProcessorTest.php index f3cf0d108..fb6eafa95 100644 --- a/tests/Processor/RemoveCookiesProcessorTest.php +++ b/tests/Processor/RemoveCookiesProcessorTest.php @@ -43,41 +43,38 @@ public function testProcess($inputData, $expectedData) public function processDataProvider() { - return array( - array( - array( - 'request' => array( + return [ + [ + [ + 'request' => [ 'foo' => 'bar', - ), - ), - array( - 'request' => array( - 'foo' => 'bar', - ), - ), - ), - array( - array( - 'request' => array( + ], + ], [ + 'request' => [ 'foo' => 'bar', + ], + ], + ], [ + [ + 'request' => [ + 'foo' => 'bar', 'cookies' => 'baz', - 'headers' => array( - 'Cookie' => 'bar', + 'headers' => [ + 'Cookie' => 'bar', 'AnotherHeader' => 'foo', - ), - ), - ), - array( - 'request' => array( - 'foo' => 'bar', + ], + ], + ], [ + 'request' => [ + 'foo' => 'bar', 'cookies' => RemoveCookiesProcessor::STRING_MASK, - 'headers' => array( - 'Cookie' => RemoveCookiesProcessor::STRING_MASK, + 'headers' => [ + 'Cookie' => RemoveCookiesProcessor::STRING_MASK, 'AnotherHeader' => 'foo', - ), - ), - ), - ), - ); + ], + ], + ], + ], + ]; } } diff --git a/tests/Processor/RemoveHttpBodyProcessorTest.php b/tests/Processor/RemoveHttpBodyProcessorTest.php index 22ebafa73..7491d6066 100644 --- a/tests/Processor/RemoveHttpBodyProcessorTest.php +++ b/tests/Processor/RemoveHttpBodyProcessorTest.php @@ -43,84 +43,76 @@ public function testProcess($inputData, $expectedData) public function processDataProvider() { - return array( - array( - array( - 'request' => array( + return [ + [ + [ + 'request' => [ 'method' => 'POST', - 'data' => array( + 'data' => [ 'foo' => 'bar', - ), - ), - ), - array( - 'request' => array( + ], + ], + ], [ + 'request' => [ 'data' => RemoveHttpBodyProcessor::STRING_MASK, - ), - ), - ), - array( - array( - 'request' => array( + ], + ], + ], [ + [ + 'request' => [ 'method' => 'PUT', - 'data' => array( + 'data' => [ 'foo' => 'bar', - ), - ), - ), - array( - 'request' => array( + ], + ], + ], [ + 'request' => [ 'data' => RemoveHttpBodyProcessor::STRING_MASK, - ), - ), - ), - array( - array( - 'request' => array( + ], + ], + ], [ + [ + 'request' => [ 'method' => 'PATCH', - 'data' => array( + 'data' => [ 'foo' => 'bar', - ), - ), - ), - array( - 'request' => array( + ], + ], + ], + [ + 'request' => [ 'data' => RemoveHttpBodyProcessor::STRING_MASK, - ), - ), - ), - array( - array( - 'request' => array( + ], + ], + ], [ + [ + 'request' => [ 'method' => 'DELETE', - 'data' => array( + 'data' => [ 'foo' => 'bar', - ), - ), - ), - array( - 'request' => array( + ], + ], + ], [ + 'request' => [ 'data' => RemoveHttpBodyProcessor::STRING_MASK, - ), - ), - ), - array( - array( - 'request' => array( + ], + ], + ], [ + [ + 'request' => [ 'method' => 'GET', - 'data' => array( + 'data' => [ 'foo' => 'bar', - ), - ), - ), - array( - 'request' => array( - 'data' => array( + ], + ], + ], [ + 'request' => [ + 'data' => [ 'foo' => 'bar', - ), - ), - ), - ), - ); + ], + ], + ], + ], + ]; } } diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index 0f792fc63..195f7e3e4 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -17,24 +17,24 @@ class SanitizeDataProcessorTest extends \PHPUnit_Framework_TestCase { public function testDoesFilterHttpData() { - $data = array( - 'request' => array( - 'data' => array( + $data = [ + 'request' => [ + 'data' => [ 'foo' => 'bar', 'password' => 'hello', 'the_secret' => 'hello', 'a_password_here' => 'hello', 'mypasswd' => 'hello', 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', - 'card_number' => array( + 'card_number' => [ '1111', '2222', '3333', '4444' - ) - ), - ) - ); + ] + ], + ] + ]; $client = new Dummy_Raven_Client(); $processor = new SanitizeDataProcessor($client); @@ -55,13 +55,13 @@ public function testDoesFilterHttpData() public function testDoesFilterSessionId() { - $data = array( - 'request' => array( - 'cookies' => array( + $data = [ + 'request' => [ + 'cookies' => [ ini_get('session.name') => 'abc', - ), - ) - ); + ], + ] + ]; $client = new Dummy_Raven_Client(); $processor = new SanitizeDataProcessor($client); @@ -73,11 +73,11 @@ public function testDoesFilterSessionId() public function testDoesFilterCreditCard() { - $data = array( - 'extra' => array( + $data = [ + 'extra' => [ 'ccnumba' => '4242424242424242', - ), - ); + ], + ]; $client = new Dummy_Raven_Client(); $processor = new SanitizeDataProcessor($client); @@ -94,10 +94,10 @@ public function testSettingProcessorOptions() $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); - $options = array( + $options = [ 'fields_re' => '/(api_token)/i', 'values_re' => '/^(?:\d[ -]*?){15,16}$/' - ); + ]; $processor->setProcessorOptions($options); @@ -135,9 +135,9 @@ public function testOverrideOptions($processorOptions, $client_options, $dsn) */ public function testOverridenSanitize($processorOptions, $client_options, $dsn) { - $data = array( - 'request' => array( - 'data' => array( + $data = [ + 'request' => [ + 'data' => [ 'foo' => 'bar', 'password' => 'hello', 'the_secret' => 'hello', @@ -145,13 +145,13 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) 'mypasswd' => 'hello', 'api_token' => 'nioenio3nrio3jfny89nby9bhr#RML#R', 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', - 'card_number' => array( + 'card_number' => [ '1111111111111111', '2222', - ) - ), - ) - ); + ] + ], + ] + ]; $client = new Dummy_Raven_Client($dsn, $client_options); /** @@ -185,22 +185,22 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) */ public static function overrideDataProvider() { - $processorOptions = array( - SanitizeDataProcessor::class => array( + $processorOptions = [ + SanitizeDataProcessor::class => [ 'fields_re' => '/(api_token)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/' - ) - ); + 'values_re' => '/^(?:\d[ -]*?){15,16}$/', + ], + ]; - $client_options = array( - 'processors' => array(SanitizeDataProcessor::class), + $client_options = [ + 'processors' => [SanitizeDataProcessor::class], 'processorOptions' => $processorOptions - ); + ]; $dsn = 'http://9aaa31f9a05b4e72aaa06aa8157a827a:9aa7aa82a9694a08a1a7589a2a035a9a@sentry.domain.tld/1'; - return array( - array($processorOptions, $client_options, $dsn) - ); + return [ + [$processorOptions, $client_options, $dsn] + ]; } } diff --git a/tests/Processor/SanitizeHttpHeadersProcessorTest.php b/tests/Processor/SanitizeHttpHeadersProcessorTest.php index 3d04345fb..9b0579197 100644 --- a/tests/Processor/SanitizeHttpHeadersProcessorTest.php +++ b/tests/Processor/SanitizeHttpHeadersProcessorTest.php @@ -29,9 +29,9 @@ protected function setUp() ->getMock(); $this->processor = new SanitizeHttpHeadersProcessor($client); - $this->processor->setProcessorOptions(array( - 'sanitize_http_headers' => array('User-Defined-Header'), - )); + $this->processor->setProcessorOptions([ + 'sanitize_http_headers' => ['User-Defined-Header'], + ]); } /** @@ -46,43 +46,40 @@ public function testProcess($inputData, $expectedData) public function processDataProvider() { - return array( - array( - array( - 'request' => array( - 'headers' => array( + return [ + [ + [ + 'request' => [ + 'headers' => [ 'Authorization' => 'foo', 'AnotherHeader' => 'bar', - ), - ), - ), - array( - 'request' => array( - 'headers' => array( + ], + ], + ], [ + 'request' => [ + 'headers' => [ 'Authorization' => SanitizeHttpHeadersProcessor::STRING_MASK, 'AnotherHeader' => 'bar', - ), - ), - ), - ), - array( - array( - 'request' => array( - 'headers' => array( + ], + ], + ], + ], [ + [ + 'request' => [ + 'headers' => [ 'User-Defined-Header' => 'foo', 'AnotherHeader' => 'bar', - ), - ), - ), - array( - 'request' => array( - 'headers' => array( + ], + ], + ], [ + 'request' => [ + 'headers' => [ 'User-Defined-Header' => SanitizeHttpHeadersProcessor::STRING_MASK, 'AnotherHeader' => 'bar', - ), - ), - ), - ), - ); + ], + ], + ], + ], + ]; } } diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php index d1a4dc6da..66fa08111 100644 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -75,25 +75,4 @@ public function testProcessWithPreviousException() } } } - - /** - * Gets all the public and abstracts methods of a given class. - * - * @param string $className The FCQN of the class - * - * @return array - */ - private function getClassMethods($className) - { - $class = new \ReflectionClass($className); - $methods = array(); - - foreach ($class->getMethods() as $method) { - if ($method->isPublic() || $method->isAbstract()) { - $methods[] = $method->getName(); - } - } - - return $methods; - } } diff --git a/tests/SerializerAbstractTest.php b/tests/SerializerAbstractTest.php index 34970ab1c..7ad3313b9 100644 --- a/tests/SerializerAbstractTest.php +++ b/tests/SerializerAbstractTest.php @@ -54,9 +54,9 @@ public function testArraysAreArrays($serialize_all_objects) if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } - $input = array(1, 2, 3); + $input = [1, 2, 3]; $result = $serializer->serialize($input); - $this->assertEquals(array('1', '2', '3'), $result); + $this->assertEquals(['1', '2', '3'], $result); } /** @@ -74,7 +74,7 @@ public function testStdClassAreArrays($serialize_all_objects) $input = new \stdClass(); $input->foo = 'BAR'; $result = $serializer->serialize($input); - $this->assertEquals(array('foo' => 'BAR'), $result); + $this->assertEquals(['foo' => 'BAR'], $result); } public function testObjectsAreStrings() @@ -95,7 +95,7 @@ public function testObjectsAreNotStrings() $serializer->setAllObjectSerialize(true); $input = new \Raven\Tests\SerializerTestObject(); $result = $serializer->serialize($input); - $this->assertEquals(array('key' => 'value'), $result); + $this->assertEquals(['key' => 'value'], $result); } /** @@ -184,10 +184,10 @@ public function testRecursionMaxDepth($serialize_all_objects) if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } - $input = array(); + $input = []; $input[] = &$input; $result = $serializer->serialize($input, 3); - $this->assertEquals(array(array(array('Array of length 1'))), $result); + $this->assertEquals([[['Array of length 1']]], $result); $result = $serializer->serialize([], 3); $this->assertEquals([], $result); @@ -327,9 +327,9 @@ public function testObjectInArray() $class_name = static::get_test_class(); /** @var \Raven\Serializer $serializer **/ $serializer = new $class_name(); - $input = array('foo' => new \Raven\Tests\SerializerTestObject()); + $input = ['foo' => new \Raven\Tests\SerializerTestObject()]; $result = $serializer->serialize($input); - $this->assertEquals(array('foo' => 'Object Raven\\Tests\\SerializerTestObject'), $result); + $this->assertEquals(['foo' => 'Object Raven\\Tests\\SerializerTestObject'], $result); } public function testObjectInArraySerializeAll() @@ -338,9 +338,9 @@ public function testObjectInArraySerializeAll() /** @var \Raven\Serializer $serializer **/ $serializer = new $class_name(); $serializer->setAllObjectSerialize(true); - $input = array('foo' => new \Raven\Tests\SerializerTestObject()); + $input = ['foo' => new \Raven\Tests\SerializerTestObject()]; $result = $serializer->serialize($input); - $this->assertEquals(array('foo' => array('key' => 'value')), $result); + $this->assertEquals(['foo' => ['key' => 'value']], $result); } /** @@ -355,12 +355,12 @@ public function testBrokenEncoding($serialize_all_objects) if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } - foreach (array('7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90') as $key) { + foreach (['7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90'] as $key) { $input = pack('H*', $key); $result = $serializer->serialize($input); $this->assertInternalType('string', $result); if (function_exists('mb_detect_encoding')) { - $this->assertContains(mb_detect_encoding($result), array('ASCII', 'UTF-8')); + $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); } } } @@ -378,7 +378,7 @@ public function testLongString($serialize_all_objects) $serializer->setAllObjectSerialize(true); } for ($i = 0; $i < 100; $i++) { - foreach (array(100, 1000, 1010, 1024, 1050, 1100, 10000) as $length) { + foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { $input = ''; for ($i = 0; $i < $length; $i++) { $input .= chr(mt_rand(0, 255)); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 623739ca6..a7bd9df51 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -241,19 +241,19 @@ public function testGetFrameArgumentsDoesNotModifyCapturedArgs() // Modification of these would be really bad, since if control is returned (non-fatal error) we'll have altered the state of things! $originalFoo = 'bloopblarp'; $newFoo = $originalFoo; - $nestedArray = array( + $nestedArray = [ 'key' => 'xxxxxxxxxx', - ); + ]; - $frame = array( + $frame = [ "file" => dirname(__FILE__) . "/resources/a.php", "line" => 9, - "args"=> array( + "args"=> [ &$newFoo, &$nestedArray, - ), + ], "function" => "a_test", - ); + ]; $result = Stacktrace::getFrameArguments($frame, 5); diff --git a/tests/UtilTest.php b/tests/UtilTest.php index 77d25067b..d492b2291 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -20,14 +20,14 @@ class Raven_Tests_UtilTest extends \PHPUnit_Framework_TestCase { public function testGetReturnsDefaultOnMissing() { - $input = array('foo' => 'bar'); + $input = ['foo' => 'bar']; $result = \Raven\Util::get($input, 'baz', 'foo'); $this->assertEquals('foo', $result); } public function testGetReturnsPresentValuesEvenWhenEmpty() { - $input = array('foo' => ''); + $input = ['foo' => '']; $result = \Raven\Util::get($input, 'foo', 'bar'); $this->assertEquals('', $result); } From 4c104eda8e609511bd60ab0236ba797909932e4f Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Mon, 8 May 2017 14:25:40 +0300 Subject: [PATCH 0296/1161] Delete always true conditions with version compare --- lib/Raven/Client.php | 8 +------- tests/ClientTest.php | 33 ++------------------------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c86fb0fcc..3a27bb27b 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -719,8 +719,6 @@ public function captureMessage($message, $params = [], $data = [], */ public function captureException($exception, $data = null, $logger = null, $vars = null) { - $has_chained_exceptions = version_compare(PHP_VERSION, '5.3.0', '>='); - if (in_array(get_class($exception), $this->exclude)) { return null; } @@ -762,7 +760,7 @@ public function captureException($exception, $data = null, $logger = null, $vars ]; $exceptions[] = $exc_data; - } while ($has_chained_exceptions && $exc = $exc->getPrevious()); + } while ($exc = $exc->getPrevious()); $data['exception'] = [ 'values' => array_reverse($exceptions), @@ -1479,12 +1477,8 @@ public function translateSeverity($severity) case E_USER_NOTICE: return \Raven\Client::INFO; case E_STRICT: return \Raven\Client::INFO; case E_RECOVERABLE_ERROR: return \Raven\Client::ERROR; - } - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - switch ($severity) { case E_DEPRECATED: return \Raven\Client::WARN; case E_USER_DEPRECATED: return \Raven\Client::WARN; - } } return \Raven\Client::ERROR; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index d16b10c40..50715372e 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -591,10 +591,6 @@ public function testCaptureExceptionSetsInterfaces() */ public function testCaptureExceptionChainedException() { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - $this->markTestSkipped('PHP 5.3 required for chained exceptions.'); - } - # TODO: it'd be nice if we could mock the stacktrace extraction function here $client = new Dummy_Raven_Client(); $ex = $this->create_chained_exception(); @@ -615,10 +611,6 @@ public function testCaptureExceptionChainedException() */ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - $this->markTestSkipped('PHP 5.3 required for chained exceptions.'); - } - $client = new Dummy_Raven_Client(); $e1 = new \ErrorException('First', 0, E_DEPRECATED); $e2 = new \ErrorException('Second', 0, E_NOTICE, __FILE__, __LINE__, $e1); @@ -1677,10 +1669,8 @@ public function testTranslateSeverity() $predefined = [E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR, ]; - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - $predefined[] = E_DEPRECATED; - $predefined[] = E_USER_DEPRECATED; - } + $predefined[] = E_DEPRECATED; + $predefined[] = E_USER_DEPRECATED; $predefined_values = ['debug', 'info', 'warning', 'warning', 'error', 'fatal', ]; // step 1 @@ -1838,25 +1828,6 @@ public function test_server_variable() } } - public function testEncodeTooDepth() - { - $client = new Dummy_Raven_Client(); - $data_broken = []; - for ($i = 0; $i < 1024; $i++) { - $data_broken = [$data_broken]; - } - $value = $client->encode($data_broken); - if (version_compare(PHP_VERSION, '5.5.0', '>=')) { - $this->assertFalse($value, 'Broken data encoded successfully with native method'); - } else { - if ($value !== false) { - $this->markTestSkipped(); - } else { - $this->assertEquals('eJyLjh4Fo2AUjFgQOwpGwSgYuQAA3Q7g1w==', $value, 'Native json_encode error'); - } - } - } - public function testEncode() { $client = new Dummy_Raven_Client(); From 13823416fa0bedabcd3d56d1d855bbaf95b049e4 Mon Sep 17 00:00:00 2001 From: Stefan Braspenning Date: Thu, 18 May 2017 14:36:36 +0200 Subject: [PATCH 0297/1161] Fix: type in documentation of ErrorHandler unhnalded should be unhandled --- lib/Raven/ErrorHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 49cfe2ca8..21358c1ad 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -144,7 +144,7 @@ public function shouldCaptureFatalError($type) } /** - * Register a handler which will intercept unhnalded exceptions and report them to the + * Register a handler which will intercept unhandled exceptions and report them to the * associated Sentry client. * * @param bool $call_existing Call any existing exception handlers after processing From 49879ab304a7d08fcd814e0c36f3ee72f7bc43e7 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Thu, 18 May 2017 20:41:25 +0300 Subject: [PATCH 0298/1161] Delete "pecl install uopz" --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1fe853dd8..87fc01183 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,8 +48,6 @@ cache: before_install: - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi - if [ "$DISABLE_ASSERTIONS" = "1" ]; then echo 'zend.assertions = -1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]] && [ "$HHVM" != "1" ]; then pecl install uopz; fi - - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]] && [ "$HHVM" != "1" ]; then echo "extension=uopz.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - composer self-update install: travis_retry composer install --no-interaction --prefer-source From f55683da13516ee6e60429b67b2d60a5692a58c6 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 22 May 2017 11:24:44 +0200 Subject: [PATCH 0299/1161] Increase minimum PHP version requirement to 5.3 --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 930e24f3b..6b494cc42 100644 --- a/composer.json +++ b/composer.json @@ -17,13 +17,14 @@ "monolog/monolog": "*" }, "require": { - "php": ">=5.2.4", + "php": "^5.3|^7.0", "ext-curl": "*" }, "suggest": { "ext-hash": "*", "ext-json": "*", "ext-mbstring": "*", + "immobiliare/sentry-php": "Fork that fixes support for PHP 5.2", "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" }, "conflict": { From a99776492b1c027b3fe14591d46526e2ebe96468 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 23 May 2017 16:04:07 +0200 Subject: [PATCH 0300/1161] Improve Travis build (#465) * remove uopz * stop allowing failures on 7.1 * prefer-dist with composer * Remove last usage of assert() to get rid of build steps with DISABLE_ASSERTIONS on Travis * Simplify the build Travis matrix --- .scrutinizer.yml | 2 +- .travis.yml | 35 ++++++--------------------------- test/Raven/Tests/ClientTest.php | 8 +------- 3 files changed, 8 insertions(+), 37 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 7059f7248..9f84d3027 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,7 +5,7 @@ tools: php_code_coverage: true external_code_coverage: timeout: 2400 # There can be another pull request in progress - runs: 6 # PHP 5.3 + PHP 5.4 + PHP 5.5 + PHP 5.6 + PHP 7.0 * 2 + runs: 6 # PHP 5.3 + PHP 5.4 + PHP 5.5 + PHP 5.6 + PHP 7.0 + PHP 7.1 build: environment: diff --git a/.travis.yml b/.travis.yml index 3bcc2d4ce..ac14dc682 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: php +sudo: false php: - 5.3 @@ -8,41 +9,19 @@ php: - 7.0 - 7.1 - nightly - - hhvm-3.12 +env: + - REMOVE_XDEBUG="0" + - REMOVE_XDEBUG="1" matrix: allow_failures: - - php: 7.1 - php: hhvm-3.12 - php: nightly fast_finish: true include: - - php: 7.0 - env: REMOVE_XDEBUG="0" DISABLE_ASSERTIONS="1" - - php: 7.0 - env: REMOVE_XDEBUG="1" DISABLE_ASSERTIONS="1" - - php: 7.1 - env: REMOVE_XDEBUG="0" DISABLE_ASSERTIONS="1" - - php: 7.1 - env: REMOVE_XDEBUG="1" DISABLE_ASSERTIONS="1" - - php: nightly - env: REMOVE_XDEBUG="0" DISABLE_ASSERTIONS="1" - php: hhvm-3.12 env: REMOVE_XDEBUG="0" HHVM="1" dist: trusty - exclude: - - php: hhvm-3.12 - env: REMOVE_XDEBUG="0" - - php: hhvm-3.12 - env: REMOVE_XDEBUG="1" - - php: nightly - env: REMOVE_XDEBUG="1" - -env: - - REMOVE_XDEBUG="0" - - REMOVE_XDEBUG="1" - -sudo: false cache: directories: @@ -50,12 +29,9 @@ cache: before_install: - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi - - if [ "$DISABLE_ASSERTIONS" = "1" ]; then echo 'zend.assertions = -1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]] && [ "$HHVM" != "1" ]; then pecl install uopz; fi - - if [[ ${TRAVIS_PHP_VERSION:0:1} > "5" ]] && [ "$HHVM" != "1" ]; then echo "extension=uopz.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - composer self-update -install: travis_retry composer install --no-interaction --prefer-source +install: travis_retry composer install --no-interaction --prefer-dist script: - composer phpcs @@ -68,3 +44,4 @@ after_script: - if [ $(phpenv version-name) = "5.5" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi - if [ $(phpenv version-name) = "5.6" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi - if [ $(phpenv version-name) = "7.0" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "7.1" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi diff --git a/test/Raven/Tests/ClientTest.php b/test/Raven/Tests/ClientTest.php index 5fe827613..bf420ee37 100644 --- a/test/Raven/Tests/ClientTest.php +++ b/test/Raven/Tests/ClientTest.php @@ -10,7 +10,7 @@ function simple_function($a=null, $b=null, $c=null) { - assert(0); + throw new \RuntimeException('This simple function should fail before reaching this line!'); } function invalid_encoding() @@ -664,12 +664,6 @@ public function testCaptureExceptionHandlesExcludeOption() public function testCaptureExceptionInvalidUTF8() { - if (version_compare(PHP_VERSION, '7.0', '>=')) { - if (ini_get('zend.assertions') != 1) { - $this->markTestSkipped('Production environment does not execute asserts'); - return; - } - } $client = new Dummy_Raven_Client(); try { invalid_encoding(); From 98ba82b2c0984f87914682136a0a074cf6078119 Mon Sep 17 00:00:00 2001 From: Nokita Kaze Date: Thu, 1 Jun 2017 13:01:19 +0300 Subject: [PATCH 0301/1161] Refactoring for async calls Curl (exec): CA-cert Coverage if events really sending Monolog fix for PHP 7 --- .travis.yml | 3 +- composer.json | 5 +- lib/Raven/Client.php | 44 +++-- lib/Raven/CurlHandler.php | 4 +- tests/ClientTest.php | 341 +++++++++++++++++++++++++++++++++++++- tests/bin/httpserver.php | 47 ++++++ 6 files changed, 425 insertions(+), 19 deletions(-) create mode 100644 tests/bin/httpserver.php diff --git a/.travis.yml b/.travis.yml index aebad09ab..d4f129c71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,13 +24,14 @@ env: - REMOVE_XDEBUG="0" - REMOVE_XDEBUG="1" -sudo: false +sudo: required cache: directories: - $HOME/.composer/cache before_install: + - sudo apt-get install --yes net-tools - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi - composer self-update diff --git a/composer.json b/composer.json index 1d58c2c07..4ae394441 100644 --- a/composer.json +++ b/composer.json @@ -11,10 +11,13 @@ "email": "dcramer@gmail.com" } ], + "minimum-stability": "dev", "require-dev": { "friendsofphp/php-cs-fixer": "~2.1", "phpunit/phpunit": "^4.8 || ^5.0", - "monolog/monolog": "*" + "ext-openssl": "*", + "nokitakaze/testhttpserver": "^0.1.0", + "monolog/monolog": "~1.0" }, "require": { "php": ">=5.5", diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 3a27bb27b..c020599cd 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -363,6 +363,7 @@ public function __destruct() { // Force close curl resource $this->close_curl_resource(); + $this->force_send_async_curl_events(); } /** @@ -1231,20 +1232,27 @@ protected function send_http($url, $data, $headers = []) } } + /** + * @param string $url + * @param string $data + * @param array $headers + * @return string + * + * This command line ensures exec returns immediately while curl runs in the background + */ protected function buildCurlCommand($url, $data, $headers) { - // TODO(dcramer): support ca_cert - $cmd = $this->curl_path.' -X POST '; + $post_fields = ''; foreach ($headers as $key => $value) { - $cmd .= '-H ' . escapeshellarg($key.': '.$value). ' '; - } - $cmd .= '-d ' . escapeshellarg($data) . ' '; - $cmd .= escapeshellarg($url) . ' '; - $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send) - if (!$this->verify_ssl) { - $cmd .= '-k '; - } - $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background + $post_fields .= ' -H '.escapeshellarg($key.': '.$value); + } + $cmd = sprintf( + '%s -X POST%s -d %s %s -m %d %s%s> /dev/null 2>&1 &', + escapeshellcmd($this->curl_path), $post_fields, + escapeshellarg($data), escapeshellarg($url), $this->timeout, + !$this->verify_ssl ? '-k ' : '', + !empty($this->ca_cert) ? '--cacert '.escapeshellarg($this->ca_cert).' ' : '' + ); return $cmd; } @@ -1301,9 +1309,10 @@ protected function send_http_synchronous($url, $data, $headers) $errno = curl_errno($this->_curl_instance); // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE - if ((($errno == 60) || ($errno == 77)) && !is_null($ca_cert)) { + if (in_array($errno, [CURLE_SSL_CACERT, 77]) && !is_null($ca_cert)) { curl_setopt($this->_curl_instance, CURLOPT_CAINFO, $ca_cert); $buffer = curl_exec($this->_curl_instance); + $errno = curl_errno($this->_curl_instance); } if ($errno != 0) { $this->_lasterror = curl_error($this->_curl_instance); @@ -1512,15 +1521,20 @@ public function set_user_data($id, $email = null, $data = []) $this->user_context(array_merge($user, $data)); } + public function force_send_async_curl_events() + { + if (!is_null($this->_curl_handler)) { + $this->_curl_handler->join(); + } + } + public function onShutdown() { if (!defined('RAVEN_CLIENT_END_REACHED')) { define('RAVEN_CLIENT_END_REACHED', true); } $this->sendUnsentErrors(); - if ($this->curl_method == 'async') { - $this->_curl_handler->join(); - } + $this->force_send_async_curl_events(); } /** diff --git a/lib/Raven/CurlHandler.php b/lib/Raven/CurlHandler.php index 43986e104..9368cf299 100644 --- a/lib/Raven/CurlHandler.php +++ b/lib/Raven/CurlHandler.php @@ -16,7 +16,6 @@ * @package raven */ -// TODO(dcramer): handle ca_cert class CurlHandler { protected $join_timeout; @@ -73,6 +72,9 @@ public function enqueue($url, $data = null, $headers = []) public function join($timeout = null) { + if (count($this->requests) == 0) { + return; + } if (!isset($timeout)) { $timeout = $this->join_timeout; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 271c11e49..8c0928987 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -11,7 +11,6 @@ namespace Raven\Tests; use Raven\Client; -use Raven\Serializer; function simple_function($a = null, $b = null, $c = null) { @@ -212,6 +211,119 @@ public function join($timeout = null) class Raven_Tests_ClientTest extends \PHPUnit_Framework_TestCase { + protected static $_folder = null; + + public static function setUpBeforeClass() + { + parent::setUpBeforeClass(); + self::$_folder = sys_get_temp_dir().'/sentry_server_'.microtime(true); + mkdir(self::$_folder); + + // Root CA #A1 + // Сертификат CA #A4, signed by CA #A1 (end-user certificate) + $temporary_openssl_file_conf_alt = self::$_folder.'/openssl-config-alt.tmp'; + file_put_contents($temporary_openssl_file_conf_alt, sprintf('HOME = . +RANDFILE = $ENV::HOME/.rnd +[ req ] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[ req_distinguished_name ] + +[ v3_req ] +subjectAltName = DNS:*.org, DNS:127.0.0.1, DNS:%s +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +extendedKeyUsage = serverAuth,clientAuth +certificatePolicies = @polsect + +[polsect] +policyIdentifier = 1.2.3.4.5.6.7 +userNotice.1 = @notice + +[notice] +explicitText = "UTF8:Please add this certificate to black list" +organization = "Sentry" +', strtolower(gethostname()))); + $certificate_offset = mt_rand(0, 10000) << 16; + + // CA #A1 + $csr_a1 = openssl_csr_new([ + "countryName" => "US", + "localityName" => "Nowhere", + "organizationName" => "Sentry", + "organizationalUnitName" => "Development Center. CA #A1", + "commonName" => "Sentry: Test HTTP Server: CA #A1", + "emailAddress" => "noreply@sentry.example.com", + ], $ca_a1_pair, [ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, // currently only RSA works + 'encrypt_key' => true, + ]); + if ($csr_a1 === false) { + throw new \Exception("Can not create CSR pair A1"); + } + $crt_a1 = openssl_csr_sign($csr_a1, null, $ca_a1_pair, 1, [ + "digest_alg" => "sha256", + ], hexdec("0A1") + $certificate_offset); + if ($crt_a1 === false) { + throw new \Exception("Can not create certificate A1"); + } + + /** @noinspection PhpUndefinedVariableInspection */ + openssl_x509_export($crt_a1, $certout); + openssl_pkey_export($ca_a1_pair, $pkeyout); + file_put_contents(self::$_folder.'/crt_a1.crt', $certout, LOCK_EX); + file_put_contents(self::$_folder.'/crt_a1.pem', $pkeyout, LOCK_EX); + openssl_pkey_export($ca_a1_pair, $pkeyout, 'password'); + file_put_contents(self::$_folder.'/crt_a1.p.pem', $pkeyout, LOCK_EX); + unset($csr_a1, $certout, $pkeyout); + + // CA #A4 + $csr_a4 = openssl_csr_new([ + "countryName" => "US", + "localityName" => "Nowhere", + "organizationName" => "Sentry", + "organizationalUnitName" => "Development Center", + "commonName" => "127.0.0.1", + "emailAddress" => "noreply@sentry.example.com", + ], $ca_a4_pair, [ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, // currently only RSA works + 'encrypt_key' => true, + ]); + if ($csr_a4 === false) { + throw new \Exception("Can not create CSR pair A4"); + } + $crt_a4 = openssl_csr_sign($csr_a4, file_get_contents(self::$_folder.'/crt_a1.crt'), + $ca_a1_pair, 1, [ + "digest_alg" => "sha256", + 'config' => $temporary_openssl_file_conf_alt, + 'x509_extensions' => 'v3_req', + ], hexdec("0A4") + $certificate_offset); + if ($crt_a4 === false) { + throw new \Exception("Can not create certificate A4"); + } + + /** @noinspection PhpUndefinedVariableInspection */ + openssl_x509_export($crt_a4, $certout); + openssl_pkey_export($ca_a4_pair, $pkeyout); + file_put_contents(self::$_folder.'/crt_a4.crt', $certout, LOCK_EX); + file_put_contents(self::$_folder.'/crt_a4.c.crt', + $certout."\n".file_get_contents(self::$_folder.'/crt_a1.crt'), LOCK_EX); + file_put_contents(self::$_folder.'/crt_a4.pem', $pkeyout, LOCK_EX); + openssl_pkey_export($ca_a4_pair, $pkeyout, 'password'); + file_put_contents(self::$_folder.'/crt_a4.p.pem', $pkeyout, LOCK_EX); + unset($csr_a4, $certout, $pkeyout); + } + + public static function tearDownAfterClass() + { + exec(sprintf('rm -rf %s', escapeshellarg(self::$_folder))); + } + public function tearDown() { parent::tearDown(); @@ -1330,6 +1442,14 @@ public function testBuildCurlCommandEscapesInput() { $data = '{"foo": "\'; ls;"}'; $client = new Dummy_Raven_Client(); + $client->timeout = 5; + $result = $client->buildCurlCommand('http://foo.com', $data, []); + $folder = realpath(__DIR__.'/../lib/Raven/data'); + $this->assertEquals( + 'curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 --cacert \''.$folder. + '/cacert.pem\' > /dev/null 2>&1 &', $result + ); + $client->ca_cert = null; $result = $client->buildCurlCommand('http://foo.com', $data, []); $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); @@ -2354,4 +2474,223 @@ public function testSetAllObjectSerialize() $this->assertFalse($o1->getAllObjectSerialize()); $this->assertFalse($o2->getAllObjectSerialize()); } + + /** + * @return int + */ + protected static function get_port() + { + exec('netstat -n -t', $buf); + $current_used_ports = []; + foreach ($buf as $line) { + list(, , , $local_addr) = preg_split('_\\s+_', $line, 7); + if (preg_match('_:([0-9]+)$_', $local_addr, $a)) { + $current_used_ports[] = (int)$a[1]; + } + } + $current_used_ports = array_unique($current_used_ports); + sort($current_used_ports); + + do { + $port = mt_rand(55000, 60000); + } while (in_array($port, $current_used_ports)); + + return $port; + } + + public function dataDirectSend() + { + $data = []; + + $block1 = []; + $block1[] = [ + 'options' => [ + 'dsn' => 'http://login:password@127.0.0.1:{port}/5', + ], + 'server_options' => [], + 'timeout' => 0, + 'is_failed' => false, + ]; + $block1[] = [ + 'options' => [ + 'dsn' => 'http://login:password@127.0.0.1:{port}/5', + 'curl_method' => 'async', + ], + 'server_options' => [], + 'timeout' => 0, + 'is_failed' => false, + ]; + $block1[] = [ + 'options' => [ + 'dsn' => 'http://login:password@127.0.0.1:{port}/5', + 'curl_method' => 'exec', + ], + 'server_options' => [], + 'timeout' => 5, + 'is_failed' => false, + ]; + + $j = count($block1); + for ($i = 0; $i < $j; $i++) { + $datum = $block1[$i]; + $datum['server_options']['http_code'] = 403; + $datum['is_failed'] = true; + $block1[] = $datum; + } + + $block_ssl = [['options' => [], 'server_options' => [], 'timeout' => 0, 'is_failed' => false]]; + $block_ssl[] = [ + 'options' => [ + 'dsn' => 'http://login:password@127.0.0.1:{port}/5', + 'ca_cert' => '{folder}/crt_a1.crt', + ], + 'server_options' => [ + 'ssl_server_certificate_file' => '{folder}/crt_a4.c.crt', + 'ssl_server_key_file' => '{folder}/crt_a4.pem', + 'is_ssl' => true, + ], + 'timeout' => 5, + 'is_failed' => false, + ]; + + foreach ($block1 as $b1) { + foreach ($block_ssl as $b2) { + $datum = [ + 'options' => array_merge( + isset($b1['options']) ? $b1['options'] : [], + isset($b2['options']) ? $b2['options'] : [] + ), + 'server_options' => array_merge( + isset($b1['server_options']) ? $b1['server_options'] : [], + isset($b2['server_options']) ? $b2['server_options'] : [] + ), + 'timeout' => max($b1['timeout'], $b2['timeout']), + 'is_failed' => ($b1['is_failed'] or $b2['is_failed']), + ]; + if (isset($datum['options']['ca_cert'])) { + $datum['options']['dsn'] = str_replace('http://', 'https://', $datum['options']['dsn']); + } + + $data[] = $datum; + } + } + + return $data; + } + + /** + * @param array $sentry_options + * @param array $server_options + * @param integer $timeout + * @param boolean $is_failed + * + * @dataProvider dataDirectSend + */ + public function testDirectSend($sentry_options, $server_options, $timeout, $is_failed) + { + foreach ($sentry_options as &$value) { + if (is_string($value)) { + $value = str_replace('{folder}', self::$_folder, $value); + } + } + foreach ($server_options as &$value) { + if (is_string($value)) { + $value = str_replace('{folder}', self::$_folder, $value); + } + $value = str_replace('{folder}', self::$_folder, $value); + } + unset($value); + + $port = self::get_port(); + $sentry_options['dsn'] = str_replace('{port}', $port, $sentry_options['dsn']); + $sentry_options['timeout'] = 10; + + $client = new Client($sentry_options); + $output_filename = tempnam(self::$_folder, 'output_http_'); + foreach ( + [ + 'port' => $port, + 'output_filename' => $output_filename, + ] as $key => $value + ) { + $server_options[$key] = $value; + } + + $filename = tempnam(self::$_folder, 'sentry_http_'); + file_put_contents($filename, serialize((object)$server_options), LOCK_EX); + + $cli_output_filename = tempnam(sys_get_temp_dir(), 'output_http_'); + exec( + sprintf( + 'php '.__DIR__.'/bin/httpserver.php --config=%s >%s 2>&1 &', + escapeshellarg($filename), + escapeshellarg($cli_output_filename) + ) + ); + $ts = microtime(true); + $u = false; + do { + if (preg_match('_listen_i', file_get_contents($cli_output_filename))) { + $u = true; + break; + } + } while ($ts + 10 > microtime(true)); + $this->assertTrue($u, 'Can not start Test HTTP Server'); + unset($u, $ts); + + $extra = ['foo'.mt_rand(0, 10000) => microtime(true).':'.mt_rand(20, 100)]; + $event = $client->captureMessage( + 'Test Message', [], [ + 'level' => Client::INFO, + 'extra' => $extra, + ] + ); + $client->sendUnsentErrors(); + $client->force_send_async_curl_events(); + if ($is_failed) { + if (!isset($sentry_options['curl_method']) or + !in_array($sentry_options['curl_method'], ['async', 'exec']) + ) { + if (isset($server_options['http_code'])) { + $this->assertNotNull($client->getLastError()); + $this->assertNotNull($client->getLastSentryError()); + } + } + } else { + $this->assertNotNull($event); + } + if ($timeout > 0) { + usleep($timeout * 1000000); + } + $this->assertFileExists($output_filename); + $buf = file_get_contents($output_filename); + $server_input = unserialize($buf); + $this->assertNotFalse($server_input); + /** @var \NokitaKaze\TestHTTPServer\ClientDatum $connection */ + $connection = $server_input['connection']; + $this->assertNotNull($connection); + $this->assertEquals('/api/5/store/', $connection->request_url); + + $body = base64_decode($connection->blob_body); + if (function_exists('gzuncompress')) { + $new_body = gzuncompress($body); + if ($new_body !== false) { + $body = $new_body; + } + unset($new_body); + } + $body = json_decode($body); + $this->assertEquals(5, $body->project); + $this->assertEquals('', $body->site); + $this->assertEquals('Test Message', $body->message); + $this->assertEquals($event, $body->event_id); + $this->assertEquals($extra, (array)$body->extra); + $this->assertEquals('info', $body->level); + + $this->assertRegExp( + '|^Sentry sentry_timestamp=[0-9.]+,\\s+sentry_client=sentry\\-php/[a-z0-9.-]+,\\s+sentry_version=[0-9]+,'. + '\\s+sentry_key=login,\\s+sentry_secret=password|', + $connection->request_head_params['X-Sentry-Auth'] + ); + } } diff --git a/tests/bin/httpserver.php b/tests/bin/httpserver.php new file mode 100644 index 000000000..c9449be29 --- /dev/null +++ b/tests/bin/httpserver.php @@ -0,0 +1,47 @@ +#!/usr/bin/php +onRequest = function ($server, $connect) { + /** @var \NokitaKaze\TestHTTPServer\Server $server */ + /** @var \NokitaKaze\TestHTTPServer\ClientDatum $connect */ + $output_filename = $server->get_option('output_filename'); + + $connect->server = null; + $saved = file_put_contents( + $output_filename, serialize( + [ + 'connection' => $connect, + ] + ), LOCK_EX + ); + if ($saved === false) { + $server->answer($connect, 500, 'OK', ''); + $server->close_connection($connect); + echo "can not save content\n"; + exit(1); + } + if ($server->get_option('http_code') == 403) { + $server->answer($connect, 403, 'Denied', json_encode(['error' => 'Denied'])); + } else { + $server->answer($connect, 200, 'OK', json_encode(['event' => uniqid()])); + } + + $server->close_connection($connect); + echo "done\n"; + exit(0); +}; +$server = new \NokitaKaze\TestHTTPServer\Server($options); + +$server->init_listening(); +echo "listen...\n"; +$server->listen(time() + 60); + +echo "No connection\n"; +exit(1); +?> \ No newline at end of file From e02e83a656643753c17e06a323922356d395d3c9 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 6 May 2017 23:48:41 +0200 Subject: [PATCH 0302/1161] Add a class to store the client configuration --- UPGRADE-2.0.md | 222 + bin/sentry | 36 +- composer.json | 5 +- lib/Raven/Client.php | 597 +- lib/Raven/ClientBuilder.php | 135 + lib/Raven/ClientBuilderInterface.php | 36 + lib/Raven/Configuration.php | 1058 ++++ lib/Raven/Stacktrace.php | 16 +- lib/Raven/data/cacert.pem | 5134 ----------------- tests/Breadcrumbs/ErrorHandlerTest.php | 10 +- tests/Breadcrumbs/MonologTest.php | 29 +- tests/ClientBuilderTest.php | 104 + tests/ClientTest.php | 1483 ++--- tests/ConfigurationTest.php | 209 + tests/IntegrationTest.php | 8 +- tests/Processor/SanitizeDataProcessorTest.php | 70 +- .../SanitizeStacktraceProcessorTest.php | 3 +- tests/StacktraceTest.php | 9 +- 18 files changed, 2473 insertions(+), 6691 deletions(-) create mode 100644 UPGRADE-2.0.md create mode 100644 lib/Raven/ClientBuilder.php create mode 100644 lib/Raven/ClientBuilderInterface.php create mode 100644 lib/Raven/Configuration.php delete mode 100644 lib/Raven/data/cacert.pem create mode 100644 tests/ClientBuilderTest.php create mode 100644 tests/ConfigurationTest.php diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md new file mode 100644 index 000000000..d86e475ef --- /dev/null +++ b/UPGRADE-2.0.md @@ -0,0 +1,222 @@ +# Upgrade from 1.7 to 2.0 + +### Client options + +- The `verify_ssl` option has been renamed to `ssl_verification`. +- The `ca_cert` option has been renamed to `ssl_ca_file`. +- The `environment` option has been renamed to `current_environment`. +- The `http_proxy` option has been renamed to `proxy`. +- The `processorOptions` option has been renamed to `processors_options`. +- The `exclude` option has been renamed to `excluded_exceptions`. +- The `send_callback` option has been renamed to `should_capture`. +- The `name` option has been renamed to `server_name`. +- The `project` option has been removed. +- The `extra_data` option has been removed in favour of setting additional data + directly in the context. +- The `open_timeout` option has been added to set the maximum number of seconds + to wait for the server connection to open. +- The `excluded_loggers` option has been added to set the list of logger 'progname's + to exclude from breadcrumbs. +- The `environments` option has been added to set the whitelist of environments + that will send notifications to Sentry. +- The `serialize_all_object` option has been added to configure whether all the + object instances should be serialized. +- The `context_lines` option has been added to configure the number of lines of + code context to capture. + +### Client + +- The constructor of the `Client` class has changed its signature. The only way + to set the DSN is now to set it in the options passed to the `Configuration` + class. + + Before: + + ```php + public function __construct($options_or_dsn = null, $options = array()) + { + // ... + } + ``` + + After: + + ```php + public function __construct(Configuration $config) + { + // ... + } + ``` + +- The methods `Client::getRelease` and `Client::setRelease` have been removed. + You should use `Configuration::getRelease()` and `Configuration::setRelease` + instead. + + Before: + + ```php + $client->getRelease(); + $client->setRelease(...); + ``` + + After: + + ```php + $client->getConfig()->getRelease(); + $client->getConfig()->setRelease(...); + ``` + +- The methods `Client::getEnvironment` and `Client::setEnvironment` have been + removed. You should use `Configuration::getCurrentEnvironment` and + `Configuration::setCurrentEnvironment` instead. + + Before: + + ```php + $client->getEnvironment(); + $client->setEnvironment(...); + ``` + + After: + + ```php + $client->getConfig()->getCurrentEnvironment(); + $client->getConfig()->setCurrentEnvironment(...); + ``` + +- The methods `Client::getDefaultPrefixes` and `Client::setPrefixes` have been + removed. You should use `Configuration::getPrefixes` and `Configuration::setPrefixes` + instead. + + Before: + + ```php + $client->getPrefixes(); + $client->setPrefixes(...); + ``` + + After: + + ```php + $client->getConfig()->getPrefixes(); + $client->getConfig()->setPrefixes(...); + ``` + +- The methods `Client::getAppPath` and `Client::setAppPath` have been removed. + You should use `Configuration::getProjectRoot` and `Configuration::setProjectRoot` + instead. + + Before: + + ```php + $client->getAppPath(); + $client->setAppPath(...); + ``` + + After: + + ```php + $client->getConfig()->getProjectRoot(); + $client->getConfig()->setProjectRoot(...); + +- The methods `Client::getExcludedAppPaths` and `Client::setExcludedAppPaths` + have been removed. You should use `Configuration::getExcludedProjectPaths` + and `Configuration::setExcludedProjectPaths` instead. + + Before: + + ```php + $client->getExcludedAppPaths(); + $client->setExcludedAppPaths(...); + ``` + + After: + + ```php + $client->getConfig()->getExcludedProjectPaths(); + $client->getConfig()->setExcludedProjectPaths(...); + +- The methods `Client::getSendCallback` and `Client::setSendCallback` have been + removed. You should use `Configuration::shouldCapture` and `Configuration::setShouldCapture` + instead. + + Before: + + ```php + $client->getSendCallback(); + $client->setSendCallback(...); + ``` + + After: + + ```php + $client->getConfig()->shouldCapture(); + $client->getConfig()->setShouldCapture(...); + +- The method `Client::getServerEndpoint` has been removed. You should use + `Configuration::getServer` instead. + + Before: + + ```php + $client->getServerEndpoint(); + ``` + + After: + + ```php + $client->getConfig()->getServer(); + ``` + +- The method `Client::getTransport` has been removed. You should use + `Configuration::getTransport` instead. + + Before: + + ```php + $client->getTransport(); + ``` + + After: + + ```php + $client->getConfig()->getTransport(); + ``` + +- The method `Client::getErrorTypes` has been removed. You should use + `Configuration::getErrorTypes` instead. + + Before: + + ```php + $client->getErrorTypes(); + ``` + + After: + + ```php + $client->getConfig()->getErrorTypes(); + ``` + +- The `Client::getDefaultProcessors` method has been removed. + +### Client builder + +- To simplify the creation of a `Client` object instance, a new builder class + has been added. + + Before: + + ```php + $client = new Client([...]); + ``` + + After: + + ```php + $client = new Client(new Configuration([...])); + + // or + + $client = ClientBuilder::create([...])->getClient(); + ``` diff --git a/bin/sentry b/bin/sentry index d6b68e88f..f11c9592e 100755 --- a/bin/sentry +++ b/bin/sentry @@ -22,36 +22,13 @@ function cmd_test($dsn) exit('ERROR: Missing DSN value'); } - // Parse DSN as a test - try { - \Raven\Client::parseDSN($dsn); - } catch (InvalidArgumentException $ex) { - exit("ERROR: There was an error parsing your DSN:\n " . $ex->getMessage()); - } - - $client = new \Raven\Client($dsn, array( - 'trace' => true, + $config = new \Raven\Configuration([ + 'server' => $dsn, 'curl_method' => 'sync', - 'app_path' => realpath(__DIR__ . '/..'), - 'base_path' => realpath(__DIR__ . '/..'), - )); - - $config = get_object_vars($client); - $required_keys = array('server', 'project', 'public_key', 'secret_key'); - - echo "Client configuration:\n"; - foreach ($required_keys as $key) { - if (empty($config[$key])) { - exit("ERROR: Missing configuration for $key"); - } - if (is_array($config[$key])) { - echo "-> $key: [".implode(", ", $config[$key])."]\n"; - } else { - echo "-> $key: $config[$key]\n"; - } + 'project_root' => realpath(__DIR__ . '/..'), + ]); - } - echo "\n"; + $client = new \Raven\Client($config); echo "Sending a test event:\n"; @@ -70,7 +47,8 @@ function cmd_test($dsn) } -function main() { +function main() +{ global $argv; if (!isset($argv[1])) { diff --git a/composer.json b/composer.json index 4ae394441..29f108bae 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,6 @@ "email": "dcramer@gmail.com" } ], - "minimum-stability": "dev", "require-dev": { "friendsofphp/php-cs-fixer": "~2.1", "phpunit/phpunit": "^4.8 || ^5.0", @@ -24,7 +23,9 @@ "ext-hash": "*", "ext-json": "*", "ext-mbstring": "*", - "ext-curl": "*" + "ext-curl": "*", + "composer/ca-bundle": "~1.0", + "symfony/options-resolver": "~2.7|~3.0" }, "suggest": { "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c020599cd..20a9f682f 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -10,6 +10,8 @@ namespace Raven; +use Raven\CurlHandler; + /** * Raven PHP Client * @@ -36,21 +38,20 @@ class Client const MESSAGE_LIMIT = 1024; /** - * @var \Raven\Breadcrumbs + * @var Breadcrumbs The breadcrumbs */ public $breadcrumbs; + /** - * @var \Raven\Context + * @var Context The context */ public $context; + /** - * @var \Raven\TransactionStack + * @var TransactionStack The transaction stack */ public $transaction; - /** - * @var array $extra_data - */ - public $extra_data; + /** * @var string[]|null */ @@ -61,10 +62,6 @@ class Client * @var \Raven\ErrorHandler $error_handler */ protected $error_handler; - /** - * @var integer|null bit mask for error_reporting used in ErrorHandler::handleError - */ - protected $error_types; /** * @var \Raven\Serializer $serializer @@ -75,133 +72,11 @@ class Client */ protected $reprSerializer; - /** - * @var string $app_path The root path to your application code - */ - protected $app_path; - /** - * @var string[] $prefixes Prefixes which should be stripped from filenames to create relative paths - */ - protected $prefixes; - /** - * @var string[]|null Paths to exclude from app_path detection - */ - protected $excluded_app_paths; - /** - * @var Callable Set a custom transport to override how Sentry events are sent upstream - */ - protected $transport; - - /** - * @var string $logger Adjust the default logger name for messages - */ - public $logger; - /** - * @var string Full URL to Sentry (not a DSN) - * @doc https://docs.sentry.io/quickstart/ - */ - public $server; - /** - * @var string $secret_key Password in Sentry Server - */ - public $secret_key; - /** - * @var string $public_key Password in Sentry Server - */ - public $public_key; - /** - * @var integer $project This project ID in Sentry Server - */ - public $project; - /** - * @var boolean $auto_log_stacks Fill stacktrace by debug_backtrace() - */ - public $auto_log_stacks; - /** - * @var string $name Override the default value for the server’s hostname - */ - public $name; - /** - * @var string $site SERVER_NAME (not a HTTP_HOST) - */ - public $site; - /** - * @var array $tags An array of tags to apply to events in this context - */ - public $tags; - /** - * @var mixed $release The version of your application (e.g. git SHA) - */ - public $release; - /** - * @var string $environment The environment your application is running in - */ - public $environment; - /** - * @var double The sampling factor to apply to events. A value of 0.00 will deny sending - * any events, and a value of 1.00 will send 100% of events - */ - public $sample_rate; - /** - * @var boolean $trace Set this to false to disable reflection tracing - * (function calling arguments) in stacktraces - */ - public $trace; - /** - * @var double $timeout Timeout for sending data - */ - public $timeout; - /** - * @var string $message_limit This value is used to truncate message and frame variables. - * However it is not guarantee that length of whole message will be restricted by this value - */ - public $message_limit; - /** - * @var string[] $exclude Excluded exceptions classes - */ - public $exclude; - public $http_proxy; - /** - * @var Callable $send_callback A function which will be called whenever data is ready to be sent. - * Within the function you can mutate the data, or alternatively return false to instruct the SDK - * to not send the event - */ - protected $send_callback; - /** - * @var string $curl_method - * sync (default): send requests immediately when they’re made - * async: uses a curl_multi handler for best-effort asynchronous submissions - * exec: asynchronously send events by forking a curl process for each item - */ - public $curl_method; - /** - * @var string $curl_path Specify the path to the curl binary to be used with the ‘exec’ curl method - */ - public $curl_path; - /** - * @var boolean $curl_ipv4 Resolve domain only with IPv4 - * @todo change to $curl_ipresolve, http://php.net/manual/ru/function.curl-setopt.php - */ - public $curl_ipv4; - /** - * @var string $ca_cert The path to the CA certificate bundle - */ - public $ca_cert; - /** - * @var boolean $verify_ssl - */ - public $verify_ssl; - /** - * @var mixed The SSL version (2 or 3) to use. By default PHP will try to determine this itself, - * although in some cases this must be set manually - */ - public $curl_ssl_version; - public $trust_x_forwarded_proto; - public $mb_detect_order; /** * @var \Raven\Processor[] $processors An array of classes to use to process data before it is sent to Sentry */ - public $processors; + protected $processors = []; + /** * @var string|int|null */ @@ -212,16 +87,14 @@ class Client protected $_last_sentry_error; public $_last_event_id; public $_user; + /** * @var array[] $_pending_events */ - public $_pending_events; - /** - * @var array User Agent showed in Sentry - */ - public $sdk; + public $_pending_events = []; + /** - * @var \Raven\CurlHandler + * @var CurlHandler */ protected $_curl_handler; /** @@ -231,132 +104,57 @@ class Client /** * @var bool */ - protected $_shutdown_function_has_been_set; + protected $_shutdown_function_has_been_set = false; + + /** + * @var Configuration The client configuration + */ + protected $config; - public function __construct($options_or_dsn = null, $options = []) + /** + * Constructor. + * + * @param Configuration $config The client configuration + */ + public function __construct(Configuration $config) { - if (is_array($options_or_dsn)) { - $options = array_merge($options_or_dsn, $options); + $this->config = $config; + $this->context = new Context(); + $this->breadcrumbs = new Breadcrumbs(); + $this->transaction = new TransactionStack(); + $this->serializer = new Serializer($this->config->getMbDetectOrder()); + $this->reprSerializer = new ReprSerializer($this->config->getMbDetectOrder()); + $this->processors = $this->createProcessors(); + + if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) { + $this->transaction->push($_SERVER['PATH_INFO']); } - if (!is_array($options_or_dsn) && !empty($options_or_dsn)) { - $dsn = $options_or_dsn; - } elseif (!empty($_SERVER['SENTRY_DSN'])) { - $dsn = @$_SERVER['SENTRY_DSN']; - } elseif (!empty($options['dsn'])) { - $dsn = $options['dsn']; - } else { - $dsn = null; + if ($this->config->getSerializeAllObjects()) { + $this->setAllObjectSerialize(true); } - if (!empty($dsn)) { - $options = array_merge($options, self::parseDSN($dsn)); + if ('async' === $this->config->getCurlMethod()) { + $this->_curl_handler = new CurlHandler($this->get_curl_options()); } - unset($dsn); - $this->init_with_options($options); - if (\Raven\Util::get($options, 'install_default_breadcrumb_handlers', true)) { + if ($this->config->shouldInstallDefaultBreadcrumbHandlers()) { $this->registerDefaultBreadcrumbHandlers(); } - if (\Raven\Util::get($options, 'install_shutdown_handler', true)) { + if ($this->config->shouldInstallShutdownHandler()) { $this->registerShutdownFunction(); } } /** - * @param array $options + * Gets the client configuration. + * + * @return Configuration */ - public function init_with_options($options) + public function getConfig() { - foreach ( - [ - ['logger', 'php',], - ['server',], - ['secret_key',], - ['public_key',], - ['project',1,], - ['auto_log_stacks', false,], - ['name', gethostname()], - ['site', self::_server_variable('SERVER_NAME')], - ['tags', []], - ['release', []], - ['environment'], - ['sample_rate', 1], - ['trace', true], - ['timeout', 2], - ['message_limit', self::MESSAGE_LIMIT], - ['exclude', []], - ['http_proxy'], - ['extra_data', [], 'extra'], - ['send_callback'], - - ['curl_method', 'sync'], - ['curl_path', 'curl'], - ['curl_ipv4', true], - ['ca_cert', static::get_default_ca_cert()], - ['verify_ssl', true], - ['curl_ssl_version'], - ['trust_x_forwarded_proto'], - ['transport'], - ['mb_detect_order'], - ['error_types'], - ] as &$set - ) { - if (count($set) == 1) { - $set = [$set[0], null, null]; - } elseif (count($set) == 2) { - $set[] = null; - } - - list($object_key, $default_value, $array_key) = $set; - if (is_null($array_key)) { - $array_key = $object_key; - } - - // @todo It should be isset or array_key_exists? - $this->{$object_key} = isset($options[$array_key]) ? $options[$array_key] : $default_value; - } - $this->auto_log_stacks = (boolean)$this->auto_log_stacks; - $this->severity_map = null; - - // app path is used to determine if code is part of your application - $this->setAppPath(\Raven\Util::get($options, 'app_path', null)); - $this->setExcludedAppPaths(\Raven\Util::get($options, 'excluded_app_paths', null)); - // a list of prefixes used to coerce absolute paths into relative - $this->setPrefixes(\Raven\Util::get($options, 'prefixes', static::getDefaultPrefixes())); - $this->processors = $this->setProcessorsFromOptions($options); - - $this->_lasterror = null; - $this->_last_sentry_error = null; - $this->_curl_instance = null; - $this->_last_event_id = null; - $this->_user = null; - $this->_pending_events = []; - $this->context = new \Raven\Context(); - $this->breadcrumbs = new \Raven\Breadcrumbs(); - $this->_shutdown_function_has_been_set = false; - - $this->sdk = \Raven\Util::get($options, 'sdk', [ - 'name' => 'sentry-php', - 'version' => self::VERSION, - ]); - $this->serializer = new \Raven\Serializer($this->mb_detect_order); - $this->reprSerializer = new \Raven\ReprSerializer($this->mb_detect_order); - if (\Raven\Util::get($options, 'serialize_all_object', false)) { - $this->setAllObjectSerialize(true); - } - - if ($this->curl_method == 'async') { - $this->_curl_handler = new \Raven\CurlHandler($this->get_curl_options()); - } - - $this->transaction = new \Raven\TransactionStack(); - if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) { - // @codeCoverageIgnoreStart - $this->transaction->push($_SERVER['PATH_INFO']); - // @codeCoverageIgnoreEnd - } + return $this->config; } public function __destruct() @@ -404,224 +202,43 @@ public function install() if ($this->error_handler) { throw new \Raven\Exception(sprintf('%s->install() must only be called once', get_class($this))); } - $this->error_handler = new \Raven\ErrorHandler($this, false, $this->error_types); + $this->error_handler = new \Raven\ErrorHandler($this, false, $this->getConfig()->getErrorTypes()); $this->error_handler->registerExceptionHandler(); $this->error_handler->registerErrorHandler(); $this->error_handler->registerShutdownFunction(); return $this; } - public function getRelease() - { - return $this->release; - } - - public function setRelease($value) - { - $this->release = $value; - return $this; - } - - public function getEnvironment() - { - return $this->environment; - } - - public function setEnvironment($value) - { - $this->environment = $value; - return $this; - } - - private static function getDefaultPrefixes() - { - $value = get_include_path(); - return explode(PATH_SEPARATOR, $value); - } - - private static function _convertPath($value) - { - $path = @realpath($value); - if ($path === false) { - $path = $value; - } - // we need app_path to have a trailing slash otherwise - // base path detection becomes complex if the same - // prefix is matched - if (substr($path, 0, 1) === DIRECTORY_SEPARATOR && substr($path, -1) !== DIRECTORY_SEPARATOR) { - $path = $path.DIRECTORY_SEPARATOR; - } - return $path; - } - - public function getAppPath() - { - return $this->app_path; - } - - public function setAppPath($value) - { - if ($value) { - $this->app_path = static::_convertPath($value); - } else { - $this->app_path = null; - } - return $this; - } - - public function getExcludedAppPaths() - { - return $this->excluded_app_paths; - } - - public function setExcludedAppPaths($value) - { - $this->excluded_app_paths = $value ? array_map([$this, '_convertPath'], $value) : null; - return $this; - } - - public function getPrefixes() - { - return $this->prefixes; - } - - /** - * @param array $value - * @return \Raven\Client - */ - public function setPrefixes($value) - { - $this->prefixes = $value ? array_map([$this, '_convertPath'], $value) : $value; - return $this; - } - - public function getSendCallback() - { - return $this->send_callback; - } - - public function setSendCallback($value) - { - $this->send_callback = $value; - return $this; - } - - public function getTransport() - { - return $this->transport; - } - - public function getServerEndpoint() - { - return $this->server; - } - public static function getUserAgent() { return 'sentry-php/' . self::VERSION; } - /** - * Set a custom transport to override how Sentry events are sent upstream. - * - * The bound function will be called with ``$client`` and ``$data`` arguments - * and is responsible for encoding the data, authenticating, and sending - * the data to the upstream Sentry server. - * - * @param Callable $value Function to be called - * @return \Raven\Client - */ - public function setTransport($value) - { - $this->transport = $value; - return $this; - } - - /** - * @return string[]|\Raven\Processor[] - */ - public static function getDefaultProcessors() - { - return [ - '\\Raven\\Processor\\SanitizeDataProcessor', - ]; - } - /** * Sets the \Raven\Processor sub-classes to be used when data is processed before being * sent to Sentry. * - * @param $options * @return \Raven\Processor[] */ - public function setProcessorsFromOptions($options) + public function createProcessors() { $processors = []; - foreach (\Raven\util::get($options, 'processors', static::getDefaultProcessors()) as $processor) { - /** - * @var \Raven\Processor $new_processor - * @var \Raven\Processor|string $processor - */ - $new_processor = new $processor($this); + $processorsOptions = $this->config->getProcessorsOptions(); - if (isset($options['processorOptions']) && is_array($options['processorOptions'])) { - if (isset($options['processorOptions'][$processor]) - && method_exists($processor, 'setProcessorOptions') - ) { - $new_processor->setProcessorOptions($options['processorOptions'][$processor]); + foreach ($this->config->getProcessors() as $processor) { + /** @var Processor $processorInstance */ + $processorInstance = new $processor($this); + + if (isset($processorsOptions[$processor])) { + if (method_exists($processor, 'setProcessorOptions')) { + $processorInstance->setProcessorOptions($processorsOptions[$processor]); } } - $processors[] = $new_processor; - } - return $processors; - } - /** - * Parses a Raven-compatible DSN and returns an array of its values. - * - * @param string $dsn Raven compatible DSN - * @return array parsed DSN - * - * @doc http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn - */ - public static function parseDSN($dsn) - { - $url = parse_url($dsn); - $scheme = (isset($url['scheme']) ? $url['scheme'] : ''); - if (!in_array($scheme, ['http', 'https'])) { - throw new \InvalidArgumentException( - 'Unsupported Sentry DSN scheme: '. - (!empty($scheme) ? $scheme : /** @lang text */'') - ); - } - $netloc = (isset($url['host']) ? $url['host'] : null); - $netloc .= (isset($url['port']) ? ':'.$url['port'] : null); - $rawpath = (isset($url['path']) ? $url['path'] : null); - if ($rawpath) { - $pos = strrpos($rawpath, '/', 1); - if ($pos !== false) { - $path = substr($rawpath, 0, $pos); - $project = substr($rawpath, $pos + 1); - } else { - $path = ''; - $project = substr($rawpath, 1); - } - } else { - $project = null; - $path = ''; - } - $username = (isset($url['user']) ? $url['user'] : null); - $password = (isset($url['pass']) ? $url['pass'] : null); - if (empty($netloc) || empty($project) || empty($username) || empty($password)) { - throw new \InvalidArgumentException('Invalid Sentry DSN: ' . $dsn); + $processors[] = $processorInstance; } - return [ - 'server' => sprintf('%s://%s%s/api/%s/store/', $scheme, $netloc, $path, $project), - 'project' => $project, - 'public_key' => $username, - 'secret_key' => $password, - ]; + return $processors; } public function getLastError() @@ -720,7 +337,7 @@ public function captureMessage($message, $params = [], $data = [], */ public function captureException($exception, $data = null, $logger = null, $vars = null) { - if (in_array(get_class($exception), $this->exclude)) { + if (in_array(get_class($exception), $this->config->getExcludedExceptions())) { return null; } @@ -916,22 +533,19 @@ protected function get_user_data() ]; } - protected function get_extra_data() - { - return $this->extra_data; - } - public function get_default_data() { return [ - 'server_name' => $this->name, - 'project' => $this->project, - 'site' => $this->site, - 'logger' => $this->logger, - 'tags' => $this->tags, + 'server_name' => $this->config->getServerName(), + 'project' => $this->config->getProjectId(), + 'logger' => $this->config->getLogger(), + 'tags' => $this->config->getTags(), 'platform' => 'php', - 'sdk' => $this->sdk, 'culprit' => $this->transaction->peek(), + 'sdk' => [ + 'name' => 'sentry-php', + 'version' => self::VERSION, + ], ]; } @@ -954,7 +568,7 @@ public function capture($data, $stack = null, $vars = null) } if (isset($data['message'])) { - $data['message'] = substr($data['message'], 0, $this->message_limit); + $data['message'] = substr($data['message'], 0, self::MESSAGE_LIMIT); } $data = array_merge($this->get_default_data(), $data); @@ -965,20 +579,20 @@ public function capture($data, $stack = null, $vars = null) $data = array_merge($this->get_user_data(), $data); - if ($this->release) { - $data['release'] = $this->release; + if (!empty($this->config->getRelease())) { + $data['release'] = $this->config->getRelease(); } - if ($this->environment) { - $data['environment'] = $this->environment; + + if (!empty($this->config->getCurrentEnvironment())) { + $data['environment'] = $this->config->getCurrentEnvironment(); } $data['tags'] = array_merge( - $this->tags, + $this->config->getTags(), $this->context->tags, $data['tags']); $data['extra'] = array_merge( - $this->get_extra_data(), $this->context->extra, $data['extra']); @@ -999,7 +613,7 @@ public function capture($data, $stack = null, $vars = null) $data['breadcrumbs'] = $this->breadcrumbs->fetch(); } - if ((!$stack && $this->auto_log_stacks) || $stack === true) { + if ((!$stack && $this->config->getAutoLogStacks()) || $stack === true) { $stack = debug_backtrace(); // Drop last stack @@ -1119,24 +733,17 @@ public function encode(&$data) */ public function send(&$data) { - if (is_callable($this->send_callback) - && call_user_func_array($this->send_callback, [&$data]) === false - ) { - // if send_callback returns false, end native send - return; - } - - if (!$this->server) { + if (false === $this->config->shouldCapture($data) || !$this->config->getServer()) { return; } - if ($this->transport) { - call_user_func($this->transport, $this, $data); + if ($this->getConfig()->getTransport()) { + call_user_func($this->getConfig()->getTransport(), $this, $data); return; } // should this event be sampled? - if (mt_rand(1, 100) / 100.0 > $this->sample_rate) { + if (mt_rand(1, 100) / 100.0 > $this->config->getSampleRate()) { return; } @@ -1148,7 +755,10 @@ public function send(&$data) 'Content-Type' => 'application/octet-stream' ]; - $this->send_remote($this->server, $message, $headers); + $config = $this->getConfig(); + $server = sprintf('%s/api/%d/store/', $config->getServer(), $config->getProjectId()); + + $this->send_remote($server, $message, $headers); } /** @@ -1165,11 +775,6 @@ protected function send_remote($url, $data, $headers = []) $this->send_http($url, $data, $headers); } - protected static function get_default_ca_cert() - { - return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem'; - } - /** * @return array * @doc http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006 @@ -1180,22 +785,22 @@ protected function get_curl_options() $options = [ CURLOPT_VERBOSE => false, CURLOPT_SSL_VERIFYHOST => 2, - CURLOPT_SSL_VERIFYPEER => $this->verify_ssl, - CURLOPT_CAINFO => $this->ca_cert, + CURLOPT_SSL_VERIFYPEER => $this->config->isSslVerificationEnabled(), + CURLOPT_CAINFO => $this->config->getSslCaFile(), CURLOPT_USERAGENT => 'sentry-php/' . self::VERSION, ]; - if ($this->http_proxy) { - $options[CURLOPT_PROXY] = $this->http_proxy; + if (!empty($this->config->getProxy())) { + $options[CURLOPT_PROXY] = $this->config->getProxy(); } - if ($this->curl_ssl_version) { - $options[CURLOPT_SSLVERSION] = $this->curl_ssl_version; + if (null !== $this->config->getCurlSslVersion()) { + $options[CURLOPT_SSLVERSION] = $this->config->getCurlSslVersion(); } - if ($this->curl_ipv4) { + if ($this->config->getCurlIpv4()) { $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; } if (defined('CURLOPT_TIMEOUT_MS')) { // MS is available in curl >= 7.16.2 - $timeout = max(1, ceil(1000 * $this->timeout)); + $timeout = max(1, ceil(1000 * $this->config->getTimeout())); // None of the versions of PHP contains this constant if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) { @@ -1207,7 +812,7 @@ protected function get_curl_options() $options[CURLOPT_TIMEOUT_MS] = $timeout; } else { // fall back to the lower-precision timeout. - $timeout = max(1, ceil($this->timeout)); + $timeout = max(1, ceil($this->config->getTimeout())); $options[CURLOPT_CONNECTTIMEOUT] = $timeout; $options[CURLOPT_TIMEOUT] = $timeout; } @@ -1223,9 +828,9 @@ protected function get_curl_options() */ protected function send_http($url, $data, $headers = []) { - if ($this->curl_method == 'async') { + if ($this->config->getCurlMethod() == 'async') { $this->_curl_handler->enqueue($url, $data, $headers); - } elseif ($this->curl_method == 'exec') { + } elseif ($this->config->getCurlMethod() == 'exec') { $this->send_http_asynchronous_curl_exec($url, $data, $headers); } else { $this->send_http_synchronous($url, $data, $headers); @@ -1248,10 +853,10 @@ protected function buildCurlCommand($url, $data, $headers) } $cmd = sprintf( '%s -X POST%s -d %s %s -m %d %s%s> /dev/null 2>&1 &', - escapeshellcmd($this->curl_path), $post_fields, - escapeshellarg($data), escapeshellarg($url), $this->timeout, - !$this->verify_ssl ? '-k ' : '', - !empty($this->ca_cert) ? '--cacert '.escapeshellarg($this->ca_cert).' ' : '' + escapeshellcmd($this->config->getCurlPath()), $post_fields, + escapeshellarg($data), escapeshellarg($url), $this->config->getTimeout(), + !$this->config->isSslVerificationEnabled() ? '-k ' : '', + !empty($this->config->getSslCaFile()) ? '--cacert '.escapeshellarg($this->config->getSslCaFile()).' ' : '' ); return $cmd; @@ -1367,7 +972,7 @@ public function getAuthHeader() { $timestamp = microtime(true); return $this->get_auth_header( - $timestamp, static::getUserAgent(), $this->public_key, $this->secret_key + $timestamp, static::getUserAgent(), $this->config->getPublicKey(), $this->config->getSecretKey() ); } @@ -1437,7 +1042,7 @@ protected function isHttps() return true; } - if (!empty($this->trust_x_forwarded_proto) && + if (!empty($this->config->isTrustXForwardedProto()) && !empty($_SERVER['X-FORWARDED-PROTO']) && $_SERVER['X-FORWARDED-PROTO'] === 'https') { return true; diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php new file mode 100644 index 000000000..77d382bc2 --- /dev/null +++ b/lib/Raven/ClientBuilder.php @@ -0,0 +1,135 @@ + + * + * @method bool isTrustXForwardedProto() + * @method setIsTrustXForwardedProto(bool $value) + * @method string[] getPrefixes() + * @method setPrefixes(array $prefixes) + * @method bool getSerializeAllObjects() + * @method setSerializeAllObjects(bool $serializeAllObjects) + * @method string getCurlMethod() + * @method setCurlMethod(string $method) + * @method string getCurlPath() + * @method setCurlPath(string $path) + * @method bool getCurlIpv4() + * @method setCurlIpv4(bool $enable) + * @method string getCurlSslVersion() + * @method setCurlSslVersion(string $version) + * @method float getSampleRate() + * @method setSampleRate(float $sampleRate) + * @method bool shouldInstallDefaultBreadcrumbHandlers() + * @method setInstallDefaultBreadcrumbHandlers($installDefaultBreadcrumbHandlers) + * @method bool shouldInstallShutdownHandler() + * @method setInstallShutdownHandler(bool $installShutdownHandler) + * @method string getMbDetectOrder() + * @method setMbDetectOrder(string $detectOrder) + * @method bool getAutoLogStacks() + * @method setAutoLogStacks(bool $enable) + * @method int getContextLines() + * @method setContextLines(int $contextLines) + * @method string getCurrentEnvironment() + * @method setCurrentEnvironment(string $environment) + * @method string[] getEnvironments() + * @method setEnvironments(string[] $environments) + * @method string[] getExcludedLoggers() + * @method setExcludedLoggers(string[] $loggers) + * @method string[] getExcludedExceptions() + * @method setExcludedExceptions(string[] $exceptions) + * @method string[] getExcludedProjectPaths() + * @method setExcludedProjectPaths(string[] $paths) + * @method string getProjectRoot() + * @method setProjectRoot(string $path) + * @method string getLogger() + * @method setLogger(string $logger) + * @method int getOpenTimeout() + * @method setOpenTimeout(int $timeout) + * @method int getTimeout() + * @method setTimeout(int $timeout) + * @method string getProxy() + * @method setProxy(string $proxy) + * @method string getRelease() + * @method setRelease(string $release) + * @method string getServerName() + * @method setServerName(string $serverName) + * @method array getSslOptions() + * @method setSslOptions(array $options) + * @method bool isSslVerificationEnabled() + * @method setSslVerificationEnabled(bool $enable) + * @method string getSslCaFile() + * @method setSslCaFile(string $path) + * @method string[] getTags() + * @method setTags(string[] $tags) + * @method string[] getProcessors() + * @method setProcessors(string[] $processors) + * @method array getProcessorsOptions() + * @method setProcessorsOptions(array $options) + */ +class ClientBuilder implements ClientBuilderInterface +{ + /** + * @var Configuration The client configuration + */ + protected $configuration; + + /** + * Class constructor. + * + * @param array $options The client options + */ + public function __construct(array $options = []) + { + $this->configuration = new Configuration($options); + } + + /** + * {@inheritdoc} + */ + public static function create(array $options = []) + { + return new static($options); + } + + /** + * {@inheritdoc} + */ + public function getClient() + { + return new Client($this->configuration); + } + + /** + * This method forwards all methods calls to the configuration object. + * + * @param string $name The name of the method being called + * @param array $arguments Parameters passed to the $name'ed method + * + * @return $this + * + * @throws \BadMethodCallException If the called method does not exists + */ + public function __call($name, $arguments) + { + if (!method_exists($this->configuration, $name)) { + throw new \BadMethodCallException(sprintf('The method named "%s" does not exists.', $name)); + } + + call_user_func_array([$this->configuration, $name], $arguments); + + return $this; + } +} diff --git a/lib/Raven/ClientBuilderInterface.php b/lib/Raven/ClientBuilderInterface.php new file mode 100644 index 000000000..a477c8a45 --- /dev/null +++ b/lib/Raven/ClientBuilderInterface.php @@ -0,0 +1,36 @@ + + */ +interface ClientBuilderInterface +{ + /** + * Creates a new instance of this builder. + * + * @param array $options The client options + * + * @return static + */ + public static function create(array $options = []); + + /** + * Gets the instance of the client built using the configured options. + * + * @return Client + */ + public function getClient(); +} diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php new file mode 100644 index 000000000..838d5f996 --- /dev/null +++ b/lib/Raven/Configuration.php @@ -0,0 +1,1058 @@ + + */ +class Configuration +{ + /** + * @var array The configuration options + */ + private $options = []; + + /** + * @var string A simple server string, set to the DSN found on your Sentry settings + */ + private $server; + + /** + * @var string The project ID number to send to the Sentry server + */ + private $projectId; + + /** + * @var string The public key to authenticate the SDK + */ + private $publicKey; + + /** + * @var string The secret key to authenticate the SDK + */ + private $secretKey; + + /** + * @var OptionsResolver The options resolver + */ + private $resolver; + + /** + * Class constructor. + * + * @param array $options The configuration options + */ + public function __construct(array $options = []) + { + $this->resolver = new OptionsResolver(); + + $this->configureOptions($this->resolver); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Checks whether the X-FORWARDED-PROTO header should be trusted. + * + * @return bool + */ + public function isTrustXForwardedProto() + { + return $this->options['trust_x_forwarded_proto']; + } + + /** + * Sets whether the X-FORWARDED-PROTO header should be trusted. + * + * @param bool $value The value of the option + */ + public function setIsTrustXForwardedProto($value) + { + $options = array_merge($this->options, ['trust_x_forwarded_proto' => $value]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the prefixes which should be stripped from filenames to create + * relative paths. + * + * @return string[] + */ + public function getPrefixes() + { + return $this->options['prefixes']; + } + + /** + * Sets the prefixes which should be stripped from filenames to create + * relative paths. + * + * @param array $prefixes The prefixes + */ + public function setPrefixes(array $prefixes) + { + $options = array_merge($this->options, ['prefixes' => $prefixes]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets whether all the objects should be serialized. + * + * @return bool + */ + public function getSerializeAllObjects() + { + return $this->options['serialize_all_object']; + } + + /** + * Sets whether all the objects should be serialized. + * + * @param bool $serializeAllObjects Flag indicating if all objects should be serialized + */ + public function setSerializeAllObjects($serializeAllObjects) + { + $options = array_merge($this->options, ['serialize_all_object' => $serializeAllObjects]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the cURL method to use to send data. + * + * @return string + */ + public function getCurlMethod() + { + return $this->options['curl_method']; + } + + /** + * Sets the cURL method to use to send data. + * + * @param bool $method The cURL method + */ + public function setCurlMethod($method) + { + $options = array_merge($this->options, ['curl_method' => $method]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the path to the cURL binary to be used with the "exec" curl method. + * + * @return string + */ + public function getCurlPath() + { + return $this->options['curl_path']; + } + + /** + * Sets the path to the cURL binary to be used with the "exec" curl method. + * + * @param string $path The path + */ + public function setCurlPath($path) + { + $options = array_merge($this->options, ['curl_path' => $path]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets whether cURL must resolve domains only with IPv4. + * + * @return bool + */ + public function getCurlIpv4() + { + return $this->options['curl_ipv4']; + } + + /** + * Sets whether cURL must resolve domains only with IPv4. + * + * @param bool $enable Whether only IPv4 domains should be resolved + */ + public function setCurlIpv4($enable) + { + $options = array_merge($this->options, ['curl_ipv4' => $enable]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the version of SSL/TLS to attempt to use when using cURL. + * + * @return int + */ + public function getCurlSslVersion() + { + return $this->options['curl_ssl_version']; + } + + /** + * Sets the version of SSL/TLS to attempt to use when using cURL. + * + * @param int $version The protocol version (one of the `CURL_SSLVERSION_*` constants) + */ + public function setCurlSslVersion($version) + { + $options = array_merge($this->options, ['curl_ssl_version' => $version]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the sampling factor to apply to events. A value of 0 will deny + * sending any events, and a value of 1 will send 100% of events. + * + * @return float + */ + public function getSampleRate() + { + return $this->options['sample_rate']; + } + + /** + * Sets the sampling factor to apply to events. A value of 0 will deny + * sending any events, and a value of 1 will send 100% of events. + * + * @param float $sampleRate The sampling factor + */ + public function setSampleRate($sampleRate) + { + $options = array_merge($this->options, ['sample_rate' => $sampleRate]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets whether the default breadcrumb handlers should be installed. + * + * @return bool + */ + public function shouldInstallDefaultBreadcrumbHandlers() + { + return $this->options['install_default_breadcrumb_handlers']; + } + + /** + * Sets whether the default breadcrumb handlers should be installed. + * + * @param bool $installDefaultBreadcrumbHandlers Flag indicating if the default handlers should be installed + */ + public function setInstallDefaultBreadcrumbHandlers($installDefaultBreadcrumbHandlers) + { + $options = array_merge($this->options, ['install_default_breadcrumb_handlers' => $installDefaultBreadcrumbHandlers]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets whether the shutdown hundler should be installed. + * + * @return bool + */ + public function shouldInstallShutdownHandler() + { + return $this->options['install_shutdown_handler']; + } + + /** + * Sets whether the shutdown hundler should be installed. + * + * @param bool $installShutdownHandler Flag indicating if the shutdown handler should be installed + */ + public function setInstallShutdownHandler($installShutdownHandler) + { + $options = array_merge($this->options, ['install_shutdown_handler' => $installShutdownHandler]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the character encoding detection order. + * + * @return string[]|null + */ + public function getMbDetectOrder() + { + return $this->options['mb_detect_order']; + } + + /** + * Sets the character encoding detection order. + * + * @param string[]|null $detectOrder The detection order + */ + public function setMbDetectOrder($detectOrder) + { + $options = array_merge($this->options, ['mb_detect_order' => $detectOrder]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets whether the stacktrace must be auto-filled. + * + * @return bool + */ + public function getAutoLogStacks() + { + return $this->options['auto_log_stacks']; + } + + /** + * Sets whether the stacktrace must be auto-filled. + * + * @param bool $enable Flag indicating if the stacktrace must be auto-filled + */ + public function setAutoLogStacks($enable) + { + $options = array_merge($this->options, ['auto_log_stacks' => $enable]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the number of lines of code context to capture, or null if none. + * + * @return int|null + */ + public function getContextLines() + { + return $this->options['context_lines']; + } + + /** + * Sets the number of lines of code context to capture, or null if none. + * + * @param int|null $contextLines The number of lines of code + */ + public function setContextLines($contextLines) + { + $options = array_merge($this->options, ['context_lines' => $contextLines]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the current environment. + * + * @return string + */ + public function getCurrentEnvironment() + { + return $this->options['current_environment']; + } + + /** + * Sets the current environment. + * + * @param string $environment The environment + */ + public function setCurrentEnvironment($environment) + { + $options = array_merge($this->options, ['current_environment' => $environment]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the whitelist of environments that will send notifications to + * Sentry. + * + * @return string[] + */ + public function getEnvironments() + { + return $this->options['environments']; + } + + /** + * Sets the whitelist of environments that will send notifications to + * Sentry. + * + * @param string[] $environments The environments + */ + public function setEnvironments(array $environments) + { + $options = array_merge($this->options, ['environments' => $environments]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the list of logger 'progname's to exclude from breadcrumbs. + * + * @return string[] + */ + public function getExcludedLoggers() + { + return $this->options['excluded_loggers']; + } + + /** + * Sets the list of logger 'progname's to exclude from breadcrumbs. + * + * @param string[] $loggers The list of logger 'progname's + */ + public function setExcludedLoggers(array $loggers) + { + $options = array_merge($this->options, ['excluded_loggers' => $loggers]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the list of exception classes that should be ignored when sending + * events to Sentry. + * + * @return string[] + */ + public function getExcludedExceptions() + { + return $this->options['excluded_exceptions']; + } + + /** + * Sets the list of exception classes that should be ignored when sending + * events to Sentry. + * + * @param string[] $exceptions The list of exception classes + */ + public function setExcludedExceptions(array $exceptions) + { + $options = array_merge($this->options, ['excluded_exceptions' => $exceptions]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the list of paths to exclude from app_path detection. + * + * @return string[] + */ + public function getExcludedProjectPaths() + { + return $this->options['excluded_app_paths']; + } + + /** + * Sets the list of paths to exclude from app_path detection. + * + * @param array $paths The list of paths + */ + public function setExcludedProjectPaths(array $paths) + { + $options = array_merge($this->options, ['excluded_app_paths' => $paths]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the custom transport set to override how Sentry events are sent + * upstream. + * + * @return callable|null + */ + public function getTransport() + { + return $this->options['transport']; + } + + /** + * Set a custom transport to override how Sentry events are sent upstream. + * The bound function will be called with `$client` and `$data` arguments + * and is responsible for encoding the data, authenticating, and sending + * the data to the upstream Sentry server. + * + * @param callable|null $transport The callable + */ + public function setTransport(callable $transport = null) + { + $options = array_merge($this->options, ['transport' => $transport]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the project ID number to send to the Sentry server. + * + * @return string + */ + public function getProjectId() + { + return $this->projectId; + } + + /** + * Gets the project which the authenticated user is bound to. + * + * @return string + */ + public function getProjectRoot() + { + return $this->options['project_root']; + } + + /** + * Sets the project which the authenticated user is bound to. + * + * @param string $path The path to the project root + */ + public function setProjectRoot($path) + { + $options = array_merge($this->options, ['project_root' => $path]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the public key to authenticate the SDK. + * + * @return string|null + */ + public function getPublicKey() + { + return $this->publicKey; + } + + /** + * Gets the secret key to authenticate the SDK. + * + * @return string|null + */ + public function getSecretKey() + { + return $this->secretKey; + } + + /** + * Gets the logger used by Sentry. + * + * @return string + */ + public function getLogger() + { + return $this->options['logger']; + } + + /** + * Sets the logger used by Sentry. + * + * @param string $logger The logger + */ + public function setLogger($logger) + { + $options = array_merge($this->options, ['logger' => $logger]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the maximum number of seconds to wait for the Sentry server connection + * to open. + * + * @return int + */ + public function getOpenTimeout() + { + return $this->options['open_timeout']; + } + + /** + * Sets the maximum number of seconds to wait for the Sentry server connection + * to open. + * + * @param array $timeout The timeout in seconds + */ + public function setOpenTimeout($timeout) + { + $options = array_merge($this->options, ['open_timeout' => $timeout]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the maximum number of seconds to wait for the server to return data. + * + * @return int + */ + public function getTimeout() + { + return $this->options['timeout']; + } + + /** + * Sets the maximum number of seconds to wait for the server to return data. + * + * @param array $timeout The timeout in seconds + */ + public function setTimeout($timeout) + { + $options = array_merge($this->options, ['timeout' => $timeout]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the proxy information to pass to the transport adapter. + * + * @return array + */ + public function getProxy() + { + return $this->options['proxy']; + } + + /** + * Sets the proxy information to pass to the transport adapter. + * + * @param string $proxy The proxy information + */ + public function setProxy($proxy) + { + $options = array_merge($this->options, ['proxy' => $proxy]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the release tag to be passed with every event sent to Sentry. + * + * @return string + */ + public function getRelease() + { + return $this->options['release']; + } + + /** + * Sets the release tag to be passed with every event sent to Sentry. + * + * @param string $release The release + */ + public function setRelease($release) + { + $options = array_merge($this->options, ['release' => $release]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the DSN of the Sentry server the authenticated user is bound to. + * + * @return string + */ + public function getServer() + { + return $this->server; + } + + /** + * Gets the name of the server the SDK is running on (e.g. the hostname). + * + * @return string + */ + public function getServerName() + { + return $this->options['server_name']; + } + + /** + * Sets the name of the server the SDK is running on (e.g. the hostname). + * + * @param string $serverName The server name + */ + public function setServerName($serverName) + { + $options = array_merge($this->options, ['server_name' => $serverName]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Checks whether all events or a specific exception or event (if provided) + * are allowed to be captured. + * + * @param object|\Exception $value An optional event or exception to test + * + * @return bool + */ + public function shouldCapture(&$value = null) + { + $result = true; + + if (!empty($this->options['environments']) && !in_array($this->options['current_environment'], $this->options['environments'])) { + $result = false; + } + + if (null !== $this->options['should_capture'] && null !== $value) { + $result = $result && $this->options['should_capture']($value); + } + + return $result; + } + + /** + * Sets an optional callable to be called to decide whether an event should + * be captured or not. + * + * @param callable|null $callable The callable + */ + public function setShouldCapture(callable $callable = null) + { + $options = array_merge($this->options, ['should_capture' => $callable]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the options that configure the SSL for cURL. + * + * @return array + */ + public function getSslOptions() + { + return $this->options['ssl']; + } + + /** + * Sets the options that configure the SSL for cURL. + * + * @param array $options The options + */ + public function setSslOptions(array $options) + { + $options = array_merge($this->options, ['ssl' => $options]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets whether the SSL certificate of the server should be verified. + * + * @return bool + */ + public function isSslVerificationEnabled() + { + return $this->options['ssl_verification']; + } + + /** + * Sets whether the SSL certificate of the server should be verified. + * + * @param bool $enable Whether the SSL certificate should be verified + */ + public function setSslVerificationEnabled($enable) + { + $options = array_merge($this->options, ['ssl_verification' => $enable]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the path to the SSL certificate file. + * + * @return string + */ + public function getSslCaFile() + { + return $this->options['ssl_ca_file']; + } + + /** + * Sets the path to the SSL certificate file. + * + * @param string $path The path + */ + public function setSslCaFile($path) + { + $options = array_merge($this->options, ['ssl_ca_file' => $path]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets a list of default tags for events. + * + * @return string[] + */ + public function getTags() + { + return $this->options['tags']; + } + + /** + * Sets a list of default tags for events. + * + * @param string[] $tags A list of tags + */ + public function setTags(array $tags) + { + $options = array_merge($this->options, ['tags' => $tags]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets a bit mask for error_reporting used in {@link ErrorHandler::handleError}. + * + * @return int + */ + public function getErrorTypes() + { + return $this->options['error_types']; + } + + /** + * Sets a bit mask for error_reporting used in {@link ErrorHandler::handleError}. + * + * @param int $errorTypes The bit mask + */ + public function setErrorTypes($errorTypes) + { + $options = array_merge($this->options, ['error_types' => $errorTypes]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the list of enabled processors. + * + * @return string[] + */ + public function getProcessors() + { + return $this->options['processors']; + } + + /** + * Sets the list of enabled processors. + * + * @param string[] $processors A list of FCQN + */ + public function setProcessors(array $processors) + { + $this->options = $this->resolver->resolve(['processors' => $processors]); + } + + /** + * Gets the options to configure the processors. + * + * @return array + */ + public function getProcessorsOptions() + { + return $this->options['processors_options']; + } + + /** + * Sets the options to configure the processors. + * + * @param array $options The options + */ + public function setProcessorsOptions(array $options) + { + $this->options = $this->resolver->resolve(['processors_options' => $options]); + } + + /** + * Configures the options for this processor. + * + * @param OptionsResolver $resolver The resolver for the options + */ + private function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'trust_x_forwarded_proto' => false, + 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), + 'serialize_all_object' => false, + 'curl_method' => 'sync', + 'curl_path' => 'curl', + 'curl_ipv4' => true, + 'curl_ssl_version' => null, + 'sample_rate' => 1, + 'install_default_breadcrumb_handlers' => true, + 'install_shutdown_handler' => true, + 'mb_detect_order' => null, + 'auto_log_stacks' => false, + 'context_lines' => 3, + 'current_environment' => 'default', + 'environments' => [], + 'excluded_loggers' => [], + 'excluded_exceptions' => [], + 'excluded_app_paths' => [], + 'transport' => null, + 'project_root' => null, + 'logger' => 'php', + 'open_timeout' => 1, + 'timeout' => 2, + 'proxy' => null, + 'release' => null, + 'server' => isset($_SERVER['SENTRY_DSN']) ? $_SERVER['SENTRY_DSN'] : null, + 'server_name' => gethostname(), + 'should_capture' => null, + 'ssl' => [], + 'ssl_verification' => true, + 'ssl_ca_file' => CaBundle::getSystemCaRootBundlePath(), + 'tags' => [], + 'error_types' => null, + 'processors_options' => [], + 'processors' => [ + SanitizeDataProcessor::class, + RemoveCookiesProcessor::class, + RemoveHttpBodyProcessor::class, + SanitizeHttpHeadersProcessor::class, + ], + ]); + + $resolver->setAllowedTypes('trust_x_forwarded_proto', 'bool'); + $resolver->setAllowedTypes('prefixes', 'array'); + $resolver->setAllowedTypes('serialize_all_object', 'bool'); + $resolver->setAllowedTypes('curl_method', 'string'); + $resolver->setAllowedTypes('curl_path', 'string'); + $resolver->setAllowedTypes('curl_ssl_version', ['null', 'int']); + $resolver->setAllowedTypes('sample_rate', ['int', 'float']); + $resolver->setAllowedTypes('install_default_breadcrumb_handlers', 'bool'); + $resolver->setAllowedTypes('install_shutdown_handler', 'bool'); + $resolver->setAllowedTypes('mb_detect_order', ['null', 'array']); + $resolver->setAllowedTypes('auto_log_stacks', 'bool'); + $resolver->setAllowedTypes('context_lines', 'int'); + $resolver->setAllowedTypes('current_environment', 'string'); + $resolver->setAllowedTypes('environments', 'array'); + $resolver->setAllowedTypes('excluded_loggers', 'array'); + $resolver->setAllowedTypes('excluded_exceptions', 'array'); + $resolver->setAllowedTypes('excluded_app_paths', 'array'); + $resolver->setAllowedTypes('transport', ['null', 'callable']); + $resolver->setAllowedTypes('project_root', ['null', 'string']); + $resolver->setAllowedTypes('logger', 'string'); + $resolver->setAllowedTypes('open_timeout', 'int'); + $resolver->setAllowedTypes('timeout', 'int'); + $resolver->setAllowedTypes('proxy', ['null', 'string']); + $resolver->setAllowedTypes('release', ['null', 'string']); + $resolver->setAllowedTypes('server', ['null', 'string']); + $resolver->setAllowedTypes('server_name', 'string'); + $resolver->setAllowedTypes('should_capture', ['null', 'callable']); + $resolver->setAllowedTypes('ssl', 'array'); + $resolver->setAllowedTypes('ssl_verification', 'bool'); + $resolver->setAllowedTypes('ssl_ca_file', ['null', 'string']); + $resolver->setAllowedTypes('tags', 'array'); + $resolver->setAllowedTypes('error_types', ['null', 'int']); + $resolver->setAllowedTypes('processors_options', 'array'); + $resolver->setAllowedTypes('processors', 'array'); + + $resolver->setAllowedValues('server', function ($value) { + if (null === $value) { + return true; + } + + $parsed = @parse_url($value); + + if (false === $parsed) { + return false; + } + + if (!isset($parsed['scheme'], $parsed['user'], $parsed['pass'], $parsed['host'], $parsed['path'])) { + return false; + } + + if (empty($parsed['user']) || empty($parsed['pass'])) { + return false; + } + + if (!in_array(strtolower($parsed['scheme']), ['http', 'https'])) { + return false; + } + + return true; + }); + + $resolver->setNormalizer('server', function (Options $options, $value) { + if (null === $value) { + return $value; + } + + $parsed = @parse_url($value); + + $this->server = $parsed['scheme'] . '://' . $parsed['host']; + + if (isset($parsed['port']) && ((80 !== $parsed['port'] && 'http' === $parsed['scheme']) || (443 !== $parsed['port'] && 'https' === $parsed['scheme']))) { + $this->server .= ':' . $parsed['port']; + } + + $this->server .= substr($parsed['path'], 0, strripos($parsed['path'], '/')); + $this->publicKey = $parsed['user']; + $this->secretKey = $parsed['pass']; + + $parts = explode('/', $parsed['path']); + + $this->projectId = array_pop($parts); + + return $value; + }); + + $resolver->setNormalizer('project_root', function (Options $options, $value) { + if (null === $value) { + return null; + } + + return $this->normalizeAbsolutePath($value); + }); + + $resolver->setNormalizer('prefixes', function (Options $options, $value) { + return array_map([$this, 'normalizeAbsolutePath'], $value); + }); + + $resolver->setNormalizer('excluded_app_paths', function (Options $options, $value) { + return array_map([$this, 'normalizeAbsolutePath'], $value); + }); + } + + /** + * Normalizes the given path as an absolute path. + * + * @param string $value The path + * + * @return string + */ + private function normalizeAbsolutePath($value) + { + $path = @realpath($value); + + if (false === $path) { + $path = $value; + } + + if (DIRECTORY_SEPARATOR === substr($path, 0, 1) && DIRECTORY_SEPARATOR !== substr($path, -1)) { + $path = $path . DIRECTORY_SEPARATOR; + } + + return $path; + } +} diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 944c95648..0b0402cd8 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -129,10 +129,10 @@ public function addFrame($file, $line, array $backtraceFrame) self::getSourceCodeExcerpt($file, $line, self::CONTEXT_NUM_LINES) ); - if (null !== $this->client->getAppPath()) { - $excludedAppPaths = $this->client->getExcludedAppPaths(); + if (null !== $this->client->getConfig()->getProjectRoot()) { + $excludedAppPaths = $this->client->getConfig()->getExcludedProjectPaths(); $absoluteFilePath = @realpath($file) ?: $file; - $isApplicationFile = 0 === strpos($absoluteFilePath, $this->client->getAppPath()); + $isApplicationFile = 0 === strpos($absoluteFilePath, $this->client->getConfig()->getProjectRoot()); if ($isApplicationFile && !empty($excludedAppPaths)) { foreach ($excludedAppPaths as $path) { @@ -210,16 +210,16 @@ public function jsonSerialize() */ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) { + if (!is_file($path) || !is_readable($path)) { + return []; + } + $frame = [ 'pre_context' => [], 'context_line' => '', 'post_context' => [], ]; - if (!is_file($path) || !is_readable($path)) { - return []; - } - $target = max(0, ($lineNumber - ($linesNum + 1))); $currentLineNumber = $target + 1; @@ -265,7 +265,7 @@ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) */ protected function stripPrefixFromFilePath($filePath) { - foreach ($this->client->getPrefixes() as $prefix) { + foreach ($this->client->getConfig()->getPrefixes() as $prefix) { if (0 === strpos($filePath, $prefix)) { return substr($filePath, strlen($prefix)); } diff --git a/lib/Raven/data/cacert.pem b/lib/Raven/data/cacert.pem deleted file mode 100644 index 3346ab5ca..000000000 --- a/lib/Raven/data/cacert.pem +++ /dev/null @@ -1,5134 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Label: "GTE CyberTrust Global Root" -# Serial: 421 -# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db -# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 -# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD -VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv -bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv -b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV -UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU -cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds -b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH -iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS -r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 -04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r -GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 -3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P -lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ ------END CERTIFICATE----- - -# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Server CA" -# Serial: 1 -# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d -# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c -# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm -MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx -MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 -dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl -cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 -DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 -yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX -L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj -EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG -7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e -QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ -qdq5snUb9kLy78fyGPmJvKP/iiMucEc= ------END CERTIFICATE----- - -# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Premium Server CA" -# Serial: 1 -# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a -# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a -# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 ------BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy -dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t -MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB -MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG -A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp -b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl -cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv -bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE -VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ -ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR -uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG -9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI -hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM -pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- - -# Issuer: O=Equifax OU=Equifax Secure Certificate Authority -# Subject: O=Equifax OU=Equifax Secure Certificate Authority -# Label: "Equifax Secure CA" -# Serial: 903804111 -# MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4 -# SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a -# SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78 ------BEGIN CERTIFICATE----- -MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV -UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy -dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 -MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx -dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f -BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A -cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC -AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ -MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm -aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw -ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj -IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF -MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA -A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y -7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh -1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 149843929435818692848040365716851702463 -# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67 -# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2 -# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do -lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc -AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Label: "Verisign Class 3 Public Primary Certification Authority - G2" -# Serial: 167285380242319648451154478808036881606 -# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 -# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f -# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Label: "GlobalSign Root CA" -# Serial: 4835703278459707669005204 -# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a -# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c -# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Label: "GlobalSign Root CA - R2" -# Serial: 4835703278459682885658125 -# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 -# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe -# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e ------BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 -MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL -v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 -eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq -tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd -C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa -zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB -mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH -V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n -bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG -3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs -J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO -291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS -ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd -AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 -TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Label: "ValiCert Class 1 VA" -# Serial: 1 -# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb -# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e -# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy -NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y -LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ -TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y -TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 -LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW -I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw -nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Label: "ValiCert Class 2 VA" -# Serial: 1 -# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 -# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 -# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Label: "RSA Root Certificate 1" -# Serial: 1 -# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 -# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb -# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy -NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD -cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs -2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY -JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE -Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ -n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A -PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 3 Public Primary Certification Authority - G3" -# Serial: 206684696279472310254277870180966723415 -# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 -# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 -# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 4 Public Primary Certification Authority - G3" -# Serial: 314531972711909413743075096039378935511 -# MD5 Fingerprint: db:c8:f2:27:2e:b1:ea:6a:29:23:5d:fe:56:3e:33:df -# SHA1 Fingerprint: c8:ec:8c:87:92:69:cb:4b:ab:39:e9:8d:7e:57:67:f3:14:95:73:9d -# SHA256 Fingerprint: e3:89:36:0d:0f:db:ae:b3:d2:50:58:4b:47:30:31:4e:22:2f:39:c1:56:a0:20:14:4e:8d:96:05:61:79:15:06 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 -GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ -+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd -U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm -NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY -ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ -ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 -CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq -g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm -fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c -2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ -bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Secure Server CA" -# Serial: 927650371 -# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee -# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 -# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 ------BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC -VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u -ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc -KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u -ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 -MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE -ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j -b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg -U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA -A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ -I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 -wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC -AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb -oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 -BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p -dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk -MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp -b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 -MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi -E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa -MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI -hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN -95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd -2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Premium 2048 Secure Server CA" -# Serial: 946069240 -# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 -# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 -# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML -RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp -bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 -IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 -MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 -LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp -YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG -A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq -K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe -sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX -MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT -XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ -HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH -4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub -j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo -U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf -zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b -u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ -bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er -fF6adulZkMV8gzURZVE= ------END CERTIFICATE----- - -# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Label: "Baltimore CyberTrust Root" -# Serial: 33554617 -# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 -# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 -# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- - -# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure Global eBusiness CA" -# Serial: 1 -# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc -# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45 -# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07 ------BEGIN CERTIFICATE----- -MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT -ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw -MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj -dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l -c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC -UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc -58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ -o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr -aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA -A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA -Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv -8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV ------END CERTIFICATE----- - -# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure eBusiness CA 1" -# Serial: 4 -# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d -# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 -# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT -ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw -MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j -LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo -RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu -WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw -Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK -eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM -zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ -WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN -/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Low-Value Services Root" -# Serial: 1 -# MD5 Fingerprint: 1e:42:95:02:33:92:6b:b9:5f:c0:7f:da:d6:b2:4b:fc -# SHA1 Fingerprint: cc:ab:0e:a0:4c:23:01:d6:69:7b:dd:37:9f:cd:12:eb:24:e3:94:9d -# SHA256 Fingerprint: 8c:72:09:27:9a:c0:4e:27:5e:16:d0:7f:d3:b7:75:e8:01:54:b5:96:80:46:e3:1f:52:dd:25:76:63:24:e9:a7 ------BEGIN CERTIFICATE----- -MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw -MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD -VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul -CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n -tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl -dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch -PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC -+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O -BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl -MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk -ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB -IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X -7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz -43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY -eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl -pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA -WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Label: "AddTrust External Root" -# Serial: 1 -# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f -# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 -# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Public Services Root" -# Serial: 1 -# MD5 Fingerprint: c1:62:3e:23:c5:82:73:9c:03:59:4b:2b:e9:77:49:7f -# SHA1 Fingerprint: 2a:b6:28:48:5e:78:fb:f3:ad:9e:79:10:dd:6b:df:99:72:2c:96:e5 -# SHA256 Fingerprint: 07:91:ca:07:49:b2:07:82:aa:d3:c7:d7:bd:0c:df:c9:48:58:35:84:3e:b2:d7:99:60:09:ce:43:ab:6c:69:27 ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx -MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB -ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV -BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV -6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX -GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP -dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH -1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF -62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW -BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw -AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL -MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU -cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv -b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6 -IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/ -iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao -GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh -4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm -XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Qualified Certificates Root" -# Serial: 1 -# MD5 Fingerprint: 27:ec:39:47:cd:da:5a:af:e2:9a:01:65:21:a9:4c:bb -# SHA1 Fingerprint: 4d:23:78:ec:91:95:39:b5:00:7f:75:8f:03:3b:21:1e:c5:4d:8b:cf -# SHA256 Fingerprint: 80:95:21:08:05:db:4b:bc:35:5e:44:28:d8:fd:6e:c2:cd:e3:ab:5f:b9:7a:99:42:98:8e:b8:f4:dc:d0:60:16 ------BEGIN CERTIFICATE----- -MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1 -MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK -EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh -BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq -xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G -87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i -2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U -WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1 -0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G -A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr -pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL -ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm -aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv -hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm -hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X -dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3 -P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y -iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no -xqE= ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Label: "Entrust Root Certification Authority" -# Serial: 1164660820 -# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 -# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 -# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c ------BEGIN CERTIFICATE----- -MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 -Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW -KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl -cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw -NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw -NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy -ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV -BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo -Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 -4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 -KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI -rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi -94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB -sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi -gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo -kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE -vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA -A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t -O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua -AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP -9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ -eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m -0vdXcDazv/wor3ElhVsT/h5/WrQ8 ------END CERTIFICATE----- - -# Issuer: O=RSA Security Inc OU=RSA Security 2048 V3 -# Subject: O=RSA Security Inc OU=RSA Security 2048 V3 -# Label: "RSA Security 2048 v3" -# Serial: 13297492616345471454730593562152402946 -# MD5 Fingerprint: 77:0d:19:b1:21:fd:00:42:9c:3e:0c:a5:dd:0b:02:8e -# SHA1 Fingerprint: 25:01:90:19:cf:fb:d9:99:1c:b7:68:25:74:8d:94:5f:30:93:95:42 -# SHA256 Fingerprint: af:8b:67:62:a1:e5:28:22:81:61:a9:5d:5c:55:9e:e2:66:27:8f:75:d7:9e:83:01:89:a5:03:50:6a:bd:6b:4c ------BEGIN CERTIFICATE----- -MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6 -MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp -dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX -BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy -MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp -eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg -/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl -wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh -AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2 -PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu -AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR -MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc -HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/ -Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+ -f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO -rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch -6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3 -7CAFYd4= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. -# Label: "GeoTrust Global CA" -# Serial: 144470 -# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 -# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 -# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i -YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg -R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 -9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq -fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv -iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU -1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ -bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW -MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA -ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l -uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn -Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS -tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF -PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un -hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV -5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Global CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Global CA 2" -# Serial: 1 -# MD5 Fingerprint: 0e:40:a7:6c:de:03:5d:8f:d1:0f:e4:d1:8d:f9:6c:a9 -# SHA1 Fingerprint: a9:e9:78:08:14:37:58:88:f2:05:19:b0:6d:2b:0d:2b:60:16:90:7d -# SHA256 Fingerprint: ca:2d:82:a0:86:77:07:2f:8a:b6:76:4f:f0:35:67:6c:fe:3e:5e:32:5e:01:21:72:df:3f:92:09:6d:b7:9b:85 ------BEGIN CERTIFICATE----- -MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs -IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg -R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A -PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 -Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL -TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL -5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 -S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe -2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE -FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap -EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td -EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv -/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN -A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 -abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF -I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz -4iIprn2DQKi6bA== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Label: "GeoTrust Universal CA" -# Serial: 1 -# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 -# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 -# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy -c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 -IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV -VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 -cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT -QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh -F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v -c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w -mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd -VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX -teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ -f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe -Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ -nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY -MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG -9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX -IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn -ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z -uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN -Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja -QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW -koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 -ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt -DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm -bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Universal CA 2" -# Serial: 1 -# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 -# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 -# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Label: "America Online Root Certification Authority 1" -# Serial: 1 -# MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e -# SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a -# SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3 ------BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk -hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym -1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW -OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb -2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko -O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU -AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB -BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF -Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb -LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir -oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C -MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Label: "America Online Root Certification Authority 2" -# Serial: 1 -# MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf -# SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84 -# SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC -206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci -KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 -JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 -BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e -Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B -PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 -Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq -Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ -o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 -+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj -YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj -FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn -xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 -LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc -obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 -CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe -IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA -DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F -AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX -Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb -AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl -Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw -RY8mkaKO/qk= ------END CERTIFICATE----- - -# Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association -# Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association -# Label: "Visa eCommerce Root" -# Serial: 25952180776285836048024890241505565794 -# MD5 Fingerprint: fc:11:b8:d8:08:93:30:00:6d:23:f9:7e:eb:52:1e:02 -# SHA1 Fingerprint: 70:17:9b:86:8c:00:a4:fa:60:91:52:22:3f:9f:3e:32:bd:e0:05:62 -# SHA256 Fingerprint: 69:fa:c9:bd:55:fb:0a:c7:8d:53:bb:ee:5c:f1:d5:97:98:9f:d0:aa:ab:20:a2:51:51:bd:f1:73:3e:e7:d1:22 ------BEGIN CERTIFICATE----- -MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr -MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl -cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv -bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw -CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h -dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l -cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h -2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E -lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV -ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq -299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t -vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL -dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD -AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF -AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR -zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3 -LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd -7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw -++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt -398znM/jra6O1I7mT1GvFpLgXPYHDw== ------END CERTIFICATE----- - -# Issuer: CN=Certum CA O=Unizeto Sp. z o.o. -# Subject: CN=Certum CA O=Unizeto Sp. z o.o. -# Label: "Certum Root CA" -# Serial: 65568 -# MD5 Fingerprint: 2c:8f:9f:66:1d:18:90:b1:47:26:9d:8e:86:82:8c:a9 -# SHA1 Fingerprint: 62:52:dc:40:f7:11:43:a2:2f:de:9e:f7:34:8e:06:42:51:b1:81:18 -# SHA256 Fingerprint: d8:e0:fe:bc:1d:b2:e3:8d:00:94:0f:37:d2:7d:41:34:4d:99:3e:73:4b:99:d5:65:6d:97:78:d4:d8:14:36:24 ------BEGIN CERTIFICATE----- -MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM -MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD -QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM -MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD -QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E -jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo -ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI -ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu -Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg -AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 -HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA -uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa -TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg -xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q -CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x -O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs -6GAqm4VKQPNriiTsBhYscw== ------END CERTIFICATE----- - -# Issuer: CN=AAA Certificate Services O=Comodo CA Limited -# Subject: CN=AAA Certificate Services O=Comodo CA Limited -# Label: "Comodo AAA Services root" -# Serial: 1 -# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 -# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 -# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 ------BEGIN CERTIFICATE----- -MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj -YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM -GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua -BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe -3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 -YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR -rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm -ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU -oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF -MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v -QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t -b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF -AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q -GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz -Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 -G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi -l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 -smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== ------END CERTIFICATE----- - -# Issuer: CN=Secure Certificate Services O=Comodo CA Limited -# Subject: CN=Secure Certificate Services O=Comodo CA Limited -# Label: "Comodo Secure Services root" -# Serial: 1 -# MD5 Fingerprint: d3:d9:bd:ae:9f:ac:67:24:b3:c8:1b:52:e1:b9:a9:bd -# SHA1 Fingerprint: 4a:65:d5:f4:1d:ef:39:b8:b8:90:4a:4a:d3:64:81:33:cf:c7:a1:d1 -# SHA256 Fingerprint: bd:81:ce:3b:4f:65:91:d1:1a:67:b5:fc:7a:47:fd:ef:25:52:1b:f9:aa:4e:18:b9:e3:df:2e:34:a7:80:3b:e8 ------BEGIN CERTIFICATE----- -MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp -ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow -fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV -BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM -cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S -HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996 -CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk -3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz -6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV -HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud -EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv -Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw -Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww -DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0 -5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj -Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI -gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ -aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl -izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk= ------END CERTIFICATE----- - -# Issuer: CN=Trusted Certificate Services O=Comodo CA Limited -# Subject: CN=Trusted Certificate Services O=Comodo CA Limited -# Label: "Comodo Trusted Services root" -# Serial: 1 -# MD5 Fingerprint: 91:1b:3f:6e:cd:9e:ab:ee:07:fe:1f:71:d2:b3:61:27 -# SHA1 Fingerprint: e1:9f:e3:0e:8b:84:60:9e:80:9b:17:0d:72:a8:c5:ba:6e:14:09:bd -# SHA256 Fingerprint: 3f:06:e5:56:81:d4:96:f5:be:16:9e:b5:38:9f:9f:2b:8f:f6:1e:17:08:df:68:81:72:48:49:cd:5d:27:cb:69 ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 -aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla -MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO -BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD -VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW -fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt -TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL -fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW -1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 -kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G -A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v -ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo -dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu -Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ -HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 -pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS -jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ -xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn -dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Label: "QuoVadis Root CA" -# Serial: 985026699 -# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 -# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 -# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 ------BEGIN CERTIFICATE----- -MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz -MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw -IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR -dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp -li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D -rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ -WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug -F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU -xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC -Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv -dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw -ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl -IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh -c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy -ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh -Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI -KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T -KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq -y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p -dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD -VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL -MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk -fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 -7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R -cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y -mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW -xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK -SnQ2+Q== ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2" -# Serial: 1289 -# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b -# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 -# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 ------BEGIN CERTIFICATE----- -MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa -GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg -Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J -WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB -rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp -+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 -ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i -Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz -PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og -/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH -oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI -yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud -EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 -A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL -MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT -ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f -BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn -g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl -fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K -WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha -B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc -hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR -TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD -mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z -ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y -4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza -8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3" -# Serial: 1478 -# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf -# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 -# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 ------BEGIN CERTIFICATE----- -MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM -V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB -4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr -H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd -8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv -vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT -mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe -btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc -T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt -WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ -c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A -4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD -VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG -CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 -aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 -aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu -dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw -czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G -A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg -Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 -7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem -d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd -+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B -4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN -t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x -DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 -k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s -zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j -Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT -mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK -4SVhM7JZG+Ju1zdXtg2pEto= ------END CERTIFICATE----- - -# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 -# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 -# Label: "Security Communication Root CA" -# Serial: 0 -# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a -# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 -# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY -MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t -dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 -WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD -VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 -9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ -DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 -Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N -QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ -xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G -A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG -kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr -Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 -Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU -JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot -RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== ------END CERTIFICATE----- - -# Issuer: CN=Sonera Class2 CA O=Sonera -# Subject: CN=Sonera Class2 CA O=Sonera -# Label: "Sonera Class 2 Root CA" -# Serial: 29 -# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb -# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 -# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 ------BEGIN CERTIFICATE----- -MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP -MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx -MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV -BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o -Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt -5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s -3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej -vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu -8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw -DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG -MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil -zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ -3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD -FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 -Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 -ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M ------END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden Root CA O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA" -# Serial: 10000010 -# MD5 Fingerprint: 60:84:7c:5a:ce:db:0c:d4:cb:a7:e9:fe:02:c6:a9:c0 -# SHA1 Fingerprint: 10:1d:fa:3f:d5:0b:cb:bb:9b:b5:60:0c:19:55:a4:1a:f4:73:3a:04 -# SHA256 Fingerprint: d4:1d:82:9e:8c:16:59:82:2a:f9:3f:ce:62:bf:fc:de:26:4f:c8:4e:8b:95:0c:5f:f2:75:d0:52:35:46:95:a3 ------BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO -TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy -MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk -ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn -ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71 -9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO -hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U -tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o -BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh -SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww -OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv -cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA -7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k -/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm -eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6 -u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy -7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR -iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== ------END CERTIFICATE----- - -# Issuer: O=TDC Internet OU=TDC Internet Root CA -# Subject: O=TDC Internet OU=TDC Internet Root CA -# Label: "TDC Internet Root CA" -# Serial: 986490188 -# MD5 Fingerprint: 91:f4:03:55:20:a1:f8:63:2c:62:de:ac:fb:61:1c:8e -# SHA1 Fingerprint: 21:fc:bd:8e:7f:6c:af:05:1b:d1:b3:43:ec:a8:e7:61:47:f2:0f:8a -# SHA256 Fingerprint: 48:98:c6:88:8c:0c:ff:b0:d3:e3:1a:ca:8a:37:d4:e3:51:5f:f7:46:d0:26:35:d8:66:46:cf:a0:a3:18:5a:e7 ------BEGIN CERTIFICATE----- -MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE -SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg -Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV -BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl -cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA -vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu -Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a -0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1 -4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN -eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD -R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG -A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu -dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME -Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3 -WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw -HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ -KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO -Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX -wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ -2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89 -9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0 -jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38 -aQNiuJkFBT1reBK9sG9l ------END CERTIFICATE----- - -# Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com -# Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com -# Label: "UTN DATACorp SGC Root CA" -# Serial: 91374294542884689855167577680241077609 -# MD5 Fingerprint: b3:a5:3e:77:21:6d:ac:4a:c0:c9:fb:d5:41:3d:ca:06 -# SHA1 Fingerprint: 58:11:9f:0e:12:82:87:ea:50:fd:d9:87:45:6f:4f:78:dc:fa:d6:d4 -# SHA256 Fingerprint: 85:fb:2f:91:dd:12:27:5a:01:45:b6:36:53:4f:84:02:4a:d6:8b:69:b8:ee:88:68:4f:f7:11:37:58:05:b3:48 ------BEGIN CERTIFICATE----- -MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG -EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD -VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu -dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 -E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ -D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK -4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq -lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW -bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB -o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT -MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js -LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr -BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB -AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft -Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj -j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH -KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv -2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 -mfnGV/TJVTl4uix5yaaIK/QI ------END CERTIFICATE----- - -# Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com -# Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com -# Label: "UTN USERFirst Hardware Root CA" -# Serial: 91374294542884704022267039221184531197 -# MD5 Fingerprint: 4c:56:41:e5:0d:bb:2b:e8:ca:a3:ed:18:08:ad:43:39 -# SHA1 Fingerprint: 04:83:ed:33:99:ac:36:08:05:87:22:ed:bc:5e:46:00:e3:be:f9:d7 -# SHA256 Fingerprint: 6e:a5:47:41:d0:04:66:7e:ed:1b:48:16:63:4a:a3:a7:9e:6e:4b:96:95:0f:82:79:da:fc:8d:9b:d8:81:21:37 ------BEGIN CERTIFICATE----- -MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB -lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt -SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG -A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe -MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v -d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh -cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn -0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ -M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a -MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd -oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI -DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy -oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD -VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 -dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy -bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF -BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM -//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli -CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE -CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t -3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS -KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== ------END CERTIFICATE----- - -# Issuer: CN=Chambers of Commerce Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org -# Subject: CN=Chambers of Commerce Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org -# Label: "Camerfirma Chambers of Commerce Root" -# Serial: 0 -# MD5 Fingerprint: b0:01:ee:14:d9:af:29:18:94:76:8e:f1:69:33:2a:84 -# SHA1 Fingerprint: 6e:3a:55:a4:19:0c:19:5c:93:84:3c:c0:db:72:2e:31:30:61:f0:b1 -# SHA256 Fingerprint: 0c:25:8a:12:a5:67:4a:ef:25:f2:8b:a7:dc:fa:ec:ee:a3:48:e5:41:e6:f5:cc:4e:e6:3b:71:b3:61:60:6a:c3 ------BEGIN CERTIFICATE----- -MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn -MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL -ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg -b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa -MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB -ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw -IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B -AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb -unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d -BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq -7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3 -0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX -roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG -A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j -aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p -26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA -BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud -EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN -BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz -aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB -AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd -p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi -1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc -XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0 -eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu -tGWaIZDgqtCYvDi1czyL+Nw= ------END CERTIFICATE----- - -# Issuer: CN=Global Chambersign Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org -# Subject: CN=Global Chambersign Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org -# Label: "Camerfirma Global Chambersign Root" -# Serial: 0 -# MD5 Fingerprint: c5:e6:7b:bf:06:d0:4f:43:ed:c4:7a:65:8a:fb:6b:19 -# SHA1 Fingerprint: 33:9b:6b:14:50:24:9b:55:7a:01:87:72:84:d9:e0:2f:c3:d2:d8:e9 -# SHA256 Fingerprint: ef:3c:b4:17:fc:8e:bf:6f:97:87:6c:9e:4e:ce:39:de:1e:a5:fe:64:91:41:d1:02:8b:7d:11:c0:b2:29:8c:ed ------BEGIN CERTIFICATE----- -MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn -MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL -ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo -YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9 -MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy -NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G -A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA -A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0 -Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s -QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV -eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795 -B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh -z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T -AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i -ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w -TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH -MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD -VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE -VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh -bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B -AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM -bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi -ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG -VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c -ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/ -AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== ------END CERTIFICATE----- - -# Issuer: CN=NetLock Kozjegyzoi (Class A) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Kozjegyzoi (Class A) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Notary (Class A) Root" -# Serial: 259 -# MD5 Fingerprint: 86:38:6d:5e:49:63:6c:85:5c:db:6d:dc:94:b7:d0:f7 -# SHA1 Fingerprint: ac:ed:5f:65:53:fd:25:ce:01:5f:1f:7a:48:3b:6a:74:9f:61:78:c6 -# SHA256 Fingerprint: 7f:12:cd:5f:7e:5e:29:0e:c7:d8:51:79:d5:b7:2c:20:a5:be:75:08:ff:db:5b:f8:1a:b9:68:4a:7f:c9:f6:67 ------BEGIN CERTIFICATE----- -MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV -MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe -TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0 -dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB -KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0 -N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC -dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu -MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL -b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD -zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi -3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8 -WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY -Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi -NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC -ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4 -QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0 -YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz -aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu -IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm -ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg -ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs -amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv -IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3 -Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6 -ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1 -YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg -dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs -b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G -CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO -xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP -0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ -QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk -f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK -8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI ------END CERTIFICATE----- - -# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Business (Class B) Root" -# Serial: 105 -# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6 -# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af -# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12 ------BEGIN CERTIFICATE----- -MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD -EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 -OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G -A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh -Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l -dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG -SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK -gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX -iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc -Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E -BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G -SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu -b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh -bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv -Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln -aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 -IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh -c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph -biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo -ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP -UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj -YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo -dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA -bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 -sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa -n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS -NitjrFgBazMpUIaD8QFI ------END CERTIFICATE----- - -# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Express (Class C) Root" -# Serial: 104 -# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4 -# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b -# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f ------BEGIN CERTIFICATE----- -MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD -EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X -DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw -DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u -c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr -TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN -BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA -OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC -2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW -RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P -AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW -ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 -YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz -b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO -ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB -IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs -b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs -ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s -YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg -a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g -SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 -aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg -YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg -Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY -ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g -pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 -Fp1hBWeAyNDYpQcCNJgEjTME1A== ------END CERTIFICATE----- - -# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Label: "XRamp Global CA Root" -# Serial: 107108908803651509692980124233745014957 -# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 -# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 -# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB -gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk -MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY -UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx -NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 -dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy -dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 -38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP -KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q -DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 -qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa -JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi -PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P -BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs -jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 -eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD -ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR -vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt -qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa -IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy -i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ -O+7ETPTsJ3xCwnR8gooJybQDJbw= ------END CERTIFICATE----- - -# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Label: "Go Daddy Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 -# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 -# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh -MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE -YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 -MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo -ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg -MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN -ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA -PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w -wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi -EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY -avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ -YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE -sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h -/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 -IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy -OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P -TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ -HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER -dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf -ReYNnyicsbkqWletNw+vHX/bvZ8= ------END CERTIFICATE----- - -# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Label: "Starfield Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 -# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a -# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl -MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp -U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw -NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE -ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp -ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 -DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf -8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN -+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 -X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa -K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA -1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G -A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR -zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 -YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD -bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 -L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D -eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl -xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp -VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY -WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= ------END CERTIFICATE----- - -# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Label: "StartCom Certification Authority" -# Serial: 1 -# MD5 Fingerprint: 22:4d:8f:8a:fc:f7:35:c2:bb:57:34:90:7b:8b:22:16 -# SHA1 Fingerprint: 3e:2b:f7:f2:03:1b:96:f3:8c:e6:c4:d8:a8:5d:3e:2d:58:47:6a:0f -# SHA256 Fingerprint: c7:66:a9:be:f2:d4:07:1c:86:3a:31:aa:49:20:e8:13:b2:d1:98:60:8c:b7:b7:cf:e2:11:43:b8:36:df:09:ea ------BEGIN CERTIFICATE----- -MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg -Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 -MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi -U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh -cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk -pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf -OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C -Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT -Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi -HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM -Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w -+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ -Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 -Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B -26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID -AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE -FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j -ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js -LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM -BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 -Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy -dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh -cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh -YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg -dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp -bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ -YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT -TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ -9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 -jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW -FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz -ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 -ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L -EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu -L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq -yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC -O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V -um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh -NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= ------END CERTIFICATE----- - -# Issuer: O=Government Root Certification Authority -# Subject: O=Government Root Certification Authority -# Label: "Taiwan GRCA" -# Serial: 42023070807708724159991140556527066870 -# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e -# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 -# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ -MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow -PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR -IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q -gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy -yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts -F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 -jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx -ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC -VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK -YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH -EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN -Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud -DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE -MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK -UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ -TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf -qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK -ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE -JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 -hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 -EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm -nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX -udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz -ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe -LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl -pYYsfPQS ------END CERTIFICATE----- - -# Issuer: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services -# Subject: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services -# Label: "Swisscom Root CA 1" -# Serial: 122348795730808398873664200247279986742 -# MD5 Fingerprint: f8:38:7c:77:88:df:2c:16:68:2e:c2:e2:52:4b:b8:f9 -# SHA1 Fingerprint: 5f:3a:fc:0a:8b:64:f6:86:67:34:74:df:7e:a9:a2:fe:f9:fa:7a:51 -# SHA256 Fingerprint: 21:db:20:12:36:60:bb:2e:d4:18:20:5d:a1:1e:e7:a8:5a:65:e2:bc:6e:55:b5:af:7e:78:99:c8:a2:66:d9:2e ------BEGIN CERTIFICATE----- -MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk -MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 -YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg -Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT -AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp -Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN -BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9 -m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih -FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/ -TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F -EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco -kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu -HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF -vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo -19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC -L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW -bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX -JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw -FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j -BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc -K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf -ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik -Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB -sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e -3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR -ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip -mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH -b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf -rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms -hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y -zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6 -MBr1mmz0DlP5OlvRHA== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root CA" -# Serial: 17154717934120587862167794914071425081 -# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 -# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 -# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c ------BEGIN CERTIFICATE----- -MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c -JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP -mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ -wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 -VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ -AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB -AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun -pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC -dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf -fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm -NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx -H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe -+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root CA" -# Serial: 10944719598952040374951832963794454346 -# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e -# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 -# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert High Assurance EV Root CA" -# Serial: 3553400076410547919724730734378100087 -# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a -# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 -# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- - -# Issuer: CN=Class 2 Primary CA O=Certplus -# Subject: CN=Class 2 Primary CA O=Certplus -# Label: "Certplus Class 2 Primary CA" -# Serial: 177770208045934040241468760488327595043 -# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b -# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb -# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb ------BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw -PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz -cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 -MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz -IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ -ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR -VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL -kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd -EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas -H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 -HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud -DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 -QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu -Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ -AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 -yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR -FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA -ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB -kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 -l7+ijrRU ------END CERTIFICATE----- - -# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. -# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. -# Label: "DST Root CA X3" -# Serial: 91299735575339953335919266965803778155 -# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 -# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 -# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O -rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq -OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b -xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw -7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD -aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG -SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 -ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr -AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz -R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 -JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo -Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ------END CERTIFICATE----- - -# Issuer: CN=DST ACES CA X6 O=Digital Signature Trust OU=DST ACES -# Subject: CN=DST ACES CA X6 O=Digital Signature Trust OU=DST ACES -# Label: "DST ACES CA X6" -# Serial: 17771143917277623872238992636097467865 -# MD5 Fingerprint: 21:d8:4c:82:2b:99:09:33:a2:eb:14:24:8d:8e:5f:e8 -# SHA1 Fingerprint: 40:54:da:6f:1c:3f:40:74:ac:ed:0f:ec:cd:db:79:d1:53:fb:90:1d -# SHA256 Fingerprint: 76:7c:95:5a:76:41:2c:89:af:68:8e:90:a1:c7:0f:55:6c:fd:6b:60:25:db:ea:10:41:6d:7e:b6:83:1f:8c:40 ------BEGIN CERTIFICATE----- -MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx -ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w -MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD -VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx -FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu -ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7 -gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH -fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a -ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT -ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF -MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk -c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto -dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt -aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI -hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk -QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/ -h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq -nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR -rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2 -9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis= ------END CERTIFICATE----- - -# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=(c) 2005 TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. -# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=(c) 2005 TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. -# Label: "TURKTRUST Certificate Services Provider Root 1" -# Serial: 1 -# MD5 Fingerprint: f1:6a:22:18:c9:cd:df:ce:82:1d:1d:b7:78:5c:a9:a5 -# SHA1 Fingerprint: 79:98:a3:08:e1:4d:65:85:e6:c2:1e:15:3a:71:9f:ba:5a:d3:4a:d9 -# SHA256 Fingerprint: 44:04:e3:3b:5e:14:0d:cf:99:80:51:fd:fc:80:28:c7:c8:16:15:c5:ee:73:7b:11:1b:58:82:33:a9:b5:35:a0 ------BEGIN CERTIFICATE----- -MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg -MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 -dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz -MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy -dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD -VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg -xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu -xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7 -XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k -heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J -YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C -urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1 -JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51 -b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV -9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7 -kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh -fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy -B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA -aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS -RGQDJereW26fyfJOrN3H ------END CERTIFICATE----- - -# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005 -# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005 -# Label: "TURKTRUST Certificate Services Provider Root 2" -# Serial: 1 -# MD5 Fingerprint: 37:a5:6e:d4:b1:25:84:97:b7:fd:56:15:7a:f9:a2:00 -# SHA1 Fingerprint: b4:35:d4:e1:11:9d:1c:66:90:a7:49:eb:b3:94:bd:63:7b:a7:82:b7 -# SHA256 Fingerprint: c4:70:cf:54:7e:23:02:b9:77:fb:29:dd:71:a8:9a:7b:6c:1f:60:77:7b:03:29:f5:60:17:f3:28:bf:4f:6b:e6 ------BEGIN CERTIFICATE----- -MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS -S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg -SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3 -WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv -bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU -UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw -bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe -LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef -J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh -R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ -Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX -JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p -zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S -Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ -KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq -ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 -Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz -gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH -uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS -y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI= ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Label: "SwissSign Gold CA - G2" -# Serial: 13492815561806991280 -# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 -# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 -# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 ------BEGIN CERTIFICATE----- -MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV -BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln -biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF -MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT -d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 -76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ -bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c -6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE -emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd -MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt -MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y -MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y -FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi -aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM -gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB -qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 -lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn -8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov -L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 -45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO -UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 -O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC -bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv -GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a -77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC -hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 -92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp -Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w -ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt -Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Label: "SwissSign Silver CA - G2" -# Serial: 5700383053117599563 -# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 -# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb -# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 ------BEGIN CERTIFICATE----- -MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE -BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu -IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow -RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY -U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A -MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv -Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br -YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF -nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH -6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt -eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ -c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ -MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH -HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf -jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 -5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB -rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU -F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c -wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 -cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB -AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp -WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 -xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ -2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ -IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 -aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X -em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR -dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ -OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ -hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy -tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Label: "GeoTrust Primary Certification Authority" -# Serial: 32798226551256963324313806436981982369 -# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf -# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 -# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY -MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo -R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx -MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 -AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA -ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 -7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W -kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI -mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ -KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 -6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl -4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K -oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj -UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU -AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA" -# Serial: 69529181992039203566298953787712940909 -# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 -# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 -# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" -# Serial: 33037644167568058970164719475676101450 -# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c -# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 -# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 -nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex -t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz -SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG -BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ -rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ -NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH -BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv -MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE -p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y -5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK -WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ -4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N -hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------END CERTIFICATE----- - -# Issuer: CN=SecureTrust CA O=SecureTrust Corporation -# Subject: CN=SecureTrust CA O=SecureTrust Corporation -# Label: "SecureTrust CA" -# Serial: 17199774589125277788362757014266862032 -# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 -# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 -# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 ------BEGIN CERTIFICATE----- -MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz -MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv -cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz -Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO -0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao -wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj -7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS -8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT -BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg -JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 -6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ -3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm -D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS -CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR -3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE----- - -# Issuer: CN=Secure Global CA O=SecureTrust Corporation -# Subject: CN=Secure Global CA O=SecureTrust Corporation -# Label: "Secure Global CA" -# Serial: 9751836167731051554232119481456978597 -# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de -# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b -# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 ------BEGIN CERTIFICATE----- -MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx -MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg -Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ -iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa -/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ -jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI -HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 -sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w -gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw -KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG -AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L -URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO -H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm -I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY -iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc -f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW ------END CERTIFICATE----- - -# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO Certification Authority O=COMODO CA Limited -# Label: "COMODO Certification Authority" -# Serial: 104350513648249232941998508985834464573 -# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 -# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b -# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 ------BEGIN CERTIFICATE----- -MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB -gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV -BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw -MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl -YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P -RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 -UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI -2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 -Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp -+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ -DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O -nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW -/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g -PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u -QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY -SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv -IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ -RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 -zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd -BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB -ZQ== ------END CERTIFICATE----- - -# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Label: "Network Solutions Certificate Authority" -# Serial: 116697915152937497490437556386812487904 -# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e -# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce -# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c ------BEGIN CERTIFICATE----- -MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi -MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu -MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp -dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV -UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO -ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz -c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP -OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl -mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF -BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 -qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw -gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu -bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp -dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 -6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ -h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH -/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv -wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN -pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey ------END CERTIFICATE----- - -# Issuer: CN=WellsSecure Public Root Certificate Authority O=Wells Fargo WellsSecure OU=Wells Fargo Bank NA -# Subject: CN=WellsSecure Public Root Certificate Authority O=Wells Fargo WellsSecure OU=Wells Fargo Bank NA -# Label: "WellsSecure Public Root Certificate Authority" -# Serial: 1 -# MD5 Fingerprint: 15:ac:a5:c2:92:2d:79:bc:e8:7f:cb:67:ed:02:cf:36 -# SHA1 Fingerprint: e7:b4:f6:9d:61:ec:90:69:db:7e:90:a7:40:1a:3c:f4:7d:4f:e8:ee -# SHA256 Fingerprint: a7:12:72:ae:aa:a3:cf:e8:72:7f:7f:b3:9f:0f:b3:d1:e5:42:6e:90:60:b0:6e:e6:f1:3e:9a:3c:58:33:cd:43 ------BEGIN CERTIFICATE----- -MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx -IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs -cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v -dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0 -MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl -bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD -DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r -WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU -Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs -HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj -z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf -SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl -AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG -KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P -AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j -BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC -VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX -ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg -Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB -ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd -/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB -A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn -k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9 -iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv -2G0xffX8oRAHh84vWdw+WNs= ------END CERTIFICATE----- - -# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Label: "COMODO ECC Certification Authority" -# Serial: 41578283867086692638256921589707938090 -# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 -# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 -# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 ------BEGIN CERTIFICATE----- -MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT -IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw -MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy -ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N -T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv -biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR -FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J -cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW -BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm -fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv -GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= ------END CERTIFICATE----- - -# Issuer: CN=IGC/A O=PM/SGDN OU=DCSSI -# Subject: CN=IGC/A O=PM/SGDN OU=DCSSI -# Label: "IGC/A" -# Serial: 245102874772 -# MD5 Fingerprint: 0c:7f:dd:6a:f4:2a:b9:c8:9b:bd:20:7e:a9:db:5c:37 -# SHA1 Fingerprint: 60:d6:89:74:b5:c2:65:9e:8a:0f:c1:88:7c:88:d2:46:69:1b:18:2c -# SHA256 Fingerprint: b9:be:a7:86:0a:96:2e:a3:61:1d:ab:97:ab:6d:a3:e2:1c:10:68:b9:7d:55:57:5e:d0:e1:12:79:c1:1c:89:32 ------BEGIN CERTIFICATE----- -MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT -AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ -TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG -9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw -MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM -BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO -MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2 -LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI -s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2 -xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4 -u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b -F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx -Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd -PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV -HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx -NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF -AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ -L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY -YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg -Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a -NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R -0982gaEbeC9xs/FZTEYYKKuF0mBWWg== ------END CERTIFICATE----- - -# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication EV RootCA1 -# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication EV RootCA1 -# Label: "Security Communication EV RootCA1" -# Serial: 0 -# MD5 Fingerprint: 22:2d:a6:01:ea:7c:0a:f7:f0:6c:56:43:3f:77:76:d3 -# SHA1 Fingerprint: fe:b8:c4:32:dc:f9:76:9a:ce:ae:3d:d8:90:8f:fd:28:86:65:64:7d -# SHA256 Fingerprint: a2:2d:ba:68:1e:97:37:6e:2d:39:7d:72:8a:ae:3a:9b:62:96:b9:fd:ba:60:bc:2e:11:f6:47:f2:c6:75:fb:37 ------BEGIN CERTIFICATE----- -MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl -MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh -U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz -MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N -IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11 -bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE -RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO -zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5 -bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF -MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1 -VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC -OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G -CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW -tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ -q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb -EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+ -Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O -VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 ------END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GA CA" -# Serial: 86718877871133159090080555911823548314 -# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 -# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 -# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 ------BEGIN CERTIFICATE----- -MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB -ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly -aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl -ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w -NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G -A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD -VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX -SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR -VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 -w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF -mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg -4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 -4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw -EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx -SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 -ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 -vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa -hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi -Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ -/L7fCg0= ------END CERTIFICATE----- - -# Issuer: CN=Microsec e-Szigno Root CA O=Microsec Ltd. OU=e-Szigno CA -# Subject: CN=Microsec e-Szigno Root CA O=Microsec Ltd. OU=e-Szigno CA -# Label: "Microsec e-Szigno Root CA" -# Serial: 272122594155480254301341951808045322001 -# MD5 Fingerprint: f0:96:b6:2f:c5:10:d5:67:8e:83:25:32:e8:5e:2e:e5 -# SHA1 Fingerprint: 23:88:c9:d3:71:cc:9e:96:3d:ff:7d:3c:a7:ce:fc:d6:25:ec:19:0d -# SHA256 Fingerprint: 32:7a:3d:76:1a:ba:de:a0:34:eb:99:84:06:27:5c:b1:a4:77:6e:fd:ae:2f:df:6d:01:68:ea:1c:4f:55:67:d0 ------BEGIN CERTIFICATE----- -MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw -cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy -b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z -ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4 -NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN -TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p -Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u -uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+ -LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA -vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770 -Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx -62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB -AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw -LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP -BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB -AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov -MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5 -ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn -AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT -AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh -ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo -AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa -AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln -bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p -Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP -PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv -Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB -EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu -w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj -cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV -HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI -VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS -BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS -b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS -8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds -ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl -7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a -86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR -hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/ -MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= ------END CERTIFICATE----- - -# Issuer: CN=Certigna O=Dhimyotis -# Subject: CN=Certigna O=Dhimyotis -# Label: "Certigna" -# Serial: 18364802974209362175 -# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff -# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 -# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d ------BEGIN CERTIFICATE----- -MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV -BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X -DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ -BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 -QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny -gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw -zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q -130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 -JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw -ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT -AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj -AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG -9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h -bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc -fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu -HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w -t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw -WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== ------END CERTIFICATE----- - -# Issuer: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. -# Subject: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. -# Label: "AC Ra\xC3\xADz Certic\xC3\xA1mara S.A." -# Serial: 38908203973182606954752843738508300 -# MD5 Fingerprint: 93:2a:3e:f6:fd:23:69:0d:71:20:d4:2b:47:99:2b:a6 -# SHA1 Fingerprint: cb:a1:c5:f8:b0:e3:5e:b8:b9:45:12:d3:f9:34:a2:e9:06:10:d3:36 -# SHA256 Fingerprint: a6:c5:1e:0d:a5:ca:0a:93:09:d2:e4:c0:e4:0c:2a:f9:10:7a:ae:82:03:85:7f:e1:98:e3:e7:69:e3:43:08:5c ------BEGIN CERTIFICATE----- -MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx -CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp -ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa -QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw -NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft -ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu -QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG -qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL -fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ -Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4 -Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ -54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b -MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j -ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej -YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt -A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF -rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ -pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB -lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy -YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50 -7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs -YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6 -xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc -unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/ -Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp -ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42 -gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0 -jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+ -XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD -W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/ -RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r -MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk -BYn8eNZcLCZDqQ== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Label: "TC TrustCenter Class 2 CA II" -# Serial: 941389028203453866782103406992443 -# MD5 Fingerprint: ce:78:33:5c:59:78:01:6e:18:ea:b9:36:a0:b9:2e:23 -# SHA1 Fingerprint: ae:50:83:ed:7c:f4:5c:bc:8f:61:c6:21:fe:68:5d:79:42:21:15:6e -# SHA256 Fingerprint: e6:b8:f8:76:64:85:f8:07:ae:7f:8d:ac:16:70:46:1f:07:c0:a1:3e:ef:3a:1f:f7:17:53:8d:7a:ba:d3:91:b4 ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf -tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg -uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J -XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK -8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99 -5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3 -kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS -GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt -ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8 -au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV -hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI -dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Subject: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Label: "TC TrustCenter Class 3 CA II" -# Serial: 1506523511417715638772220530020799 -# MD5 Fingerprint: 56:5f:aa:80:61:12:17:f6:67:21:e6:2b:6d:61:56:8e -# SHA1 Fingerprint: 80:25:ef:f4:6e:70:c8:d4:72:24:65:84:fe:40:3b:8a:8d:6a:db:f5 -# SHA256 Fingerprint: 8d:a0:84:fc:f9:9c:e0:77:22:f8:9b:32:05:93:98:06:fa:5c:b8:11:e1:c8:13:f6:a1:08:c7:d3:36:b3:40:8e ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW -Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q -Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2 -1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq -ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1 -Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX -XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN -irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8 -TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6 -g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB -95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj -S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Subject: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Label: "TC TrustCenter Universal CA I" -# Serial: 601024842042189035295619584734726 -# MD5 Fingerprint: 45:e1:a5:72:c5:a9:36:64:40:9e:f5:e4:58:84:67:8c -# SHA1 Fingerprint: 6b:2f:34:ad:89:58:be:62:fd:b0:6b:5c:ce:bb:9d:d9:4f:4e:39:f3 -# SHA256 Fingerprint: eb:f3:c0:2a:87:89:b1:fb:7d:51:19:95:d6:63:b7:29:06:d9:13:ce:0d:5e:10:56:8a:8a:77:e2:58:61:67:e7 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV -BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1 -c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx -MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg -R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD -VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR -JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T -fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu -jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z -wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ -fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD -VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G -CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1 -7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn -8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs -ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT -ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/ -2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY ------END CERTIFICATE----- - -# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Label: "Deutsche Telekom Root CA 2" -# Serial: 38 -# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 -# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf -# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 ------BEGIN CERTIFICATE----- -MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc -MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj -IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB -IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE -RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl -U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 -IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU -ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC -QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr -rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S -NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc -QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH -txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP -BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC -AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp -tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa -IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl -6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ -xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU -Cm26OWMohpLzGITY+9HPBVZkVw== ------END CERTIFICATE----- - -# Issuer: CN=ComSign Secured CA O=ComSign -# Subject: CN=ComSign Secured CA O=ComSign -# Label: "ComSign Secured CA" -# Serial: 264725503855295744117309814499492384489 -# MD5 Fingerprint: 40:01:25:06:8d:21:43:6a:0e:43:00:9c:e7:43:f3:d5 -# SHA1 Fingerprint: f9:cd:0e:2c:da:76:24:c1:8f:bd:f0:f0:ab:b6:45:b8:f7:fe:d5:7a -# SHA256 Fingerprint: 50:79:41:c7:44:60:a0:b4:70:86:22:0d:4e:99:32:57:2a:b5:d1:b5:bb:cb:89:80:ab:1c:b1:76:51:a8:44:d2 ------BEGIN CERTIFICATE----- -MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw -PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu -MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx -GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL -MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf -HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh -gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW -v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue -Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr -9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt -6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7 -MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl -Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58 -ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq -hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p -iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC -dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL -kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL -hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz -OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== ------END CERTIFICATE----- - -# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc -# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc -# Label: "Cybertrust Global Root" -# Serial: 4835703278459682877484360 -# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 -# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 -# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 ------BEGIN CERTIFICATE----- -MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG -A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh -bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE -ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS -b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 -7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS -J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y -HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP -t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz -FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY -XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ -MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw -hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js -MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA -A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj -Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx -XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o -omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc -A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW -WL1WMRJOEcgh4LMRkWXbtKaIOM5V ------END CERTIFICATE----- - -# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Label: "ePKI Root Certification Authority" -# Serial: 28956088682735189655030529057352760477 -# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 -# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 -# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 ------BEGIN CERTIFICATE----- -MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe -MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 -ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw -IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL -SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH -SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh -ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X -DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 -TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ -fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA -sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU -WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS -nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH -dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip -NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC -AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF -MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH -ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB -uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl -PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP -JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ -gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 -j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 -5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB -o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS -/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z -Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE -W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D -hNQ+IIX3Sj0rnP0qCglN6oH4EZw= ------END CERTIFICATE----- - -# Issuer: CN=TÜBİTAK UEKAE Kök Sertifika Hizmet Sağlayıcısı - Sürüm 3 O=Türkiye Bilimsel ve Teknolojik Araştırma Kurumu - TÜBİTAK OU=Ulusal Elektronik ve Kriptoloji Araştırma Enstitüsü - UEKAE/Kamu Sertifikasyon Merkezi -# Subject: CN=TÜBİTAK UEKAE Kök Sertifika Hizmet Sağlayıcısı - Sürüm 3 O=Türkiye Bilimsel ve Teknolojik Araştırma Kurumu - TÜBİTAK OU=Ulusal Elektronik ve Kriptoloji Araştırma Enstitüsü - UEKAE/Kamu Sertifikasyon Merkezi -# Label: "T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3" -# Serial: 17 -# MD5 Fingerprint: ed:41:f5:8c:50:c5:2b:9c:73:e6:ee:6c:eb:c2:a8:26 -# SHA1 Fingerprint: 1b:4b:39:61:26:27:6b:64:91:a2:68:6d:d7:02:43:21:2d:1f:1d:96 -# SHA256 Fingerprint: e4:c7:34:30:d7:a5:b5:09:25:df:43:37:0a:0d:21:6e:9a:79:b9:d6:db:83:73:a0:c6:9e:b1:cc:31:c7:c5:2a ------BEGIN CERTIFICATE----- -MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS -MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp -bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw -VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy -YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy -dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2 -ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe -Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx -GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls -aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU -QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh -xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0 -aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr -IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB -IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h -gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK -O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO -fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw -lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL -hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID -AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP -NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t -wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM -7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh -gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n -oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs -yZyQ2uypQjyttgI= ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 2 CA 1 O=Buypass AS-983163327 -# Subject: CN=Buypass Class 2 CA 1 O=Buypass AS-983163327 -# Label: "Buypass Class 2 CA 1" -# Serial: 1 -# MD5 Fingerprint: b8:08:9a:f0:03:cc:1b:0d:c8:6c:0b:76:a1:75:64:23 -# SHA1 Fingerprint: a0:a1:ab:90:c9:fc:84:7b:3b:12:61:e8:97:7d:5f:d3:22:61:d3:cc -# SHA256 Fingerprint: 0f:4e:9c:dd:26:4b:02:55:50:d1:70:80:63:40:21:4f:e9:44:34:c9:b0:2f:69:7e:c7:10:fc:5f:ea:fb:5e:38 ------BEGIN CERTIFICATE----- -MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg -Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL -MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD -VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0 -ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX -l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB -HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B -5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3 -WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD -AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP -gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+ -DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu -BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs -h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk -LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 3 CA 1 O=Buypass AS-983163327 -# Subject: CN=Buypass Class 3 CA 1 O=Buypass AS-983163327 -# Label: "Buypass Class 3 CA 1" -# Serial: 2 -# MD5 Fingerprint: df:3c:73:59:81:e7:39:50:81:04:4c:34:a2:cb:b3:7b -# SHA1 Fingerprint: 61:57:3a:11:df:0e:d8:7e:d5:92:65:22:ea:d0:56:d7:44:b3:23:71 -# SHA256 Fingerprint: b7:b1:2b:17:1f:82:1d:aa:99:0c:d0:fe:50:87:b1:28:44:8b:a8:e5:18:4f:84:c5:1e:02:b5:c8:fb:96:2b:24 ------BEGIN CERTIFICATE----- -MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg -Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL -MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD -VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg -isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z -NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI -+MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R -hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+ -mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD -AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP -Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s -EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2 -mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC -e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow -dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 ------END CERTIFICATE----- - -# Issuer: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. -# Subject: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. -# Label: "EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1" -# Serial: 5525761995591021570 -# MD5 Fingerprint: 2c:20:26:9d:cb:1a:4a:00:85:b5:b7:5a:ae:c2:01:37 -# SHA1 Fingerprint: 8c:96:ba:eb:dd:2b:07:07:48:ee:30:32:66:a0:f3:98:6e:7c:ae:58 -# SHA256 Fingerprint: 35:ae:5b:dd:d8:f7:ae:63:5c:ff:ba:56:82:a8:f0:0b:95:f4:84:62:c7:10:8e:e9:a0:e5:29:2b:07:4a:af:b2 ------BEGIN CERTIFICATE----- -MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV -BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt -ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4 -MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg -SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl -a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h -4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk -tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s -tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL -dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4 -c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um -TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z -+kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O -Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW -OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW -fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2 -l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB -/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw -FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+ -8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI -6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO -TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME -wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY -Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn -xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q -DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q -Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t -hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4 -7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7 -QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT ------END CERTIFICATE----- - -# Issuer: O=certSIGN OU=certSIGN ROOT CA -# Subject: O=certSIGN OU=certSIGN ROOT CA -# Label: "certSIGN ROOT CA" -# Serial: 35210227249154 -# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 -# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b -# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb ------BEGIN CERTIFICATE----- -MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT -AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD -QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP -MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do -0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ -UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d -RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ -OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv -JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C -AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O -BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ -LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY -MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ -44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I -Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw -i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN -9u6wWk5JRFRYX0KD ------END CERTIFICATE----- - -# Issuer: CN=CNNIC ROOT O=CNNIC -# Subject: CN=CNNIC ROOT O=CNNIC -# Label: "CNNIC ROOT" -# Serial: 1228079105 -# MD5 Fingerprint: 21:bc:82:ab:49:c4:13:3b:4b:b2:2b:5c:6b:90:9c:19 -# SHA1 Fingerprint: 8b:af:4c:9b:1d:f0:2a:92:f7:da:12:8e:b9:1b:ac:f4:98:60:4b:6f -# SHA256 Fingerprint: e2:83:93:77:3d:a8:45:a6:79:f2:08:0c:c7:fb:44:a3:b7:a1:c3:79:2c:b7:eb:77:29:fd:cb:6a:8d:99:ae:a7 ------BEGIN CERTIFICATE----- -MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD -TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2 -MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF -Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh -IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6 -dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO -V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC -GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN -v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB -AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB -Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO -76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK -OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH -ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi -yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL -buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj -2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE= ------END CERTIFICATE----- - -# Issuer: O=Japanese Government OU=ApplicationCA -# Subject: O=Japanese Government OU=ApplicationCA -# Label: "ApplicationCA - Japanese Government" -# Serial: 49 -# MD5 Fingerprint: 7e:23:4e:5b:a7:a5:b4:25:e9:00:07:74:11:62:ae:d6 -# SHA1 Fingerprint: 7f:8a:b0:cf:d0:51:87:6a:66:f3:36:0f:47:c8:8d:8c:d3:35:fc:74 -# SHA256 Fingerprint: 2d:47:43:7d:e1:79:51:21:5a:12:f3:c5:8e:51:c7:29:a5:80:26:ef:1f:cc:0a:5f:b3:d9:dc:01:2f:60:0d:19 ------BEGIN CERTIFICATE----- -MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc -MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp -b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT -AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs -aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H -j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K -f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55 -IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw -FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht -QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm -/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ -k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ -MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC -seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ -hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+ -eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U -DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj -B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL -rosot4LKGAfmt1t06SAZf7IbiVQ= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G3" -# Serial: 28809105769928564313984085209975885599 -# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 -# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd -# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB -mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT -MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ -BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 -BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz -+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm -hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn -5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W -JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL -DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC -huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB -AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB -zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN -kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH -SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G -spki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G2" -# Serial: 71758320672825410020661621085256472406 -# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f -# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 -# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G3" -# Serial: 127614157056681299805556476275995414779 -# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 -# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 -# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB -rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV -BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa -Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl -LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u -MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl -ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm -gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 -YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf -b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 -9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S -zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk -OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA -2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW -oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c -KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM -m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu -MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G2" -# Serial: 80682863203381065782177908751794619243 -# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a -# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 -# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL -MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj -KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 -MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV -BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw -NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV -BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL -So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal -tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG -CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT -qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz -rD6ogRLQy7rQkgu2npaqBA+K ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Universal Root Certification Authority" -# Serial: 85209574734084581917763752644031726877 -# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 -# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 -# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" -# Serial: 63143484348153506665311985501458640051 -# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 -# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a -# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------END CERTIFICATE----- - -# Issuer: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) -# Subject: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) -# Label: "NetLock Arany (Class Gold) Főtanúsítvány" -# Serial: 80544274841616 -# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 -# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 -# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG -EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 -MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl -cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR -dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB -pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM -b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm -aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz -IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT -lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz -AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 -VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG -ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 -BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG -AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M -U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh -bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C -+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC -bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F -uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 -XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= ------END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G2" -# Serial: 10000012 -# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a -# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 -# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f ------BEGIN CERTIFICATE----- -MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX -DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 -qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp -uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU -Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE -pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp -5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M -UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN -GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy -5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv -6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK -eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 -B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ -BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov -L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG -SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS -CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen -5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 -IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK -gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL -+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL -vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm -bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk -N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC -Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z -ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== ------END CERTIFICATE----- - -# Issuer: CN=CA Disig O=Disig a.s. -# Subject: CN=CA Disig O=Disig a.s. -# Label: "CA Disig" -# Serial: 1 -# MD5 Fingerprint: 3f:45:96:39:e2:50:87:f7:bb:fe:98:0c:3c:20:98:e6 -# SHA1 Fingerprint: 2a:c8:d5:8b:57:ce:bf:2f:49:af:f2:fc:76:8f:51:14:62:90:7a:41 -# SHA256 Fingerprint: 92:bf:51:19:ab:ec:ca:d0:b1:33:2d:c4:e1:d0:5f:ba:75:b5:67:90:44:ee:0c:a2:6e:93:1f:74:4f:2f:33:cf ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET -MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE -AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw -CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg -YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE -Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX -mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD -XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW -S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp -FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD -AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu -ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z -ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv -Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw -DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6 -yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq -EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ -CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB -EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN -PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag= ------END CERTIFICATE----- - -# Issuer: CN=Juur-SK O=AS Sertifitseerimiskeskus -# Subject: CN=Juur-SK O=AS Sertifitseerimiskeskus -# Label: "Juur-SK" -# Serial: 999181308 -# MD5 Fingerprint: aa:8e:5d:d9:f8:db:0a:58:b7:8d:26:87:6c:82:35:55 -# SHA1 Fingerprint: 40:9d:4b:d9:17:b5:5c:27:b6:9b:64:cb:98:22:44:0d:cd:09:b8:89 -# SHA256 Fingerprint: ec:c3:e9:c3:40:75:03:be:e0:91:aa:95:2f:41:34:8f:f8:8b:aa:86:3b:22:64:be:fa:c8:07:90:15:74:e9:39 ------BEGIN CERTIFICATE----- -MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN -AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp -dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw -MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw -CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ -MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB -SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz -ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH -LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP -PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL -2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w -ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC -MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk -AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0 -AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz -AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz -AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f -BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE -FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY -P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi -CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g -kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95 -HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS -na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q -qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z -TbvGRNs2yyqcjg== ------END CERTIFICATE----- - -# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Label: "Hongkong Post Root CA 1" -# Serial: 1000 -# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca -# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 -# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 ------BEGIN CERTIFICATE----- -MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx -FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg -Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG -A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr -b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ -jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn -PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh -ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 -nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h -q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED -MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC -mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 -7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB -oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs -EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO -fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi -AmvZWg== ------END CERTIFICATE----- - -# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Label: "SecureSign RootCA11" -# Serial: 1 -# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 -# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 -# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 ------BEGIN CERTIFICATE----- -MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr -MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG -A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 -MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp -Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD -QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz -i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 -h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV -MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 -UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni -8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC -h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD -VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB -AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm -KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ -X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr -QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 -pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN -QSdJQO7e5iNEOdyhIta6A/I= ------END CERTIFICATE----- - -# Issuer: CN=ACEDICOM Root O=EDICOM OU=PKI -# Subject: CN=ACEDICOM Root O=EDICOM OU=PKI -# Label: "ACEDICOM Root" -# Serial: 7029493972724711941 -# MD5 Fingerprint: 42:81:a0:e2:1c:e3:55:10:de:55:89:42:65:96:22:e6 -# SHA1 Fingerprint: e0:b4:32:2e:b2:f6:a5:68:b6:54:53:84:48:18:4a:50:36:87:43:84 -# SHA256 Fingerprint: 03:95:0f:b4:9a:53:1f:3e:19:91:94:23:98:df:a9:e0:ea:32:d7:ba:1c:dd:9b:c8:5d:b5:7e:d9:40:0b:43:4a ------BEGIN CERTIFICATE----- -MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE -AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x -CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW -MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF -RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7 -09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7 -XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P -Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK -t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb -X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28 -MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU -fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI -2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH -K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae -ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP -BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ -MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw -RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv -bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm -fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3 -gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe -I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i -5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi -ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn -MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ -o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6 -zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN -GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt -r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK -Z05phkOTOPu220+DkdRgfks+KzgHVZhepA== ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 80507572722862485515306429940691309246 -# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4 -# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b -# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i -2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ -2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ ------END CERTIFICATE----- - -# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Label: "Microsec e-Szigno Root CA 2009" -# Serial: 14014712776195784473 -# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 -# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e -# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 ------BEGIN CERTIFICATE----- -MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD -VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 -ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G -CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y -OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx -FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp -Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o -dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP -kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc -cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U -fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 -N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC -xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 -+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G -A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM -Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG -SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h -mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk -ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 -tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c -2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t -HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW ------END CERTIFICATE----- - -# Issuer: CN=e-Guven Kok Elektronik Sertifika Hizmet Saglayicisi O=Elektronik Bilgi Guvenligi A.S. -# Subject: CN=e-Guven Kok Elektronik Sertifika Hizmet Saglayicisi O=Elektronik Bilgi Guvenligi A.S. -# Label: "E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi" -# Serial: 91184789765598910059173000485363494069 -# MD5 Fingerprint: 3d:41:29:cb:1e:aa:11:74:cd:5d:b0:62:af:b0:43:5b -# SHA1 Fingerprint: dd:e1:d2:a9:01:80:2e:1d:87:5e:84:b3:80:7e:4b:b1:fd:99:41:34 -# SHA256 Fingerprint: e6:09:07:84:65:a4:19:78:0c:b6:ac:4c:1c:0b:fb:46:53:d9:d9:cc:6e:b3:94:6e:b7:f3:d6:99:97:ba:d5:98 ------BEGIN CERTIFICATE----- -MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxp -Z2kgQS5TLjE8MDoGA1UEAxMzZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZp -a2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3MDEwNDExMzI0OFoXDTE3MDEwNDEx -MzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0cm9uaWsgQmlsZ2kg -R3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9uaWsg -U2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdU -MZTe1RK6UxYC6lhj71vY8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlT -L/jDj/6z/P2douNffb7tC+Bg62nsM+3YjfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H -5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAIJjjcJRFHLfO6IxClv7wC -90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk9Ok0oSy1 -c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoE -VtstxNulMA0GCSqGSIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLP -qk/CaOv/gKlR6D1id4k9CnU58W5dF4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S -/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwqD2fK/A+JYZ1lpTzlvBNbCNvj -/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4Vwpm+Vganf2X -KWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq -fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Label: "GlobalSign Root CA - R3" -# Serial: 4835703278459759426209954 -# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 -# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad -# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 -MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 -RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT -gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm -KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd -QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ -XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o -LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU -RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp -jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK -6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX -mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs -Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH -WD9f ------END CERTIFICATE----- - -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" -# Serial: 6047274297262753887 -# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 -# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa -# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE -BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h -cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy -MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg -Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 -thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM -cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG -L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i -NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h -X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b -m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy -Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja -EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T -KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF -6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh -OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD -VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD -VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp -cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv -ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl -AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF -661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 -am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 -ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 -PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS -3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k -SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF -3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM -ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g -StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz -Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB -jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V ------END CERTIFICATE----- - -# Issuer: CN=Izenpe.com O=IZENPE S.A. -# Subject: CN=Izenpe.com O=IZENPE S.A. -# Label: "Izenpe.com" -# Serial: 917563065490389241595536686991402621 -# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 -# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 -# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f ------BEGIN CERTIFICATE----- -MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 -MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 -ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD -VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j -b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq -scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO -xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H -LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX -uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD -yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ -JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q -rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN -BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L -hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB -QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ -HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu -Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg -QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB -BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx -MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA -A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb -laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 -awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo -JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw -LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT -VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk -LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb -UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ -QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ -naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls -QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== ------END CERTIFICATE----- - -# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Label: "Chambers of Commerce Root - 2008" -# Serial: 11806822484801597146 -# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 -# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c -# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 ------BEGIN CERTIFICATE----- -MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz -IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz -MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj -dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw -EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp -MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 -28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq -VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q -DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR -5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL -ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a -Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl -UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s -+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 -Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj -ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx -hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV -HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 -+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN -YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t -L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy -ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt -IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV -HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w -DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW -PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF -5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 -glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH -FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 -pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD -xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG -tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq -jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De -fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg -OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ -d0jQ ------END CERTIFICATE----- - -# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Label: "Global Chambersign Root - 2008" -# Serial: 14541511773111788494 -# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 -# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c -# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca ------BEGIN CERTIFICATE----- -MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD -aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx -MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy -cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG -A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl -BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed -KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 -G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 -zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 -ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG -HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 -Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V -yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e -beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r -6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh -wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog -zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW -BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr -ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp -ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk -cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt -YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC -CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow -KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI -hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ -UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz -X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x -fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz -a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd -Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd -SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O -AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso -M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge -v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z -09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B ------END CERTIFICATE----- - -# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Label: "Go Daddy Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 -# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b -# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT -EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp -ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz -NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH -EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE -AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD -E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH -/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy -DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh -GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR -tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA -AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX -WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu -9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr -gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo -2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO -LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI -4uJEvlz36hz1 ------END CERTIFICATE----- - -# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 -# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e -# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs -ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw -MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 -b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj -aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp -Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg -nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 -HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N -Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN -dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 -HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G -CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU -sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 -4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg -8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K -pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 -mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 ------END CERTIFICATE----- - -# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Services Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 -# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f -# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 ------BEGIN CERTIFICATE----- -MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs -ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 -MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD -VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy -ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy -dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p -OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 -8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K -Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe -hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk -6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw -DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q -AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI -bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB -ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z -qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd -iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn -0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN -sSi6 ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Commercial O=AffirmTrust -# Subject: CN=AffirmTrust Commercial O=AffirmTrust -# Label: "AffirmTrust Commercial" -# Serial: 8608355977964138876 -# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 -# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 -# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 ------BEGIN CERTIFICATE----- -MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz -dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL -MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp -cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP -Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr -ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL -MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 -yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr -VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ -nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ -KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG -XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj -vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt -Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g -N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC -nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Networking O=AffirmTrust -# Subject: CN=AffirmTrust Networking O=AffirmTrust -# Label: "AffirmTrust Networking" -# Serial: 8957382827206547757 -# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f -# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f -# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b ------BEGIN CERTIFICATE----- -MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz -dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL -MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp -cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y -YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua -kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL -QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp -6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG -yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i -QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ -KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO -tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu -QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ -Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u -olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 -x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium O=AffirmTrust -# Subject: CN=AffirmTrust Premium O=AffirmTrust -# Label: "AffirmTrust Premium" -# Serial: 7893706540734352110 -# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 -# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 -# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a ------BEGIN CERTIFICATE----- -MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz -dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG -A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U -cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf -qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ -JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ -+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS -s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 -HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 -70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG -V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S -qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S -5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia -C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX -OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE -FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ -BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 -KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg -Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B -8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ -MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc -0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ -u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF -u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH -YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 -GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO -RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e -KeC2uAloGRwYQw== ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust -# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust -# Label: "AffirmTrust Premium ECC" -# Serial: 8401224907861490260 -# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d -# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb -# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 ------BEGIN CERTIFICATE----- -MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC -VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ -cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ -BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt -VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D -0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 -ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G -A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs -aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I -flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA" -# Serial: 279744 -# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 -# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e -# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e ------BEGIN CERTIFICATE----- -MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM -MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D -ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU -cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 -WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg -Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw -IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH -UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM -TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU -BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM -kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x -AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV -HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y -sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL -I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 -J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY -VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI -03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= ------END CERTIFICATE----- - -# Issuer: CN=Certinomis - Autorité Racine O=Certinomis OU=0002 433998903 -# Subject: CN=Certinomis - Autorité Racine O=Certinomis OU=0002 433998903 -# Label: "Certinomis - Autorité Racine" -# Serial: 1 -# MD5 Fingerprint: 7f:30:78:8c:03:e3:ca:c9:0a:e2:c9:ea:1e:aa:55:1a -# SHA1 Fingerprint: 2e:14:da:ec:28:f0:fa:1e:8e:38:9a:4e:ab:eb:26:c0:0a:d3:83:c3 -# SHA256 Fingerprint: fc:bf:e2:88:62:06:f7:2b:27:59:3c:8b:07:02:97:e1:2d:76:9e:d1:0e:d7:93:07:05:a8:09:8e:ff:c1:4d:17 ------BEGIN CERTIFICATE----- -MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET -MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk -BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4 -Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl -cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0 -aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY -F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N -8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe -rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K -/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu -7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC -28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6 -lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E -nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB -0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09 -5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj -WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN -jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ -KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s -ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM -OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q -619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn -2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj -o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v -nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG -5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq -pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb -dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0 -BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5 ------END CERTIFICATE----- - -# Issuer: CN=Root CA Generalitat Valenciana O=Generalitat Valenciana OU=PKIGVA -# Subject: CN=Root CA Generalitat Valenciana O=Generalitat Valenciana OU=PKIGVA -# Label: "Root CA Generalitat Valenciana" -# Serial: 994436456 -# MD5 Fingerprint: 2c:8c:17:5e:b1:54:ab:93:17:b5:36:5a:db:d1:c6:f2 -# SHA1 Fingerprint: a0:73:e5:c5:bd:43:61:0d:86:4c:21:13:0a:85:58:57:cc:9c:ea:46 -# SHA256 Fingerprint: 8c:4e:df:d0:43:48:f3:22:96:9e:7e:29:a4:cd:4d:ca:00:46:55:06:1c:16:e1:b0:76:42:2e:f3:42:ad:63:0e ------BEGIN CERTIFICATE----- -MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF -UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ -R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN -MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G -A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw -JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+ -WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj -SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl -u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy -A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk -Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7 -MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr -aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC -IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A -cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA -YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA -bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA -bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA -aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA -aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA -ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA -YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA -ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA -LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6 -Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y -eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw -CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G -A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu -Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn -lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt -b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg -9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF -ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC -IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= ------END CERTIFICATE----- - -# Issuer: CN=A-Trust-nQual-03 O=A-Trust Ges. f. Sicherheitssysteme im elektr. Datenverkehr GmbH OU=A-Trust-nQual-03 -# Subject: CN=A-Trust-nQual-03 O=A-Trust Ges. f. Sicherheitssysteme im elektr. Datenverkehr GmbH OU=A-Trust-nQual-03 -# Label: "A-Trust-nQual-03" -# Serial: 93214 -# MD5 Fingerprint: 49:63:ae:27:f4:d5:95:3d:d8:db:24:86:b8:9c:07:53 -# SHA1 Fingerprint: d3:c0:63:f2:19:ed:07:3e:34:ad:5d:75:0b:32:76:29:ff:d5:9a:f2 -# SHA256 Fingerprint: 79:3c:bf:45:59:b9:fd:e3:8a:b2:2d:f1:68:69:f6:98:81:ae:14:c4:b0:13:9a:c7:88:a7:8a:1a:fc:ca:02:fb ------BEGIN CERTIFICATE----- -MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJB -VDFIMEYGA1UECgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBp -bSBlbGVrdHIuIERhdGVudmVya2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5R -dWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5RdWFsLTAzMB4XDTA1MDgxNzIyMDAw -MFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgwRgYDVQQKDD9BLVRy -dXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0ZW52 -ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMM -EEEtVHJ1c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCtPWFuA/OQO8BBC4SAzewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUj -lUC5B3ilJfYKvUWG6Nm9wASOhURh73+nyfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZ -znF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPESU7l0+m0iKsMrmKS1GWH -2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4iHQF63n1 -k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs -2e3Vcuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYD -VR0OBAoECERqlWdVeRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC -AQEAVdRU0VlIXLOThaq/Yy/kgM40ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fG -KOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmrsQd7TZjTXLDR8KdCoLXEjq/+ -8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZdJXDRZslo+S4R -FGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS -mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmE -DNuxUCAKGkq6ahq97BvIxYSazQ== ------END CERTIFICATE----- - -# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Label: "TWCA Root Certification Authority" -# Serial: 1 -# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 -# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 -# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 ------BEGIN CERTIFICATE----- -MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES -MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU -V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz -WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO -LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE -AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH -K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX -RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z -rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx -3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq -hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC -MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls -XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D -lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn -aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ -YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== ------END CERTIFICATE----- - -# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Label: "Security Communication RootCA2" -# Serial: 0 -# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 -# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 -# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl -MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe -U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX -DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy -dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj -YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV -OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr -zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM -VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ -hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO -ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw -awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs -OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF -coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc -okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 -t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy -1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ -SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions RootCA 2011" -# Serial: 0 -# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 -# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d -# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 ------BEGIN CERTIFICATE----- -MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix -RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 -dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p -YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw -NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK -EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl -cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl -c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz -dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ -fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns -bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD -75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP -FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV -HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp -5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu -b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA -A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p -6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 -TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 -dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys -Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI -l7WdmplNsDz4SgCbZN2fOUvRJ9e4 ------END CERTIFICATE----- - -# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Label: "Actalis Authentication Root CA" -# Serial: 6271844772424770508 -# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 -# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac -# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 ------BEGIN CERTIFICATE----- -MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE -BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w -MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 -IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC -SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 -ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv -UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX -4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 -KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ -gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb -rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ -51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F -be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe -KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F -v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn -fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 -jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz -ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt -ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL -e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 -jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz -WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V -SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j -pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX -X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok -fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R -K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU -ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU -LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT -LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== ------END CERTIFICATE----- - -# Issuer: O=Trustis Limited OU=Trustis FPS Root CA -# Subject: O=Trustis Limited OU=Trustis FPS Root CA -# Label: "Trustis FPS Root CA" -# Serial: 36053640375399034304724988975563710553 -# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d -# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 -# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d ------BEGIN CERTIFICATE----- -MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF -MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL -ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx -MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc -MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ -AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH -iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj -vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA -0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB -OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ -BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E -FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 -GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW -zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 -1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE -f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F -jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN -ZetX2fNXlrtIzYE= ------END CERTIFICATE----- - -# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Label: "StartCom Certification Authority" -# Serial: 45 -# MD5 Fingerprint: c9:3b:0d:84:41:fc:a4:76:79:23:08:57:de:10:19:16 -# SHA1 Fingerprint: a3:f1:33:3f:e2:42:bf:cf:c5:d1:4e:8f:39:42:98:40:68:10:d1:a0 -# SHA256 Fingerprint: e1:78:90:ee:09:a3:fb:f4:f4:8b:9c:41:4a:17:d6:37:b7:a5:06:47:e9:bc:75:23:22:72:7f:cc:17:42:a9:11 ------BEGIN CERTIFICATE----- -MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg -Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 -MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi -U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh -cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk -pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf -OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C -Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT -Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi -HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM -Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w -+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ -Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 -Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B -26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID -AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul -F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC -ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w -ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk -aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 -YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg -c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 -d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG -CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 -dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF -wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS -Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst -0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc -pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl -CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF -P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK -1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm -KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE -JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ -8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm -fyWl8kgAwKQB2j8= ------END CERTIFICATE----- - -# Issuer: CN=StartCom Certification Authority G2 O=StartCom Ltd. -# Subject: CN=StartCom Certification Authority G2 O=StartCom Ltd. -# Label: "StartCom Certification Authority G2" -# Serial: 59 -# MD5 Fingerprint: 78:4b:fb:9e:64:82:0a:d3:b8:4c:62:f3:64:f2:90:64 -# SHA1 Fingerprint: 31:f1:fd:68:22:63:20:ee:c6:3b:3f:9d:ea:4a:3e:53:7c:7c:39:17 -# SHA256 Fingerprint: c7:ba:65:67:de:93:a7:98:ae:1f:aa:79:1e:71:2d:37:8f:ae:1f:93:c4:39:7f:ea:44:1b:b7:cb:e6:fd:59:95 ------BEGIN CERTIFICATE----- -MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1 -OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG -A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ -JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD -vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo -D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/ -Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW -RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK -HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN -nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM -0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i -UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9 -Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg -TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL -BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K -2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX -UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl -6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK -9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ -HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI -wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY -XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l -IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo -hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr -so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 2 Root CA" -# Serial: 2 -# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 -# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 -# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 ------BEGIN CERTIFICATE----- -MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg -Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow -TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw -HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB -BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr -6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV -L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 -1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx -MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ -QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB -arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr -Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi -FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS -P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN -9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP -AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz -uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h -9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s -A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t -OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo -+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 -KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 -DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us -H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ -I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 -5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h -3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz -Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 3 Root CA" -# Serial: 2 -# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec -# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 -# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d ------BEGIN CERTIFICATE----- -MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg -Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow -TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw -HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB -BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y -ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E -N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 -tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX -0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c -/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X -KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY -zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS -O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D -34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP -K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 -AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv -Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj -QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV -cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS -IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 -HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa -O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv -033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u -dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE -kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 -3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD -u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq -4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 3" -# Serial: 1 -# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef -# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 -# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN -8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ -RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 -hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 -ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM -EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 -A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy -WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ -1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 -6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT -91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml -e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p -TpPDpFQUWw== ------END CERTIFICATE----- - -# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Label: "EE Certification Centre Root CA" -# Serial: 112324828676200291871926431888494945866 -# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f -# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 -# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 -czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG -CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy -MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl -ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS -b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy -euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO -bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw -WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d -MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE -1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ -zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB -BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF -BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV -v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG -E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u -uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW -iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v -GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= ------END CERTIFICATE----- - -# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Aralık 2007 -# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Aralık 2007 -# Label: "TURKTRUST Certificate Services Provider Root 2007" -# Serial: 1 -# MD5 Fingerprint: 2b:70:20:56:86:82:a0:18:c8:07:53:12:28:70:21:72 -# SHA1 Fingerprint: f1:7f:6f:b6:31:dc:99:e3:a3:c8:7f:fe:1c:f1:81:10:88:d9:60:33 -# SHA256 Fingerprint: 97:8c:d9:66:f2:fa:a0:7b:a7:aa:95:00:d9:c0:2e:9d:77:f2:cd:ad:a6:ad:6b:a7:4a:f4:b9:1c:66:59:3c:50 ------BEGIN CERTIFICATE----- -MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS -S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg -SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx -OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry -b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC -VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE -sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F -ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY -KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG -+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG -HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P -IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M -733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk -Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G -CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW -AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I -aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5 -mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa -XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ -qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9 ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 2009" -# Serial: 623603 -# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f -# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 -# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 ------BEGIN CERTIFICATE----- -MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF -MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD -bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha -ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM -HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 -UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 -tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R -ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM -lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp -/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G -A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G -A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj -dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy -MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl -cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js -L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL -BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni -acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 -o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K -zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 -PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y -Johw1+qRzT65ysCQblrGXnRl11z+o+I= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 EV 2009" -# Serial: 623604 -# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 -# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 -# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF -MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD -bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw -NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV -BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn -ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 -3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z -qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR -p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 -HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw -ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea -HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw -Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh -c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E -RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt -dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku -Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp -3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 -nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF -CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na -xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX -KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 ------END CERTIFICATE----- - -# Issuer: CN=Autoridad de Certificacion Raiz del Estado Venezolano O=Sistema Nacional de Certificacion Electronica OU=Superintendencia de Servicios de Certificacion Electronica -# Subject: CN=PSCProcert O=Sistema Nacional de Certificacion Electronica OU=Proveedor de Certificados PROCERT -# Label: "PSCProcert" -# Serial: 11 -# MD5 Fingerprint: e6:24:e9:12:01:ae:0c:de:8e:85:c4:ce:a3:12:dd:ec -# SHA1 Fingerprint: 70:c1:8d:74:b4:28:81:0a:e4:fd:a5:75:d7:01:9f:99:b0:3d:50:74 -# SHA256 Fingerprint: 3c:fc:3c:14:d1:f6:84:ff:17:e3:8c:43:ca:44:0c:00:b9:67:ec:93:3e:8b:fe:06:4c:a1:d7:2c:90:f2:ad:b0 ------BEGIN CERTIFICATE----- -MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1 -dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s -YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz -dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0 -aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh -IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ -KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw -MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy -b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx -KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG -A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u -aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9 -7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74 -BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G -ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9 -JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0 -PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2 -0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH -0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/ -6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m -v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7 -K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev -bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw -MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w -MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD -gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0 -b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh -bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0 -cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp -ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg -ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq -hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD -AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w -MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag -RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t -UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl -cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v -Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG -AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN -AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS -1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB -3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv -Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh -HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm -pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz -sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE -qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb -mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9 -opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H -YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km ------END CERTIFICATE----- - -# Issuer: CN=China Internet Network Information Center EV Certificates Root O=China Internet Network Information Center -# Subject: CN=China Internet Network Information Center EV Certificates Root O=China Internet Network Information Center -# Label: "China Internet Network Information Center EV Certificates Root" -# Serial: 1218379777 -# MD5 Fingerprint: 55:5d:63:00:97:bd:6a:97:f5:67:ab:4b:fb:6e:63:15 -# SHA1 Fingerprint: 4f:99:aa:93:fb:2b:d1:37:26:a1:99:4a:ce:7f:f0:05:f2:93:5d:1e -# SHA256 Fingerprint: 1c:01:c6:f4:db:b2:fe:fc:22:55:8b:2b:ca:32:56:3f:49:84:4a:cf:c3:2b:7b:e4:b0:ff:59:9f:9e:8c:7a:f7 ------BEGIN CERTIFICATE----- -MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC -Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g -Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0 -aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa -Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg -SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo -aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp -ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z -7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA// -DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx -zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8 -hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs -4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u -gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY -NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E -FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3 -j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG -52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB -echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws -ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI -zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy -wy39FCqQmbkHzJ8= ------END CERTIFICATE----- - -# Issuer: CN=Swisscom Root CA 2 O=Swisscom OU=Digital Certificate Services -# Subject: CN=Swisscom Root CA 2 O=Swisscom OU=Digital Certificate Services -# Label: "Swisscom Root CA 2" -# Serial: 40698052477090394928831521023204026294 -# MD5 Fingerprint: 5b:04:69:ec:a5:83:94:63:18:a7:86:d0:e4:f2:6e:19 -# SHA1 Fingerprint: 77:47:4f:c6:30:e4:0f:4c:47:64:3f:84:ba:b8:c6:95:4a:8a:41:ec -# SHA256 Fingerprint: f0:9b:12:2c:71:14:f4:a0:9b:d4:ea:4f:4a:99:d5:58:b4:6e:4c:25:cd:81:14:0d:29:c0:56:13:91:4c:38:41 ------BEGIN CERTIFICATE----- -MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk -MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 -YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg -Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT -AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp -Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN -BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr -jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r -0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f -2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP -ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF -y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA -tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL -6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0 -uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL -acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh -k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q -VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw -FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O -BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh -b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R -fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv -/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI -REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx -srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv -aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT -woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n -Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W -t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N -8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2 -9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5 -wSsSnqaeG8XmDtkx2Q== ------END CERTIFICATE----- - -# Issuer: CN=Swisscom Root EV CA 2 O=Swisscom OU=Digital Certificate Services -# Subject: CN=Swisscom Root EV CA 2 O=Swisscom OU=Digital Certificate Services -# Label: "Swisscom Root EV CA 2" -# Serial: 322973295377129385374608406479535262296 -# MD5 Fingerprint: 7b:30:34:9f:dd:0a:4b:6b:35:ca:31:51:28:5d:ae:ec -# SHA1 Fingerprint: e7:a1:90:29:d3:d5:52:dc:0d:0f:c6:92:d3:ea:88:0d:15:2e:1a:6b -# SHA256 Fingerprint: d9:5f:ea:3c:a4:ee:dc:e7:4c:d7:6e:75:fc:6d:1f:f6:2c:44:1f:0f:a8:bc:77:f0:34:b1:9e:5d:b2:58:01:5d ------BEGIN CERTIFICATE----- -MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw -ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp -dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290 -IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD -VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy -dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg -MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx -UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD -1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH -oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR -HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/ -5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv -idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL -OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC -NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f -46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB -UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth -7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G -A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED -MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB -bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x -XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T -PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0 -Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70 -WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL -Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm -7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S -nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN -vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB -WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI -fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb -I+2ksx0WckNLIOFZfsLorSa/ovc= ------END CERTIFICATE----- - -# Issuer: CN=CA Disig Root R1 O=Disig a.s. -# Subject: CN=CA Disig Root R1 O=Disig a.s. -# Label: "CA Disig Root R1" -# Serial: 14052245610670616104 -# MD5 Fingerprint: be:ec:11:93:9a:f5:69:21:bc:d7:c1:c0:67:89:cc:2a -# SHA1 Fingerprint: 8e:1c:74:f8:a6:20:b9:e5:8a:f4:61:fa:ec:2b:47:56:51:1a:52:c6 -# SHA256 Fingerprint: f9:6f:23:f4:c3:e7:9c:07:7a:46:98:8d:5a:f5:90:06:76:a0:f0:39:cb:64:5d:d1:75:49:b2:16:c8:24:40:ce ------BEGIN CERTIFICATE----- -MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV -BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu -MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy -MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx -EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw -ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk -D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o -OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A -fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe -IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n -oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK -/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj -rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD -3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE -7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC -yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd -qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud -DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI -hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR -xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA -SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo -HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB -emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC -AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb -7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x -DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk -F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF -a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT -Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL ------END CERTIFICATE----- - -# Issuer: CN=CA Disig Root R2 O=Disig a.s. -# Subject: CN=CA Disig Root R2 O=Disig a.s. -# Label: "CA Disig Root R2" -# Serial: 10572350602393338211 -# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 -# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 -# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 ------BEGIN CERTIFICATE----- -MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV -BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu -MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy -MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx -EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw -ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe -NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH -PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I -x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe -QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR -yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO -QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 -H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ -QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD -i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs -nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 -rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud -DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI -hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM -tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf -GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb -lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka -+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal -TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i -nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 -gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr -G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os -zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x -L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL ------END CERTIFICATE----- - -# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Label: "ACCVRAIZ1" -# Serial: 6828503384748696800 -# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 -# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 -# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 ------BEGIN CERTIFICATE----- -MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE -AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw -CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ -BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND -VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb -qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY -HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo -G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA -lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr -IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ -0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH -k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 -4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO -m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa -cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl -uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI -KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls -ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG -AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 -VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT -VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG -CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA -cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA -QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA -7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA -cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA -QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA -czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu -aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt -aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud -DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF -BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp -D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU -JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m -AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD -vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms -tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH -7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h -I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA -h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF -d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H -pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 ------END CERTIFICATE----- - -# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Label: "TWCA Global Root CA" -# Serial: 3262 -# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 -# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 -# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b ------BEGIN CERTIFICATE----- -MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx -EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT -VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 -NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT -B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF -10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz -0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh -MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH -zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc -46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 -yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi -laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP -oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA -BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE -qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm -4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL -1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn -LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF -H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo -RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ -nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh -15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW -6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW -nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j -wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz -aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy -KwbQBM0= ------END CERTIFICATE----- - -# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Label: "TeliaSonera Root CA v1" -# Serial: 199041966741090107964904287217786801558 -# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c -# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 -# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 ------BEGIN CERTIFICATE----- -MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw -NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv -b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD -VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F -VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 -7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X -Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ -/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs -81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm -dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe -Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu -sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 -pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs -slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ -arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD -VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG -9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl -dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx -0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj -TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed -Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 -Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI -OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 -vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW -t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn -HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx -SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= ------END CERTIFICATE----- - -# Issuer: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi -# Subject: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi -# Label: "E-Tugra Certification Authority" -# Serial: 7667447206703254355 -# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 -# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 -# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c ------BEGIN CERTIFICATE----- -MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV -BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC -aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV -BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 -Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz -MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ -BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp -em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN -ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY -B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH -D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF -Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo -q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D -k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH -fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut -dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM -ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 -zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn -rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX -U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 -Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 -XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF -Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR -HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY -GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c -77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 -+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK -vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 -FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl -yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P -AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD -y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d -NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 2" -# Serial: 1 -# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a -# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 -# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd -AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC -FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi -1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq -jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ -wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ -WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy -NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC -uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw -IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 -g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN -9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP -BSeOE6Fuwg== ------END CERTIFICATE----- - -# Issuer: CN=Atos TrustedRoot 2011 O=Atos -# Subject: CN=Atos TrustedRoot 2011 O=Atos -# Label: "Atos TrustedRoot 2011" -# Serial: 6643877497813316402 -# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 -# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 -# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE -AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG -EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM -FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC -REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp -Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM -VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ -SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ -4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L -cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi -eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG -A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 -DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j -vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP -DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc -maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D -lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv -KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed ------END CERTIFICATE----- diff --git a/tests/Breadcrumbs/ErrorHandlerTest.php b/tests/Breadcrumbs/ErrorHandlerTest.php index 8909fe3b8..3078ea78e 100644 --- a/tests/Breadcrumbs/ErrorHandlerTest.php +++ b/tests/Breadcrumbs/ErrorHandlerTest.php @@ -9,19 +9,23 @@ * file that was distributed with this source code. */ +use Raven\Client; +use Raven\Configuration; + class Raven_Tests_ErrorHandlerBreadcrumbHandlerTest extends PHPUnit_Framework_TestCase { public function testSimple() { - $client = new \Raven\Client([ + $client = new Client(new Configuration([ 'install_default_breadcrumb_handlers' => false, - ]); + ])); $handler = new \Raven\Breadcrumbs\ErrorHandler($client); $handler->handleError(E_WARNING, 'message'); $crumbs = $client->breadcrumbs->fetch(); - $this->assertEquals(count($crumbs), 1); + + $this->assertCount(1, $crumbs); $this->assertEquals($crumbs[0]['message'], 'message'); $this->assertEquals($crumbs[0]['category'], 'error_reporting'); $this->assertEquals($crumbs[0]['level'], 'warning'); diff --git a/tests/Breadcrumbs/MonologTest.php b/tests/Breadcrumbs/MonologTest.php index 6165324d1..64914aa4a 100644 --- a/tests/Breadcrumbs/MonologTest.php +++ b/tests/Breadcrumbs/MonologTest.php @@ -11,6 +11,11 @@ namespace Raven\Tests\Breadcrumbs; +use Monolog\Logger; +use Raven\Breadcrumbs\MonologHandler; +use Raven\Client; +use Raven\Configuration; + class MonologTest extends \PHPUnit_Framework_TestCase { protected function getSampleErrorMessage() @@ -37,17 +42,19 @@ protected function getSampleErrorMessage() public function testSimple() { - $client = new \Raven\Client([ + $client = new Client(new Configuration([ 'install_default_breadcrumb_handlers' => false, - ]); - $handler = new \Raven\Breadcrumbs\MonologHandler($client); + ])); + + $handler = new MonologHandler($client); - $logger = new \Monolog\Logger('sentry'); + $logger = new Logger('sentry'); $logger->pushHandler($handler); $logger->addWarning('Foo'); $crumbs = $client->breadcrumbs->fetch(); - $this->assertEquals(count($crumbs), 1); + + $this->assertCount(1, $crumbs); $this->assertEquals($crumbs[0]['message'], 'Foo'); $this->assertEquals($crumbs[0]['category'], 'sentry'); $this->assertEquals($crumbs[0]['level'], 'warning'); @@ -55,17 +62,19 @@ public function testSimple() public function testErrorInMessage() { - $client = new \Raven\Client([ + $client = new Client(new Configuration([ 'install_default_breadcrumb_handlers' => false, - ]); - $handler = new \Raven\Breadcrumbs\MonologHandler($client); + ])); - $logger = new \Monolog\Logger('sentry'); + $handler = new MonologHandler($client); + + $logger = new Logger('sentry'); $logger->pushHandler($handler); $logger->addError($this->getSampleErrorMessage()); $crumbs = $client->breadcrumbs->fetch(); - $this->assertEquals(count($crumbs), 1); + + $this->assertCount(1, $crumbs); $this->assertEquals($crumbs[0]['data']['type'], 'Exception'); $this->assertEquals($crumbs[0]['data']['value'], 'An unhandled exception'); $this->assertEquals($crumbs[0]['category'], 'sentry'); diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php new file mode 100644 index 000000000..b9fce0c13 --- /dev/null +++ b/tests/ClientBuilderTest.php @@ -0,0 +1,104 @@ +assertInstanceOf(ClientBuilder::class, $clientBuilder); + } + + public function testGetClient() + { + $clientBuilder = new ClientBuilder(); + + $this->assertInstanceOf(Client::class, $clientBuilder->getClient()); + } + + /** + * @expectedException \BadMethodCallException + * @expectedExceptionMessage The method named "methodThatDoesNotExists" does not exists. + */ + public function testCallInvalidMethodThrowsException() + { + $clientBuilder = new ClientBuilder(); + $clientBuilder->methodThatDoesNotExists(); + } + + /** + * @dataProvider optionsDataProvider + */ + public function testCallExistingMethodForwardsCallToConfiguration($setterMethod, $value) + { + $configuration = $this->getMockBuilder(Configuration::class) + ->getMock(); + + $configuration->expects($this->once()) + ->method($setterMethod) + ->with($this->equalTo($value)); + + $clientBuilder = new ClientBuilder(); + + $reflectionProperty = new \ReflectionProperty(ClientBuilder::class, 'configuration'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($clientBuilder, $configuration); + $reflectionProperty->setAccessible(false); + + $clientBuilder->$setterMethod($value); + } + + public function optionsDataProvider() + { + return [ + ['setIsTrustXForwardedProto', true], + ['setPrefixes', ['foo', 'bar']], + ['setSerializeAllObjects', false], + ['setCurlMethod', 'async'], + ['setCurlPath', 'foo'], + ['setCurlIpv4', true], + ['setCurlSslVersion', CURL_SSLVERSION_DEFAULT], + ['setSampleRate', 0.5], + ['setInstallDefaultBreadcrumbHandlers', false], + ['setInstallShutdownHandler', false], + ['setMbDetectOrder', ['foo', 'bar']], + ['setAutoLogStacks', false], + ['setContextLines', 0], + ['setCurrentEnvironment', 'test'], + ['setEnvironments', ['default']], + ['setExcludedLoggers', ['foo', 'bar']], + ['setExcludedExceptions', ['foo', 'bar']], + ['setExcludedProjectPaths', ['foo', 'bar']], + ['setTransport', null], + ['setProjectRoot', 'foo'], + ['setLogger', 'bar'], + ['setOpenTimeout', 1], + ['setTimeout', 3], + ['setProxy', 'foo'], + ['setRelease', 'dev'], + ['setServerName', 'example.com'], + ['setSslOptions', ['foo' => 'bar']], + ['setSslVerificationEnabled', false], + ['setSslCaFile', 'foo'], + ['setTags', ['foo', 'bar']], + ['setErrorTypes', 0], + ['setProcessors', ['foo']], + ['setProcessorsOptions', ['foo']], + ]; + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 8c0928987..7eebd57ce 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,7 +10,12 @@ namespace Raven\Tests; +use Composer\CaBundle\CaBundle; +use Raven\Breadcrumbs\ErrorHandler; use Raven\Client; +use Raven\Configuration; +use Raven\CurlHandler; +use Raven\Processor\SanitizeDataProcessor; function simple_function($a = null, $b = null, $c = null) { @@ -24,7 +29,6 @@ function invalid_encoding() fclose($fp); } - // XXX: Is there a better way to stub the client? class Dummy_Raven_Client extends \Raven\Client { @@ -39,10 +43,10 @@ public function getSentEvents() public function send(&$data) { - if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, [&$data]) === false) { - // if send_callback returns falsely, end native send + if (!$this->config->shouldCapture($data)) { return; } + $this->__sent_events[] = $data; } @@ -209,7 +213,7 @@ public function join($timeout = null) } } -class Raven_Tests_ClientTest extends \PHPUnit_Framework_TestCase +class ClientTest extends \PHPUnit_Framework_TestCase { protected static $_folder = null; @@ -354,318 +358,122 @@ private function create_chained_exception() } } - public function testParseDSNHttp() - { - $result = \Raven\Client::ParseDSN('http://public:secret@example.com/1'); - - $this->assertEquals(1, $result['project']); - $this->assertEquals('http://example.com/api/1/store/', $result['server']); - $this->assertEquals('public', $result['public_key']); - $this->assertEquals('secret', $result['secret_key']); - } - - public function testParseDSNHttps() - { - $result = \Raven\Client::ParseDSN('https://public:secret@example.com/1'); - - $this->assertEquals(1, $result['project']); - $this->assertEquals('https://example.com/api/1/store/', $result['server']); - $this->assertEquals('public', $result['public_key']); - $this->assertEquals('secret', $result['secret_key']); - } - - public function testParseDSNPath() - { - $result = \Raven\Client::ParseDSN('http://public:secret@example.com/app/1'); - - $this->assertEquals(1, $result['project']); - $this->assertEquals('http://example.com/app/api/1/store/', $result['server']); - $this->assertEquals('public', $result['public_key']); - $this->assertEquals('secret', $result['secret_key']); - } - - public function testParseDSNPort() - { - $result = \Raven\Client::ParseDSN('http://public:secret@example.com:9000/app/1'); - - $this->assertEquals(1, $result['project']); - $this->assertEquals('http://example.com:9000/app/api/1/store/', $result['server']); - $this->assertEquals('public', $result['public_key']); - $this->assertEquals('secret', $result['secret_key']); - } - - public function testParseDSNInvalidScheme() - { - try { - \Raven\Client::ParseDSN('gopher://public:secret@/1'); - $this->fail(); - } catch (\Exception $e) { - return; - } - } - - public function testParseDSNMissingNetloc() - { - try { - \Raven\Client::ParseDSN('http://public:secret@/1'); - $this->fail(); - } catch (\Exception $e) { - return; - } - } - - public function testParseDSNMissingProject() - { - try { - \Raven\Client::ParseDSN('http://public:secret@example.com'); - $this->fail(); - } catch (\Exception $e) { - return; - } - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testParseDSNMissingPublicKey() - { - \Raven\Client::ParseDSN('http://:secret@example.com/1'); - } - /** - * @expectedException \InvalidArgumentException - */ - public function testParseDSNMissingSecretKey() - { - \Raven\Client::ParseDSN('http://public@example.com/1'); - } - - /** - * @covers \Raven\Client::__construct - */ - public function testDsnFirstArgument() - { - $client = new Dummy_Raven_Client('http://public:secret@example.com/1'); - - $this->assertEquals(1, $client->project); - $this->assertEquals('http://example.com/api/1/store/', $client->server); - $this->assertEquals('public', $client->public_key); - $this->assertEquals('secret', $client->secret_key); - } - - /** - * @covers \Raven\Client::__construct - */ - public function testDsnFirstArgumentWithOptions() - { - $client = new Dummy_Raven_Client('http://public:secret@example.com/1', [ - 'site' => 'foo', - ]); - - $this->assertEquals(1, $client->project); - $this->assertEquals('http://example.com/api/1/store/', $client->server); - $this->assertEquals('public', $client->public_key); - $this->assertEquals('secret', $client->secret_key); - $this->assertEquals('foo', $client->site); - } - - /** - * @covers \Raven\Client::__construct - */ - public function testOptionsFirstArgument() - { - $client = new Dummy_Raven_Client([ - 'server' => 'http://example.com/api/1/store/', - 'project' => 1, - ]); - - $this->assertEquals('http://example.com/api/1/store/', $client->server); - } - - /** - * @covers \Raven\Client::__construct - */ - public function testDsnInOptionsFirstArg() - { - $client = new Dummy_Raven_Client([ - 'dsn' => 'http://public:secret@example.com/1', - ]); - - $this->assertEquals(1, $client->project); - $this->assertEquals('http://example.com/api/1/store/', $client->server); - $this->assertEquals('public', $client->public_key); - $this->assertEquals('secret', $client->secret_key); - } - - /** - * @covers \Raven\Client::__construct - */ - public function testDsnInOptionsSecondArg() - { - $client = new Dummy_Raven_Client(null, [ - 'dsn' => 'http://public:secret@example.com/1', - ]); - - $this->assertEquals(1, $client->project); - $this->assertEquals('http://example.com/api/1/store/', $client->server); - $this->assertEquals('public', $client->public_key); - $this->assertEquals('secret', $client->secret_key); - } - - /** - * @covers \Raven\Client::__construct - */ - public function testOptionsFirstArgumentWithOptions() - { - $client = new Dummy_Raven_Client([ - 'server' => 'http://example.com/api/1/store/', - 'project' => 1, - ], [ - 'site' => 'foo', - ]); - - $this->assertEquals('http://example.com/api/1/store/', $client->server); - $this->assertEquals('foo', $client->site); - } - - /** - * @covers \Raven\Client::captureMessage - */ public function testOptionsExtraData() { - $client = new Dummy_Raven_Client(['extra' => ['foo' => 'bar']]); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + $client->extra_context(['foo' => 'bar']); $client->captureMessage('Test Message %s', ['foo']); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals('bar', $event['extra']['foo']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('bar', $client->_pending_events[0]['extra']['foo']); } - /** - * @covers \Raven\Client::captureMessage - */ public function testOptionsExtraDataWithNull() { - $client = new Dummy_Raven_Client(['extra' => ['foo' => 'bar']]); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + $client->extra_context(['foo' => 'bar']); $client->captureMessage('Test Message %s', ['foo'], null); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals('bar', $event['extra']['foo']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('bar', $client->_pending_events[0]['extra']['foo']); } - /** - * @covers \Raven\Client::captureMessage - */ public function testEmptyExtraData() { - $client = new Dummy_Raven_Client(['extra' => []]); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; $client->captureMessage('Test Message %s', ['foo']); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals(array_key_exists('extra', $event), false); + + $this->assertCount(1, $client->_pending_events); + $this->assertArrayNotHasKey('extra', $client->_pending_events[0]); } - /** - * @covers \Raven\Client::captureMessage - */ public function testCaptureMessageDoesHandleUninterpolatedMessage() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); - $client->captureMessage('Test Message %s'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals('Test Message %s', $event['message']); + $client->store_errors_for_bulk_send = true; + + $client->captureMessage('foo %s'); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('foo %s', $client->_pending_events[0]['message']); } - /** - * @covers \Raven\Client::captureMessage - */ public function testCaptureMessageDoesHandleInterpolatedMessage() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); - $client->captureMessage('Test Message %s', ['foo']); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals('Test Message foo', $event['message']); + $client->store_errors_for_bulk_send = true; + + $client->captureMessage('foo %s', ['bar']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('foo bar', $client->_pending_events[0]['message']); } - /** - * @covers \Raven\Client::captureMessage - */ public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() { - $client = new Dummy_Raven_Client(); - $client->setRelease(20160909144742); + $config = new Configuration(['release' => '1.2.3']); + $client = new Client($config); - $this->assertEquals(20160909144742, $client->getRelease()); + $this->assertEquals('1.2.3', $client->getConfig()->getRelease()); - $client->captureMessage('Test Message %s', ['foo']); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals(20160909144742, $event['release']); - $this->assertEquals('Test Message foo', $event['message']); + $client->store_errors_for_bulk_send = true; + + $client->captureMessage('foo %s', ['bar']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('foo bar', $client->_pending_events[0]['message']); + $this->assertEquals('1.2.3', $client->_pending_events[0]['release']); } - /** - * @covers \Raven\Client::captureMessage - */ public function testCaptureMessageSetsInterface() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; - $client->captureMessage('Test Message %s', ['foo']); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals([ - 'message' => 'Test Message %s', - 'params' => ['foo'], - 'formatted' => 'Test Message foo', - ], $event['sentry.interfaces.Message']); - $this->assertEquals('Test Message foo', $event['message']); + $client->captureMessage('foo %s', ['bar']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('foo bar', $client->_pending_events[0]['message']); + $this->assertArrayHasKey('sentry.interfaces.Message', $client->_pending_events[0]); + $this->assertEquals('foo bar', $client->_pending_events[0]['sentry.interfaces.Message']['formatted']); + $this->assertEquals('foo %s', $client->_pending_events[0]['sentry.interfaces.Message']['message']); + $this->assertEquals(['bar'], $client->_pending_events[0]['sentry.interfaces.Message']['params']); } - /** - * @covers \Raven\Client::captureMessage - */ public function testCaptureMessageHandlesOptionsAsThirdArg() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; $client->captureMessage('Test Message %s', ['foo'], [ 'level' => Dummy_Raven_Client::WARNING, 'extra' => ['foo' => 'bar'] ]); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); - $this->assertEquals('bar', $event['extra']['foo']); - $this->assertEquals('Test Message foo', $event['message']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals(Dummy_Raven_Client::WARNING, $client->_pending_events[0]['level']); + $this->assertEquals('bar', $client->_pending_events[0]['extra']['foo']); + $this->assertEquals('Test Message foo', $client->_pending_events[0]['message']); } - /** - * @covers \Raven\Client::captureMessage - */ public function testCaptureMessageHandlesLevelAsThirdArg() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; $client->captureMessage('Test Message %s', ['foo'], Dummy_Raven_Client::WARNING); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); - $this->assertEquals('Test Message foo', $event['message']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals(Dummy_Raven_Client::WARNING, $client->_pending_events[0]['level']); + $this->assertEquals('Test Message foo', $client->_pending_events[0]['message']); } /** @@ -674,56 +482,53 @@ public function testCaptureMessageHandlesLevelAsThirdArg() public function testCaptureExceptionSetsInterfaces() { # TODO: it'd be nice if we could mock the stacktrace extraction function here - $client = new Dummy_Raven_Client(); - $ex = $this->create_exception(); - $client->captureException($ex); + $client = new Dummy_Raven_Client(new Configuration()); + $client->captureException($this->create_exception()); $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); + + $this->assertCount(1, $events); + $event = array_pop($events); $exc = $event['exception']; + $this->assertEquals(1, count($exc['values'])); $this->assertEquals('Foo bar', $exc['values'][0]['value']); $this->assertEquals('Exception', $exc['values'][0]['type']); - $this->assertFalse(empty($exc['values'][0]['stacktrace']['frames'])); + $this->assertNotEmpty($exc['values'][0]['stacktrace']['frames']); + $frames = $exc['values'][0]['stacktrace']['frames']; $frame = $frames[count($frames) - 1]; + $this->assertTrue($frame['lineno'] > 0); - $this->assertEquals('Raven\Tests\Raven_Tests_ClientTest::create_exception', $frame['function']); + $this->assertEquals('Raven\Tests\ClientTest::create_exception', $frame['function']); $this->assertFalse(isset($frame['vars'])); $this->assertEquals(' throw new \Exception(\'Foo bar\');', $frame['context_line']); $this->assertFalse(empty($frame['pre_context'])); $this->assertFalse(empty($frame['post_context'])); } - /** - * @covers \Raven\Client::captureException - */ public function testCaptureExceptionChainedException() { # TODO: it'd be nice if we could mock the stacktrace extraction function here - $client = new Dummy_Raven_Client(); - $ex = $this->create_chained_exception(); - $client->captureException($ex); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); + $client->captureException($this->create_chained_exception()); - $exc = $event['exception']; - $this->assertEquals(2, count($exc['values'])); - $this->assertEquals('Foo bar', $exc['values'][0]['value']); - $this->assertEquals('Child exc', $exc['values'][1]['value']); + $this->assertCount(1, $client->_pending_events); + $this->assertCount(2, $client->_pending_events[0]['exception']['values']); + $this->assertEquals('Foo bar', $client->_pending_events[0]['exception']['values'][0]['value']); + $this->assertEquals('Child exc', $client->_pending_events[0]['exception']['values'][1]['value']); } - /** - * @covers \Raven\Client::captureException - */ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + $e1 = new \ErrorException('First', 0, E_DEPRECATED); $e2 = new \ErrorException('Second', 0, E_NOTICE, __FILE__, __LINE__, $e1); $e3 = new \ErrorException('Third', 0, E_ERROR, __FILE__, __LINE__, $e2); @@ -731,138 +536,100 @@ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() $client->captureException($e1); $client->captureException($e2); $client->captureException($e3); - $events = $client->getSentEvents(); - - $event = array_pop($events); - $this->assertEquals(Dummy_Raven_Client::ERROR, $event['level']); - $event = array_pop($events); - $this->assertEquals(Dummy_Raven_Client::INFO, $event['level']); - - $event = array_pop($events); - $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); + $this->assertCount(3, $client->_pending_events); + $this->assertEquals(Client::WARNING, $client->_pending_events[0]['level']); + $this->assertEquals(Client::INFO, $client->_pending_events[1]['level']); + $this->assertEquals(Client::ERROR, $client->_pending_events[2]['level']); } - /** - * @covers \Raven\Client::captureException - */ public function testCaptureExceptionHandlesOptionsAsSecondArg() { - $client = new Dummy_Raven_Client(); - $ex = $this->create_exception(); - $client->captureException($ex, ['culprit' => 'test']); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals('test', $event['culprit']); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + + $client->captureException($this->create_exception(), ['culprit' => 'test']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('test', $client->_pending_events[0]['culprit']); } - /** - * @covers \Raven\Client::captureException - */ public function testCaptureExceptionHandlesExcludeOption() { - $client = new Dummy_Raven_Client([ - 'exclude' => ['Exception'], - ]); - $ex = $this->create_exception(); - $client->captureException($ex, 'test'); - $events = $client->getSentEvents(); - $this->assertEquals(0, count($events)); + $client = new Client(new Configuration(['excluded_exceptions' => ['Exception']])); + $client->store_errors_for_bulk_send = true; + + $client->captureException($this->create_exception(), 'test'); + + $this->assertEmpty($client->_pending_events); } public function testCaptureExceptionInvalidUTF8() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + try { invalid_encoding(); } catch (\Exception $ex) { $client->captureException($ex); } - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - // if this fails to encode it returns false - $message = $client->encode($events[0]); - $this->assertNotFalse($message, $client->getLastError()); + $this->assertCount(1, $client->_pending_events); + + $this->assertNotFalse($client->encode($client->_pending_events[0])); } - /** - * @covers \Raven\Client::__construct - */ public function testDoesRegisterProcessors() { - $client = new Dummy_Raven_Client([ - 'processors' => ['\\Raven\\Processor\\SanitizeDataProcessor'], - ]); + $client = new Client(new Configuration(['processors' => [SanitizeDataProcessor::class]])); - $this->assertEquals(1, count($client->processors)); - $this->assertInstanceOf('\\Raven\\Processor\\SanitizeDataProcessor', $client->processors[0]); + $this->assertInstanceOf(SanitizeDataProcessor::class, $this->getObjectAttribute($client, 'processors')[0]); } public function testProcessDoesCallProcessors() { - $data = ["key"=>"value"]; + $data = ['key' => 'value']; $processor = $this->getMockBuilder('Processor') - ->setMethods(['process']) - ->getMock(); - $processor->expects($this->once()) - ->method('process') - ->with($data); + ->setMethods(['process']) + ->getMock(); - $client = new Dummy_Raven_Client(); - $client->processors[] = $processor; - $client->process($data); - } - - /** - * @covers \Raven\Client::__construct - * @covers \Raven\Client::getDefaultProcessors - */ - public function testDefaultProcessorsAreUsed() - { - $client = new Dummy_Raven_Client(); - $defaults = Dummy_Raven_Client::getDefaultProcessors(); + $processor->expects($this->once()) + ->method('process') + ->with($data); - $this->assertEquals(count($defaults), count($client->processors)); - } + $client = new Dummy_Raven_Client(new Configuration()); - /** - * @covers \Raven\Client::getDefaultProcessors - */ - public function testDefaultProcessorsContainSanitizeDataProcessor() - { - $this->assertContains('\\Raven\\Processor\\SanitizeDataProcessor', Dummy_Raven_Client::getDefaultProcessors()); + $client->setProcessors([$processor]); + $client->process($data); } - /** - * @covers \Raven\Client::__construct - * @covers \Raven\Client::get_default_data - */ public function testGetDefaultData() { - $client = new Dummy_Raven_Client(); + $config = new Configuration(); + $client = new Client($config); + $client->transaction->push('test'); + $expected = [ 'platform' => 'php', - 'project' => $client->project, - 'server_name' => $client->name, - 'site' => $client->site, - 'logger' => $client->logger, - 'tags' => $client->tags, + 'project' => $config->getProjectId(), + 'server_name' => $config->getServerName(), + 'logger' => $config->getLogger(), + 'tags' => $config->getTags(), + 'culprit' => 'test', 'sdk' => [ 'name' => 'sentry-php', 'version' => $client::VERSION, ], - 'culprit' => 'test', ]; + $this->assertEquals($expected, $client->get_default_data()); } /** * @backupGlobals - * @covers \Raven\Client::get_http_data */ public function testGetHttpData() { @@ -910,54 +677,44 @@ public function testGetHttpData() ] ]; - $client = new Dummy_Raven_Client(); + $client = new Dummy_Raven_Client(new Configuration()); + $this->assertEquals($expected, $client->get_http_data()); } - /** - * @covers \Raven\Client::user_context - * @covers \Raven\Client::get_user_data - */ - public function testGetUserDataWithSetUser() + public function testCaptureMessageWithUserData() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; - $id = 'unique_id'; - $email = 'foo@example.com'; - $client->user_context(['id' => $id, 'email' => $email, 'username' => 'my_user', ]); + $client->user_context([ + 'id' => 'unique_id', + 'email' => 'foo@example.com', + 'username' => 'my_user' + ]); - $expected = [ - 'user' => [ - 'id' => $id, - 'username' => 'my_user', - 'email' => $email, - ] - ]; + $client->captureMessage('foo'); - $this->assertEquals($expected, $client->get_user_data()); + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('unique_id', $client->_pending_events[0]['user']['id']); + $this->assertEquals('my_user', $client->_pending_events[0]['user']['username']); + $this->assertEquals('foo@example.com', $client->_pending_events[0]['user']['email']); } - /** - * @covers \Raven\Client::get_user_data - */ - public function testGetUserDataWithNoUser() + public function testCaptureMessageWithNoUserData() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; - $expected = [ - 'user' => [ - 'id' => session_id(), - ] - ]; - $this->assertEquals($expected, $client->get_user_data()); + $client->captureMessage('foo'); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals(session_id(), $client->_pending_events[0]['user']['id']); } - /** - * @covers \Raven\Client::get_auth_header - */ public function testGet_Auth_Header() { - $client = new Dummy_Raven_Client(); + $client = new Dummy_Raven_Client(new Configuration()); $clientstring = 'sentry-php/test'; $timestamp = '1234341324.340000'; @@ -969,12 +726,9 @@ public function testGet_Auth_Header() $this->assertEquals($expected, $client->get_auth_header($timestamp, 'sentry-php/test', 'publickey', 'secretkey')); } - /** - * @covers \Raven\Client::getAuthHeader - */ public function testGetAuthHeader() { - $client = new Dummy_Raven_Client(); + $client = new Dummy_Raven_Client(new Configuration()); $ts1 = microtime(true); $header = $client->getAuthHeader(); $ts2 = microtime(true); @@ -984,102 +738,57 @@ public function testGetAuthHeader() $this->assertLessThanOrEqual($ts2, (double)$a[1]); } - /** - * @covers \Raven\Client::captureMessage - */ - public function testCaptureMessageWithUserContext() - { - $client = new Dummy_Raven_Client(); - - $client->user_context(['email' => 'foo@example.com']); - - $client->captureMessage('test'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals([ - 'email' => 'foo@example.com', - ], $event['user']); - } - - /** - * @covers \Raven\Client::captureMessage - */ public function testCaptureMessageWithUnserializableUserData() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; $client->user_context([ 'email' => 'foo@example.com', 'data' => [ 'error' => new \Exception('test'), - ] + ], ]); $client->captureMessage('test'); - $events = $client->getSentEvents(); - // we're just asserting that this goes off without a hitch - $this->assertEquals(1, count($events)); - array_pop($events); + + $this->assertCount(1, $client->_pending_events); } - /** - * @covers \Raven\Client::captureMessage - * @covers \Raven\Client::tags_context - */ public function testCaptureMessageWithTagsContext() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; $client->tags_context(['foo' => 'bar']); $client->tags_context(['biz' => 'boz']); $client->tags_context(['biz' => 'baz']); $client->captureMessage('test'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals([ - 'foo' => 'bar', - 'biz' => 'baz', - ], $event['tags']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals(['foo' => 'bar', 'biz' => 'baz'], $client->_pending_events[0]['tags']); } - /** - * @covers \Raven\Client::captureMessage - * @covers \Raven\Client::extra_context - */ public function testCaptureMessageWithExtraContext() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; $client->extra_context(['foo' => 'bar']); $client->extra_context(['biz' => 'boz']); $client->extra_context(['biz' => 'baz']); $client->captureMessage('test'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals([ - 'foo' => 'bar', - 'biz' => 'baz', - ], $event['extra']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals(['foo' => 'bar', 'biz' => 'baz'], $client->_pending_events[0]['extra']); } - /** - * @covers \Raven\Client::captureException - */ public function testCaptureExceptionContainingLatin1() { - // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order - $options = [ - 'mb_detect_order' => [ - 'ISO-8859-1', 'ASCII', 'UTF-8' - ] - ]; - - $client = new Dummy_Raven_Client($options); + $client = new Client(new Configuration(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])); + $client->store_errors_for_bulk_send = true; // we need a non-utf8 string here. // nobody writes non-utf8 in exceptions, but it is the easiest way to test. @@ -1088,36 +797,30 @@ public function testCaptureExceptionContainingLatin1() $latin1String = utf8_decode($utf8String); $client->captureException(new \Exception($latin1String)); - $events = $client->getSentEvents(); - $event = array_pop($events); - - $this->assertEquals($utf8String, $event['exception']['values'][0]['value']); + $this->assertCount(1, $client->_pending_events); + $this->assertEquals($utf8String, $client->_pending_events[0]['exception']['values'][0]['value']); } - public function testCaptureExceptionInLatin1File() { - // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order - $options = [ - 'mb_detect_order' => [ - 'ISO-8859-1', 'ASCII', 'UTF-8' - ] - ]; - - $client = new Dummy_Raven_Client($options); + $client = new Client(new Configuration(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])); + $client->store_errors_for_bulk_send = true; require_once(__DIR__ . '/Fixtures/code/Latin1File.php'); - $events = $client->getSentEvents(); - $event = array_pop($events); - - $stackTrace = array_pop($event['exception']['values'][0]['stacktrace']['frames']); + $frames = $client->_pending_events[0]['exception']['values'][0]['stacktrace']['frames']; $utf8String = "// äöü"; $found = false; - foreach ($stackTrace['pre_context'] as $line) { - if ($line == $utf8String) { + + foreach ($frames as $frame) { + if (!isset($frame['pre_context'])) { + continue; + } + + if (in_array($utf8String, $frame['pre_context'])) { $found = true; + break; } } @@ -1125,140 +828,136 @@ public function testCaptureExceptionInLatin1File() $this->assertTrue($found); } - /** - * @covers \Raven\Client::captureLastError - */ public function testCaptureLastError() { if (function_exists('error_clear_last')) { error_clear_last(); } - $client = new Dummy_Raven_Client(); + + $client = new Dummy_Raven_Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + $this->assertNull($client->captureLastError()); - $this->assertEquals(0, count($client->getSentEvents())); + $this->assertEmpty($client->getSentEvents()); /** @var $undefined */ /** @noinspection PhpExpressionResultUnusedInspection */ @$undefined; $client->captureLastError(); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals('Undefined variable: undefined', $event['exception']['values'][0]['value']); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('Undefined variable: undefined', $client->_pending_events[0]['exception']['values'][0]['value']); } - /** - * @covers \Raven\Client::getLastEventID - */ public function testGetLastEventID() { - $client = new Dummy_Raven_Client(); - $client->capture(['message' => 'test', 'event_id' => 'abc']); + $client = new Client(new Configuration()); + $client->capture([ + 'message' => 'test', + 'event_id' => 'abc' + ]); + $this->assertEquals('abc', $client->getLastEventID()); } - /** - * @covers \Raven\Client::setTransport - */ public function testCustomTransport() { $events = []; - // transport test requires default client - $client = new \Raven\Client('https://public:secret@sentry.example.com/1', [ + $client = new Client(new Configuration([ + 'server' => 'https://public:secret@sentry.example.com/1', 'install_default_breadcrumb_handlers' => false, - ]); - $client->setTransport(function ($client, $data) use (&$events) { + ])); + + $client->getConfig()->setTransport(function ($client, $data) use (&$events) { $events[] = $data; }); + $client->capture(['message' => 'test', 'event_id' => 'abc']); - $this->assertEquals(1, count($events)); + + $this->assertCount(1, $events); } - /** - * @covers \Raven\Client::setAppPath - */ public function testAppPathLinux() { - $client = new Dummy_Raven_Client(); - $client->setAppPath('/foo/bar'); + $client = new Client(new Configuration(['project_root' => '/foo/bar'])); - $this->assertEquals('/foo/bar/', $client->getAppPath()); + $this->assertEquals('/foo/bar/', $client->getConfig()->getProjectRoot()); - $client->setAppPath('/foo/baz/'); + $client->getConfig()->setProjectRoot('/foo/baz/'); - $this->assertEquals('/foo/baz/', $client->getAppPath()); + $this->assertEquals('/foo/baz/', $client->getConfig()->getProjectRoot()); } - /** - * @covers \Raven\Client::setAppPath - */ public function testAppPathWindows() { - $client = new Dummy_Raven_Client(); - $client->setAppPath('C:\\foo\\bar\\'); + $client = new Client(new Configuration(['project_root' => 'C:\\foo\\bar\\'])); - $this->assertEquals('C:\\foo\\bar\\', $client->getAppPath()); + $this->assertEquals('C:\\foo\\bar\\', $client->getConfig()->getProjectRoot()); } /** * @expectedException \Raven\Exception - * @expectedExceptionMessage Raven_Client->install() must only be called once + * @expectedExceptionMessage Raven\Client->install() must only be called once */ public function testCannotInstallTwice() { - $client = new Dummy_Raven_Client('https://public:secret@sentry.example.com/1'); + $client = new Client(new Configuration()); + $client->install(); $client->install(); } - public function cb1($data) - { - $this->assertEquals('test', $data['message']); - return false; - } - - public function cb2($data) + public function testSendCallback() { - $this->assertEquals('test', $data['message']); - return true; - } + $client = new Dummy_Raven_Client(new Configuration([ + 'should_capture' => function ($data) { + $this->assertEquals('test', $data['message']); - public function cb3(&$data) - { - unset($data['message']); - return true; - } + return false; + } + ])); - /** - * @covers \Raven\Client::send - */ - public function testSendCallback() - { - $client = new Dummy_Raven_Client(['send_callback' => [$this, 'cb1']]); $client->captureMessage('test'); + $events = $client->getSentEvents(); - $this->assertEquals(0, count($events)); - $client = new Dummy_Raven_Client(['send_callback' => [$this, 'cb2']]); + $this->assertEmpty($events); + + $client = new Dummy_Raven_Client(new Configuration([ + 'should_capture' => function ($data) { + $this->assertEquals('test', $data['message']); + + return true; + } + ])); + $client->captureMessage('test'); + $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $client = new Dummy_Raven_Client(['send_callback' => [$this, 'cb3']]); + $this->assertCount(1, $events); + + $client = new Dummy_Raven_Client(new Configuration([ + 'should_capture' => function (&$data) { + unset($data['message']); + + return true; + } + ])); + $client->captureMessage('test'); + $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $this->assertTrue(empty($events[0]['message'])); + + $this->assertCount(1, $events); + $this->assertArrayNotHasKey('message', $events[0]); } - /** - * @covers \Raven\Client::sanitize - */ public function testSanitizeExtra() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $data = ['extra' => [ 'context' => [ 'line' => 1216, @@ -1279,17 +978,10 @@ public function testSanitizeExtra() ]], $data); } - /** - * @covers \Raven\Client::sanitize - */ public function testSanitizeObjects() { - $client = new Dummy_Raven_Client( - null, [ - 'serialize_all_object' => true, - ] - ); - $clone = new Dummy_Raven_Client(); + $client = new Dummy_Raven_Client(new Configuration(['serialize_all_object' => true])); + $clone = new Client(new Configuration()); $data = [ 'extra' => [ 'object' => $clone, @@ -1343,12 +1035,9 @@ public function testSanitizeObjects() $this->assertEquals(['extra' => ['object' => $expected]], $data); } - /** - * @covers \Raven\Client::sanitize - */ public function testSanitizeTags() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $data = ['tags' => [ 'foo' => 'bar', 'baz' => ['biz'], @@ -1361,12 +1050,9 @@ public function testSanitizeTags() ]], $data); } - /** - * @covers \Raven\Client::sanitize - */ public function testSanitizeUser() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $data = ['user' => [ 'email' => 'foo@example.com', ]]; @@ -1377,12 +1063,9 @@ public function testSanitizeUser() ]], $data); } - /** - * @covers \Raven\Client::sanitize - */ public function testSanitizeRequest() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $data = ['request' => [ 'context' => [ 'line' => 1216, @@ -1403,12 +1086,9 @@ public function testSanitizeRequest() ]], $data); } - /** - * @covers \Raven\Client::sanitize - */ public function testSanitizeContexts() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $data = ['contexts' => [ 'context' => [ 'line' => 1216, @@ -1435,65 +1115,62 @@ public function testSanitizeContexts() ]], $data); } - /** - * @covers \Raven\Client::buildCurlCommand - */ public function testBuildCurlCommandEscapesInput() { $data = '{"foo": "\'; ls;"}'; - $client = new Dummy_Raven_Client(); - $client->timeout = 5; + $client = new Dummy_Raven_Client(new Configuration(['timeout' => 5])); $result = $client->buildCurlCommand('http://foo.com', $data, []); - $folder = realpath(__DIR__.'/../lib/Raven/data'); - $this->assertEquals( - 'curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 --cacert \''.$folder. - '/cacert.pem\' > /dev/null 2>&1 &', $result - ); - $client->ca_cert = null; + $folder = CaBundle::getSystemCaRootBundlePath(); + $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 --cacert \''.$folder.'\' > /dev/null 2>&1 &', $result); + + $client->getConfig()->setSslCaFile(null); + $result = $client->buildCurlCommand('http://foo.com', $data, []); + $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); $result = $client->buildCurlCommand('http://foo.com', $data, ['key' => 'value']); + $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); - $client->verify_ssl = false; + $client->getConfig()->setSslVerificationEnabled(false); + $result = $client->buildCurlCommand('http://foo.com', $data, []); + $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); $result = $client->buildCurlCommand('http://foo.com', $data, ['key' => 'value']); + $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); } - /** - * @covers \Raven\Client::user_context - */ public function testUserContextWithoutMerge() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->user_context(['foo' => 'bar'], false); $client->user_context(['baz' => 'bar'], false); + $this->assertEquals(['baz' => 'bar'], $client->context->user); } - /** - * @covers \Raven\Client::user_context - */ public function testUserContextWithMerge() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->user_context(['foo' => 'bar'], true); $client->user_context(['baz' => 'bar'], true); + $this->assertEquals(['foo' => 'bar', 'baz' => 'bar'], $client->context->user); } - /** - * @covers \Raven\Client::user_context - */ public function testUserContextWithMergeAndNull() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->user_context(['foo' => 'bar'], true); $client->user_context(null, true); + $this->assertEquals(['foo' => 'bar'], $client->context->user); } @@ -1512,7 +1189,7 @@ public function testCurrentUrl($serverVars, $options, $expected, $message) { $_SERVER = $serverVars; - $client = new Dummy_Raven_Client($options); + $client = new Dummy_Raven_Client(new Configuration($options)); $result = $client->test_get_current_url(); $this->assertSame($expected, $result, $message); @@ -1601,64 +1278,23 @@ public function testUuid4() } /** - * @covers \Raven\Client::getEnvironment - * @covers \Raven\Client::setEnvironment - * @covers \Raven\Client::getRelease - * @covers \Raven\Client::setRelease - * @covers \Raven\Client::getAppPath - * @covers \Raven\Client::setAppPath - * @covers \Raven\Client::getExcludedAppPaths - * @covers \Raven\Client::setExcludedAppPaths - * @covers \Raven\Client::getPrefixes - * @covers \Raven\Client::setPrefixes - * @covers \Raven\Client::getSendCallback - * @covers \Raven\Client::setSendCallback - * @covers \Raven\Client::getTransport - * @covers \Raven\Client::setTransport - * @covers \Raven\Client::getServerEndpoint * @covers \Raven\Client::getLastError * @covers \Raven\Client::getLastEventID - * @covers \Raven\Client::get_extra_data - * @covers \Raven\Client::setProcessors * @covers \Raven\Client::getLastSentryError * @covers \Raven\Client::getShutdownFunctionHasBeenSet */ public function testGettersAndSetters() { - $client = new Dummy_Raven_Client(); - $property_method__convert_path = new \ReflectionMethod('\\Raven\\Client', '_convertPath'); - $property_method__convert_path->setAccessible(true); + $client = new Client(new Configuration()); $callable = [$this, 'stabClosureVoid']; $data = [ - ['environment', null, 'value', ], - ['environment', null, null, ], - ['release', null, 'value', ], - ['release', null, null, ], - ['app_path', null, 'value', $property_method__convert_path->invoke($client, 'value')], - ['app_path', null, null,], - ['app_path', null, false, null,], - ['excluded_app_paths', null, ['value'], - [$property_method__convert_path->invoke($client, 'value')]], - ['excluded_app_paths', null, [], null], - ['excluded_app_paths', null, null], - ['prefixes', null, ['value'], [$property_method__convert_path->invoke($client, 'value')]], - ['prefixes', null, []], - ['send_callback', null, $callable], - ['send_callback', null, null], - ['transport', null, $callable], - ['transport', null, null], - ['server', 'ServerEndpoint', 'http://example.com/'], - ['server', 'ServerEndpoint', 'http://example.org/'], ['_lasterror', null, null,], ['_lasterror', null, 'value',], ['_lasterror', null, mt_rand(100, 999),], ['_last_sentry_error', null, (object)['error' => 'test',],], ['_last_event_id', null, mt_rand(100, 999),], ['_last_event_id', null, 'value',], - ['extra_data', '_extra_data', ['key' => 'value'],], - ['processors', 'processors', [],], - ['processors', 'processors', ['key' => 'value'],], ['_shutdown_function_has_been_set', null, true], ['_shutdown_function_has_been_set', null, false], ]; @@ -1666,7 +1302,7 @@ public function testGettersAndSetters() $this->subTestGettersAndSettersDatum($client, $datum); } foreach ($data as &$datum) { - $client = new Dummy_Raven_Client(); + $client = new Dummy_Raven_Client(new Configuration()); $this->subTestGettersAndSettersDatum($client, $datum); } } @@ -1730,46 +1366,6 @@ private function assertMixedValueAndArray($expected_value, $actual_value) } } - /** - * @covers \Raven\Client::_convertPath - */ - public function test_convertPath() - { - $property = new \ReflectionMethod('\\Raven\Client', '_convertPath'); - $property->setAccessible(true); - - $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar')); - $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar/')); - $this->assertEquals('foo/bar', $property->invoke(null, 'foo/bar')); - $this->assertEquals('foo/bar/', $property->invoke(null, 'foo/bar/')); - $this->assertEquals(dirname(__DIR__).'/', $property->invoke(null, __DIR__.'/../')); - $this->assertEquals(dirname(dirname(__DIR__)).'/', $property->invoke(null, __DIR__.'/../../')); - } - - /** - * @covers \Raven\Client::getDefaultProcessors - */ - public function testGetDefaultProcessors() - { - foreach (\Raven\Client::getDefaultProcessors() as $class_name) { - $this->assertInternalType('string', $class_name); - $this->assertTrue(class_exists($class_name)); - $reflection = new \ReflectionClass($class_name); - $this->assertTrue($reflection->isSubclassOf('\\Raven\\Processor')); - $this->assertFalse($reflection->isAbstract()); - } - } - - /** - * @covers \Raven\Client::get_default_ca_cert - */ - public function testGet_default_ca_cert() - { - $reflection = new \ReflectionMethod('\\Raven\Client', 'get_default_ca_cert'); - $reflection->setAccessible(true); - $this->assertFileExists($reflection->invoke(null)); - } - /** * @covers \Raven\Client::translateSeverity * @covers \Raven\Client::registerSeverityMap @@ -1778,7 +1374,7 @@ public function testTranslateSeverity() { $reflection = new \ReflectionProperty('\\Raven\Client', 'severity_map'); $reflection->setAccessible(true); - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $predefined = [E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR, E_USER_WARNING, @@ -1820,102 +1416,74 @@ public function testTranslateSeverity() $this->assertEquals('error', $client->translateSeverity(123457)); } - /** - * @covers \Raven\Client::getUserAgent - */ public function testGetUserAgent() { - $this->assertRegExp('|^[0-9a-z./_-]+$|i', \Raven\Client::getUserAgent()); + $this->assertRegExp('|^[0-9a-z./_-]+$|i', Client::getUserAgent()); } public function testCaptureExceptionWithLogger() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + $client->captureException(new \Exception(), null, 'foobar'); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - $this->assertEquals('foobar', $event['logger']); + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('foobar', $client->_pending_events[0]['logger']); } - /** - * @covers \Raven\Client::__construct - * @covers \Raven\Client::send - * @covers \Raven\Client::send_remote - * @covers \Raven\Client::send_http - */ public function testCurl_method() { - // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ] + ]) ); + $client->captureMessage('foobar'); + $this->assertTrue($client->_send_http_synchronous); $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); - // step 2 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'curl_method' => 'exec', 'install_default_breadcrumb_handlers' => false, - ] + ]) ); $client->captureMessage('foobar'); $this->assertFalse($client->_send_http_synchronous); $this->assertTrue($client->_send_http_asynchronous_curl_exec_called); } - /** - * @covers \Raven\Client::__construct - * @covers \Raven\Client::send - * @covers \Raven\Client::send_remote - * @covers \Raven\Client::send_http - */ public function testCurl_method_async() { - // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'curl_method' => 'async', 'install_default_breadcrumb_handlers' => false, - ] + ]) ); + $object = $client->get_curl_handler(); - $this->assertInternalType('object', $object); - $this->assertEquals('Raven\\CurlHandler', get_class($object)); - $reflection = new \ReflectionProperty('Raven\\CurlHandler', 'options'); - $reflection->setAccessible(true); - $this->assertEquals($reflection->getValue($object), $client->get_curl_options()); + $this->assertInstanceOf(CurlHandler::class, $object); + $this->assertAttributeEquals($client->get_curl_options(), 'options', $object); - // step 2 $ch = new Dummy_Raven_CurlHandler(); + $client->set_curl_handler($ch); $client->captureMessage('foobar'); + $this->assertFalse($client->_send_http_synchronous); $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); $this->assertTrue($ch->_enqueue_called); } - /** - * @backupGlobals - * @covers \Raven\Client::__construct - */ - public function testConstructWithServerDSN() - { - $_SERVER['SENTRY_DSN'] = 'http://public:secret@example.com/1'; - $client = new Dummy_Raven_Client(); - $this->assertEquals(1, $client->project); - $this->assertEquals('http://example.com/api/1/store/', $client->server); - $this->assertEquals('public', $client->public_key); - $this->assertEquals('secret', $client->secret_key); - } - /** * @backupGlobals * @covers \Raven\Client::_server_variable @@ -1944,7 +1512,7 @@ public function test_server_variable() public function testEncode() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $data = ['some' => (object)['value' => 'data'], 'foo' => ['bar', null, 123], false]; $json_stringify = json_encode($data); $value = $client->encode($data); @@ -1960,34 +1528,34 @@ public function testEncode() } } - /** - * @covers \Raven\Client::__construct - * @covers \Raven\Client::registerDefaultBreadcrumbHandlers - */ public function testRegisterDefaultBreadcrumbHandlers() { if (isset($_ENV['HHVM']) and ($_ENV['HHVM'] == 1)) { $this->markTestSkipped('HHVM stacktrace behaviour'); + return; } + $previous = set_error_handler([$this, 'stabClosureErrorHandler'], E_USER_NOTICE); - new \Raven\Client(null, []); + + new Client(new Configuration()); + $this->_closure_called = false; + trigger_error('foobar', E_USER_NOTICE); - $u = $this->_closure_called; - $debug_backtrace = $this->_debug_backtrace; set_error_handler($previous, E_ALL); - $this->assertTrue($u); - if (isset($debug_backtrace[1]['function']) and ($debug_backtrace[1]['function'] == 'call_user_func') - and version_compare(PHP_VERSION, '7.0', '>=') - ) { + + $this->assertTrue($this->_closure_called); + + if (isset($this->_debug_backtrace[1]['function']) && ($this->_debug_backtrace[1]['function'] == 'call_user_func') && version_compare(PHP_VERSION, '7.0', '>=')) { $offset = 2; } elseif (version_compare(PHP_VERSION, '7.0', '>=')) { $offset = 1; } else { $offset = 2; } - $this->assertEquals('Raven\\Breadcrumbs\\ErrorHandler', $debug_backtrace[$offset]['class']); + + $this->assertEquals(ErrorHandler::class, $this->_debug_backtrace[$offset]['class']); } private $_closure_called = false; @@ -2021,19 +1589,15 @@ public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, return true; } - /** - * @covers \Raven\Client::onShutdown - * @covers \Raven\Client::sendUnsentErrors - * @covers \Raven\Client::capture - */ public function testOnShutdown() { // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ] + ]) ); $this->assertEquals(0, count($client->_pending_events)); $client->_pending_events[] = ['foo' => 'bar']; @@ -2062,10 +1626,11 @@ public function testOnShutdown() // step 1 $client = null; $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'curl_method' => 'async', 'install_default_breadcrumb_handlers' => false, - ] + ]) ); $ch = new Dummy_Raven_CurlHandler(); $client->set_curl_handler($ch); @@ -2075,127 +1640,134 @@ public function testOnShutdown() $this->assertTrue($ch->_join_called); } - /** - * @covers \Raven\Client::send - */ - public function testNonWorkingSendSendCallback() + public function testSendChecksShouldCaptureOption() { - // step 1 + $this->_closure_called = false; + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ] + 'should_capture' => function () { + $this->_closure_called = true; + + return false; + }, + ]) ); - $this->_closure_called = false; - $client->setSendCallback([$this, 'stabClosureNull']); - $this->assertFalse($this->_closure_called); - $data = ['foo' => 'bar']; - $client->send($data); - $this->assertTrue($this->_closure_called); - $this->assertTrue($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); - // step 2 - $this->_closure_called = false; - $client->_send_http_synchronous = false; - $client->_send_http_asynchronous_curl_exec_called = false; - $client->setSendCallback([$this, 'stabClosureFalse']); - $this->assertFalse($this->_closure_called); + $data = ['foo' => 'bar']; + $client->send($data); + $this->assertTrue($this->_closure_called); - $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + $this->assertFalse($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); } - /** - * @covers \Raven\Client::send - */ - public function testNonWorkingSendDSNEmpty() + public function testSendFailsWhenNoServerIsConfigured() { - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - ] - ); - $client->server = null; + /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(Client::class) + ->setConstructorArgs([new Configuration()]) + ->getMock(); + + $client->expects($this->never()) + ->method('encode'); + $data = ['foo' => 'bar']; + $client->send($data); - $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); } - /** - * @covers \Raven\Client::send - */ public function testNonWorkingSendSetTransport() { - // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - ] + ]) ); + $this->_closure_called = false; - $client->setTransport([$this, 'stabClosureNull']); + + $client->getConfig()->setTransport([$this, 'stabClosureNull']); + $this->assertFalse($this->_closure_called); + $data = ['foo' => 'bar']; + $client->send($data); + $this->assertTrue($this->_closure_called); - $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); - // step 2 + $this->assertFalse($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); + $this->_closure_called = false; - $client->setSendCallback([$this, 'stabClosureFalse']); + + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + new Configuration([ + 'server' => 'http://public:secret@example.com/1', + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + 'should_capture' => function () { + $this->_closure_called = true; + + return false; + } + ]) + ); + $this->assertFalse($this->_closure_called); + $data = ['foo' => 'bar']; + $client->send($data); + $this->assertTrue($this->_closure_called); - $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + $this->assertFalse($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); } - /** - * @covers \Raven\Client::__construct - */ public function test__construct_handlers() { foreach ([true, false] as $u1) { foreach ([true, false] as $u2) { $client = new Dummy_Raven_Client( - null, [ + new Configuration([ 'install_default_breadcrumb_handlers' => $u1, 'install_shutdown_handler' => $u2, - ] + ]) ); + $this->assertEquals($u1, $client->dummy_breadcrumbs_handlers_has_set); $this->assertEquals($u2, $client->dummy_shutdown_handlers_has_set); } } } - /** - * @covers \Raven\Client::__destruct - * @covers \Raven\Client::close_all_children_link - */ public function test__destruct_calls_close_functions() { $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'install_default_breadcrumb_handlers' => false, 'install_shutdown_handler' => false, - ] + ]) ); + $client::$_close_curl_resource_called = false; + $client->close_all_children_link(); + unset($client); + $this->assertTrue(Dummy_Raven_Client_With_Overrided_Direct_Send::$_close_curl_resource_called); } - /** - * @covers \Raven\Client::get_user_data - */ public function testGet_user_data() { // step 1 - $client = new Dummy_Raven_Client(); + $client = new Dummy_Raven_Client(new Configuration()); $output = $client->get_user_data(); $this->assertInternalType('array', $output); $this->assertArrayHasKey('user', $output); @@ -2224,222 +1796,227 @@ public function testGet_user_data() $_SESSION = $session_old; } - /** - * @covers \Raven\Client::capture - * @covers \Raven\Client::setRelease - * @covers \Raven\Client::setEnvironment - */ public function testCaptureLevel() { - foreach ([\Raven\Client::MESSAGE_LIMIT * 3, 100] as $length) { + foreach ([Client::MESSAGE_LIMIT * 3, 100] as $length) { $message = ''; + for ($i = 0; $i < $length; $i++) { $message .= chr($i % 256); } - $client = new Dummy_Raven_Client(); - $client->capture(['message' => $message, ]); - $events = $client->getSentEvents(); - $this->assertEquals(1, count($events)); - $event = array_pop($events); - - $this->assertEquals('error', $event['level']); - $this->assertEquals(substr($message, 0, min(\Raven\Client::MESSAGE_LIMIT, $length)), $event['message']); - $this->assertArrayNotHasKey('release', $event); - $this->assertArrayNotHasKey('environment', $event); + + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + + $client->capture(['message' => $message]); + + $this->assertCount(1, $client->_pending_events); + $this->assertEquals('error', $client->_pending_events[0]['level']); + $this->assertEquals(substr($message, 0, min(\Raven\Client::MESSAGE_LIMIT, $length)), $client->_pending_events[0]['message']); + $this->assertArrayNotHasKey('release', $client->_pending_events[0]); } - $client = new Dummy_Raven_Client(); - $client->capture(['message' => 'foobar', ]); - $events = $client->getSentEvents(); - $event = array_pop($events); + $client = new Dummy_Raven_Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + + $client->capture(['message' => 'foobar']); + $input = $client->get_http_data(); - $this->assertEquals($input['request'], $event['request']); - $this->assertArrayNotHasKey('release', $event); - $this->assertArrayNotHasKey('environment', $event); - $client = new Dummy_Raven_Client(); - $client->capture(['message' => 'foobar', 'request' => ['foo' => 'bar'], ]); - $events = $client->getSentEvents(); - $event = array_pop($events); - $this->assertEquals(['foo' => 'bar'], $event['request']); - $this->assertArrayNotHasKey('release', $event); - $this->assertArrayNotHasKey('environment', $event); + $this->assertEquals($input['request'], $client->_pending_events[0]['request']); + $this->assertArrayNotHasKey('release', $client->_pending_events[0]); + + $client = new Dummy_Raven_Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + + $client->capture(['message' => 'foobar', 'request' => ['foo' => 'bar']]); + + $this->assertEquals(['foo' => 'bar'], $client->_pending_events[0]['request']); + $this->assertArrayNotHasKey('release', $client->_pending_events[0]); foreach ([false, true] as $u1) { foreach ([false, true] as $u2) { - $client = new Dummy_Raven_Client(); + $options = []; + if ($u1) { - $client->setRelease('foo'); + $options['release'] = 'foo'; } + if ($u2) { - $client->setEnvironment('bar'); + $options['current_environment'] = 'bar'; } - $client->capture(['message' => 'foobar', ]); - $events = $client->getSentEvents(); - $event = array_pop($events); + + $client = new Client(new Configuration($options)); + $client->store_errors_for_bulk_send = true; + + $client->capture(['message' => 'foobar']); + if ($u1) { - $this->assertEquals('foo', $event['release']); + $this->assertEquals('foo', $client->_pending_events[0]['release']); } else { - $this->assertArrayNotHasKey('release', $event); + $this->assertArrayNotHasKey('release', $client->_pending_events[0]); } + if ($u2) { - $this->assertEquals('bar', $event['environment']); - } else { - $this->assertArrayNotHasKey('environment', $event); + $this->assertEquals('bar', $client->_pending_events[0]['environment']); } } } } - /** - * @covers \Raven\Client::capture - */ public function testCaptureNoUserAndRequest() { - $client = new Dummy_Raven_Client_No_Http(null, [ - 'install_default_breadcrumb_handlers' => false, - ]); + $client = new Dummy_Raven_Client_No_Http(new Configuration(['install_default_breadcrumb_handlers' => false])); + $client->store_errors_for_bulk_send = true; + $session_id = session_id(); + session_write_close(); session_id(''); + $client->capture(['user' => '', 'request' => '']); - $events = $client->getSentEvents(); - $event = array_pop($events); - $this->assertArrayNotHasKey('user', $event); - $this->assertArrayNotHasKey('request', $event); - // step 3 + $this->assertCount(1, $client->_pending_events); + $this->assertArrayNotHasKey('user', $client->_pending_events[0]); + $this->assertArrayNotHasKey('request', $client->_pending_events[0]); + session_id($session_id); - @session_start(['use_cookies' => false, ]); + @session_start(['use_cookies' => false]); } - /** - * @covers \Raven\Client::capture - */ public function testCaptureNonEmptyBreadcrumb() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + $ts1 = microtime(true); + $client->breadcrumbs->record(['foo' => 'bar']); $client->breadcrumbs->record(['honey' => 'clover']); + $client->capture([]); - $events = $client->getSentEvents(); - $event = array_pop($events); - foreach ($event['breadcrumbs'] as &$crumb) { + + foreach ($client->_pending_events[0]['breadcrumbs'] as &$crumb) { $this->assertGreaterThanOrEqual($ts1, $crumb['timestamp']); + unset($crumb['timestamp']); } + $this->assertEquals([ ['foo' => 'bar'], ['honey' => 'clover'], - ], $event['breadcrumbs']); + ], $client->_pending_events[0]['breadcrumbs']); } - - /** - * @covers \Raven\Client::capture - */ public function testCaptureAutoLogStacks() { - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); + $client->store_errors_for_bulk_send = true; + $client->capture(['auto_log_stacks' => true], true); - $events = $client->getSentEvents(); - $event = array_pop($events); - $this->assertArrayHasKey('stacktrace', $event); - $this->assertInternalType('array', $event['stacktrace']['frames']); + + $this->assertCount(1, $client->_pending_events); + $this->assertArrayHasKey('stacktrace', $client->_pending_events[0]); + $this->assertInternalType('array', $client->_pending_events[0]['stacktrace']['frames']); } - /** - * @covers \Raven\Client::send_http_asynchronous_curl_exec - */ public function testSend_http_asynchronous_curl_exec() { $client = new Dummy_Raven_Client_With_Sync_Override( - 'http://public:secret@example.com/1', [ + new Configuration([ + 'server' => 'http://public:secret@example.com/1', 'curl_method' => 'exec', 'install_default_breadcrumb_handlers' => false, - ] + ]) ); + if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { unlink(Dummy_Raven_Client_With_Sync_Override::test_filename()); } + $client->captureMessage('foobar'); + $test_data = Dummy_Raven_Client_With_Sync_Override::get_test_data(); - $this->assertStringEqualsFile(Dummy_Raven_Client_With_Sync_Override::test_filename(), $test_data."\n"); + + $this->assertStringEqualsFile(Dummy_Raven_Client_With_Sync_Override::test_filename(), $test_data . "\n"); } - /** - * @covers \Raven\Client::close_curl_resource - */ public function testClose_curl_resource() { - $raven = new Dummy_Raven_Client(); - $reflection = new \ReflectionProperty('\\Raven\Client', '_curl_instance'); + $client = new Client(new Configuration()); + + $reflection = new \ReflectionProperty(Client::class, '_curl_instance'); $reflection->setAccessible(true); + $ch = curl_init(); - $reflection->setValue($raven, $ch); + + $reflection->setValue($client, $ch); + unset($ch); - $this->assertInternalType('resource', $reflection->getValue($raven)); - $raven->close_curl_resource(); - $this->assertNull($reflection->getValue($raven)); + $this->assertInternalType('resource', $reflection->getValue($client)); + + $client->close_curl_resource(); + + $this->assertNull($reflection->getValue($client)); } - /** - * @covers \Raven\Client::send - */ public function testSampleRateAbsolute() { - // step 1 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ - 'curl_method' => 'foobar', + new Configuration([ + 'server' => 'http://public:secret@example.com/1', + 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - 'sample_rate' => 0, - ] + 'sample_rate' => 0, + ]) ); + for ($i = 0; $i < 1000; $i++) { $client->captureMessage('foobar'); - $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + + $this->assertFalse($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); } - // step 2 $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ - 'curl_method' => 'foobar', + new Configuration([ + 'server' => 'http://public:secret@example.com/1', + 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - 'sample_rate' => 1, - ] + 'sample_rate' => 1, + ]) ); + for ($i = 0; $i < 1000; $i++) { $client->captureMessage('foobar'); - $this->assertTrue($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + + $this->assertTrue($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); } } - /** - * @covers \Raven\Client::send - */ public function testSampleRatePrc() { $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - 'http://public:secret@example.com/1', [ - 'curl_method' => 'foobar', + new Configuration([ + 'sample_rate' => 0.5, + 'curl_method' => 'foobar', 'install_default_breadcrumb_handlers' => false, - 'sample_rate' => 0.5, - ] + ]) ); + $u_true = false; $u_false = false; + for ($i = 0; $i < 1000; $i++) { $client->captureMessage('foobar'); - if ($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called) { + + if ($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called) { $u_true = true; } else { $u_false = true; } - if ($u_true or $u_false) { + if ($u_true || $u_false) { return; } } @@ -2447,32 +2024,19 @@ public function testSampleRatePrc() $this->fail('sample_rate=0.5 can not produce fails and successes at the same time'); } - /** - * @covers \Raven\Client::setAllObjectSerialize - */ public function testSetAllObjectSerialize() { - $client = new \Raven\Client; - - $ref1 = new \ReflectionProperty($client, 'serializer'); - $ref1->setAccessible(true); - $ref2 = new \ReflectionProperty($client, 'reprSerializer'); - $ref2->setAccessible(true); - - /** - * @var \Raven\Serializer $o1 - * @var \Raven\Serializer $o2 - */ - $o1 = $ref1->getValue($client); - $o2 = $ref2->getValue($client); + $client = new Client(new Configuration()); $client->setAllObjectSerialize(true); - $this->assertTrue($o1->getAllObjectSerialize()); - $this->assertTrue($o2->getAllObjectSerialize()); + + $this->assertTrue($client->getSerializer()->getAllObjectSerialize()); + $this->assertTrue($client->getReprSerializer()->getAllObjectSerialize()); $client->setAllObjectSerialize(false); - $this->assertFalse($o1->getAllObjectSerialize()); - $this->assertFalse($o2->getAllObjectSerialize()); + + $this->assertFalse($client->getSerializer()->getAllObjectSerialize()); + $this->assertFalse($client->getReprSerializer()->getAllObjectSerialize()); } /** @@ -2505,7 +2069,7 @@ public function dataDirectSend() $block1 = []; $block1[] = [ 'options' => [ - 'dsn' => 'http://login:password@127.0.0.1:{port}/5', + 'server' => 'http://login:password@127.0.0.1:{port}/5', ], 'server_options' => [], 'timeout' => 0, @@ -2513,7 +2077,7 @@ public function dataDirectSend() ]; $block1[] = [ 'options' => [ - 'dsn' => 'http://login:password@127.0.0.1:{port}/5', + 'server' => 'http://login:password@127.0.0.1:{port}/5', 'curl_method' => 'async', ], 'server_options' => [], @@ -2522,7 +2086,7 @@ public function dataDirectSend() ]; $block1[] = [ 'options' => [ - 'dsn' => 'http://login:password@127.0.0.1:{port}/5', + 'server' => 'http://login:password@127.0.0.1:{port}/5', 'curl_method' => 'exec', ], 'server_options' => [], @@ -2541,8 +2105,8 @@ public function dataDirectSend() $block_ssl = [['options' => [], 'server_options' => [], 'timeout' => 0, 'is_failed' => false]]; $block_ssl[] = [ 'options' => [ - 'dsn' => 'http://login:password@127.0.0.1:{port}/5', - 'ca_cert' => '{folder}/crt_a1.crt', + 'server' => 'http://login:password@127.0.0.1:{port}/5', + 'ssl_ca_file' => '{folder}/crt_a1.crt', ], 'server_options' => [ 'ssl_server_certificate_file' => '{folder}/crt_a4.c.crt', @@ -2567,8 +2131,8 @@ public function dataDirectSend() 'timeout' => max($b1['timeout'], $b2['timeout']), 'is_failed' => ($b1['is_failed'] or $b2['is_failed']), ]; - if (isset($datum['options']['ca_cert'])) { - $datum['options']['dsn'] = str_replace('http://', 'https://', $datum['options']['dsn']); + if (isset($datum['options']['ssl_ca_file'])) { + $datum['options']['server'] = str_replace('http://', 'https://', $datum['options']['server']); } $data[] = $datum; @@ -2602,10 +2166,10 @@ public function testDirectSend($sentry_options, $server_options, $timeout, $is_f unset($value); $port = self::get_port(); - $sentry_options['dsn'] = str_replace('{port}', $port, $sentry_options['dsn']); + $sentry_options['server'] = str_replace('{port}', $port, $sentry_options['server']); $sentry_options['timeout'] = 10; - $client = new Client($sentry_options); + $client = new Client(new Configuration($sentry_options)); $output_filename = tempnam(self::$_folder, 'output_http_'); foreach ( [ @@ -2681,7 +2245,6 @@ public function testDirectSend($sentry_options, $server_options, $timeout, $is_f } $body = json_decode($body); $this->assertEquals(5, $body->project); - $this->assertEquals('', $body->site); $this->assertEquals('Test Message', $body->message); $this->assertEquals($event, $body->event_id); $this->assertEquals($extra, (array)$body->extra); diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php new file mode 100644 index 000000000..bcc944a65 --- /dev/null +++ b/tests/ConfigurationTest.php @@ -0,0 +1,209 @@ + $value]); + + $this->assertEquals($value, $configuration->$getterMethod()); + } + + /** + * @dataProvider optionsDataProvider + */ + public function testGettersAndSetters($option, $value, $getterMethod, $setterMethod = null) + { + $configuration = new Configuration(); + + if (null !== $setterMethod) { + $configuration->$setterMethod($value); + } + + $this->assertEquals($value, $configuration->$getterMethod()); + } + + public function optionsDataProvider() + { + return [ + ['trust_x_forwarded_proto', false, 'isTrustXForwardedProto', 'setIsTrustXForwardedProto'], + ['prefixes', ['foo', 'bar'], 'getPrefixes', 'setPrefixes'], + ['serialize_all_object', false, 'getSerializeAllObjects', 'setSerializeAllObjects'], + ['curl_method', 'sync', 'getCurlMethod', 'setCurlMethod'], + ['curl_path', 'curl', 'getCurlPath', 'setCurlPath'], + ['curl_ipv4', true, 'getCurlIpv4', 'setCurlIpv4'], + ['curl_ssl_version', CURL_SSLVERSION_DEFAULT, 'getCurlSslVersion', 'setCurlSslVersion'], + ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], + ['install_default_breadcrumb_handlers', false, 'shouldInstallDefaultBreadcrumbHandlers', 'setInstallDefaultBreadcrumbHandlers'], + ['install_shutdown_handler', false, 'shouldInstallShutdownHandler', 'setInstallShutdownHandler'], + ['mb_detect_order', null, 'getMbDetectOrder', 'setMbDetectOrder'], + ['auto_log_stacks', false, 'getAutoLogStacks', 'setAutoLogStacks'], + ['context_lines', 3, 'getContextLines', 'setContextLines'], + ['current_environment', 'foo', 'getCurrentEnvironment', 'setCurrentEnvironment'], + ['environments', ['foo', 'bar'], 'getEnvironments', 'setEnvironments'], + ['excluded_loggers', ['bar', 'foo'], 'getExcludedLoggers', 'setExcludedLoggers'], + ['excluded_exceptions', ['foo', 'bar', 'baz'], 'getExcludedExceptions', 'setExcludedExceptions'], + ['excluded_app_paths', ['foo', 'bar'], 'getExcludedProjectPaths', 'setExcludedProjectPaths'], + ['project_root', 'baz', 'getProjectRoot', 'setProjectRoot'], + ['logger', 'foo', 'getLogger', 'setLogger'], + ['open_timeout', 2, 'getOpenTimeout', 'setOpenTimeout'], + ['timeout', 3, 'getTimeout', 'setTimeout'], + ['proxy', 'tcp://localhost:8125', 'getProxy', 'setProxy'], + ['release', 'dev', 'getRelease', 'setRelease'], + ['server_name', 'foo', 'getServerName', 'setServerName'], + ['ssl', [], 'getSslOptions', 'setSslOptions'], + ['ssl_verification', false, 'isSslVerificationEnabled', 'setSslVerificationEnabled'], + ['ssl_ca_file', 'path/to/file', 'getSslCaFile', 'setSslCaFile'], + ['tags', ['foo', 'bar'], 'getTags', 'setTags'], + ['error_types', 0, 'getErrorTypes', 'setErrorTypes'], + ['processors', [SanitizeDataProcessor::class, RemoveCookiesProcessor::class, RemoveHttpBodyProcessor::class, SanitizeHttpHeadersProcessor::class], 'getProcessors', 'setProcessors'], + ['processors_options', ['foo' => 'bar'], 'getProcessorsOptions', 'setProcessorsOptions'], + ]; + } + + /** + * @dataProvider serverOptionDataProvider + */ + public function testServerOption($dsn, $options) + { + $configuration = new Configuration(['server' => $dsn]); + + $this->assertEquals($options['project_id'], $configuration->getProjectId()); + $this->assertEquals($options['public_key'], $configuration->getPublicKey()); + $this->assertEquals($options['secret_key'], $configuration->getSecretKey()); + $this->assertEquals($options['server'], $configuration->getServer()); + } + + public function serverOptionDataProvider() + { + return [ + [ + 'http://public:secret@example.com/1', + [ + 'project_id' => 1, + 'public_key' => 'public', + 'secret_key' => 'secret', + 'server' => 'http://example.com', + ], + ], + [ + 'http://public:secret@example.com:80/1', + [ + 'project_id' => 1, + 'public_key' => 'public', + 'secret_key' => 'secret', + 'server' => 'http://example.com', + ], + ], + [ + 'https://public:secret@example.com/1', + [ + 'project_id' => 1, + 'public_key' => 'public', + 'secret_key' => 'secret', + 'server' => 'https://example.com', + ], + ], + [ + 'https://public:secret@example.com:443/1', + [ + 'project_id' => 1, + 'public_key' => 'public', + 'secret_key' => 'secret', + 'server' => 'https://example.com', + ], + ], + [ + 'http://public:secret@example.com/sentry/1', + [ + 'project_id' => 1, + 'public_key' => 'public', + 'secret_key' => 'secret', + 'server' => 'http://example.com/sentry', + ], + ], + [ + 'http://public:secret@example.com:3000/sentry/1', + [ + 'project_id' => 1, + 'public_key' => 'public', + 'secret_key' => 'secret', + 'server' => 'http://example.com:3000/sentry', + ], + ], + ]; + } + + /** + * @dataProvider invalidServerOptionDataProvider + * + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessageRegExp /^The option "server" with value "(.*)" is invalid.$/ + */ + public function testServerOptionsWithInvalidServer($dsn) + { + new Configuration(['server' => $dsn]); + } + + public function invalidServerOptionDataProvider() + { + return [ + ['http://public:secret@/1'], + ['http://public:secret@example.com'], + ['http://:secret@example.com/1'], + ['http://public@example.com/1'], + ['tcp://public:secret@example.com/1'], + ]; + } + + public function testShouldCapture() + { + $configuration = new Configuration(); + + $this->assertTrue($configuration->shouldCapture()); + + $configuration->setCurrentEnvironment('foo'); + $configuration->setEnvironments(['bar']); + + $this->assertFalse($configuration->shouldCapture()); + + $configuration->setCurrentEnvironment('foo'); + $configuration->setEnvironments(['foo']); + + $this->assertTrue($configuration->shouldCapture()); + + $configuration->setEnvironments([]); + + $this->assertTrue($configuration->shouldCapture()); + + $configuration->setShouldCapture(function ($value) { + return false; + }); + + $this->assertTrue($configuration->shouldCapture()); + + $data = 'foo'; + + $this->assertFalse($configuration->shouldCapture($data)); + } +} diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index d808e00e9..bd4bbdf2b 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -10,6 +10,8 @@ namespace Raven\Tests; +use Raven\Configuration; + class DummyIntegration_Raven_Client extends \Raven\Client { private $__sent_events = []; @@ -20,7 +22,7 @@ public function getSentEvents() } public function send(&$data) { - if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, [&$data]) === false) { + if (false === $this->config->shouldCapture($data)) { // if send_callback returns falsely, end native send return; } @@ -53,7 +55,9 @@ private function create_chained_exception() public function testCaptureSimpleError() { - $client = new DummyIntegration_Raven_Client('https://public:secret@example.com/1'); + $client = new DummyIntegration_Raven_Client(new Configuration([ + 'server' => 'https://public:secret@example.com/1', + ])); @mkdir('/no/way'); diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index 195f7e3e4..dc8cceb59 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -11,6 +11,8 @@ namespace Raven\Tests; +use Raven\Client; +use Raven\Configuration; use Raven\Processor\SanitizeDataProcessor; class SanitizeDataProcessorTest extends \PHPUnit_Framework_TestCase @@ -36,7 +38,7 @@ public function testDoesFilterHttpData() ] ]; - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $processor = new SanitizeDataProcessor($client); $processor->process($data); @@ -63,7 +65,7 @@ public function testDoesFilterSessionId() ] ]; - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $processor = new SanitizeDataProcessor($client); $processor->process($data); @@ -79,7 +81,7 @@ public function testDoesFilterCreditCard() ], ]; - $client = new Dummy_Raven_Client(); + $client = new Client(new Configuration()); $processor = new SanitizeDataProcessor($client); $processor->process($data); @@ -88,8 +90,8 @@ public function testDoesFilterCreditCard() public function testSettingProcessorOptions() { - $client = new Dummy_Raven_Client(); - $processor = new SanitizeDataProcessor($client); + $client = new Client(new Configuration()); + $processor = new SanitizeDataProcessor($client); $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); @@ -107,18 +109,13 @@ public function testSettingProcessorOptions() /** * @dataProvider overrideDataProvider - * - * @param $processorOptions - * @param $client_options - * @param $dsn */ - public function testOverrideOptions($processorOptions, $client_options, $dsn) + public function testOverrideOptions($processorOptions, $clientOptions) { - $client = new Dummy_Raven_Client($dsn, $client_options); - /** - * @var SanitizeDataProcessor $processor - */ - $processor = $client->processors[0]; + $client = new Client(new Configuration($clientOptions)); + + /** @var SanitizeDataProcessor $processor */ + $processor = $this->getObjectAttribute($client, 'processors')[0]; $this->assertInstanceOf(SanitizeDataProcessor::class, $processor); $this->assertEquals($processor->getFieldsRe(), $processorOptions[SanitizeDataProcessor::class]['fields_re'], 'overwrote fields'); @@ -128,24 +125,20 @@ public function testOverrideOptions($processorOptions, $client_options, $dsn) /** * @depends testOverrideOptions * @dataProvider overrideDataProvider - * - * @param $processorOptions - * @param $client_options - * @param $dsn */ - public function testOverridenSanitize($processorOptions, $client_options, $dsn) + public function testOverridenSanitize($processorOptions, $clientOptions) { $data = [ 'request' => [ 'data' => [ - 'foo' => 'bar', - 'password' => 'hello', - 'the_secret' => 'hello', - 'a_password_here' => 'hello', - 'mypasswd' => 'hello', - 'api_token' => 'nioenio3nrio3jfny89nby9bhr#RML#R', - 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', - 'card_number' => [ + 'foo' => 'bar', + 'password' => 'hello', + 'the_secret' => 'hello', + 'a_password_here' => 'hello', + 'mypasswd' => 'hello', + 'api_token' => 'nioenio3nrio3jfny89nby9bhr#RML#R', + 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', + 'card_number' => [ '1111111111111111', '2222', ] @@ -153,11 +146,10 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) ] ]; - $client = new Dummy_Raven_Client($dsn, $client_options); - /** - * @var SanitizeDataProcessor $processor - */ - $processor = $client->processors[0]; + $client = new Client(new Configuration($clientOptions)); + + /** @var SanitizeDataProcessor $processor */ + $processor = $this->getObjectAttribute($client, 'processors')[0]; $this->assertInstanceOf(SanitizeDataProcessor::class, $processor); $this->assertEquals($processor->getFieldsRe(), $processorOptions[SanitizeDataProcessor::class]['fields_re'], 'overwrote fields'); @@ -166,6 +158,7 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $processor->process($data); $vars = $data['request']['data']; + $this->assertEquals($vars['foo'], 'bar', 'did not alter foo'); $this->assertEquals($vars['password'], 'hello', 'did not alter password'); $this->assertEquals($vars['the_secret'], 'hello', 'did not alter the_secret'); @@ -178,11 +171,6 @@ public function testOverridenSanitize($processorOptions, $client_options, $dsn) $this->assertEquals($vars['card_number']['1'], $vars['card_number']['1'], 'did not alter card_number[1]'); } - /** - * Provides data for testing overriding the processor options - * - * @return array - */ public static function overrideDataProvider() { $processorOptions = [ @@ -194,13 +182,11 @@ public static function overrideDataProvider() $client_options = [ 'processors' => [SanitizeDataProcessor::class], - 'processorOptions' => $processorOptions + 'processors_options' => $processorOptions ]; - $dsn = 'http://9aaa31f9a05b4e72aaa06aa8157a827a:9aa7aa82a9694a08a1a7589a2a035a9a@sentry.domain.tld/1'; - return [ - [$processorOptions, $client_options, $dsn] + [$processorOptions, $client_options] ]; } } diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php index 66fa08111..34f0507e0 100644 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -12,6 +12,7 @@ namespace Raven\Tests; use Raven\Client; +use Raven\Configuration; use Raven\Processor\SanitizeStacktraceProcessor; class SanitizeStacktraceProcessorTest extends \PHPUnit_Framework_TestCase @@ -28,7 +29,7 @@ class SanitizeStacktraceProcessorTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->client = new Client(); + $this->client = new Client(new Configuration()); $this->client->store_errors_for_bulk_send = true; $this->processor = new SanitizeStacktraceProcessor($this->client); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index a7bd9df51..18e73fdda 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -12,6 +12,7 @@ namespace Raven\Tests; use Raven\Client; +use Raven\Configuration; use Raven\Stacktrace; class StacktraceTest extends \PHPUnit_Framework_TestCase @@ -23,7 +24,7 @@ class StacktraceTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->client = new Client(); + $this->client = new Client(new Configuration()); } public function testGetFramesAndToArray() @@ -93,7 +94,7 @@ public function testAddFrameSerializesMethodArguments() public function testAddFrameStripsPath() { - $this->client->setPrefixes(['path/to/', 'path/to/app']); + $this->client->getConfig()->setPrefixes(['path/to/', 'path/to/app']); $stacktrace = new Stacktrace($this->client); @@ -112,8 +113,8 @@ public function testAddFrameStripsPath() public function testAddFrameMarksAsInApp() { - $this->client->setAppPath('path/to'); - $this->client->setExcludedAppPaths(['path/to/excluded/path']); + $this->client->getConfig()->setProjectRoot('path/to'); + $this->client->getConfig()->setExcludedProjectPaths(['path/to/excluded/path']); $stacktrace = new Stacktrace($this->client); From 35666c3c3f00db49703feac33f81ac312cf3546b Mon Sep 17 00:00:00 2001 From: Igor Santos Date: Thu, 15 Jun 2017 00:38:40 -0300 Subject: [PATCH 0303/1161] Adding the common X-Authorization to sanitized headers --- lib/Raven/Processor/SanitizeHttpHeadersProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php index 3e712d689..a2ecbded8 100644 --- a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php +++ b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -67,6 +67,6 @@ public function process(&$data) */ private function getDefaultHeaders() { - return ['Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN']; + return ['Authorization', 'Proxy-Authorization', 'X-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN']; } } From 1a3be1c71486a21c29842294a5acee77595c7c75 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 3 Jul 2017 21:03:45 +0200 Subject: [PATCH 0304/1161] Implement a network transport layer that uses Httplug --- .scrutinizer.yml | 2 +- .travis.yml | 10 - README.md | 40 +- UPGRADE-2.0.md | 18 +- bin/sentry | 9 +- composer.json | 28 +- lib/Raven/Client.php | 379 ++---- lib/Raven/ClientBuilder.php | 142 +- lib/Raven/ClientBuilderInterface.php | 52 +- lib/Raven/Configuration.php | 267 +--- lib/Raven/CurlHandler.php | 123 -- .../HttpClient/Authentication/SentryAuth.php | 58 + .../Encoding/Base64EncodingStream.php | 67 + lib/Raven/Util/JSON.php | 38 + phpunit.xml.dist | 12 +- tests/Breadcrumbs/ErrorHandlerTest.php | 7 +- tests/Breadcrumbs/MonologTest.php | 11 +- tests/ClientBuilderTest.php | 109 +- tests/ClientTest.php | 1205 +++++------------ tests/ConfigurationTest.php | 11 +- .../Authentication/SentryAuthTest.php | 51 + .../Encoding/Base64EncodingStreamTest.php | 71 + tests/IntegrationTest.php | 16 +- tests/Processor/SanitizeDataProcessorTest.php | 15 +- .../SanitizeStacktraceProcessorTest.php | 4 +- tests/StacktraceTest.php | 4 +- tests/Util/Fixtures/JsonSerializableClass.php | 24 + tests/Util/Fixtures/SimpleClass.php | 21 + tests/Util/JSONTest.php | 56 + 29 files changed, 1265 insertions(+), 1585 deletions(-) delete mode 100644 lib/Raven/CurlHandler.php create mode 100644 lib/Raven/HttpClient/Authentication/SentryAuth.php create mode 100644 lib/Raven/HttpClient/Encoding/Base64EncodingStream.php create mode 100644 lib/Raven/Util/JSON.php create mode 100644 tests/HttpClient/Authentication/SentryAuthTest.php create mode 100644 tests/HttpClient/Encoding/Base64EncodingStreamTest.php create mode 100644 tests/Util/Fixtures/JsonSerializableClass.php create mode 100644 tests/Util/Fixtures/SimpleClass.php create mode 100644 tests/Util/JSONTest.php diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 714636de8..1a7fcfcf5 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,7 +5,7 @@ tools: php_code_coverage: true external_code_coverage: timeout: 2400 # There can be another pull request in progress - runs: 4 # PHP 5.5 + PHP 5.6 + PHP 7.0 + PHP 7.1 + runs: 3 # PHP 5.6 + PHP 7.0 + PHP 7.1 build: environment: diff --git a/.travis.yml b/.travis.yml index d4f129c71..bde000397 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 5.5 - 5.6 - 7.0 - 7.1 @@ -9,13 +8,8 @@ php: matrix: allow_failures: - - php: hhvm-3.12 - php: nightly fast_finish: true - include: - - php: hhvm-3.12 - env: REMOVE_XDEBUG="0" HHVM="1" - dist: trusty exclude: - php: nightly env: REMOVE_XDEBUG="1" @@ -24,14 +18,11 @@ env: - REMOVE_XDEBUG="0" - REMOVE_XDEBUG="1" -sudo: required - cache: directories: - $HOME/.composer/cache before_install: - - sudo apt-get install --yes net-tools - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi - composer self-update @@ -43,7 +34,6 @@ script: after_script: - wget https://scrutinizer-ci.com/ocular.phar - - if [ $(phpenv version-name) = "5.5" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi - if [ $(phpenv version-name) = "5.6" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi - if [ $(phpenv version-name) = "7.0" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi - if [ $(phpenv version-name) = "7.1" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi diff --git a/README.md b/README.md index 25d895972..fa59d3543 100644 --- a/README.md +++ b/README.md @@ -25,19 +25,47 @@ more about [automatic PHP error reporting with Sentry](https://sentry.io/for/php - Send customized diagnostic data - Process and sanitize data before sending it over the network +## Install + +To install the SDK you will need to be using [Composer]([https://getcomposer.org/) +in your project. To install it please see the [docs](https://getcomposer.org/download/). + +Sentry PHP is not tied to any specific library that sends HTTP messages. Instead, +it uses [Httplug](https://github.com/php-http/httplug) to let users choose whichever +PSR-7 implementation and HTTP client they want to use. + +If you just want to get started quickly you should run the following command: + +```bash +php composer.phar require sentry/sentry php-http/curl-client guzzlehttp/psr7 +``` + +This will install the library itself along with an HTTP client adapter that uses +cURL as transport method (provided by Httplug) and a PSR-7 implementation +(provided by Guzzle). You do not have to use those packages if you do not want to. +The SDK does not care about which transport method you want to use because it's +an implementation detail of your application. You may use any package that provides +[`php-http/async-client-implementation`](https://packagist.org/providers/php-http/async-client-implementation) +and [`http-message-implementation`](https://packagist.org/providers/psr/http-message-implementation). + ## Usage ```php -// Instantiate a new client with a compatible DSN and install built-in -// handlers -$client = (new Raven_Client('http://public:secret@example.com/1'))->install(); +namespace XXX; + +use Raven\ClientBuilder; + +require 'vendor/autoload.php'; + +// Instantiate the SDK with your DSN +$client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1'])->getClient(); // Capture an exception -$event_id = $client->captureException($ex); +$eventId = $client->captureException(new \RuntimeException('Hello World!')); // Give the user feedback -echo "Sorry, there was an error!"; -echo "Your reference ID is " . $event_id; +echo 'Sorry, there was an error!'; +echo 'Your reference ID is ' . $eventId; ``` For more information, see our [documentation](https://docs.getsentry.com/hosted/clients/php/). diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index d86e475ef..7dcc892ec 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -2,8 +2,6 @@ ### Client options -- The `verify_ssl` option has been renamed to `ssl_verification`. -- The `ca_cert` option has been renamed to `ssl_ca_file`. - The `environment` option has been renamed to `current_environment`. - The `http_proxy` option has been renamed to `proxy`. - The `processorOptions` option has been renamed to `processors_options`. @@ -13,6 +11,16 @@ - The `project` option has been removed. - The `extra_data` option has been removed in favour of setting additional data directly in the context. +- The `curl_method` option has been removed in favour of leaving to the user the + choice of setting an HTTP client supporting syncronous, asyncronous or both + transport methods. +- The `curl_path` option has been removed. +- The `curl_ipv4` option has been removed. +- The `curl_ssl_version` option has been removed. +- The `verify_ssl` option has been removed. +- The `ca_cert` option has been removed. +- The `http_client_options` has been added to set the options that applies to the + HTTP client chosen by the user as underlying transport method. - The `open_timeout` option has been added to set the maximum number of seconds to wait for the server connection to open. - The `excluded_loggers` option has been added to set the list of logger 'progname's @@ -42,7 +50,7 @@ After: ```php - public function __construct(Configuration $config) + public function __construct(Configuration $config, HttpAsyncClient $httpClient, RequestFactory $requestFactory) { // ... } @@ -214,7 +222,9 @@ After: ```php - $client = new Client(new Configuration([...])); + $httpClient = new HttpClient(); // This can be any Httplug client adapter + $requestFactory = new RequestFactory(); // This can be any Httplug PSR-7 request factory + $client = new Client(new Configuration([...], $httpClient, $requestFactory)); // or diff --git a/bin/sentry b/bin/sentry index f11c9592e..a15a4cde1 100755 --- a/bin/sentry +++ b/bin/sentry @@ -22,13 +22,10 @@ function cmd_test($dsn) exit('ERROR: Missing DSN value'); } - $config = new \Raven\Configuration([ + $client = \Raven\ClientBuilder::create([ 'server' => $dsn, - 'curl_method' => 'sync', - 'project_root' => realpath(__DIR__ . '/..'), - ]); - - $client = new \Raven\Client($config); + 'project_root' => realpath(__DIR__ . '/../'), + ])->getClient(); echo "Sending a test event:\n"; diff --git a/composer.json b/composer.json index 29f108bae..4a8c4a25a 100644 --- a/composer.json +++ b/composer.json @@ -11,22 +11,25 @@ "email": "dcramer@gmail.com" } ], - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.1", - "phpunit/phpunit": "^4.8 || ^5.0", - "ext-openssl": "*", - "nokitakaze/testhttpserver": "^0.1.0", - "monolog/monolog": "~1.0" - }, "require": { - "php": ">=5.5", - "ext-hash": "*", + "php": "^5.6|^7.0", "ext-json": "*", "ext-mbstring": "*", - "ext-curl": "*", - "composer/ca-bundle": "~1.0", + "php-http/async-client-implementation": "~1.0", + "php-http/discovery": "~1.2", + "php-http/httplug": "~1.1", + "psr/http-message-implementation": "~1.0", "symfony/options-resolver": "~2.7|~3.0" }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.1", + "monolog/monolog": "~1.0", + "php-http/curl-client": "~1.7", + "php-http/mock-client": "~1.0", + "phpunit/phpunit": "^4.8 || ^5.0", + "symfony/phpunit-bridge": "~2.7|~3.0", + "zendframework/zend-diactoros": "~1.4" + }, "suggest": { "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" }, @@ -63,6 +66,9 @@ "vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run" ] }, + "config": { + "sort-packages": true + }, "extra": { "branch-alias": { "dev-master": "2.0.x-dev" diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 20a9f682f..3c4c8d5c9 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -10,7 +10,13 @@ namespace Raven; -use Raven\CurlHandler; +use Http\Client\HttpAsyncClient; +use Http\Message\Encoding\CompressStream; +use Http\Message\RequestFactory; +use Http\Promise\Promise; +use Psr\Http\Message\ResponseInterface; +use Raven\HttpClient\Encoding\Base64EncodingStream; +use Raven\Util\JSON; /** * Raven PHP Client @@ -37,6 +43,11 @@ class Client */ const MESSAGE_LIMIT = 1024; + /** + * This constant defines the client's user-agent string + */ + const USER_AGENT = 'sentry-php/' . self::VERSION; + /** * @var Breadcrumbs The breadcrumbs */ @@ -93,14 +104,6 @@ class Client */ public $_pending_events = []; - /** - * @var CurlHandler - */ - protected $_curl_handler; - /** - * @var resource|null - */ - protected $_curl_instance; /** * @var bool */ @@ -111,14 +114,33 @@ class Client */ protected $config; + /** + * @var HttpAsyncClient The HTTP client + */ + private $httpClient; + + /** + * @var RequestFactory The PSR-7 request factory + */ + private $requestFactory; + + /** + * @var Promise[] The list of pending requests + */ + private $pendingRequests = []; + /** * Constructor. * - * @param Configuration $config The client configuration + * @param Configuration $config The client configuration + * @param HttpAsyncClient $httpClient The HTTP client + * @param RequestFactory $requestFactory The PSR-7 request factory */ - public function __construct(Configuration $config) + public function __construct(Configuration $config, HttpAsyncClient $httpClient, RequestFactory $requestFactory) { $this->config = $config; + $this->httpClient = $httpClient; + $this->requestFactory = $requestFactory; $this->context = new Context(); $this->breadcrumbs = new Breadcrumbs(); $this->transaction = new TransactionStack(); @@ -134,10 +156,6 @@ public function __construct(Configuration $config) $this->setAllObjectSerialize(true); } - if ('async' === $this->config->getCurlMethod()) { - $this->_curl_handler = new CurlHandler($this->get_curl_options()); - } - if ($this->config->shouldInstallDefaultBreadcrumbHandlers()) { $this->registerDefaultBreadcrumbHandlers(); } @@ -148,30 +166,19 @@ public function __construct(Configuration $config) } /** - * Gets the client configuration. - * - * @return Configuration + * Destructor. */ - public function getConfig() - { - return $this->config; - } - public function __destruct() { - // Force close curl resource - $this->close_curl_resource(); - $this->force_send_async_curl_events(); + $this->sendUnsentErrors(); } /** - * Destruct all objects contain link to this object - * - * This method can not delete shutdown handler + * {@inheritdoc} */ - public function close_all_children_link() + public function getConfig() { - $this->processors = []; + return $this->config; } /** @@ -209,11 +216,6 @@ public function install() return $this; } - public static function getUserAgent() - { - return 'sentry-php/' . self::VERSION; - } - /** * Sets the \Raven\Processor sub-classes to be used when data is processed before being * sent to Sentry. @@ -691,53 +693,31 @@ public function sendUnsentErrors() foreach ($this->_pending_events as $data) { $this->send($data); } + $this->_pending_events = []; + if ($this->store_errors_for_bulk_send) { //in case an error occurs after this is called, on shutdown, send any new errors. $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED'); } - } - /** - * @param array $data - * @return string|bool - */ - public function encode(&$data) - { - $message = json_encode($data); - if ($message === false) { - if (function_exists('json_last_error_msg')) { - $this->_lasterror = json_last_error_msg(); - } else { - // @codeCoverageIgnoreStart - $this->_lasterror = json_last_error(); - // @codeCoverageIgnoreEnd - } - return false; - } - - if (function_exists("gzcompress")) { - $message = gzcompress($message); + foreach ($this->pendingRequests as $pendingRequest) { + $pendingRequest->wait(); } - - // PHP's builtin curl_* function are happy without this, but the exec method requires it - $message = base64_encode($message); - - return $message; } /** - * Wrapper to handle encoding and sending data to the Sentry API server. + * Sends the given event to the Sentry server. * - * @param array $data Associative array of data to log + * @param array $data Associative array of data to log */ public function send(&$data) { - if (false === $this->config->shouldCapture($data) || !$this->config->getServer()) { + if (!$this->config->shouldCapture($data) || !$this->config->getServer()) { return; } - if ($this->getConfig()->getTransport()) { + if ($this->config->getTransport()) { call_user_func($this->getConfig()->getTransport(), $this, $data); return; } @@ -747,233 +727,40 @@ public function send(&$data) return; } - $message = $this->encode($data); - - $headers = [ - 'User-Agent' => static::getUserAgent(), - 'X-Sentry-Auth' => $this->getAuthHeader(), - 'Content-Type' => 'application/octet-stream' - ]; - - $config = $this->getConfig(); - $server = sprintf('%s/api/%d/store/', $config->getServer(), $config->getProjectId()); - - $this->send_remote($server, $message, $headers); - } - - /** - * Send data to Sentry - * - * @param string $url Full URL to Sentry - * @param array|string $data Associative array of data to log - * @param array $headers Associative array of headers - */ - protected function send_remote($url, $data, $headers = []) - { - $parts = parse_url($url); - $parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null); - $this->send_http($url, $data, $headers); - } - - /** - * @return array - * @doc http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006 - * @doc https://3v4l.org/4I7F5 - */ - protected function get_curl_options() - { - $options = [ - CURLOPT_VERBOSE => false, - CURLOPT_SSL_VERIFYHOST => 2, - CURLOPT_SSL_VERIFYPEER => $this->config->isSslVerificationEnabled(), - CURLOPT_CAINFO => $this->config->getSslCaFile(), - CURLOPT_USERAGENT => 'sentry-php/' . self::VERSION, - ]; - if (!empty($this->config->getProxy())) { - $options[CURLOPT_PROXY] = $this->config->getProxy(); - } - if (null !== $this->config->getCurlSslVersion()) { - $options[CURLOPT_SSLVERSION] = $this->config->getCurlSslVersion(); - } - if ($this->config->getCurlIpv4()) { - $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; - } - if (defined('CURLOPT_TIMEOUT_MS')) { - // MS is available in curl >= 7.16.2 - $timeout = max(1, ceil(1000 * $this->config->getTimeout())); - - // None of the versions of PHP contains this constant - if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) { - //see stackoverflow link in the phpdoc - define('CURLOPT_CONNECTTIMEOUT_MS', 156); - } - - $options[CURLOPT_CONNECTTIMEOUT_MS] = $timeout; - $options[CURLOPT_TIMEOUT_MS] = $timeout; - } else { - // fall back to the lower-precision timeout. - $timeout = max(1, ceil($this->config->getTimeout())); - $options[CURLOPT_CONNECTTIMEOUT] = $timeout; - $options[CURLOPT_TIMEOUT] = $timeout; - } - return $options; - } - - /** - * Send the message over http to the sentry url given - * - * @param string $url URL of the Sentry instance to log to - * @param array|string $data Associative array of data to log - * @param array $headers Associative array of headers - */ - protected function send_http($url, $data, $headers = []) - { - if ($this->config->getCurlMethod() == 'async') { - $this->_curl_handler->enqueue($url, $data, $headers); - } elseif ($this->config->getCurlMethod() == 'exec') { - $this->send_http_asynchronous_curl_exec($url, $data, $headers); - } else { - $this->send_http_synchronous($url, $data, $headers); - } - } - - /** - * @param string $url - * @param string $data - * @param array $headers - * @return string - * - * This command line ensures exec returns immediately while curl runs in the background - */ - protected function buildCurlCommand($url, $data, $headers) - { - $post_fields = ''; - foreach ($headers as $key => $value) { - $post_fields .= ' -H '.escapeshellarg($key.': '.$value); - } - $cmd = sprintf( - '%s -X POST%s -d %s %s -m %d %s%s> /dev/null 2>&1 &', - escapeshellcmd($this->config->getCurlPath()), $post_fields, - escapeshellarg($data), escapeshellarg($url), $this->config->getTimeout(), - !$this->config->isSslVerificationEnabled() ? '-k ' : '', - !empty($this->config->getSslCaFile()) ? '--cacert '.escapeshellarg($this->config->getSslCaFile()).' ' : '' + $request = $this->requestFactory->createRequest( + 'POST', + sprintf('api/%d/store/', $this->getConfig()->getProjectId()), + ['Content-Type' => $this->isEncodingCompressed() ? 'application/octet-stream' : 'application/json'], + JSON::encode($data) ); - return $cmd; - } - - /** - * Send the cURL to Sentry asynchronously. No errors will be returned from cURL - * - * @param string $url URL of the Sentry instance to log to - * @param array|string $data Associative array of data to log - * @param array $headers Associative array of headers - * @return bool - */ - protected function send_http_asynchronous_curl_exec($url, $data, $headers) - { - exec($this->buildCurlCommand($url, $data, $headers)); - return true; // The exec method is just fire and forget, so just assume it always works - } - - /** - * Send a blocking cURL to Sentry and check for errors from cURL - * - * @param string $url URL of the Sentry instance to log to - * @param array|string $data Associative array of data to log - * @param array $headers Associative array of headers - * @return bool - */ - protected function send_http_synchronous($url, $data, $headers) - { - $new_headers = []; - foreach ($headers as $key => $value) { - array_push($new_headers, $key .': '. $value); + if ($this->isEncodingCompressed()) { + $request = $request->withBody( + new Base64EncodingStream( + new CompressStream($request->getBody()) + ) + ); } - // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216) - $new_headers[] = 'Expect:'; - if (is_null($this->_curl_instance)) { - $this->_curl_instance = curl_init($url); - } - curl_setopt($this->_curl_instance, CURLOPT_POST, 1); - curl_setopt($this->_curl_instance, CURLOPT_HTTPHEADER, $new_headers); - curl_setopt($this->_curl_instance, CURLOPT_POSTFIELDS, $data); - curl_setopt($this->_curl_instance, CURLOPT_RETURNTRANSFER, true); - - $options = $this->get_curl_options(); - if (isset($options[CURLOPT_CAINFO])) { - $ca_cert = $options[CURLOPT_CAINFO]; - unset($options[CURLOPT_CAINFO]); - } else { - $ca_cert = null; - } - curl_setopt_array($this->_curl_instance, $options); + $promise = $this->httpClient->sendAsyncRequest($request); - $buffer = curl_exec($this->_curl_instance); + // This function is defined in-line so it doesn't show up for + // type-hinting on classes that implement this trait. + $cleanupPromiseCallback = function (ResponseInterface $response) use ($promise) { + $index = array_search($promise, $this->pendingRequests, true); - $errno = curl_errno($this->_curl_instance); - // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE - if (in_array($errno, [CURLE_SSL_CACERT, 77]) && !is_null($ca_cert)) { - curl_setopt($this->_curl_instance, CURLOPT_CAINFO, $ca_cert); - $buffer = curl_exec($this->_curl_instance); - $errno = curl_errno($this->_curl_instance); - } - if ($errno != 0) { - $this->_lasterror = curl_error($this->_curl_instance); - $this->_last_sentry_error = null; - return false; - } - - $code = curl_getinfo($this->_curl_instance, CURLINFO_HTTP_CODE); - $success = ($code == 200); - if ($success) { - $this->_lasterror = null; - $this->_last_sentry_error = null; - } else { - // It'd be nice just to raise an exception here, but it's not very PHP-like - $this->_lasterror = curl_error($this->_curl_instance); - $this->_last_sentry_error = @json_decode($buffer); - } - - return $success; - } - - /** - * Generate a Sentry authorization header string - * - * @param string $timestamp Timestamp when the event occurred - * @param string $client HTTP client name (not \Raven\Client object) - * @param string $api_key Sentry API key - * @param string $secret_key Sentry API key - * @return string - */ - protected static function get_auth_header($timestamp, $client, $api_key, $secret_key) - { - $header = [ - sprintf('sentry_timestamp=%F', $timestamp), - "sentry_client={$client}", - sprintf('sentry_version=%s', self::PROTOCOL), - ]; - - if ($api_key) { - $header[] = "sentry_key={$api_key}"; - } + if (false === $index) { + return $response; + } - if ($secret_key) { - $header[] = "sentry_secret={$secret_key}"; - } + unset($this->pendingRequests[$index]); + return $response; + }; - return sprintf('Sentry %s', implode(', ', $header)); - } + $promise->then($cleanupPromiseCallback, $cleanupPromiseCallback); - public function getAuthHeader() - { - $timestamp = microtime(true); - return $this->get_auth_header( - $timestamp, static::getUserAgent(), $this->config->getPublicKey(), $this->config->getSecretKey() - ); + $this->pendingRequests[] = $promise; } /** @@ -1126,20 +913,12 @@ public function set_user_data($id, $email = null, $data = []) $this->user_context(array_merge($user, $data)); } - public function force_send_async_curl_events() - { - if (!is_null($this->_curl_handler)) { - $this->_curl_handler->join(); - } - } - public function onShutdown() { if (!defined('RAVEN_CLIENT_END_REACHED')) { define('RAVEN_CLIENT_END_REACHED', true); } $this->sendUnsentErrors(); - $this->force_send_async_curl_events(); } /** @@ -1205,17 +984,19 @@ public function getShutdownFunctionHasBeenSet() return $this->_shutdown_function_has_been_set; } - public function close_curl_resource() - { - if (!is_null($this->_curl_instance)) { - curl_close($this->_curl_instance); - $this->_curl_instance = null; - } - } - public function setAllObjectSerialize($value) { $this->serializer->setAllObjectSerialize($value); $this->reprSerializer->setAllObjectSerialize($value); } + + /** + * Checks whether the encoding is compressed. + * + * @return bool + */ + private function isEncodingCompressed() + { + return 'gzip' === $this->config->getEncoding(); + } } diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index 77d382bc2..a408ae1f6 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -11,25 +11,34 @@ namespace Raven; +use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\AuthenticationPlugin; +use Http\Client\Common\Plugin\BaseUriPlugin; +use Http\Client\Common\Plugin\ErrorPlugin; +use Http\Client\Common\Plugin\HeaderSetPlugin; +use Http\Client\Common\Plugin\RetryPlugin; +use Http\Client\Common\PluginClient; +use Http\Client\HttpAsyncClient; +use Http\Discovery\HttpAsyncClientDiscovery; +use Http\Discovery\MessageFactoryDiscovery; +use Http\Discovery\UriFactoryDiscovery; +use Http\Message\MessageFactory; +use Http\Message\UriFactory; +use Raven\HttpClient\Authentication\SentryAuth; + /** * The default implementation of {@link ClientBuilderInterface}. * * @author Stefano Arlandini * + * @method int getSendAttempts() + * @method setSendAttempts(int $attemptsCount) * @method bool isTrustXForwardedProto() * @method setIsTrustXForwardedProto(bool $value) * @method string[] getPrefixes() * @method setPrefixes(array $prefixes) * @method bool getSerializeAllObjects() * @method setSerializeAllObjects(bool $serializeAllObjects) - * @method string getCurlMethod() - * @method setCurlMethod(string $method) - * @method string getCurlPath() - * @method setCurlPath(string $path) - * @method bool getCurlIpv4() - * @method setCurlIpv4(bool $enable) - * @method string getCurlSslVersion() - * @method setCurlSslVersion(string $version) * @method float getSampleRate() * @method setSampleRate(float $sampleRate) * @method bool shouldInstallDefaultBreadcrumbHandlers() @@ -56,22 +65,13 @@ * @method setProjectRoot(string $path) * @method string getLogger() * @method setLogger(string $logger) - * @method int getOpenTimeout() - * @method setOpenTimeout(int $timeout) - * @method int getTimeout() - * @method setTimeout(int $timeout) * @method string getProxy() * @method setProxy(string $proxy) * @method string getRelease() * @method setRelease(string $release) + * @method string getServer() * @method string getServerName() * @method setServerName(string $serverName) - * @method array getSslOptions() - * @method setSslOptions(array $options) - * @method bool isSslVerificationEnabled() - * @method setSslVerificationEnabled(bool $enable) - * @method string getSslCaFile() - * @method setSslCaFile(string $path) * @method string[] getTags() * @method setTags(string[] $tags) * @method string[] getProcessors() @@ -79,12 +79,32 @@ * @method array getProcessorsOptions() * @method setProcessorsOptions(array $options) */ -class ClientBuilder implements ClientBuilderInterface +final class ClientBuilder implements ClientBuilderInterface { /** * @var Configuration The client configuration */ - protected $configuration; + private $configuration; + + /** + * @var UriFactory The PSR-7 URI factory + */ + private $uriFactory; + + /** + * @var MessageFactory The PSR-7 message factory + */ + private $messageFactory; + + /** + * @var HttpAsyncClient The HTTP client + */ + private $httpClient; + + /** + * @var Plugin[] The list of Httplug plugins + */ + private $httpClientPlugins = []; /** * Class constructor. @@ -104,12 +124,73 @@ public static function create(array $options = []) return new static($options); } + /** + * {@inheritdoc} + */ + public function setUriFactory(UriFactory $uriFactory) + { + $this->uriFactory = $uriFactory; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setMessageFactory(MessageFactory $messageFactory) + { + $this->messageFactory = $messageFactory; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setHttpClient(HttpAsyncClient $httpClient) + { + $this->httpClient = $httpClient; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function addHttpClientPlugin(Plugin $plugin) + { + $this->httpClientPlugins[] = $plugin; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function removeHttpClientPlugin($className) + { + foreach ($this->httpClientPlugins as $index => $httpClientPlugin) { + if (!$httpClientPlugin instanceof $className) { + continue; + } + + unset($this->httpClientPlugins[$index]); + } + + return $this; + } + /** * {@inheritdoc} */ public function getClient() { - return new Client($this->configuration); + $this->messageFactory = $this->messageFactory ?: MessageFactoryDiscovery::find(); + $this->uriFactory = $this->uriFactory ?: UriFactoryDiscovery::find(); + $this->messageFactory = $this->messageFactory ?: MessageFactoryDiscovery::find(); + $this->httpClient = $this->httpClient ?: HttpAsyncClientDiscovery::find(); + + return new Client($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); } /** @@ -132,4 +213,23 @@ public function __call($name, $arguments) return $this; } + + /** + * Creates a new instance of the HTTP client. + * + * @return HttpAsyncClient + */ + private function createHttpClientInstance() + { + if (null !== $this->configuration->getServer()) { + $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->configuration->getServer()))); + } + + $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => Client::USER_AGENT])); + $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuth($this->configuration))); + $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->configuration->getSendAttempts()])); + $this->addHttpClientPlugin(new ErrorPlugin()); + + return new PluginClient($this->httpClient, $this->httpClientPlugins); + } } diff --git a/lib/Raven/ClientBuilderInterface.php b/lib/Raven/ClientBuilderInterface.php index a477c8a45..bbe27eca4 100644 --- a/lib/Raven/ClientBuilderInterface.php +++ b/lib/Raven/ClientBuilderInterface.php @@ -11,10 +11,15 @@ namespace Raven; +use Http\Client\Common\Plugin; +use Http\Client\HttpAsyncClient; +use Http\Message\MessageFactory; +use Http\Message\UriFactory; + /** * A configurable builder for Client objects. * - * @author Stefano Arlandini + * @author Stefano Arlandini */ interface ClientBuilderInterface { @@ -27,6 +32,51 @@ interface ClientBuilderInterface */ public static function create(array $options = []); + /** + * Sets the factory to use to create URIs. + * + * @param UriFactory $uriFactory The factory + * + * @return $this + */ + public function setUriFactory(UriFactory $uriFactory); + + /** + * Sets the factory to use to create PSR-7 messages. + * + * @param MessageFactory $messageFactory The factory + * + * @return $this + */ + public function setMessageFactory(MessageFactory $messageFactory); + + /** + * Sets the HTTP client. + * + * @param HttpAsyncClient $httpClient The HTTP client + * + * @return $this + */ + public function setHttpClient(HttpAsyncClient $httpClient); + + /** + * Adds a new HTTP client plugin to the end of the plugins chain. + * + * @param Plugin $plugin The plugin instance + * + * @return $this + */ + public function addHttpClientPlugin(Plugin $plugin); + + /** + * Removes a HTTP client plugin by its fully qualified class name (FQCN). + * + * @param string $className The class name + * + * @return $this + */ + public function removeHttpClientPlugin($className); + /** * Gets the instance of the client built using the configured options. * diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 838d5f996..a69919d87 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -11,7 +11,6 @@ namespace Raven; -use Composer\CaBundle\CaBundle; use Raven\Processor\RemoveCookiesProcessor; use Raven\Processor\RemoveHttpBodyProcessor; use Raven\Processor\SanitizeDataProcessor; @@ -70,6 +69,28 @@ public function __construct(array $options = []) $this->options = $this->resolver->resolve($options); } + /** + * Gets the number of attempts to resend an event that failed to be sent. + * + * @return int + */ + public function getSendAttempts() + { + return $this->options['send_attempts']; + } + + /** + * Sets the number of attempts to resend an event that failed to be sent. + * + * @param int $attemptsCount The number of attempts + */ + public function setSendAttempts($attemptsCount) + { + $options = array_merge($this->options, ['send_attempts' => $attemptsCount]); + + $this->options = $this->resolver->resolve($options); + } + /** * Checks whether the X-FORWARDED-PROTO header should be trusted. * @@ -138,94 +159,6 @@ public function setSerializeAllObjects($serializeAllObjects) $this->options = $this->resolver->resolve($options); } - /** - * Gets the cURL method to use to send data. - * - * @return string - */ - public function getCurlMethod() - { - return $this->options['curl_method']; - } - - /** - * Sets the cURL method to use to send data. - * - * @param bool $method The cURL method - */ - public function setCurlMethod($method) - { - $options = array_merge($this->options, ['curl_method' => $method]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Gets the path to the cURL binary to be used with the "exec" curl method. - * - * @return string - */ - public function getCurlPath() - { - return $this->options['curl_path']; - } - - /** - * Sets the path to the cURL binary to be used with the "exec" curl method. - * - * @param string $path The path - */ - public function setCurlPath($path) - { - $options = array_merge($this->options, ['curl_path' => $path]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Gets whether cURL must resolve domains only with IPv4. - * - * @return bool - */ - public function getCurlIpv4() - { - return $this->options['curl_ipv4']; - } - - /** - * Sets whether cURL must resolve domains only with IPv4. - * - * @param bool $enable Whether only IPv4 domains should be resolved - */ - public function setCurlIpv4($enable) - { - $options = array_merge($this->options, ['curl_ipv4' => $enable]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Gets the version of SSL/TLS to attempt to use when using cURL. - * - * @return int - */ - public function getCurlSslVersion() - { - return $this->options['curl_ssl_version']; - } - - /** - * Sets the version of SSL/TLS to attempt to use when using cURL. - * - * @param int $version The protocol version (one of the `CURL_SSLVERSION_*` constants) - */ - public function setCurlSslVersion($version) - { - $options = array_merge($this->options, ['curl_ssl_version' => $version]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the sampling factor to apply to events. A value of 0 will deny * sending any events, and a value of 1 will send 100% of events. @@ -360,6 +293,28 @@ public function setContextLines($contextLines) $this->options = $this->resolver->resolve($options); } + /** + * Gets the encoding type for event bodies (GZIP or JSON). + * + * @return string + */ + public function getEncoding() + { + return $this->options['encoding']; + } + + /** + * Sets the encoding type for event bodies (GZIP or JSON). + * + * @param string $encoding The encoding type + */ + public function setEncoding($encoding) + { + $options = array_merge($this->options, ['encoding' => $encoding]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets the current environment. * @@ -574,52 +529,6 @@ public function setLogger($logger) $this->options = $this->resolver->resolve($options); } - /** - * Gets the maximum number of seconds to wait for the Sentry server connection - * to open. - * - * @return int - */ - public function getOpenTimeout() - { - return $this->options['open_timeout']; - } - - /** - * Sets the maximum number of seconds to wait for the Sentry server connection - * to open. - * - * @param array $timeout The timeout in seconds - */ - public function setOpenTimeout($timeout) - { - $options = array_merge($this->options, ['open_timeout' => $timeout]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Gets the maximum number of seconds to wait for the server to return data. - * - * @return int - */ - public function getTimeout() - { - return $this->options['timeout']; - } - - /** - * Sets the maximum number of seconds to wait for the server to return data. - * - * @param array $timeout The timeout in seconds - */ - public function setTimeout($timeout) - { - $options = array_merge($this->options, ['timeout' => $timeout]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the proxy information to pass to the transport adapter. * @@ -732,72 +641,6 @@ public function setShouldCapture(callable $callable = null) $this->options = $this->resolver->resolve($options); } - /** - * Gets the options that configure the SSL for cURL. - * - * @return array - */ - public function getSslOptions() - { - return $this->options['ssl']; - } - - /** - * Sets the options that configure the SSL for cURL. - * - * @param array $options The options - */ - public function setSslOptions(array $options) - { - $options = array_merge($this->options, ['ssl' => $options]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Gets whether the SSL certificate of the server should be verified. - * - * @return bool - */ - public function isSslVerificationEnabled() - { - return $this->options['ssl_verification']; - } - - /** - * Sets whether the SSL certificate of the server should be verified. - * - * @param bool $enable Whether the SSL certificate should be verified - */ - public function setSslVerificationEnabled($enable) - { - $options = array_merge($this->options, ['ssl_verification' => $enable]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Gets the path to the SSL certificate file. - * - * @return string - */ - public function getSslCaFile() - { - return $this->options['ssl_ca_file']; - } - - /** - * Sets the path to the SSL certificate file. - * - * @param string $path The path - */ - public function setSslCaFile($path) - { - $options = array_merge($this->options, ['ssl_ca_file' => $path]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets a list of default tags for events. * @@ -890,19 +733,17 @@ public function setProcessorsOptions(array $options) private function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ + 'send_attempts' => 6, 'trust_x_forwarded_proto' => false, 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'serialize_all_object' => false, - 'curl_method' => 'sync', - 'curl_path' => 'curl', - 'curl_ipv4' => true, - 'curl_ssl_version' => null, 'sample_rate' => 1, 'install_default_breadcrumb_handlers' => true, 'install_shutdown_handler' => true, 'mb_detect_order' => null, 'auto_log_stacks' => false, 'context_lines' => 3, + 'encoding' => 'gzip', 'current_environment' => 'default', 'environments' => [], 'excluded_loggers' => [], @@ -911,16 +752,11 @@ private function configureOptions(OptionsResolver $resolver) 'transport' => null, 'project_root' => null, 'logger' => 'php', - 'open_timeout' => 1, - 'timeout' => 2, 'proxy' => null, 'release' => null, 'server' => isset($_SERVER['SENTRY_DSN']) ? $_SERVER['SENTRY_DSN'] : null, 'server_name' => gethostname(), 'should_capture' => null, - 'ssl' => [], - 'ssl_verification' => true, - 'ssl_ca_file' => CaBundle::getSystemCaRootBundlePath(), 'tags' => [], 'error_types' => null, 'processors_options' => [], @@ -932,18 +768,17 @@ private function configureOptions(OptionsResolver $resolver) ], ]); + $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('trust_x_forwarded_proto', 'bool'); $resolver->setAllowedTypes('prefixes', 'array'); $resolver->setAllowedTypes('serialize_all_object', 'bool'); - $resolver->setAllowedTypes('curl_method', 'string'); - $resolver->setAllowedTypes('curl_path', 'string'); - $resolver->setAllowedTypes('curl_ssl_version', ['null', 'int']); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('install_default_breadcrumb_handlers', 'bool'); $resolver->setAllowedTypes('install_shutdown_handler', 'bool'); $resolver->setAllowedTypes('mb_detect_order', ['null', 'array']); $resolver->setAllowedTypes('auto_log_stacks', 'bool'); $resolver->setAllowedTypes('context_lines', 'int'); + $resolver->setAllowedTypes('encoding', 'string'); $resolver->setAllowedTypes('current_environment', 'string'); $resolver->setAllowedTypes('environments', 'array'); $resolver->setAllowedTypes('excluded_loggers', 'array'); @@ -952,21 +787,17 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('transport', ['null', 'callable']); $resolver->setAllowedTypes('project_root', ['null', 'string']); $resolver->setAllowedTypes('logger', 'string'); - $resolver->setAllowedTypes('open_timeout', 'int'); - $resolver->setAllowedTypes('timeout', 'int'); $resolver->setAllowedTypes('proxy', ['null', 'string']); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('server', ['null', 'string']); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('should_capture', ['null', 'callable']); - $resolver->setAllowedTypes('ssl', 'array'); - $resolver->setAllowedTypes('ssl_verification', 'bool'); - $resolver->setAllowedTypes('ssl_ca_file', ['null', 'string']); $resolver->setAllowedTypes('tags', 'array'); $resolver->setAllowedTypes('error_types', ['null', 'int']); $resolver->setAllowedTypes('processors_options', 'array'); $resolver->setAllowedTypes('processors', 'array'); + $resolver->setAllowedValues('encoding', ['gzip', 'json']); $resolver->setAllowedValues('server', function ($value) { if (null === $value) { return true; diff --git a/lib/Raven/CurlHandler.php b/lib/Raven/CurlHandler.php deleted file mode 100644 index 9368cf299..000000000 --- a/lib/Raven/CurlHandler.php +++ /dev/null @@ -1,123 +0,0 @@ -options = $options; - $this->multi_handle = curl_multi_init(); - $this->requests = []; - $this->join_timeout = 5; - - register_shutdown_function([$this, 'join']); - } - - public function __destruct() - { - $this->join(); - } - - public function enqueue($url, $data = null, $headers = []) - { - $ch = curl_init(); - - $new_headers = []; - foreach ($headers as $key => $value) { - array_push($new_headers, $key .': '. $value); - } - // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216) - $new_headers[] = 'Expect:'; - - curl_setopt($ch, CURLOPT_HTTPHEADER, $new_headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_URL, $url); - - curl_setopt_array($ch, $this->options); - - if (isset($data)) { - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); - } - - curl_multi_add_handle($this->multi_handle, $ch); - - $fd = (int)$ch; - $this->requests[$fd] = 1; - - $this->select(); - - return $fd; - } - - public function join($timeout = null) - { - if (count($this->requests) == 0) { - return; - } - if (!isset($timeout)) { - $timeout = $this->join_timeout; - } - $start = time(); - do { - $this->select(); - if (count($this->requests) === 0) { - break; - } - usleep(10000); - } while ($timeout !== 0 && time() - $start < $timeout); - } - - /** - * @doc http://php.net/manual/en/function.curl-multi-exec.php - */ - protected function select() - { - do { - $mrc = curl_multi_exec($this->multi_handle, $active); - } while ($mrc == CURLM_CALL_MULTI_PERFORM); - - while ($active && $mrc == CURLM_OK) { - if (curl_multi_select($this->multi_handle) !== -1) { - do { - $mrc = curl_multi_exec($this->multi_handle, $active); - } while ($mrc == CURLM_CALL_MULTI_PERFORM); - } else { - return; - } - } - - while ($info = curl_multi_info_read($this->multi_handle)) { - $ch = $info['handle']; - $fd = (int)$ch; - - curl_multi_remove_handle($this->multi_handle, $ch); - - if (!isset($this->requests[$fd])) { - return; - } - - unset($this->requests[$fd]); - } - } -} diff --git a/lib/Raven/HttpClient/Authentication/SentryAuth.php b/lib/Raven/HttpClient/Authentication/SentryAuth.php new file mode 100644 index 000000000..d6ecb743a --- /dev/null +++ b/lib/Raven/HttpClient/Authentication/SentryAuth.php @@ -0,0 +1,58 @@ + + */ +final class SentryAuth implements Authentication +{ + /** + * @var Configuration The Raven client configuration + */ + private $configuration; + + /** + * Constructor. + * + * @param Configuration $configuration The Raven client configuration + */ + public function __construct(Configuration $configuration) + { + $this->configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + public function authenticate(RequestInterface $request) + { + $header = sprintf( + 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=%s, sentry_secret=%s', + Client::PROTOCOL, + Client::USER_AGENT, + microtime(true), + $this->configuration->getPublicKey(), + $this->configuration->getSecretKey() + ); + + return $request->withHeader('X-Sentry-Auth', $header); + } +} diff --git a/lib/Raven/HttpClient/Encoding/Base64EncodingStream.php b/lib/Raven/HttpClient/Encoding/Base64EncodingStream.php new file mode 100644 index 000000000..a1e4fd248 --- /dev/null +++ b/lib/Raven/HttpClient/Encoding/Base64EncodingStream.php @@ -0,0 +1,67 @@ + + */ +final class Base64EncodingStream extends FilteredStream +{ + /** + * {@inheritdoc} + */ + public function __construct(StreamInterface $stream, $readFilterOptions = null, $writeFilterOptions = null) + { + // $readFilterOptions and $writeFilterOptions arguments are overridden + // because otherwise an error stating that the filter parameter is + // invalid is thrown when appending the filter to the stream + parent::__construct($stream, [], []); + } + + /** + * {@inheritdoc} + */ + public function getSize() + { + $inputSize = $this->stream->getSize(); + + if (null === $inputSize) { + return $inputSize; + } + + // See https://stackoverflow.com/questions/1533113/calculate-the-size-to-a-base-64-encoded-message + $adjustment = (($inputSize % 3) ? (3 - ($inputSize % 3)) : 0); + + return (($inputSize + $adjustment) / 3) * 4; + } + + /** + * {@inheritdoc} + */ + protected function readFilter() + { + return 'convert.base64-encode'; + } + + /** + * {@inheritdoc} + */ + protected function writeFilter() + { + return 'convert.base64-decode'; + } +} diff --git a/lib/Raven/Util/JSON.php b/lib/Raven/Util/JSON.php new file mode 100644 index 000000000..f674129dd --- /dev/null +++ b/lib/Raven/Util/JSON.php @@ -0,0 +1,38 @@ + + */ +final class JSON +{ + /** + * Encodes the given data into JSON. + * + * @param mixed $data The data to encode + * + * @return string + */ + public static function encode($data) + { + $encoded = json_encode($data, JSON_UNESCAPED_UNICODE); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); + } + + return $encoded; + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9ce9d295d..2a0c9c8e0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,8 @@ - + + + + tests @@ -21,4 +27,8 @@ lib + + + + diff --git a/tests/Breadcrumbs/ErrorHandlerTest.php b/tests/Breadcrumbs/ErrorHandlerTest.php index 3078ea78e..2032346a0 100644 --- a/tests/Breadcrumbs/ErrorHandlerTest.php +++ b/tests/Breadcrumbs/ErrorHandlerTest.php @@ -9,16 +9,15 @@ * file that was distributed with this source code. */ -use Raven\Client; -use Raven\Configuration; +use Raven\ClientBuilder; class Raven_Tests_ErrorHandlerBreadcrumbHandlerTest extends PHPUnit_Framework_TestCase { public function testSimple() { - $client = new Client(new Configuration([ + $client = $client = ClientBuilder::create([ 'install_default_breadcrumb_handlers' => false, - ])); + ])->getClient(); $handler = new \Raven\Breadcrumbs\ErrorHandler($client); $handler->handleError(E_WARNING, 'message'); diff --git a/tests/Breadcrumbs/MonologTest.php b/tests/Breadcrumbs/MonologTest.php index 64914aa4a..9fab59f5b 100644 --- a/tests/Breadcrumbs/MonologTest.php +++ b/tests/Breadcrumbs/MonologTest.php @@ -13,8 +13,7 @@ use Monolog\Logger; use Raven\Breadcrumbs\MonologHandler; -use Raven\Client; -use Raven\Configuration; +use Raven\ClientBuilder; class MonologTest extends \PHPUnit_Framework_TestCase { @@ -42,9 +41,9 @@ protected function getSampleErrorMessage() public function testSimple() { - $client = new Client(new Configuration([ + $client = $client = ClientBuilder::create([ 'install_default_breadcrumb_handlers' => false, - ])); + ])->getClient(); $handler = new MonologHandler($client); @@ -62,9 +61,9 @@ public function testSimple() public function testErrorInMessage() { - $client = new Client(new Configuration([ + $client = $client = ClientBuilder::create([ 'install_default_breadcrumb_handlers' => false, - ])); + ])->getClient(); $handler = new MonologHandler($client); diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index b9fce0c13..545e060d9 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -11,6 +11,12 @@ namespace Raven\Tests; +use Http\Client\Common\Plugin; +use Http\Client\HttpAsyncClient; +use Http\Message\MessageFactory; +use Http\Message\StreamFactory; +use Http\Message\UriFactory; +use Psr\Http\Message\RequestInterface; use Raven\Client; use Raven\ClientBuilder; use Raven\Configuration; @@ -24,6 +30,85 @@ public function testCreate() $this->assertInstanceOf(ClientBuilder::class, $clientBuilder); } + public function testSetUriFactory() + { + /** @var UriFactory|\PHPUnit_Framework_MockObject_MockObject $uriFactory */ + $uriFactory = $this->getMockBuilder(UriFactory::class) + ->getMock(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->setUriFactory($uriFactory); + + $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); + } + + public function testSetMessageFactory() + { + /** @var MessageFactory|\PHPUnit_Framework_MockObject_MockObject $messageFactory */ + $messageFactory = $this->getMockBuilder(MessageFactory::class) + ->getMock(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->setMessageFactory($messageFactory); + + $this->assertAttributeSame($messageFactory, 'messageFactory', $clientBuilder); + + $client = $clientBuilder->getClient(); + + $this->assertAttributeSame($messageFactory, 'requestFactory', $client); + } + + public function testSetHttpClient() + { + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->setHttpClient($httpClient); + + $this->assertAttributeSame($httpClient, 'httpClient', $clientBuilder); + + $client = $this->getObjectAttribute($clientBuilder->getClient(), 'httpClient'); + + $this->assertAttributeSame($httpClient, 'client', $client); + } + + public function testAddHttpClientPlugin() + { + /** @var Plugin|\PHPUnit_Framework_MockObject_MockObject $plugin */ + $plugin = $this->getMockBuilder(Plugin::class) + ->getMock(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->addHttpClientPlugin($plugin); + + $plugins = $this->getObjectAttribute($clientBuilder, 'httpClientPlugins'); + + $this->assertCount(1, $plugins); + $this->assertSame($plugin, $plugins[0]); + } + + public function testRemoveHttpClientPlugin() + { + $plugin = new PluginStub1(); + $plugin2 = new PluginStub2(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->addHttpClientPlugin($plugin); + $clientBuilder->addHttpClientPlugin($plugin); + $clientBuilder->addHttpClientPlugin($plugin2); + + $this->assertAttributeCount(3, 'httpClientPlugins', $clientBuilder); + + $clientBuilder->removeHttpClientPlugin(PluginStub1::class); + + $plugins = $this->getObjectAttribute($clientBuilder, 'httpClientPlugins'); + + $this->assertCount(1, $plugins); + $this->assertSame($plugin2, reset($plugins)); + } + public function testGetClient() { $clientBuilder = new ClientBuilder(); @@ -69,16 +154,13 @@ public function optionsDataProvider() ['setIsTrustXForwardedProto', true], ['setPrefixes', ['foo', 'bar']], ['setSerializeAllObjects', false], - ['setCurlMethod', 'async'], - ['setCurlPath', 'foo'], - ['setCurlIpv4', true], - ['setCurlSslVersion', CURL_SSLVERSION_DEFAULT], ['setSampleRate', 0.5], ['setInstallDefaultBreadcrumbHandlers', false], ['setInstallShutdownHandler', false], ['setMbDetectOrder', ['foo', 'bar']], ['setAutoLogStacks', false], ['setContextLines', 0], + ['setEncoding', 'gzip'], ['setCurrentEnvironment', 'test'], ['setEnvironments', ['default']], ['setExcludedLoggers', ['foo', 'bar']], @@ -87,14 +169,9 @@ public function optionsDataProvider() ['setTransport', null], ['setProjectRoot', 'foo'], ['setLogger', 'bar'], - ['setOpenTimeout', 1], - ['setTimeout', 3], ['setProxy', 'foo'], ['setRelease', 'dev'], ['setServerName', 'example.com'], - ['setSslOptions', ['foo' => 'bar']], - ['setSslVerificationEnabled', false], - ['setSslCaFile', 'foo'], ['setTags', ['foo', 'bar']], ['setErrorTypes', 0], ['setProcessors', ['foo']], @@ -102,3 +179,17 @@ public function optionsDataProvider() ]; } } + +class PluginStub1 implements Plugin +{ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + } +} + +class PluginStub2 implements Plugin +{ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 7eebd57ce..f6aa96202 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,11 +10,16 @@ namespace Raven\Tests; -use Composer\CaBundle\CaBundle; +use Http\Client\HttpAsyncClient; +use Http\Message\RequestFactory; +use Http\Mock\Client as MockClient; +use Http\Promise\Promise; +use Psr\Http\Message\ResponseInterface; use Raven\Breadcrumbs\ErrorHandler; use Raven\Client; +use Raven\ClientBuilder; use Raven\Configuration; -use Raven\CurlHandler; +use Raven\HttpClient\HttpClientFactoryInterface; use Raven\Processor\SanitizeDataProcessor; function simple_function($a = null, $b = null, $c = null) @@ -24,7 +29,7 @@ function simple_function($a = null, $b = null, $c = null) function invalid_encoding() { - $fp = fopen(__DIR__ . '/../../data/binary', 'r'); + $fp = fopen(__DIR__ . '/data/binary', 'r'); simple_function(fread($fp, 64)); fclose($fp); } @@ -55,11 +60,6 @@ public static function is_http_request() return true; } - public static function get_auth_header($timestamp, $client, $api_key, $secret_key) - { - return parent::get_auth_header($timestamp, $client, $api_key, $secret_key); - } - public function get_http_data() { return parent::get_http_data(); @@ -70,11 +70,6 @@ public function get_user_data() return parent::get_user_data(); } - public function buildCurlCommand($url, $data, $headers) - { - return parent::buildCurlCommand($url, $data, $headers); - } - // short circuit breadcrumbs public function registerDefaultBreadcrumbHandlers() { @@ -97,55 +92,6 @@ public function test_get_current_url() } } -class Dummy_Raven_Client_With_Overrided_Direct_Send extends \Raven\Client -{ - public $_send_http_asynchronous_curl_exec_called = false; - public $_send_http_synchronous = false; - public $_set_url; - public $_set_data; - public $_set_headers; - public static $_close_curl_resource_called = false; - - public function send_http_asynchronous_curl_exec($url, $data, $headers) - { - $this->_send_http_asynchronous_curl_exec_called = true; - $this->_set_url = $url; - $this->_set_data = $data; - $this->_set_headers = $headers; - } - - public function send_http_synchronous($url, $data, $headers) - { - $this->_send_http_synchronous = true; - $this->_set_url = $url; - $this->_set_data = $data; - $this->_set_headers = $headers; - } - - public function get_curl_options() - { - $options = parent::get_curl_options(); - - return $options; - } - - public function get_curl_handler() - { - return $this->_curl_handler; - } - - public function set_curl_handler(\Raven\CurlHandler $value) - { - $this->_curl_handler = $value; - } - - public function close_curl_resource() - { - parent::close_curl_resource(); - self::$_close_curl_resource_called = true; - } -} - class Dummy_Raven_Client_No_Http extends Dummy_Raven_Client { /** @@ -177,40 +123,6 @@ public static function test_filename() { return sys_get_temp_dir().'/clientraven.tmp'; } - - protected function buildCurlCommand($url, $data, $headers) - { - return 'echo '.escapeshellarg(self::get_test_data()).' > '.self::test_filename(); - } -} - -class Dummy_Raven_CurlHandler extends \Raven\CurlHandler -{ - public $_set_url; - public $_set_data; - public $_set_headers; - public $_enqueue_called = false; - public $_join_called = false; - - public function __construct($options = [], $join_timeout = 5) - { - parent::__construct($options, $join_timeout); - } - - public function enqueue($url, $data = null, $headers = []) - { - $this->_enqueue_called = true; - $this->_set_url = $url; - $this->_set_data = $data; - $this->_set_headers = $headers; - - return 0; - } - - public function join($timeout = null) - { - $this->_join_called = true; - } } class ClientTest extends \PHPUnit_Framework_TestCase @@ -222,105 +134,6 @@ public static function setUpBeforeClass() parent::setUpBeforeClass(); self::$_folder = sys_get_temp_dir().'/sentry_server_'.microtime(true); mkdir(self::$_folder); - - // Root CA #A1 - // Сертификат CA #A4, signed by CA #A1 (end-user certificate) - $temporary_openssl_file_conf_alt = self::$_folder.'/openssl-config-alt.tmp'; - file_put_contents($temporary_openssl_file_conf_alt, sprintf('HOME = . -RANDFILE = $ENV::HOME/.rnd -[ req ] -distinguished_name = req_distinguished_name -req_extensions = v3_req - -[ req_distinguished_name ] - -[ v3_req ] -subjectAltName = DNS:*.org, DNS:127.0.0.1, DNS:%s -keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign -basicConstraints = CA:FALSE -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer -extendedKeyUsage = serverAuth,clientAuth -certificatePolicies = @polsect - -[polsect] -policyIdentifier = 1.2.3.4.5.6.7 -userNotice.1 = @notice - -[notice] -explicitText = "UTF8:Please add this certificate to black list" -organization = "Sentry" -', strtolower(gethostname()))); - $certificate_offset = mt_rand(0, 10000) << 16; - - // CA #A1 - $csr_a1 = openssl_csr_new([ - "countryName" => "US", - "localityName" => "Nowhere", - "organizationName" => "Sentry", - "organizationalUnitName" => "Development Center. CA #A1", - "commonName" => "Sentry: Test HTTP Server: CA #A1", - "emailAddress" => "noreply@sentry.example.com", - ], $ca_a1_pair, [ - 'private_key_bits' => 2048, - 'private_key_type' => OPENSSL_KEYTYPE_RSA, // currently only RSA works - 'encrypt_key' => true, - ]); - if ($csr_a1 === false) { - throw new \Exception("Can not create CSR pair A1"); - } - $crt_a1 = openssl_csr_sign($csr_a1, null, $ca_a1_pair, 1, [ - "digest_alg" => "sha256", - ], hexdec("0A1") + $certificate_offset); - if ($crt_a1 === false) { - throw new \Exception("Can not create certificate A1"); - } - - /** @noinspection PhpUndefinedVariableInspection */ - openssl_x509_export($crt_a1, $certout); - openssl_pkey_export($ca_a1_pair, $pkeyout); - file_put_contents(self::$_folder.'/crt_a1.crt', $certout, LOCK_EX); - file_put_contents(self::$_folder.'/crt_a1.pem', $pkeyout, LOCK_EX); - openssl_pkey_export($ca_a1_pair, $pkeyout, 'password'); - file_put_contents(self::$_folder.'/crt_a1.p.pem', $pkeyout, LOCK_EX); - unset($csr_a1, $certout, $pkeyout); - - // CA #A4 - $csr_a4 = openssl_csr_new([ - "countryName" => "US", - "localityName" => "Nowhere", - "organizationName" => "Sentry", - "organizationalUnitName" => "Development Center", - "commonName" => "127.0.0.1", - "emailAddress" => "noreply@sentry.example.com", - ], $ca_a4_pair, [ - 'private_key_bits' => 2048, - 'private_key_type' => OPENSSL_KEYTYPE_RSA, // currently only RSA works - 'encrypt_key' => true, - ]); - if ($csr_a4 === false) { - throw new \Exception("Can not create CSR pair A4"); - } - $crt_a4 = openssl_csr_sign($csr_a4, file_get_contents(self::$_folder.'/crt_a1.crt'), - $ca_a1_pair, 1, [ - "digest_alg" => "sha256", - 'config' => $temporary_openssl_file_conf_alt, - 'x509_extensions' => 'v3_req', - ], hexdec("0A4") + $certificate_offset); - if ($crt_a4 === false) { - throw new \Exception("Can not create certificate A4"); - } - - /** @noinspection PhpUndefinedVariableInspection */ - openssl_x509_export($crt_a4, $certout); - openssl_pkey_export($ca_a4_pair, $pkeyout); - file_put_contents(self::$_folder.'/crt_a4.crt', $certout, LOCK_EX); - file_put_contents(self::$_folder.'/crt_a4.c.crt', - $certout."\n".file_get_contents(self::$_folder.'/crt_a1.crt'), LOCK_EX); - file_put_contents(self::$_folder.'/crt_a4.pem', $pkeyout, LOCK_EX); - openssl_pkey_export($ca_a4_pair, $pkeyout, 'password'); - file_put_contents(self::$_folder.'/crt_a4.p.pem', $pkeyout, LOCK_EX); - unset($csr_a4, $certout, $pkeyout); } public static function tearDownAfterClass() @@ -358,9 +171,54 @@ private function create_chained_exception() } } + public function testDestructor() + { + $waitCalled = false; + + /** @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject $response */ + $response = $this->getMockBuilder(ResponseInterface::class) + ->getMock(); + + $promise = new PromiseMock($response); + $promise->then(function (ResponseInterface $response) use (&$waitCalled) { + $waitCalled = true; + + return $response; + }); + + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); + + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willReturn($promise); + + $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1']) + ->setHttpClient($httpClient) + ->getClient(); + + $data = ['foo']; + + $this->assertAttributeEmpty('pendingRequests', $client); + + $client->send($data); + + $this->assertAttributeNotEmpty('pendingRequests', $client); + $this->assertFalse($waitCalled); + + // The destructor should never be called explicitly because it simply + // does nothing in PHP, but it's the only way to assert that the + // $waitCalled variable is true because PHPUnit maintains references + // to all mocks instances + $client->__destruct(); + + $this->assertTrue($waitCalled); + } + public function testOptionsExtraData() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->extra_context(['foo' => 'bar']); @@ -372,7 +230,7 @@ public function testOptionsExtraData() public function testOptionsExtraDataWithNull() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->extra_context(['foo' => 'bar']); @@ -384,7 +242,7 @@ public function testOptionsExtraDataWithNull() public function testEmptyExtraData() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->captureMessage('Test Message %s', ['foo']); @@ -395,7 +253,7 @@ public function testEmptyExtraData() public function testCaptureMessageDoesHandleUninterpolatedMessage() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; @@ -407,7 +265,7 @@ public function testCaptureMessageDoesHandleUninterpolatedMessage() public function testCaptureMessageDoesHandleInterpolatedMessage() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; @@ -419,13 +277,11 @@ public function testCaptureMessageDoesHandleInterpolatedMessage() public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() { - $config = new Configuration(['release' => '1.2.3']); - $client = new Client($config); + $client = ClientBuilder::create(['release' => '1.2.3'])->getClient(); + $client->store_errors_for_bulk_send = true; $this->assertEquals('1.2.3', $client->getConfig()->getRelease()); - $client->store_errors_for_bulk_send = true; - $client->captureMessage('foo %s', ['bar']); $this->assertCount(1, $client->_pending_events); @@ -435,7 +291,7 @@ public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() public function testCaptureMessageSetsInterface() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->captureMessage('foo %s', ['bar']); @@ -450,7 +306,7 @@ public function testCaptureMessageSetsInterface() public function testCaptureMessageHandlesOptionsAsThirdArg() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->captureMessage('Test Message %s', ['foo'], [ @@ -466,7 +322,7 @@ public function testCaptureMessageHandlesOptionsAsThirdArg() public function testCaptureMessageHandlesLevelAsThirdArg() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->captureMessage('Test Message %s', ['foo'], Dummy_Raven_Client::WARNING); @@ -482,24 +338,19 @@ public function testCaptureMessageHandlesLevelAsThirdArg() public function testCaptureExceptionSetsInterfaces() { # TODO: it'd be nice if we could mock the stacktrace extraction function here - $client = new Dummy_Raven_Client(new Configuration()); - $client->captureException($this->create_exception()); - - $events = $client->getSentEvents(); - - $this->assertCount(1, $events); - - $event = array_pop($events); + $client = ClientBuilder::create()->getClient(); + $client->store_errors_for_bulk_send = true; - $exc = $event['exception']; + $client->captureException($this->create_exception()); - $this->assertEquals(1, count($exc['values'])); - $this->assertEquals('Foo bar', $exc['values'][0]['value']); - $this->assertEquals('Exception', $exc['values'][0]['type']); + $this->assertCount(1, $client->_pending_events); - $this->assertNotEmpty($exc['values'][0]['stacktrace']['frames']); + $this->assertCount(1, $client->_pending_events[0]['exception']['values']); + $this->assertEquals('Foo bar', $client->_pending_events[0]['exception']['values'][0]['value']); + $this->assertEquals('Exception', $client->_pending_events[0]['exception']['values'][0]['type']); + $this->assertNotEmpty($client->_pending_events[0]['exception']['values'][0]['stacktrace']['frames']); - $frames = $exc['values'][0]['stacktrace']['frames']; + $frames = $client->_pending_events[0]['exception']['values'][0]['stacktrace']['frames']; $frame = $frames[count($frames) - 1]; $this->assertTrue($frame['lineno'] > 0); @@ -513,7 +364,7 @@ public function testCaptureExceptionSetsInterfaces() public function testCaptureExceptionChainedException() { # TODO: it'd be nice if we could mock the stacktrace extraction function here - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->captureException($this->create_chained_exception()); @@ -526,7 +377,7 @@ public function testCaptureExceptionChainedException() public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $e1 = new \ErrorException('First', 0, E_DEPRECATED); @@ -545,7 +396,7 @@ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() public function testCaptureExceptionHandlesOptionsAsSecondArg() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->captureException($this->create_exception(), ['culprit' => 'test']); @@ -556,7 +407,7 @@ public function testCaptureExceptionHandlesOptionsAsSecondArg() public function testCaptureExceptionHandlesExcludeOption() { - $client = new Client(new Configuration(['excluded_exceptions' => ['Exception']])); + $client = ClientBuilder::create(['excluded_exceptions' => ['Exception']])->getClient(); $client->store_errors_for_bulk_send = true; $client->captureException($this->create_exception(), 'test'); @@ -566,7 +417,7 @@ public function testCaptureExceptionHandlesExcludeOption() public function testCaptureExceptionInvalidUTF8() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; try { @@ -577,12 +428,16 @@ public function testCaptureExceptionInvalidUTF8() $this->assertCount(1, $client->_pending_events); - $this->assertNotFalse($client->encode($client->_pending_events[0])); + try { + $client->send($client->_pending_events[0]); + } catch (\Exception $ex) { + $this->fail(); + } } public function testDoesRegisterProcessors() { - $client = new Client(new Configuration(['processors' => [SanitizeDataProcessor::class]])); + $client = ClientBuilder::create(['processors' => [SanitizeDataProcessor::class]])->getClient(); $this->assertInstanceOf(SanitizeDataProcessor::class, $this->getObjectAttribute($client, 'processors')[0]); } @@ -599,7 +454,7 @@ public function testProcessDoesCallProcessors() ->method('process') ->with($data); - $client = new Dummy_Raven_Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->setProcessors([$processor]); $client->process($data); @@ -607,8 +462,8 @@ public function testProcessDoesCallProcessors() public function testGetDefaultData() { - $config = new Configuration(); - $client = new Client($config); + $client = ClientBuilder::create()->getClient(); + $config = $client->getConfig(); $client->transaction->push('test'); @@ -677,14 +532,27 @@ public function testGetHttpData() ] ]; - $client = new Dummy_Raven_Client(new Configuration()); + $config = new Configuration([ + 'install_default_breadcrumb_handlers' => false, + 'install_shutdown_handler' => false, + ]); + + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); + + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); + + $client = new Dummy_Raven_Client($config, $httpClient, $requestFactory); $this->assertEquals($expected, $client->get_http_data()); } public function testCaptureMessageWithUserData() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->user_context([ @@ -703,7 +571,7 @@ public function testCaptureMessageWithUserData() public function testCaptureMessageWithNoUserData() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->captureMessage('foo'); @@ -712,35 +580,9 @@ public function testCaptureMessageWithNoUserData() $this->assertEquals(session_id(), $client->_pending_events[0]['user']['id']); } - public function testGet_Auth_Header() - { - $client = new Dummy_Raven_Client(new Configuration()); - - $clientstring = 'sentry-php/test'; - $timestamp = '1234341324.340000'; - - $expected = "Sentry sentry_timestamp={$timestamp}, sentry_client={$clientstring}, " . - "sentry_version=" . Dummy_Raven_Client::PROTOCOL . ", " . - "sentry_key=publickey, sentry_secret=secretkey"; - - $this->assertEquals($expected, $client->get_auth_header($timestamp, 'sentry-php/test', 'publickey', 'secretkey')); - } - - public function testGetAuthHeader() - { - $client = new Dummy_Raven_Client(new Configuration()); - $ts1 = microtime(true); - $header = $client->getAuthHeader(); - $ts2 = microtime(true); - $this->assertEquals(1, preg_match('/sentry_timestamp=([0-9.]+)/', $header, $a)); - $this->assertRegExp('/^[0-9]+(\\.[0-9]+)?$/', $a[1]); - $this->assertGreaterThanOrEqual($ts1, (double)$a[1]); - $this->assertLessThanOrEqual($ts2, (double)$a[1]); - } - public function testCaptureMessageWithUnserializableUserData() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->user_context([ @@ -757,7 +599,7 @@ public function testCaptureMessageWithUnserializableUserData() public function testCaptureMessageWithTagsContext() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->tags_context(['foo' => 'bar']); @@ -772,7 +614,7 @@ public function testCaptureMessageWithTagsContext() public function testCaptureMessageWithExtraContext() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->extra_context(['foo' => 'bar']); @@ -787,7 +629,7 @@ public function testCaptureMessageWithExtraContext() public function testCaptureExceptionContainingLatin1() { - $client = new Client(new Configuration(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])); + $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])->getClient(); $client->store_errors_for_bulk_send = true; // we need a non-utf8 string here. @@ -803,7 +645,7 @@ public function testCaptureExceptionContainingLatin1() public function testCaptureExceptionInLatin1File() { - $client = new Client(new Configuration(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])); + $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])->getClient(); $client->store_errors_for_bulk_send = true; require_once(__DIR__ . '/Fixtures/code/Latin1File.php'); @@ -834,11 +676,11 @@ public function testCaptureLastError() error_clear_last(); } - $client = new Dummy_Raven_Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $this->assertNull($client->captureLastError()); - $this->assertEmpty($client->getSentEvents()); + $this->assertEmpty($client->_pending_events); /** @var $undefined */ /** @noinspection PhpExpressionResultUnusedInspection */ @@ -852,7 +694,9 @@ public function testCaptureLastError() public function testGetLastEventID() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); + $client->store_errors_for_bulk_send = true; + $client->capture([ 'message' => 'test', 'event_id' => 'abc' @@ -865,10 +709,10 @@ public function testCustomTransport() { $events = []; - $client = new Client(new Configuration([ + $client = ClientBuilder::create([ 'server' => 'https://public:secret@sentry.example.com/1', 'install_default_breadcrumb_handlers' => false, - ])); + ])->getClient(); $client->getConfig()->setTransport(function ($client, $data) use (&$events) { $events[] = $data; @@ -881,7 +725,7 @@ public function testCustomTransport() public function testAppPathLinux() { - $client = new Client(new Configuration(['project_root' => '/foo/bar'])); + $client = ClientBuilder::create(['project_root' => '/foo/bar'])->getClient(); $this->assertEquals('/foo/bar/', $client->getConfig()->getProjectRoot()); @@ -892,7 +736,7 @@ public function testAppPathLinux() public function testAppPathWindows() { - $client = new Client(new Configuration(['project_root' => 'C:\\foo\\bar\\'])); + $client = ClientBuilder::create(['project_root' => 'C:\\foo\\bar\\'])->getClient(); $this->assertEquals('C:\\foo\\bar\\', $client->getConfig()->getProjectRoot()); } @@ -903,61 +747,117 @@ public function testAppPathWindows() */ public function testCannotInstallTwice() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->install(); $client->install(); } + /** + * @dataProvider sendWithEncodingDataProvider + */ + public function testSendWithEncoding($options, $expectedRequest) + { + $httpClient = new MockClient(); + + $client = ClientBuilder::create($options) + ->setHttpClient($httpClient) + ->getClient(); + + $data = ['foo bar']; + + $client->send($data); + + $requests = $httpClient->getRequests(); + + $this->assertCount(1, $requests); + + $stream = $requests[0]->getBody(); + $stream->rewind(); + + $this->assertEquals($expectedRequest['body'], (string) $stream); + $this->assertArraySubset($expectedRequest['headers'], $requests[0]->getHeaders()); + } + + public function sendWithEncodingDataProvider() + { + return [ + [ + [ + 'server' => 'http://public:secret@example.com/1', + 'encoding' => 'json', + ], + [ + 'body' => '["foo bar"]', + 'headers' => [ + 'Content-Type' => ['application/json'], + ], + ], + ], + [ + [ + 'server' => 'http://public:secret@example.com/1', + 'encoding' => 'gzip', + ], + [ + 'body' => 'eJyLVkrLz1dISixSigUAFYQDlg==', + 'headers' => [ + 'Content-Type' => ['application/octet-stream'], + ], + ], + ], + ]; + } + public function testSendCallback() { - $client = new Dummy_Raven_Client(new Configuration([ + $config = new Configuration([ 'should_capture' => function ($data) { $this->assertEquals('test', $data['message']); return false; } - ])); + ]); - $client->captureMessage('test'); + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); - $events = $client->getSentEvents(); + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); - $this->assertEmpty($events); + $client = new Dummy_Raven_Client($config, $httpClient, $requestFactory); - $client = new Dummy_Raven_Client(new Configuration([ - 'should_capture' => function ($data) { - $this->assertEquals('test', $data['message']); + $client->captureMessage('test'); - return true; - } - ])); + $this->assertEmpty($client->getSentEvents()); - $client->captureMessage('test'); + $config->setShouldCapture(function ($data) { + $this->assertEquals('test', $data['message']); - $events = $client->getSentEvents(); + return true; + }); - $this->assertCount(1, $events); + $client->captureMessage('test'); - $client = new Dummy_Raven_Client(new Configuration([ - 'should_capture' => function (&$data) { - unset($data['message']); + $this->assertCount(1, $client->getSentEvents()); - return true; - } - ])); + $config->setShouldCapture(function (&$data) { + unset($data['message']); - $client->captureMessage('test'); + return true; + }); - $events = $client->getSentEvents(); + $client->captureMessage('test'); - $this->assertCount(1, $events); - $this->assertArrayNotHasKey('message', $events[0]); + $this->assertCount(2, $client->getSentEvents()); + $this->assertArrayNotHasKey('message', $client->getSentEvents()[1]); } public function testSanitizeExtra() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $data = ['extra' => [ 'context' => [ 'line' => 1216, @@ -980,8 +880,8 @@ public function testSanitizeExtra() public function testSanitizeObjects() { - $client = new Dummy_Raven_Client(new Configuration(['serialize_all_object' => true])); - $clone = new Client(new Configuration()); + $client = ClientBuilder::create(['serialize_all_object' => true])->getClient(); + $clone = ClientBuilder::create()->getClient(); $data = [ 'extra' => [ 'object' => $clone, @@ -1037,7 +937,7 @@ public function testSanitizeObjects() public function testSanitizeTags() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $data = ['tags' => [ 'foo' => 'bar', 'baz' => ['biz'], @@ -1052,7 +952,7 @@ public function testSanitizeTags() public function testSanitizeUser() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $data = ['user' => [ 'email' => 'foo@example.com', ]]; @@ -1065,7 +965,7 @@ public function testSanitizeUser() public function testSanitizeRequest() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $data = ['request' => [ 'context' => [ 'line' => 1216, @@ -1088,7 +988,7 @@ public function testSanitizeRequest() public function testSanitizeContexts() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $data = ['contexts' => [ 'context' => [ 'line' => 1216, @@ -1115,38 +1015,9 @@ public function testSanitizeContexts() ]], $data); } - public function testBuildCurlCommandEscapesInput() - { - $data = '{"foo": "\'; ls;"}'; - $client = new Dummy_Raven_Client(new Configuration(['timeout' => 5])); - $result = $client->buildCurlCommand('http://foo.com', $data, []); - $folder = CaBundle::getSystemCaRootBundlePath(); - $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 --cacert \''.$folder.'\' > /dev/null 2>&1 &', $result); - - $client->getConfig()->setSslCaFile(null); - - $result = $client->buildCurlCommand('http://foo.com', $data, []); - - $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); - - $result = $client->buildCurlCommand('http://foo.com', $data, ['key' => 'value']); - - $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); - - $client->getConfig()->setSslVerificationEnabled(false); - - $result = $client->buildCurlCommand('http://foo.com', $data, []); - - $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); - - $result = $client->buildCurlCommand('http://foo.com', $data, ['key' => 'value']); - - $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); - } - public function testUserContextWithoutMerge() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->user_context(['foo' => 'bar'], false); $client->user_context(['baz' => 'bar'], false); @@ -1156,7 +1027,7 @@ public function testUserContextWithoutMerge() public function testUserContextWithMerge() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->user_context(['foo' => 'bar'], true); $client->user_context(['baz' => 'bar'], true); @@ -1166,7 +1037,7 @@ public function testUserContextWithMerge() public function testUserContextWithMergeAndNull() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->user_context(['foo' => 'bar'], true); $client->user_context(null, true); @@ -1189,7 +1060,15 @@ public function testCurrentUrl($serverVars, $options, $expected, $message) { $_SERVER = $serverVars; - $client = new Dummy_Raven_Client(new Configuration($options)); + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); + + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); + + $client = new Dummy_Raven_Client(new Configuration($options), $httpClient, $requestFactory); $result = $client->test_get_current_url(); $this->assertSame($expected, $result, $message); @@ -1285,7 +1164,7 @@ public function testUuid4() */ public function testGettersAndSetters() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $callable = [$this, 'stabClosureVoid']; $data = [ @@ -1302,7 +1181,7 @@ public function testGettersAndSetters() $this->subTestGettersAndSettersDatum($client, $datum); } foreach ($data as &$datum) { - $client = new Dummy_Raven_Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $this->subTestGettersAndSettersDatum($client, $datum); } } @@ -1374,7 +1253,7 @@ public function testTranslateSeverity() { $reflection = new \ReflectionProperty('\\Raven\Client', 'severity_map'); $reflection->setAccessible(true); - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $predefined = [E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR, E_USER_WARNING, @@ -1416,14 +1295,9 @@ public function testTranslateSeverity() $this->assertEquals('error', $client->translateSeverity(123457)); } - public function testGetUserAgent() - { - $this->assertRegExp('|^[0-9a-z./_-]+$|i', Client::getUserAgent()); - } - public function testCaptureExceptionWithLogger() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->captureException(new \Exception(), null, 'foobar'); @@ -1432,58 +1306,6 @@ public function testCaptureExceptionWithLogger() $this->assertEquals('foobar', $client->_pending_events[0]['logger']); } - public function testCurl_method() - { - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - ]) - ); - - $client->captureMessage('foobar'); - - $this->assertTrue($client->_send_http_synchronous); - $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); - - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'exec', - 'install_default_breadcrumb_handlers' => false, - ]) - ); - $client->captureMessage('foobar'); - $this->assertFalse($client->_send_http_synchronous); - $this->assertTrue($client->_send_http_asynchronous_curl_exec_called); - } - - public function testCurl_method_async() - { - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'async', - 'install_default_breadcrumb_handlers' => false, - ]) - ); - - $object = $client->get_curl_handler(); - - $this->assertInstanceOf(CurlHandler::class, $object); - $this->assertAttributeEquals($client->get_curl_options(), 'options', $object); - - $ch = new Dummy_Raven_CurlHandler(); - - $client->set_curl_handler($ch); - $client->captureMessage('foobar'); - - $this->assertFalse($client->_send_http_synchronous); - $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); - $this->assertTrue($ch->_enqueue_called); - } - /** * @backupGlobals * @covers \Raven\Client::_server_variable @@ -1510,24 +1332,6 @@ public function test_server_variable() } } - public function testEncode() - { - $client = new Client(new Configuration()); - $data = ['some' => (object)['value' => 'data'], 'foo' => ['bar', null, 123], false]; - $json_stringify = json_encode($data); - $value = $client->encode($data); - $this->assertNotFalse($value); - $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value, '\\Raven\\Client::encode returned malformed data'); - $decoded = base64_decode($value); - $this->assertInternalType('string', $decoded, 'Can not use base64 decode on the encoded blob'); - if (function_exists("gzcompress")) { - $decoded = gzuncompress($decoded); - $this->assertEquals($json_stringify, $decoded, 'Can not decompress compressed blob'); - } else { - $this->assertEquals($json_stringify, $decoded); - } - } - public function testRegisterDefaultBreadcrumbHandlers() { if (isset($_ENV['HHVM']) and ($_ENV['HHVM'] == 1)) { @@ -1538,7 +1342,7 @@ public function testRegisterDefaultBreadcrumbHandlers() $previous = set_error_handler([$this, 'stabClosureErrorHandler'], E_USER_NOTICE); - new Client(new Configuration()); + ClientBuilder::create()->getClient(); $this->_closure_called = false; @@ -1591,152 +1395,98 @@ public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, public function testOnShutdown() { - // step 1 - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - ]) - ); + $httpClient = new MockClient(); + + $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1']) + ->setHttpClient($httpClient) + ->getClient(); + $this->assertEquals(0, count($client->_pending_events)); $client->_pending_events[] = ['foo' => 'bar']; $client->sendUnsentErrors(); - $this->assertTrue($client->_send_http_synchronous); - $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + $this->assertCount(1, $httpClient->getRequests()); $this->assertEquals(0, count($client->_pending_events)); - // step 2 - $client->_send_http_synchronous = false; - $client->_send_http_asynchronous_curl_exec_called = false; - $client->store_errors_for_bulk_send = true; $client->captureMessage('foobar'); $this->assertEquals(1, count($client->_pending_events)); - $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); - $client->_send_http_synchronous = false; - $client->_send_http_asynchronous_curl_exec_called = false; // step 3 $client->onShutdown(); - $this->assertTrue($client->_send_http_synchronous); - $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + $this->assertCount(2, $httpClient->getRequests()); $this->assertEquals(0, count($client->_pending_events)); - - // step 1 - $client = null; - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'async', - 'install_default_breadcrumb_handlers' => false, - ]) - ); - $ch = new Dummy_Raven_CurlHandler(); - $client->set_curl_handler($ch); - $client->captureMessage('foobar'); - $client->onShutdown(); - $client = null; - $this->assertTrue($ch->_join_called); } public function testSendChecksShouldCaptureOption() { - $this->_closure_called = false; - - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - 'should_capture' => function () { - $this->_closure_called = true; - - return false; - }, - ]) - ); - - $data = ['foo' => 'bar']; - - $client->send($data); - - $this->assertTrue($this->_closure_called); - $this->assertFalse($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); - } - - public function testSendFailsWhenNoServerIsConfigured() - { - /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder(Client::class) - ->setConstructorArgs([new Configuration()]) + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) ->getMock(); - $client->expects($this->never()) - ->method('encode'); - - $data = ['foo' => 'bar']; + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); - $client->send($data); - } + $httpClient->expects($this->never()) + ->method('sendAsyncRequest'); - public function testNonWorkingSendSetTransport() - { - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - ]) - ); - - $this->_closure_called = false; + $config = new Configuration([ + 'server' => 'http://public:secret@example.com/1', + 'install_default_breadcrumb_handlers' => false, + 'should_capture' => function () use (&$shouldCaptureCalled) { + $shouldCaptureCalled = true; - $client->getConfig()->setTransport([$this, 'stabClosureNull']); + return false; + }, + ]); - $this->assertFalse($this->_closure_called); + $client = new Client($config, $httpClient, $requestFactory); $data = ['foo' => 'bar']; $client->send($data); - $this->assertTrue($this->_closure_called); - $this->assertFalse($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); + $this->assertTrue($shouldCaptureCalled); + } - $this->_closure_called = false; + public function testSendFailsWhenNoServerIsConfigured() + { + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - 'should_capture' => function () { - $this->_closure_called = true; + $httpClient->expects($this->never()) + ->method('sendAsyncRequest'); - return false; - } - ]) - ); - - $this->assertFalse($this->_closure_called); + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); + $client = new Client(new Configuration(), $httpClient, $requestFactory); $data = ['foo' => 'bar']; $client->send($data); - - $this->assertTrue($this->_closure_called); - $this->assertFalse($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); } public function test__construct_handlers() { + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); + + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); + foreach ([true, false] as $u1) { foreach ([true, false] as $u2) { $client = new Dummy_Raven_Client( new Configuration([ 'install_default_breadcrumb_handlers' => $u1, 'install_shutdown_handler' => $u2, - ]) + ]), + $httpClient, + $requestFactory ); $this->assertEquals($u1, $client->dummy_breadcrumbs_handlers_has_set); @@ -1745,29 +1495,18 @@ public function test__construct_handlers() } } - public function test__destruct_calls_close_functions() + public function testGet_user_data() { - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'install_default_breadcrumb_handlers' => false, - 'install_shutdown_handler' => false, - ]) - ); - - $client::$_close_curl_resource_called = false; - - $client->close_all_children_link(); - - unset($client); + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); - $this->assertTrue(Dummy_Raven_Client_With_Overrided_Direct_Send::$_close_curl_resource_called); - } + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); - public function testGet_user_data() - { // step 1 - $client = new Dummy_Raven_Client(new Configuration()); + $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); $output = $client->get_user_data(); $this->assertInternalType('array', $output); $this->assertArrayHasKey('user', $output); @@ -1798,6 +1537,14 @@ public function testGet_user_data() public function testCaptureLevel() { + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); + + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); + foreach ([Client::MESSAGE_LIMIT * 3, 100] as $length) { $message = ''; @@ -1805,7 +1552,7 @@ public function testCaptureLevel() $message .= chr($i % 256); } - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->capture(['message' => $message]); @@ -1816,7 +1563,7 @@ public function testCaptureLevel() $this->assertArrayNotHasKey('release', $client->_pending_events[0]); } - $client = new Dummy_Raven_Client(new Configuration()); + $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); $client->store_errors_for_bulk_send = true; $client->capture(['message' => 'foobar']); @@ -1826,7 +1573,7 @@ public function testCaptureLevel() $this->assertEquals($input['request'], $client->_pending_events[0]['request']); $this->assertArrayNotHasKey('release', $client->_pending_events[0]); - $client = new Dummy_Raven_Client(new Configuration()); + $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); $client->store_errors_for_bulk_send = true; $client->capture(['message' => 'foobar', 'request' => ['foo' => 'bar']]); @@ -1846,7 +1593,7 @@ public function testCaptureLevel() $options['current_environment'] = 'bar'; } - $client = new Client(new Configuration($options)); + $client = new Client(new Configuration($options), $httpClient, $requestFactory); $client->store_errors_for_bulk_send = true; $client->capture(['message' => 'foobar']); @@ -1866,7 +1613,15 @@ public function testCaptureLevel() public function testCaptureNoUserAndRequest() { - $client = new Dummy_Raven_Client_No_Http(new Configuration(['install_default_breadcrumb_handlers' => false])); + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->getMockBuilder(HttpAsyncClient::class) + ->getMock(); + + /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ + $requestFactory = $this->getMockBuilder(RequestFactory::class) + ->getMock(); + + $client = new Dummy_Raven_Client_No_Http(new Configuration(['install_default_breadcrumb_handlers' => false]), $httpClient, $requestFactory); $client->store_errors_for_bulk_send = true; $session_id = session_id(); @@ -1886,7 +1641,7 @@ public function testCaptureNoUserAndRequest() public function testCaptureNonEmptyBreadcrumb() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $ts1 = microtime(true); @@ -1910,7 +1665,7 @@ public function testCaptureNonEmptyBreadcrumb() public function testCaptureAutoLogStacks() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; $client->capture(['auto_log_stacks' => true], true); @@ -1920,113 +1675,52 @@ public function testCaptureAutoLogStacks() $this->assertInternalType('array', $client->_pending_events[0]['stacktrace']['frames']); } - public function testSend_http_asynchronous_curl_exec() - { - $client = new Dummy_Raven_Client_With_Sync_Override( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'exec', - 'install_default_breadcrumb_handlers' => false, - ]) - ); - - if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { - unlink(Dummy_Raven_Client_With_Sync_Override::test_filename()); - } - - $client->captureMessage('foobar'); - - $test_data = Dummy_Raven_Client_With_Sync_Override::get_test_data(); - - $this->assertStringEqualsFile(Dummy_Raven_Client_With_Sync_Override::test_filename(), $test_data . "\n"); - } - - public function testClose_curl_resource() + /** + * @dataProvider sampleRateAbsoluteDataProvider + */ + public function testSampleRateAbsolute($options) { - $client = new Client(new Configuration()); - - $reflection = new \ReflectionProperty(Client::class, '_curl_instance'); - $reflection->setAccessible(true); - - $ch = curl_init(); - - $reflection->setValue($client, $ch); + $httpClient = new MockClient(); - unset($ch); + $client = ClientBuilder::create($options) + ->setHttpClient($httpClient) + ->getClient(); - $this->assertInternalType('resource', $reflection->getValue($client)); - - $client->close_curl_resource(); - - $this->assertNull($reflection->getValue($client)); - } - - public function testSampleRateAbsolute() - { - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - 'sample_rate' => 0, - ]) - ); - - for ($i = 0; $i < 1000; $i++) { + for ($i = 0; $i < 10; $i++) { $client->captureMessage('foobar'); - - $this->assertFalse($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); } - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'server' => 'http://public:secret@example.com/1', - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - 'sample_rate' => 1, - ]) - ); - - for ($i = 0; $i < 1000; $i++) { - $client->captureMessage('foobar'); - - $this->assertTrue($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called); + switch ($options['sample_rate']) { + case 0: + $this->assertEmpty($httpClient->getRequests()); + break; + case 1: + $this->assertNotEmpty($httpClient->getRequests()); + break; } } - public function testSampleRatePrc() + public function sampleRateAbsoluteDataProvider() { - $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( - new Configuration([ - 'sample_rate' => 0.5, - 'curl_method' => 'foobar', - 'install_default_breadcrumb_handlers' => false, - ]) - ); - - $u_true = false; - $u_false = false; - - for ($i = 0; $i < 1000; $i++) { - $client->captureMessage('foobar'); - - if ($client->_send_http_synchronous || $client->_send_http_asynchronous_curl_exec_called) { - $u_true = true; - } else { - $u_false = true; - } - - if ($u_true || $u_false) { - return; - } - } - - $this->fail('sample_rate=0.5 can not produce fails and successes at the same time'); + return [ + [ + [ + 'server' => 'http://public:secret@example.com/1', + 'sample_rate' => 0, + ], + ], + [ + [ + 'server' => 'http://public:secret@example.com/1', + 'sample_rate' => 1, + ], + ], + ]; } public function testSetAllObjectSerialize() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $client->setAllObjectSerialize(true); @@ -2038,222 +1732,65 @@ public function testSetAllObjectSerialize() $this->assertFalse($client->getSerializer()->getAllObjectSerialize()); $this->assertFalse($client->getReprSerializer()->getAllObjectSerialize()); } +} - /** - * @return int - */ - protected static function get_port() - { - exec('netstat -n -t', $buf); - $current_used_ports = []; - foreach ($buf as $line) { - list(, , , $local_addr) = preg_split('_\\s+_', $line, 7); - if (preg_match('_:([0-9]+)$_', $local_addr, $a)) { - $current_used_ports[] = (int)$a[1]; - } - } - $current_used_ports = array_unique($current_used_ports); - sort($current_used_ports); +class PromiseMock implements Promise +{ + private $result; - do { - $port = mt_rand(55000, 60000); - } while (in_array($port, $current_used_ports)); + private $state; - return $port; - } + private $onFullfilledCallbacks = []; - public function dataDirectSend() - { - $data = []; + private $onRejectedCallbacks = []; - $block1 = []; - $block1[] = [ - 'options' => [ - 'server' => 'http://login:password@127.0.0.1:{port}/5', - ], - 'server_options' => [], - 'timeout' => 0, - 'is_failed' => false, - ]; - $block1[] = [ - 'options' => [ - 'server' => 'http://login:password@127.0.0.1:{port}/5', - 'curl_method' => 'async', - ], - 'server_options' => [], - 'timeout' => 0, - 'is_failed' => false, - ]; - $block1[] = [ - 'options' => [ - 'server' => 'http://login:password@127.0.0.1:{port}/5', - 'curl_method' => 'exec', - ], - 'server_options' => [], - 'timeout' => 5, - 'is_failed' => false, - ]; + public function __construct($result, $state = self::FULFILLED) + { + $this->result = $result; + $this->state = $state; + } - $j = count($block1); - for ($i = 0; $i < $j; $i++) { - $datum = $block1[$i]; - $datum['server_options']['http_code'] = 403; - $datum['is_failed'] = true; - $block1[] = $datum; + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + if (null !== $onFulfilled) { + $this->onFullfilledCallbacks[] = $onFulfilled; } - $block_ssl = [['options' => [], 'server_options' => [], 'timeout' => 0, 'is_failed' => false]]; - $block_ssl[] = [ - 'options' => [ - 'server' => 'http://login:password@127.0.0.1:{port}/5', - 'ssl_ca_file' => '{folder}/crt_a1.crt', - ], - 'server_options' => [ - 'ssl_server_certificate_file' => '{folder}/crt_a4.c.crt', - 'ssl_server_key_file' => '{folder}/crt_a4.pem', - 'is_ssl' => true, - ], - 'timeout' => 5, - 'is_failed' => false, - ]; - - foreach ($block1 as $b1) { - foreach ($block_ssl as $b2) { - $datum = [ - 'options' => array_merge( - isset($b1['options']) ? $b1['options'] : [], - isset($b2['options']) ? $b2['options'] : [] - ), - 'server_options' => array_merge( - isset($b1['server_options']) ? $b1['server_options'] : [], - isset($b2['server_options']) ? $b2['server_options'] : [] - ), - 'timeout' => max($b1['timeout'], $b2['timeout']), - 'is_failed' => ($b1['is_failed'] or $b2['is_failed']), - ]; - if (isset($datum['options']['ssl_ca_file'])) { - $datum['options']['server'] = str_replace('http://', 'https://', $datum['options']['server']); - } - - $data[] = $datum; - } + if (null !== $onRejected) { + $this->onRejectedCallbacks[] = $onRejected; } - return $data; + return $this; } - /** - * @param array $sentry_options - * @param array $server_options - * @param integer $timeout - * @param boolean $is_failed - * - * @dataProvider dataDirectSend - */ - public function testDirectSend($sentry_options, $server_options, $timeout, $is_failed) + public function getState() { - foreach ($sentry_options as &$value) { - if (is_string($value)) { - $value = str_replace('{folder}', self::$_folder, $value); - } - } - foreach ($server_options as &$value) { - if (is_string($value)) { - $value = str_replace('{folder}', self::$_folder, $value); - } - $value = str_replace('{folder}', self::$_folder, $value); - } - unset($value); - - $port = self::get_port(); - $sentry_options['server'] = str_replace('{port}', $port, $sentry_options['server']); - $sentry_options['timeout'] = 10; + return $this->state; + } - $client = new Client(new Configuration($sentry_options)); - $output_filename = tempnam(self::$_folder, 'output_http_'); - foreach ( - [ - 'port' => $port, - 'output_filename' => $output_filename, - ] as $key => $value - ) { - $server_options[$key] = $value; - } + public function wait($unwrap = true) + { + switch ($this->state) { + case self::FULFILLED: { + foreach ($this->onFullfilledCallbacks as $onFullfilledCallback) { + $onFullfilledCallback($this->result); + } - $filename = tempnam(self::$_folder, 'sentry_http_'); - file_put_contents($filename, serialize((object)$server_options), LOCK_EX); - - $cli_output_filename = tempnam(sys_get_temp_dir(), 'output_http_'); - exec( - sprintf( - 'php '.__DIR__.'/bin/httpserver.php --config=%s >%s 2>&1 &', - escapeshellarg($filename), - escapeshellarg($cli_output_filename) - ) - ); - $ts = microtime(true); - $u = false; - do { - if (preg_match('_listen_i', file_get_contents($cli_output_filename))) { - $u = true; break; } - } while ($ts + 10 > microtime(true)); - $this->assertTrue($u, 'Can not start Test HTTP Server'); - unset($u, $ts); - - $extra = ['foo'.mt_rand(0, 10000) => microtime(true).':'.mt_rand(20, 100)]; - $event = $client->captureMessage( - 'Test Message', [], [ - 'level' => Client::INFO, - 'extra' => $extra, - ] - ); - $client->sendUnsentErrors(); - $client->force_send_async_curl_events(); - if ($is_failed) { - if (!isset($sentry_options['curl_method']) or - !in_array($sentry_options['curl_method'], ['async', 'exec']) - ) { - if (isset($server_options['http_code'])) { - $this->assertNotNull($client->getLastError()); - $this->assertNotNull($client->getLastSentryError()); + case self::REJECTED: { + foreach ($this->onRejectedCallbacks as $onRejectedCallback) { + $onRejectedCallback($this->result); } + + break; } - } else { - $this->assertNotNull($event); } - if ($timeout > 0) { - usleep($timeout * 1000000); - } - $this->assertFileExists($output_filename); - $buf = file_get_contents($output_filename); - $server_input = unserialize($buf); - $this->assertNotFalse($server_input); - /** @var \NokitaKaze\TestHTTPServer\ClientDatum $connection */ - $connection = $server_input['connection']; - $this->assertNotNull($connection); - $this->assertEquals('/api/5/store/', $connection->request_url); - - $body = base64_decode($connection->blob_body); - if (function_exists('gzuncompress')) { - $new_body = gzuncompress($body); - if ($new_body !== false) { - $body = $new_body; - } - unset($new_body); + + if ($unwrap) { + return $this->result; } - $body = json_decode($body); - $this->assertEquals(5, $body->project); - $this->assertEquals('Test Message', $body->message); - $this->assertEquals($event, $body->event_id); - $this->assertEquals($extra, (array)$body->extra); - $this->assertEquals('info', $body->level); - - $this->assertRegExp( - '|^Sentry sentry_timestamp=[0-9.]+,\\s+sentry_client=sentry\\-php/[a-z0-9.-]+,\\s+sentry_version=[0-9]+,'. - '\\s+sentry_key=login,\\s+sentry_secret=password|', - $connection->request_head_params['X-Sentry-Auth'] - ); + + return null; } } diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index bcc944a65..c113ae45e 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -46,19 +46,17 @@ public function testGettersAndSetters($option, $value, $getterMethod, $setterMet public function optionsDataProvider() { return [ + ['send_attempts', 1, 'getSendAttempts', 'setSendAttempts'], ['trust_x_forwarded_proto', false, 'isTrustXForwardedProto', 'setIsTrustXForwardedProto'], ['prefixes', ['foo', 'bar'], 'getPrefixes', 'setPrefixes'], ['serialize_all_object', false, 'getSerializeAllObjects', 'setSerializeAllObjects'], - ['curl_method', 'sync', 'getCurlMethod', 'setCurlMethod'], - ['curl_path', 'curl', 'getCurlPath', 'setCurlPath'], - ['curl_ipv4', true, 'getCurlIpv4', 'setCurlIpv4'], - ['curl_ssl_version', CURL_SSLVERSION_DEFAULT, 'getCurlSslVersion', 'setCurlSslVersion'], ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], ['install_default_breadcrumb_handlers', false, 'shouldInstallDefaultBreadcrumbHandlers', 'setInstallDefaultBreadcrumbHandlers'], ['install_shutdown_handler', false, 'shouldInstallShutdownHandler', 'setInstallShutdownHandler'], ['mb_detect_order', null, 'getMbDetectOrder', 'setMbDetectOrder'], ['auto_log_stacks', false, 'getAutoLogStacks', 'setAutoLogStacks'], ['context_lines', 3, 'getContextLines', 'setContextLines'], + ['encoding', 'json', 'getEncoding', 'setEncoding'], ['current_environment', 'foo', 'getCurrentEnvironment', 'setCurrentEnvironment'], ['environments', ['foo', 'bar'], 'getEnvironments', 'setEnvironments'], ['excluded_loggers', ['bar', 'foo'], 'getExcludedLoggers', 'setExcludedLoggers'], @@ -66,14 +64,9 @@ public function optionsDataProvider() ['excluded_app_paths', ['foo', 'bar'], 'getExcludedProjectPaths', 'setExcludedProjectPaths'], ['project_root', 'baz', 'getProjectRoot', 'setProjectRoot'], ['logger', 'foo', 'getLogger', 'setLogger'], - ['open_timeout', 2, 'getOpenTimeout', 'setOpenTimeout'], - ['timeout', 3, 'getTimeout', 'setTimeout'], ['proxy', 'tcp://localhost:8125', 'getProxy', 'setProxy'], ['release', 'dev', 'getRelease', 'setRelease'], ['server_name', 'foo', 'getServerName', 'setServerName'], - ['ssl', [], 'getSslOptions', 'setSslOptions'], - ['ssl_verification', false, 'isSslVerificationEnabled', 'setSslVerificationEnabled'], - ['ssl_ca_file', 'path/to/file', 'getSslCaFile', 'setSslCaFile'], ['tags', ['foo', 'bar'], 'getTags', 'setTags'], ['error_types', 0, 'getErrorTypes', 'setErrorTypes'], ['processors', [SanitizeDataProcessor::class, RemoveCookiesProcessor::class, RemoveHttpBodyProcessor::class, SanitizeHttpHeadersProcessor::class], 'getProcessors', 'setProcessors'], diff --git a/tests/HttpClient/Authentication/SentryAuthTest.php b/tests/HttpClient/Authentication/SentryAuthTest.php new file mode 100644 index 000000000..e495a3c5f --- /dev/null +++ b/tests/HttpClient/Authentication/SentryAuthTest.php @@ -0,0 +1,51 @@ + 'http://public:secret@example.com/']); + $authentication = new SentryAuth($configuration); + + /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $request */ + $request = $this->getMockBuilder(RequestInterface::class) + ->getMock(); + + /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $newRequest */ + $newRequest = $this->getMockBuilder(RequestInterface::class) + ->getMock(); + + $headerValue = sprintf( + 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=public, sentry_secret=secret', + Client::PROTOCOL, + Client::USER_AGENT, + microtime(true) + ); + + $request->expects($this->once()) + ->method('withHeader') + ->with('X-Sentry-Auth', $headerValue) + ->willReturn($newRequest); + + $this->assertSame($newRequest, $authentication->authenticate($request)); + } +} diff --git a/tests/HttpClient/Encoding/Base64EncodingStreamTest.php b/tests/HttpClient/Encoding/Base64EncodingStreamTest.php new file mode 100644 index 000000000..d6bef7b82 --- /dev/null +++ b/tests/HttpClient/Encoding/Base64EncodingStreamTest.php @@ -0,0 +1,71 @@ +getMockBuilder(StreamInterface::class) + ->getMock(); + + $stream->expects($this->once()) + ->method('getSize') + ->willReturn($decodedSize); + + $encodingStream = new Base64EncodingStream($stream); + + $stream->write($content); + + $this->assertSame($encodedSize, $encodingStream->getSize()); + } + + public function getSizeDataProvider() + { + return [ + ['', null, null], + ['foo', 3, 4], + ['foo bar', 7, 12], + ]; + } + + /** + * @dataProvider readDataProvider + */ + public function testRead($decoded, $encoded) + { + $stream = new Stream('php://memory', 'r+'); + $encodingStream = new Base64EncodingStream($stream); + + $stream->write($decoded); + $stream->rewind(); + + $this->assertSame($encoded, $encodingStream->getContents()); + } + + public function readDataProvider() + { + return [ + ['', ''], + ['foo', 'Zm9v'], + ['foo bar', 'Zm9vIGJhcg=='], + ]; + } +} diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index bd4bbdf2b..261466f8d 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -10,6 +10,7 @@ namespace Raven\Tests; +use Raven\ClientBuilder; use Raven\Configuration; class DummyIntegration_Raven_Client extends \Raven\Client @@ -55,21 +56,16 @@ private function create_chained_exception() public function testCaptureSimpleError() { - $client = new DummyIntegration_Raven_Client(new Configuration([ - 'server' => 'https://public:secret@example.com/1', - ])); + $client = ClientBuilder::create([])->getClient(); + $client->store_errors_for_bulk_send = true; @mkdir('/no/way'); $client->captureLastError(); - $events = $client->getSentEvents(); - $event = array_pop($events); + $event = $client->_pending_events[0]['exception']['values'][0]; - $exc = $event['exception']['values'][0]; - $this->assertEquals($exc['value'], 'mkdir(): No such file or directory'); - $stack = $exc['stacktrace']['frames']; - $lastFrame = $stack[count($stack) - 1]; - $this->assertEquals(@$lastFrame['filename'], 'tests/IntegrationTest.php'); + $this->assertEquals($event['value'], 'mkdir(): No such file or directory'); + $this->assertEquals($event['stacktrace']['frames'][count($event['stacktrace']['frames']) - 1]['filename'], 'tests/IntegrationTest.php'); } } diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index dc8cceb59..e25a6e65e 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -11,8 +11,7 @@ namespace Raven\Tests; -use Raven\Client; -use Raven\Configuration; +use Raven\ClientBuilder; use Raven\Processor\SanitizeDataProcessor; class SanitizeDataProcessorTest extends \PHPUnit_Framework_TestCase @@ -38,7 +37,7 @@ public function testDoesFilterHttpData() ] ]; - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $processor = new SanitizeDataProcessor($client); $processor->process($data); @@ -65,7 +64,7 @@ public function testDoesFilterSessionId() ] ]; - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $processor = new SanitizeDataProcessor($client); $processor->process($data); @@ -81,7 +80,7 @@ public function testDoesFilterCreditCard() ], ]; - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $processor = new SanitizeDataProcessor($client); $processor->process($data); @@ -90,7 +89,7 @@ public function testDoesFilterCreditCard() public function testSettingProcessorOptions() { - $client = new Client(new Configuration()); + $client = ClientBuilder::create()->getClient(); $processor = new SanitizeDataProcessor($client); $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); @@ -112,7 +111,7 @@ public function testSettingProcessorOptions() */ public function testOverrideOptions($processorOptions, $clientOptions) { - $client = new Client(new Configuration($clientOptions)); + $client = ClientBuilder::create($clientOptions)->getClient(); /** @var SanitizeDataProcessor $processor */ $processor = $this->getObjectAttribute($client, 'processors')[0]; @@ -146,7 +145,7 @@ public function testOverridenSanitize($processorOptions, $clientOptions) ] ]; - $client = new Client(new Configuration($clientOptions)); + $client = ClientBuilder::create($clientOptions)->getClient(); /** @var SanitizeDataProcessor $processor */ $processor = $this->getObjectAttribute($client, 'processors')[0]; diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php index 34f0507e0..4e707c162 100644 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -12,7 +12,7 @@ namespace Raven\Tests; use Raven\Client; -use Raven\Configuration; +use Raven\ClientBuilder; use Raven\Processor\SanitizeStacktraceProcessor; class SanitizeStacktraceProcessorTest extends \PHPUnit_Framework_TestCase @@ -29,7 +29,7 @@ class SanitizeStacktraceProcessorTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->client = new Client(new Configuration()); + $this->client = ClientBuilder::create()->getClient(); $this->client->store_errors_for_bulk_send = true; $this->processor = new SanitizeStacktraceProcessor($this->client); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 18e73fdda..0bddaae7e 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -12,7 +12,7 @@ namespace Raven\Tests; use Raven\Client; -use Raven\Configuration; +use Raven\ClientBuilder; use Raven\Stacktrace; class StacktraceTest extends \PHPUnit_Framework_TestCase @@ -24,7 +24,7 @@ class StacktraceTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->client = new Client(new Configuration()); + $this->client = ClientBuilder::create()->getClient(); } public function testGetFramesAndToArray() diff --git a/tests/Util/Fixtures/JsonSerializableClass.php b/tests/Util/Fixtures/JsonSerializableClass.php new file mode 100644 index 000000000..5fd8ec747 --- /dev/null +++ b/tests/Util/Fixtures/JsonSerializableClass.php @@ -0,0 +1,24 @@ + 'value', + ]; + } +} diff --git a/tests/Util/Fixtures/SimpleClass.php b/tests/Util/Fixtures/SimpleClass.php new file mode 100644 index 000000000..4ed7f532c --- /dev/null +++ b/tests/Util/Fixtures/SimpleClass.php @@ -0,0 +1,21 @@ +assertEquals($expectedResult, JSON::encode($value)); + } + + public function encodeDataProvider() + { + $obj = new \stdClass(); + $obj->key = 'value'; + + return [ + [['key' => 'value'], '{"key":"value"}'], + ['string', '"string"'], + [123.45, '123.45'], + [null, 'null'], + [$obj, '{"key":"value"}'], + [new SimpleClass(), '{"keyPublic":"public"}'], + [new JsonSerializableClass(), '{"key":"value"}'], + ]; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Could not encode value into JSON format. Error was: "Type is not supported". + */ + public function testEncodeThrowsIfValueIsResource() + { + $resource = fopen('php://memory', 'r'); + + fclose($resource); + + JSON::encode($resource); + } +} From bef60ebb50e89900f03b69e318ba5477e4b46358 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 24 Jul 2017 21:22:00 +0200 Subject: [PATCH 0305/1161] Refactor the breadcrumbs recorder implementation (#441) --- composer.json | 1 + lib/Raven/Breadcrumbs.php | 78 ---- lib/Raven/Breadcrumbs/Breadcrumb.php | 354 ++++++++++++++++++ lib/Raven/Breadcrumbs/ErrorHandler.php | 11 +- lib/Raven/Breadcrumbs/MonologHandler.php | 71 ++-- lib/Raven/Breadcrumbs/Recorder.php | 139 +++++++ lib/Raven/Client.php | 162 +++++--- lib/Raven/ErrorHandler.php | 16 +- lib/Raven/Exception/ExceptionInterface.php | 21 ++ .../Exception/InvalidArgumentException.php | 22 ++ tests/Breadcrumbs/BreadcrumbTest.php | 134 +++++++ tests/Breadcrumbs/ErrorHandlerTest.php | 24 +- ...MonologTest.php => MonologHandlerTest.php} | 38 +- tests/Breadcrumbs/RecorderTest.php | 90 +++++ tests/BreadcrumbsTest.php | 38 -- tests/ClientBuilderTest.php | 1 - tests/ClientTest.php | 38 +- tests/IntegrationTest.php | 2 +- tests/SerializerAbstractTest.php | 3 +- tests/bin/httpserver.php | 47 --- 20 files changed, 971 insertions(+), 319 deletions(-) delete mode 100644 lib/Raven/Breadcrumbs.php create mode 100644 lib/Raven/Breadcrumbs/Breadcrumb.php create mode 100644 lib/Raven/Breadcrumbs/Recorder.php create mode 100644 lib/Raven/Exception/ExceptionInterface.php create mode 100644 lib/Raven/Exception/InvalidArgumentException.php create mode 100644 tests/Breadcrumbs/BreadcrumbTest.php rename tests/Breadcrumbs/{MonologTest.php => MonologHandlerTest.php} (69%) create mode 100644 tests/Breadcrumbs/RecorderTest.php delete mode 100644 tests/BreadcrumbsTest.php delete mode 100644 tests/bin/httpserver.php diff --git a/composer.json b/composer.json index 4a8c4a25a..29e7e0885 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "ext-json": "*", "ext-mbstring": "*", "php-http/async-client-implementation": "~1.0", + "php-http/client-common": "~1.5", "php-http/discovery": "~1.2", "php-http/httplug": "~1.1", "psr/http-message-implementation": "~1.0", diff --git a/lib/Raven/Breadcrumbs.php b/lib/Raven/Breadcrumbs.php deleted file mode 100644 index 16eab3610..000000000 --- a/lib/Raven/Breadcrumbs.php +++ /dev/null @@ -1,78 +0,0 @@ -size = $size; - $this->reset(); - } - - public function reset() - { - $this->count = 0; - $this->pos = 0; - $this->buffer = []; - } - - public function record($crumb) - { - if (empty($crumb['timestamp'])) { - $crumb['timestamp'] = microtime(true); - } - $this->buffer[$this->pos] = $crumb; - $this->pos = ($this->pos + 1) % $this->size; - $this->count++; - } - - /** - * @return array[] - */ - public function fetch() - { - $results = []; - for ($i = 0; $i <= ($this->size - 1); $i++) { - $idx = ($this->pos + $i) % $this->size; - if (isset($this->buffer[$idx])) { - $results[] = $this->buffer[$idx]; - } - } - return $results; - } - - public function is_empty() - { - return $this->count === 0; - } - - public function to_json() - { - return [ - 'values' => $this->fetch(), - ]; - } -} diff --git a/lib/Raven/Breadcrumbs/Breadcrumb.php b/lib/Raven/Breadcrumbs/Breadcrumb.php new file mode 100644 index 000000000..4b5bde52f --- /dev/null +++ b/lib/Raven/Breadcrumbs/Breadcrumb.php @@ -0,0 +1,354 @@ + + */ +final class Breadcrumb implements \JsonSerializable +{ + /** + * This constant defines the http breadcrumb type. + */ + const TYPE_HTTP = 'http'; + + /** + * This constant defines the user breadcrumb type. + */ + const TYPE_USER = 'user'; + + /** + * This constant defines the navigation breadcrumb type. + */ + const TYPE_NAVIGATION = 'navigation'; + + /** + * This constant defines the error breadcrumb type. + */ + const TYPE_ERROR = 'error'; + + /** + * @var string The category of the breadcrumb + */ + private $category; + + /** + * @var string The type of breadcrumb + */ + private $type; + + /** + * @var string The message of the breadcrumb + */ + private $message; + + /** + * @var string The level of the breadcrumb + */ + private $level; + + /** + * @var array The meta data of the breadcrumb + */ + private $metadata; + + /** + * @var float The timestamp of the breadcrumb + */ + private $timestamp; + + /** + * Constructor. + * + * @param string $level The error level of the breadcrumb + * @param string $type The type of the breadcrumb + * @param string $category The category of the breadcrumb + * @param string|null $message Optional text message + * @param array $metaData Additional information about the breadcrumb + */ + public function __construct($level, $type, $category, $message = null, array $metaData = []) + { + if (!in_array($level, self::getLevels(), true)) { + throw new InvalidArgumentException('The value of the $level argument must be one of the Raven\Client::LEVEL_* constants.'); + } + + $this->type = $type; + $this->level = $level; + $this->category = $category; + $this->message = $message; + $this->metadata = $metaData; + $this->timestamp = microtime(true); + } + + /** + * Creates a new instance of this class configured with the given params. + * + * @param string $level The error level of the breadcrumb + * @param string $type The type of the breadcrumb + * @param string $category The category of the breadcrumb + * @param string|null $message Optional text message + * @param array $metaData Additional information about the breadcrumb + * + * @return static + */ + public static function create($level, $type, $category, $message = null, array $metaData = []) + { + return new static($level, $type, $category, $message, $metaData); + } + + /** + * Gets the breadcrumb type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Sets the type of the breadcrumb + * + * @param string $type The type + * + * @return static + */ + public function withType($type) + { + if ($type === $this->type) { + return $this; + } + + $new = clone $this; + $new->type = $type; + + return $new; + } + + /** + * Gets the breadcrumb level. + * + * @return string + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the error level of the breadcrumb. + * + * @param string $level The level + * + * @return static + */ + public function withLevel($level) + { + if (!in_array($level, self::getLevels(), true)) { + throw new InvalidArgumentException('The value of the $level argument must be one of the Raven\Client::LEVEL_* constants.'); + } + + if ($level === $this->level) { + return $this; + } + + $new = clone $this; + $new->level = $level; + + return $new; + } + + /** + * Gets the breadcrumb category. + * + * @return string + */ + public function getCategory() + { + return $this->category; + } + + /** + * Sets the breadcrumb category. + * + * @param string $category The category + * + * @return static + */ + public function withCategory($category) + { + if ($category === $this->category) { + return $this; + } + + $new = clone $this; + $new->category = $category; + + return $new; + } + + /** + * Gets the breadcrumb message. + * + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Sets the breadcrumb message. + * + * @param string $message The message + * + * @return static + */ + public function withMessage($message) + { + if ($message === $this->message) { + return $this; + } + + $new = clone $this; + $new->message = $message; + + return $new; + } + + /** + * Gets the breadcrumb meta data. + * + * @return array + */ + public function getMetadata() + { + return $this->metadata; + } + + /** + * Returns an instance of this class with the provided metadata, replacing + * any existing values of any metadata with the same name. + * + * @param string $name The name of the metadata + * @param mixed $value The value + * + * @return static + */ + public function withMetadata($name, $value) + { + if (isset($this->metadata[$name]) && $value === $this->message[$name]) { + return $this; + } + + $new = clone $this; + $new->metadata[$name] = $value; + + return $new; + } + + /** + * Returns an instance of this class without the specified metadata + * information. + * + * @param string $name The name of the metadata + * + * @return static|Breadcrumb + */ + public function withoutMetadata($name) + { + if (!isset($this->metadata[$name])) { + return $this; + } + + $new = clone $this; + + unset($new->metadata[$name]); + + return $new; + } + + /** + * Gets the breadcrumb timestamp. + * + * @return float + */ + public function getTimestamp() + { + return $this->timestamp; + } + + /** + * Sets the breadcrumb timestamp. + * + * @param float $timestamp The timestamp. + * + * @return static + */ + public function withTimestamp($timestamp) + { + if ($timestamp === $this->timestamp) { + return $this; + } + + $new = clone $this; + $new->timestamp = $timestamp; + + return $new; + } + + /** + * Gets the breadcrumb as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'type' => $this->type, + 'category' => $this->category, + 'level' => $this->level, + 'message' => $this->message, + 'timestamp' => $this->timestamp, + 'data' => $this->metadata, + ]; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Gets the list of allowed breadcrumb error levels. + * + * @return string[] + */ + private static function getLevels() + { + return [ + Client::LEVEL_DEBUG, + Client::LEVEL_INFO, + Client::LEVEL_WARNING, + Client::LEVEL_ERROR, + Client::LEVEL_FATAL, + ]; + } +} diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index 411128075..1f8ef41b0 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -21,16 +21,13 @@ public function __construct(\Raven\Client $ravenClient) public function handleError($code, $message, $file = '', $line = 0, $context = []) { - $this->ravenClient->breadcrumbs->record([ - 'category' => 'error_reporting', - 'message' => $message, - 'level' => $this->ravenClient->translateSeverity($code), - 'data' => [ + $this->ravenClient->leaveBreadcrumb( + new Breadcrumb($this->ravenClient->translateSeverity($code), Breadcrumb::TYPE_ERROR, 'error_reporting', $message, [ 'code' => $code, 'line' => $line, 'file' => $file, - ], - ]); + ]) + ); if ($this->existingHandler !== null) { return call_user_func($this->existingHandler, $code, $message, $file, $line, $context); diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 26b83d66b..2a2b6fa12 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -2,22 +2,24 @@ namespace Raven\Breadcrumbs; -use \Monolog\Logger; +use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Logger; +use Raven\Client; -class MonologHandler extends \Monolog\Handler\AbstractProcessingHandler +class MonologHandler extends AbstractProcessingHandler { /** * Translates Monolog log levels to Raven log levels. */ protected $logLevels = [ - Logger::DEBUG => \Raven\Client::DEBUG, - Logger::INFO => \Raven\Client::INFO, - Logger::NOTICE => \Raven\Client::INFO, - Logger::WARNING => \Raven\Client::WARNING, - Logger::ERROR => \Raven\Client::ERROR, - Logger::CRITICAL => \Raven\Client::FATAL, - Logger::ALERT => \Raven\Client::FATAL, - Logger::EMERGENCY => \Raven\Client::FATAL, + Logger::DEBUG => Client::LEVEL_DEBUG, + Logger::INFO => Client::LEVEL_INFO, + Logger::NOTICE => Client::LEVEL_INFO, + Logger::WARNING => Client::LEVEL_WARNING, + Logger::ERROR => Client::LEVEL_ERROR, + Logger::CRITICAL => Client::LEVEL_FATAL, + Logger::ALERT => Client::LEVEL_FATAL, + Logger::EMERGENCY => Client::LEVEL_FATAL, ]; protected $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; @@ -28,11 +30,11 @@ class MonologHandler extends \Monolog\Handler\AbstractProcessingHandler protected $ravenClient; /** - * @param \Raven\Client $ravenClient - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param Client $ravenClient The Raven client + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(\Raven\Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + public function __construct(Client $ravenClient, $level = Logger::DEBUG, $bubble = true) { parent::__construct($level, $bubble); @@ -67,36 +69,27 @@ protected function write(array $record) * @var \Exception $exc */ $exc = $record['context']['exception']; - $crumb = [ - 'type' => 'error', - 'level' => $this->logLevels[$record['level']], - 'category' => $record['channel'], - 'data' => [ - 'type' => get_class($exc), - 'value' => $exc->getMessage(), - ], - ]; + + $breadcrumb = new Breadcrumb($this->logLevels[$record['level']], Breadcrumb::TYPE_ERROR, $record['channel'], null, [ + 'type' => get_class($exc), + 'value' => $exc->getMessage(), + ]); + + $this->ravenClient->leaveBreadcrumb($breadcrumb); } else { // TODO(dcramer): parse exceptions out of messages and format as above if ($error = $this->parseException($record['message'])) { - $crumb = [ - 'type' => 'error', - 'level' => $this->logLevels[$record['level']], - 'category' => $record['channel'], - 'data' => [ - 'type' => $error[0], - 'value' => $error[1], - ], - ]; + $breadcrumb = new Breadcrumb($this->logLevels[$record['level']], Breadcrumb::TYPE_ERROR, $record['channel'], null, [ + 'type' => $error[0], + 'value' => $error[1], + ]); + + $this->ravenClient->leaveBreadcrumb($breadcrumb); } else { - $crumb = [ - 'level' => $this->logLevels[$record['level']], - 'category' => $record['channel'], - 'message' => $record['message'], - ]; + $breadcrumb = new Breadcrumb($this->logLevels[$record['level']], Breadcrumb::TYPE_ERROR, $record['channel'], $record['message']); + + $this->ravenClient->leaveBreadcrumb($breadcrumb); } } - - $this->ravenClient->breadcrumbs->record($crumb); } } diff --git a/lib/Raven/Breadcrumbs/Recorder.php b/lib/Raven/Breadcrumbs/Recorder.php new file mode 100644 index 000000000..98ee528c4 --- /dev/null +++ b/lib/Raven/Breadcrumbs/Recorder.php @@ -0,0 +1,139 @@ + + */ +final class Recorder implements \Countable, \Iterator +{ + /** + * This constant defines the maximum number of breadcrumbs to store. + */ + const MAX_ITEMS = 100; + + /** + * @var int The current position of the iterator + */ + private $position = 0; + + /** + * @var int The current head position + */ + private $head = 0; + + /** + * @var int Current number of stored breadcrumbs + */ + private $size = 0; + + /** + * @var Breadcrumb[] The list of recorded breadcrumbs + */ + private $breadcrumbs; + + /** + * @var int The maximum number of breadcrumbs to store + */ + private $maxSize; + + /** + * Constructor. + * + * @param int $maxSize The maximum number of breadcrumbs to store + */ + public function __construct($maxSize = self::MAX_ITEMS) + { + if (!is_int($maxSize) || $maxSize < 1) { + throw new InvalidArgumentException(sprintf('The $maxSize argument must be an integer greater than 0.')); + } + + $this->breadcrumbs = new \SplFixedArray($maxSize); + $this->maxSize = $maxSize; + } + + /** + * Records a new breadcrumb. + * + * @param Breadcrumb $breadcrumb The breadcrumb object + */ + public function record(Breadcrumb $breadcrumb) + { + $this->breadcrumbs[$this->head] = $breadcrumb; + $this->head = ($this->head + 1) % $this->maxSize; + $this->size = min($this->size + 1, $this->maxSize); + } + + /** + * Clears all recorded breadcrumbs. + */ + public function clear() + { + $this->breadcrumbs = new \SplFixedArray($this->maxSize); + $this->position = 0; + $this->head = 0; + $this->size = 0; + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->breadcrumbs[($this->head + $this->position) % $this->size]; + } + + /** + * {@inheritdoc} + */ + public function next() + { + ++$this->position; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->position < $this->size; + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->position = 0; + } + + /** + * {@inheritdoc} + */ + public function count() + { + return $this->size; + } +} diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 3c4c8d5c9..c95c4e95e 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -15,6 +15,8 @@ use Http\Message\RequestFactory; use Http\Promise\Promise; use Psr\Http\Message\ResponseInterface; +use Raven\Breadcrumbs\Breadcrumb; +use Raven\Breadcrumbs\Recorder; use Raven\HttpClient\Encoding\Base64EncodingStream; use Raven\Util\JSON; @@ -31,12 +33,30 @@ class Client const PROTOCOL = '6'; - const DEBUG = 'debug'; - const INFO = 'info'; - const WARN = 'warning'; - const WARNING = 'warning'; - const ERROR = 'error'; - const FATAL = 'fatal'; + /** + * This constant defines the debug log level. + */ + const LEVEL_DEBUG = 'debug'; + + /** + * This constant defines the info log level. + */ + const LEVEL_INFO = 'info'; + + /** + * This constant defines the warning log level. + */ + const LEVEL_WARNING = 'warning'; + + /** + * This constant defines the error log level. + */ + const LEVEL_ERROR = 'error'; + + /** + * This constant defines the fatal log level. + */ + const LEVEL_FATAL = 'fatal'; /** * Default message limit @@ -44,14 +64,14 @@ class Client const MESSAGE_LIMIT = 1024; /** - * This constant defines the client's user-agent string + * @var Recorder The bredcrumbs recorder */ - const USER_AGENT = 'sentry-php/' . self::VERSION; + protected $recorder; /** - * @var Breadcrumbs The breadcrumbs + * This constant defines the client's user-agent string */ - public $breadcrumbs; + const USER_AGENT = 'sentry-php/' . self::VERSION; /** * @var Context The context @@ -142,7 +162,7 @@ public function __construct(Configuration $config, HttpAsyncClient $httpClient, $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; $this->context = new Context(); - $this->breadcrumbs = new Breadcrumbs(); + $this->recorder = new Recorder(); $this->transaction = new TransactionStack(); $this->serializer = new Serializer($this->config->getMbDetectOrder()); $this->reprSerializer = new ReprSerializer($this->config->getMbDetectOrder()); @@ -166,13 +186,33 @@ public function __construct(Configuration $config, HttpAsyncClient $httpClient, } /** - * Destructor. + * Destruct all objects contain link to this object + * + * This method can not delete shutdown handler */ public function __destruct() { $this->sendUnsentErrors(); } + /** + * Records the given breadcrumb. + * + * @param Breadcrumb $breadcrumb The breadcrumb instance + */ + public function leaveBreadcrumb(Breadcrumb $breadcrumb) + { + $this->recorder->record($breadcrumb); + } + + /** + * Clears all recorded breadcrumbs. + */ + public function clearBreadcrumbs() + { + $this->recorder->clear(); + } + /** * {@inheritdoc} */ @@ -271,9 +311,13 @@ public function getIdent($ident) * @deprecated * @codeCoverageIgnore */ - public function message($message, $params = [], $level = self::INFO, - $stack = false, $vars = null) - { + public function message( + $message, + $params = [], + $level = self::LEVEL_INFO, + $stack = false, + $vars = null + ) { return $this->captureMessage($message, $params, $level, $stack, $vars); } @@ -298,9 +342,13 @@ public function exception($exception) * @param mixed $vars * @return string|null */ - public function captureMessage($message, $params = [], $data = [], - $stack = false, $vars = null) - { + public function captureMessage( + $message, + $params = [], + $data = [], + $stack = false, + $vars = null + ) { // Gracefully handle messages which contain formatting characters, but were not // intended to be used with formatting. if (!empty($params)) { @@ -311,7 +359,7 @@ public function captureMessage($message, $params = [], $data = [], if ($data === null) { $data = []; - // support legacy method of passing in a level name as the third arg + // support legacy method of passing in a level name as the third arg } elseif (!is_array($data)) { $data = [ 'level' => $data, @@ -375,7 +423,10 @@ public function captureException($exception, $data = null, $logger = null, $vars $exc_data['stacktrace'] = [ 'frames' => Stacktrace::fromBacktrace( - $this, $exception->getTrace(), $exception->getFile(), $exception->getLine() + $this, + $exception->getTrace(), + $exception->getFile(), + $exception->getLine() )->getFrames(), ]; @@ -393,7 +444,7 @@ public function captureException($exception, $data = null, $logger = null, $vars if (method_exists($exception, 'getSeverity')) { $data['level'] = $this->translateSeverity($exception->getSeverity()); } else { - $data['level'] = self::ERROR; + $data['level'] = self::LEVEL_ERROR; } } @@ -414,8 +465,11 @@ public function captureLastError() } $e = new \ErrorException( - @$error['message'], 0, @$error['type'], - @$error['file'], @$error['line'] + @$error['message'], + 0, + @$error['type'], + @$error['file'], + @$error['line'] ); return $this->captureException($e); @@ -428,7 +482,7 @@ public function captureLastError() * @param string $level * @param string $engine */ - public function captureQuery($query, $level = self::INFO, $engine = '') + public function captureQuery($query, $level = self::LEVEL_INFO, $engine = '') { $data = [ 'message' => $query, @@ -557,7 +611,7 @@ public function capture($data, $stack = null, $vars = null) $data['timestamp'] = gmdate('Y-m-d\TH:i:s\Z'); } if (!isset($data['level'])) { - $data['level'] = self::ERROR; + $data['level'] = self::LEVEL_ERROR; } if (!isset($data['tags'])) { $data['tags'] = []; @@ -592,11 +646,13 @@ public function capture($data, $stack = null, $vars = null) $data['tags'] = array_merge( $this->config->getTags(), $this->context->tags, - $data['tags']); + $data['tags'] + ); $data['extra'] = array_merge( $this->context->extra, - $data['extra']); + $data['extra'] + ); if (empty($data['extra'])) { unset($data['extra']); @@ -611,8 +667,8 @@ public function capture($data, $stack = null, $vars = null) unset($data['request']); } - if (!$this->breadcrumbs->is_empty()) { - $data['breadcrumbs'] = $this->breadcrumbs->fetch(); + if (!empty($this->recorder)) { + $data['breadcrumbs'] = iterator_to_array($this->recorder); } if ((!$stack && $this->config->getAutoLogStacks()) || $stack === true) { @@ -633,7 +689,9 @@ public function capture($data, $stack = null, $vars = null) if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = [ 'frames' => Stacktrace::fromBacktrace( - $this, $stack, isset($stack['file']) ? $stack['file'] : __FILE__, + $this, + $stack, + isset($stack['file']) ? $stack['file'] : __FILE__, isset($stack['line']) ? $stack['line'] : __LINE__ - 2 )->getFrames(), ]; @@ -770,9 +828,11 @@ public function send(&$data) */ protected static function uuid4() { - $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + $uuid = sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // 32 bits for "time_low" - mt_rand(0, 0xffff), mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0xffff), // 16 bits for "time_mid" mt_rand(0, 0xffff), @@ -787,7 +847,9 @@ protected static function uuid4() mt_rand(0, 0x3fff) | 0x8000, // 48 bits for "node" - mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0xffff) ); return str_replace('-', '', $uuid); @@ -865,23 +927,23 @@ public function translateSeverity($severity) return $this->severity_map[$severity]; } switch ($severity) { - case E_ERROR: return \Raven\Client::ERROR; - case E_WARNING: return \Raven\Client::WARN; - case E_PARSE: return \Raven\Client::ERROR; - case E_NOTICE: return \Raven\Client::INFO; - case E_CORE_ERROR: return \Raven\Client::ERROR; - case E_CORE_WARNING: return \Raven\Client::WARN; - case E_COMPILE_ERROR: return \Raven\Client::ERROR; - case E_COMPILE_WARNING: return \Raven\Client::WARN; - case E_USER_ERROR: return \Raven\Client::ERROR; - case E_USER_WARNING: return \Raven\Client::WARN; - case E_USER_NOTICE: return \Raven\Client::INFO; - case E_STRICT: return \Raven\Client::INFO; - case E_RECOVERABLE_ERROR: return \Raven\Client::ERROR; - case E_DEPRECATED: return \Raven\Client::WARN; - case E_USER_DEPRECATED: return \Raven\Client::WARN; - } - return \Raven\Client::ERROR; + case E_DEPRECATED: return \Raven\Client::LEVEL_WARNING; + case E_USER_DEPRECATED: return \Raven\Client::LEVEL_WARNING; + case E_ERROR: return \Raven\Client::LEVEL_ERROR; + case E_WARNING: return \Raven\Client::LEVEL_WARNING; + case E_PARSE: return \Raven\Client::LEVEL_ERROR; + case E_NOTICE: return \Raven\Client::LEVEL_INFO; + case E_CORE_ERROR: return \Raven\Client::LEVEL_ERROR; + case E_CORE_WARNING: return \Raven\Client::LEVEL_WARNING; + case E_COMPILE_ERROR: return \Raven\Client::LEVEL_ERROR; + case E_COMPILE_WARNING: return \Raven\Client::LEVEL_WARNING; + case E_USER_ERROR: return \Raven\Client::LEVEL_ERROR; + case E_USER_WARNING: return \Raven\Client::LEVEL_WARNING; + case E_USER_NOTICE: return \Raven\Client::LEVEL_INFO; + case E_STRICT: return \Raven\Client::LEVEL_INFO; + case E_RECOVERABLE_ERROR: return \Raven\Client::LEVEL_ERROR; + } + return \Raven\Client::LEVEL_ERROR; } /** diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 816c52599..a3b05c076 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -53,9 +53,12 @@ class ErrorHandler */ protected $error_types = null; - public function __construct($client, $send_errors_last = false, $error_types = null, - $__error_types = null) - { + public function __construct( + $client, + $send_errors_last = false, + $error_types = null, + $__error_types = null + ) { // support legacy fourth argument for error types if ($error_types === null) { $error_types = $__error_types; @@ -133,8 +136,11 @@ public function handleFatalError() if ($this->shouldCaptureFatalError($error['type'])) { $e = new \ErrorException( - @$error['message'], 0, @$error['type'], - @$error['file'], @$error['line'] + @$error['message'], + 0, + @$error['type'], + @$error['file'], + @$error['line'] ); $this->handleException($e, true); } diff --git a/lib/Raven/Exception/ExceptionInterface.php b/lib/Raven/Exception/ExceptionInterface.php new file mode 100644 index 000000000..28bae2630 --- /dev/null +++ b/lib/Raven/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + */ +interface ExceptionInterface +{ +} diff --git a/lib/Raven/Exception/InvalidArgumentException.php b/lib/Raven/Exception/InvalidArgumentException.php new file mode 100644 index 000000000..ac15f92d7 --- /dev/null +++ b/lib/Raven/Exception/InvalidArgumentException.php @@ -0,0 +1,22 @@ + + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/tests/Breadcrumbs/BreadcrumbTest.php b/tests/Breadcrumbs/BreadcrumbTest.php new file mode 100644 index 000000000..8a61787f7 --- /dev/null +++ b/tests/Breadcrumbs/BreadcrumbTest.php @@ -0,0 +1,134 @@ +withLevel('bar'); + } + + public function testConstructor() + { + $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); + + $this->assertEquals('foo', $breadcrumb->getCategory()); + $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals('foo bar', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_USER, $breadcrumb->getType()); + $this->assertEquals(['baz'], $breadcrumb->getMetadata()); + $this->assertEquals(microtime(true), $breadcrumb->getTimestamp()); + } + + public function testCreate() + { + $breadcrumb = Breadcrumb::create(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); + + $this->assertEquals('foo', $breadcrumb->getCategory()); + $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals('foo bar', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_USER, $breadcrumb->getType()); + $this->assertEquals(['baz'], $breadcrumb->getMetadata()); + $this->assertEquals(microtime(true), $breadcrumb->getTimestamp()); + } + + public function testWithCategory() + { + $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $newBreadcrumb = $breadcrumb->withCategory('bar'); + + $this->assertNotSame($breadcrumb, $newBreadcrumb); + $this->assertEquals('bar', $newBreadcrumb->getCategory()); + $this->assertSame($newBreadcrumb, $newBreadcrumb->withCategory('bar')); + } + + public function testWithLevel() + { + $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $newBreadcrumb = $breadcrumb->withLevel(Client::LEVEL_WARNING); + + $this->assertNotSame($breadcrumb, $newBreadcrumb); + $this->assertEquals(Client::LEVEL_WARNING, $newBreadcrumb->getLevel()); + $this->assertSame($newBreadcrumb, $newBreadcrumb->withLevel(Client::LEVEL_WARNING)); + } + + public function testWithType() + { + $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $newBreadcrumb = $breadcrumb->withType(Breadcrumb::TYPE_ERROR); + + $this->assertNotSame($breadcrumb, $newBreadcrumb); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $newBreadcrumb->getType()); + $this->assertSame($newBreadcrumb, $newBreadcrumb->withType(Breadcrumb::TYPE_ERROR)); + } + + public function testWithMessage() + { + $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $newBreadcrumb = $breadcrumb->withMessage('foo bar'); + + $this->assertNotSame($breadcrumb, $newBreadcrumb); + $this->assertEquals('foo bar', $newBreadcrumb->getMessage()); + $this->assertSame($newBreadcrumb, $newBreadcrumb->withMessage('foo bar')); + } + + public function testWithTimestamp() + { + $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $newBreadcrumb = $breadcrumb->withTimestamp(123); + + $this->assertNotSame($breadcrumb, $newBreadcrumb); + $this->assertEquals(123, $newBreadcrumb->getTimestamp()); + $this->assertSame($newBreadcrumb, $newBreadcrumb->withTimestamp(123)); + } + + public function testWithMetadata() + { + $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $newBreadcrumb = $breadcrumb->withMetadata('foo', 'bar'); + + $this->assertNotSame($breadcrumb, $newBreadcrumb); + $this->assertNotContains('foo', $breadcrumb->getMetadata()); + $this->assertSame(['foo' => 'bar'], $newBreadcrumb->getMetadata()); + } + + public function testWithoutMetadata() + { + $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', null, ['foo' => 'bar']); + $newBreadcrumb = $breadcrumb->withoutMetadata('foo'); + + $this->assertNotSame($breadcrumb, $newBreadcrumb); + $this->assertSame(['foo' => 'bar'], $breadcrumb->getMetadata()); + $this->assertArrayNotHasKey('foo', $newBreadcrumb->getMetadata()); + } +} diff --git a/tests/Breadcrumbs/ErrorHandlerTest.php b/tests/Breadcrumbs/ErrorHandlerTest.php index 2032346a0..38f0ed9ea 100644 --- a/tests/Breadcrumbs/ErrorHandlerTest.php +++ b/tests/Breadcrumbs/ErrorHandlerTest.php @@ -9,24 +9,32 @@ * file that was distributed with this source code. */ +namespace Raven\Tests\Breadcrumbs; + +use Raven\Breadcrumbs\ErrorHandler; +use Raven\Client; use Raven\ClientBuilder; -class Raven_Tests_ErrorHandlerBreadcrumbHandlerTest extends PHPUnit_Framework_TestCase +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase { public function testSimple() { - $client = $client = ClientBuilder::create([ + $client = ClientBuilder::create([ 'install_default_breadcrumb_handlers' => false, ])->getClient(); - $handler = new \Raven\Breadcrumbs\ErrorHandler($client); + $handler = new ErrorHandler($client); $handler->handleError(E_WARNING, 'message'); - $crumbs = $client->breadcrumbs->fetch(); + $breadcrumbsRecorder = $this->getObjectAttribute($client, 'recorder'); + + /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ + $breadcrumbs = iterator_to_array($breadcrumbsRecorder); + + $this->assertCount(1, $breadcrumbs); - $this->assertCount(1, $crumbs); - $this->assertEquals($crumbs[0]['message'], 'message'); - $this->assertEquals($crumbs[0]['category'], 'error_reporting'); - $this->assertEquals($crumbs[0]['level'], 'warning'); + $this->assertEquals($breadcrumbs[0]->getMessage(), 'message'); + $this->assertEquals($breadcrumbs[0]->getLevel(), Client::LEVEL_WARNING); + $this->assertEquals($breadcrumbs[0]->getCategory(), 'error_reporting'); } } diff --git a/tests/Breadcrumbs/MonologTest.php b/tests/Breadcrumbs/MonologHandlerTest.php similarity index 69% rename from tests/Breadcrumbs/MonologTest.php rename to tests/Breadcrumbs/MonologHandlerTest.php index 9fab59f5b..22f273958 100644 --- a/tests/Breadcrumbs/MonologTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -13,9 +13,11 @@ use Monolog\Logger; use Raven\Breadcrumbs\MonologHandler; +use Raven\Breadcrumbs\Breadcrumb; +use Raven\Client; use Raven\ClientBuilder; -class MonologTest extends \PHPUnit_Framework_TestCase +class MonologHandlerTest extends \PHPUnit_Framework_TestCase { protected function getSampleErrorMessage() { @@ -49,14 +51,18 @@ public function testSimple() $logger = new Logger('sentry'); $logger->pushHandler($handler); - $logger->addWarning('Foo'); + $logger->addWarning('foo'); - $crumbs = $client->breadcrumbs->fetch(); + $breadcrumbsRecorder = $this->getObjectAttribute($client, 'recorder'); - $this->assertCount(1, $crumbs); - $this->assertEquals($crumbs[0]['message'], 'Foo'); - $this->assertEquals($crumbs[0]['category'], 'sentry'); - $this->assertEquals($crumbs[0]['level'], 'warning'); + /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ + $breadcrumbs = iterator_to_array($breadcrumbsRecorder); + + $this->assertCount(1, $breadcrumbs); + + $this->assertEquals($breadcrumbs[0]->getMessage(), 'foo'); + $this->assertEquals($breadcrumbs[0]->getLevel(), Client::LEVEL_WARNING); + $this->assertEquals($breadcrumbs[0]->getCategory(), 'sentry'); } public function testErrorInMessage() @@ -71,12 +77,18 @@ public function testErrorInMessage() $logger->pushHandler($handler); $logger->addError($this->getSampleErrorMessage()); - $crumbs = $client->breadcrumbs->fetch(); + $breadcrumbsRecorder = $this->getObjectAttribute($client, 'recorder'); + + /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ + $breadcrumbs = iterator_to_array($breadcrumbsRecorder); + + $this->assertCount(1, $breadcrumbs); + + $metaData = $breadcrumbs[0]->getMetadata(); - $this->assertCount(1, $crumbs); - $this->assertEquals($crumbs[0]['data']['type'], 'Exception'); - $this->assertEquals($crumbs[0]['data']['value'], 'An unhandled exception'); - $this->assertEquals($crumbs[0]['category'], 'sentry'); - $this->assertEquals($crumbs[0]['level'], 'error'); + $this->assertEquals($breadcrumbs[0]->getType(), Breadcrumb::TYPE_ERROR); + $this->assertEquals($breadcrumbs[0]->getLevel(), Client::LEVEL_ERROR); + $this->assertEquals($breadcrumbs[0]->getCategory(), 'sentry'); + $this->assertEquals($metaData['value'], 'An unhandled exception'); } } diff --git a/tests/Breadcrumbs/RecorderTest.php b/tests/Breadcrumbs/RecorderTest.php new file mode 100644 index 000000000..a7771ec30 --- /dev/null +++ b/tests/Breadcrumbs/RecorderTest.php @@ -0,0 +1,90 @@ +assertCount(0, $recorder); + $this->assertEquals([], iterator_to_array($recorder)); + + $recorder->record($breadcrumb); + + $this->assertCount(1, $recorder); + $this->assertEquals([$breadcrumb], iterator_to_array($recorder)); + + for ($i = 0; $i < 2; ++$i) { + $recorder->record($breadcrumb); + } + + $this->assertCount(3, $recorder); + $this->assertEquals([$breadcrumb, $breadcrumb, $breadcrumb], iterator_to_array($recorder)); + + for ($i = 0; $i < 2; ++$i) { + $recorder->record($breadcrumb2); + } + + $this->assertCount(3, $recorder); + $this->assertEquals([$breadcrumb, $breadcrumb2, $breadcrumb2], iterator_to_array($recorder)); + } + + public function testClear() + { + $recorder = new Recorder(1); + $breadcrumb = new Breadcrumb(LogLevel::DEBUG, Breadcrumb::TYPE_USER, 'foo'); + + $this->assertCount(0, $recorder); + $this->assertEquals([], iterator_to_array($recorder)); + + $recorder->record($breadcrumb); + + $this->assertCount(1, $recorder); + $this->assertEquals([$breadcrumb], iterator_to_array($recorder)); + + $recorder->clear(); + + $this->assertCount(0, $recorder); + $this->assertEquals([], iterator_to_array($recorder)); + } +} diff --git a/tests/BreadcrumbsTest.php b/tests/BreadcrumbsTest.php deleted file mode 100644 index f746856ff..000000000 --- a/tests/BreadcrumbsTest.php +++ /dev/null @@ -1,38 +0,0 @@ -record(['message' => $i]); - } - - $results = $breadcrumbs->fetch(); - - $this->assertEquals(count($results), 10); - for ($i = 1; $i <= 10; $i++) { - $this->assertEquals($results[$i - 1]['message'], $i); - } - } - - public function testJson() - { - $breadcrumbs = new \Raven\Breadcrumbs(1); - $breadcrumbs->record(['message' => 'test']); - $json = $breadcrumbs->to_json(); - - $this->assertEquals(count($json['values']), 1); - $this->assertEquals($json['values'][0]['message'], 'test'); - } -} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 545e060d9..3b6f6ba23 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -14,7 +14,6 @@ use Http\Client\Common\Plugin; use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory; -use Http\Message\StreamFactory; use Http\Message\UriFactory; use Psr\Http\Message\RequestInterface; use Raven\Client; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f6aa96202..b258639cb 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -310,12 +310,12 @@ public function testCaptureMessageHandlesOptionsAsThirdArg() $client->store_errors_for_bulk_send = true; $client->captureMessage('Test Message %s', ['foo'], [ - 'level' => Dummy_Raven_Client::WARNING, + 'level' => Dummy_Raven_Client::LEVEL_WARNING, 'extra' => ['foo' => 'bar'] ]); $this->assertCount(1, $client->_pending_events); - $this->assertEquals(Dummy_Raven_Client::WARNING, $client->_pending_events[0]['level']); + $this->assertEquals(Dummy_Raven_Client::LEVEL_WARNING, $client->_pending_events[0]['level']); $this->assertEquals('bar', $client->_pending_events[0]['extra']['foo']); $this->assertEquals('Test Message foo', $client->_pending_events[0]['message']); } @@ -325,10 +325,10 @@ public function testCaptureMessageHandlesLevelAsThirdArg() $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; - $client->captureMessage('Test Message %s', ['foo'], Dummy_Raven_Client::WARNING); + $client->captureMessage('Test Message %s', ['foo'], Dummy_Raven_Client::LEVEL_WARNING); $this->assertCount(1, $client->_pending_events); - $this->assertEquals(Dummy_Raven_Client::WARNING, $client->_pending_events[0]['level']); + $this->assertEquals(Dummy_Raven_Client::LEVEL_WARNING, $client->_pending_events[0]['level']); $this->assertEquals('Test Message foo', $client->_pending_events[0]['message']); } @@ -389,9 +389,9 @@ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() $client->captureException($e3); $this->assertCount(3, $client->_pending_events); - $this->assertEquals(Client::WARNING, $client->_pending_events[0]['level']); - $this->assertEquals(Client::INFO, $client->_pending_events[1]['level']); - $this->assertEquals(Client::ERROR, $client->_pending_events[2]['level']); + $this->assertEquals(Client::LEVEL_WARNING, $client->_pending_events[0]['level']); + $this->assertEquals(Client::LEVEL_INFO, $client->_pending_events[1]['level']); + $this->assertEquals(Client::LEVEL_ERROR, $client->_pending_events[2]['level']); } public function testCaptureExceptionHandlesOptionsAsSecondArg() @@ -1639,30 +1639,6 @@ public function testCaptureNoUserAndRequest() @session_start(['use_cookies' => false]); } - public function testCaptureNonEmptyBreadcrumb() - { - $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; - - $ts1 = microtime(true); - - $client->breadcrumbs->record(['foo' => 'bar']); - $client->breadcrumbs->record(['honey' => 'clover']); - - $client->capture([]); - - foreach ($client->_pending_events[0]['breadcrumbs'] as &$crumb) { - $this->assertGreaterThanOrEqual($ts1, $crumb['timestamp']); - - unset($crumb['timestamp']); - } - - $this->assertEquals([ - ['foo' => 'bar'], - ['honey' => 'clover'], - ], $client->_pending_events[0]['breadcrumbs']); - } - public function testCaptureAutoLogStacks() { $client = ClientBuilder::create()->getClient(); diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 261466f8d..c3accf0c5 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -39,7 +39,7 @@ public function registerDefaultBreadcrumbHandlers() } } -class Raven_Tests_IntegrationTest extends \PHPUnit_Framework_TestCase +class IntegrationTest extends \PHPUnit_Framework_TestCase { private function create_chained_exception() { diff --git a/tests/SerializerAbstractTest.php b/tests/SerializerAbstractTest.php index 7ad3313b9..6732cb241 100644 --- a/tests/SerializerAbstractTest.php +++ b/tests/SerializerAbstractTest.php @@ -317,7 +317,8 @@ public function testRecursionMaxDepthForObject() $this->assertEquals(['key' => ['key' => ['key' => 12345]]], $result); $result = $serializer->serialize( - (object)['key' => (object)['key' => (object)['key' => (object)['key' => 12345]]]], 3 + (object)['key' => (object)['key' => (object)['key' => (object)['key' => 12345]]]], + 3 ); $this->assertEquals(['key' => ['key' => ['key' => 'Object stdClass']]], $result); } diff --git a/tests/bin/httpserver.php b/tests/bin/httpserver.php deleted file mode 100644 index c9449be29..000000000 --- a/tests/bin/httpserver.php +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/php -onRequest = function ($server, $connect) { - /** @var \NokitaKaze\TestHTTPServer\Server $server */ - /** @var \NokitaKaze\TestHTTPServer\ClientDatum $connect */ - $output_filename = $server->get_option('output_filename'); - - $connect->server = null; - $saved = file_put_contents( - $output_filename, serialize( - [ - 'connection' => $connect, - ] - ), LOCK_EX - ); - if ($saved === false) { - $server->answer($connect, 500, 'OK', ''); - $server->close_connection($connect); - echo "can not save content\n"; - exit(1); - } - if ($server->get_option('http_code') == 403) { - $server->answer($connect, 403, 'Denied', json_encode(['error' => 'Denied'])); - } else { - $server->answer($connect, 200, 'OK', json_encode(['event' => uniqid()])); - } - - $server->close_connection($connect); - echo "done\n"; - exit(0); -}; -$server = new \NokitaKaze\TestHTTPServer\Server($options); - -$server->init_listening(); -echo "listen...\n"; -$server->listen(time() + 60); - -echo "No connection\n"; -exit(1); -?> \ No newline at end of file From 189f4081da9953606d7e5473ae36237c7a95edac Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 24 Aug 2017 09:27:15 +0200 Subject: [PATCH 0306/1161] Upgrade PHPUnit (#492) * Require PHPUnit 5.7/6 * Migrate to namespaced class for tests * Fix some various PSR-4 violations in tests * Backup global variables during tests to restore same behavior of PHPUnit 5 (and prevent unknonwn test that trashes $_COOKIES) --- composer.json | 2 +- phpunit.xml.dist | 1 + tests/Breadcrumbs/BreadcrumbTest.php | 3 +- tests/Breadcrumbs/ErrorHandlerTest.php | 3 +- tests/Breadcrumbs/MonologHandlerTest.php | 3 +- tests/Breadcrumbs/RecorderTest.php | 3 +- tests/ClientBuilderTest.php | 3 +- tests/ClientTest.php | 3 +- tests/ConfigurationTest.php | 3 +- tests/ErrorHandlerTest.php | 4 ++- .../Authentication/SentryAuthTest.php | 3 +- .../Encoding/Base64EncodingStreamTest.php | 3 +- tests/IntegrationTest.php | 3 +- .../Processor/RemoveCookiesProcessorTest.php | 5 ++-- .../Processor/RemoveHttpBodyProcessorTest.php | 5 ++-- tests/Processor/SanitizeDataProcessorTest.php | 5 ++-- .../SanitizeHttpHeadersProcessorTest.php | 5 ++-- .../SanitizeStacktraceProcessorTest.php | 5 ++-- tests/ReprSerializerTest.php | 2 +- tests/SerializerAbstractTest.php | 28 ++++++++++--------- tests/SerializerTest.php | 2 +- tests/StacktraceTest.php | 6 ++-- tests/TransactionStackTest.php | 4 ++- tests/Util/JSONTest.php | 3 +- tests/UtilTest.php | 7 ++--- 25 files changed, 68 insertions(+), 46 deletions(-) diff --git a/composer.json b/composer.json index 29e7e0885..344bae29b 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "monolog/monolog": "~1.0", "php-http/curl-client": "~1.7", "php-http/mock-client": "~1.0", - "phpunit/phpunit": "^4.8 || ^5.0", + "phpunit/phpunit": "^5.7|^6.0", "symfony/phpunit-bridge": "~2.7|~3.0", "zendframework/zend-diactoros": "~1.4" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2a0c9c8e0..87acf9089 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,6 +3,7 @@ assertFalse($serializer->getAllObjectSerialize()); } } + +/** + * Class SerializerTestObject + * + * @package Raven\Tests + * @property mixed $keys + */ +class SerializerTestObject +{ + private $foo = 'bar'; + + public $key = 'value'; +} diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index cfd51b4eb..c4f4dba8c 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -4,7 +4,7 @@ require_once 'SerializerAbstractTest.php'; -class SerializerTest extends \Raven\Tests\Raven_Tests_SerializerAbstractTest +class SerializerTest extends \Raven\Tests\SerializerAbstractTest { /** * @return string diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 0bddaae7e..cbe0e6544 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -11,11 +11,12 @@ namespace Raven\Tests; +use PHPUnit\Framework\TestCase; use Raven\Client; use Raven\ClientBuilder; use Raven\Stacktrace; -class StacktraceTest extends \PHPUnit_Framework_TestCase +class StacktraceTest extends TestCase { /** * @var Client @@ -184,7 +185,8 @@ public function testAddFrameReadsCodeFromLongFile() public function testRemoveFrame($index, $throwException) { if ($throwException) { - $this->setExpectedException(\OutOfBoundsException::class, 'Invalid frame index to remove.'); + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage('Invalid frame index to remove.'); } $stacktrace = new Stacktrace($this->client); diff --git a/tests/TransactionStackTest.php b/tests/TransactionStackTest.php index 08563699a..78fdde8f2 100644 --- a/tests/TransactionStackTest.php +++ b/tests/TransactionStackTest.php @@ -11,7 +11,9 @@ namespace Raven\Tests; -class Raven_Tests_TransactionStackTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class TransactionStackTest extends TestCase { public function testSimple() { diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index 397188523..d774723e8 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -11,11 +11,12 @@ namespace Raven\Tests\Util; +use PHPUnit\Framework\TestCase; use Raven\Tests\Util\Fixtures\JsonSerializableClass; use Raven\Tests\Util\Fixtures\SimpleClass; use Raven\Util\JSON; -class JSONTest extends \PHPUnit_Framework_TestCase +class JSONTest extends TestCase { /** * @dataProvider encodeDataProvider diff --git a/tests/UtilTest.php b/tests/UtilTest.php index d492b2291..3fd3f1ae2 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -11,12 +11,9 @@ namespace Raven\Tests; -class StacktraceTestObject -{ - private $foo = 'bar'; -} +use PHPUnit\Framework\TestCase; -class Raven_Tests_UtilTest extends \PHPUnit_Framework_TestCase +class UtilTest extends TestCase { public function testGetReturnsDefaultOnMissing() { From c276208b8fdaed98888860074fb02e134fa87504 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 6 Sep 2017 09:24:46 +0200 Subject: [PATCH 0307/1161] Enforce PSR-4, @Symfony and @Symfony:risky code style rules (#494) * Enforce PSR4 in codestyle check * Enforce the Symfony code style, plus a few other rules * Apply new codestyle rules * Remove code style rule about space after not operator --- .php_cs | 5 + examples/vanilla/index.php | 5 +- lib/Raven/Breadcrumbs/Breadcrumb.php | 4 +- lib/Raven/Breadcrumbs/ErrorHandler.php | 1 + lib/Raven/Breadcrumbs/MonologHandler.php | 17 +- lib/Raven/Client.php | 89 ++++++---- lib/Raven/Context.php | 2 - lib/Raven/ErrorHandler.php | 25 +-- lib/Raven/Exception.php | 1 + .../HttpClient/Authentication/SentryAuth.php | 2 +- lib/Raven/Processor.php | 4 +- lib/Raven/Processor/SanitizeDataProcessor.php | 6 +- .../SanitizeHttpHeadersProcessor.php | 2 +- lib/Raven/ReprSerializer.php | 10 +- lib/Raven/Serializer.php | 30 ++-- lib/Raven/TransactionStack.php | 6 +- lib/Raven/Util.php | 7 +- tests/Breadcrumbs/MonologHandlerTest.php | 2 +- tests/ClientTest.php | 166 +++++++++--------- tests/ErrorHandlerTest.php | 2 +- tests/Fixtures/code/Latin1File.php | 1 + tests/Fixtures/code/LongFile.php | 1 - tests/IntegrationTest.php | 4 +- .../Processor/RemoveCookiesProcessorTest.php | 8 +- tests/Processor/SanitizeDataProcessorTest.php | 18 +- tests/ReprSerializerTest.php | 16 +- tests/SerializerAbstractTest.php | 114 ++++++------ tests/SerializerTest.php | 6 +- tests/StacktraceTest.php | 10 +- tests/bootstrap.php | 2 +- 30 files changed, 291 insertions(+), 275 deletions(-) diff --git a/.php_cs b/.php_cs index a3c53c2b8..d0609d1ed 100644 --- a/.php_cs +++ b/.php_cs @@ -3,7 +3,12 @@ return PhpCsFixer\Config::create() ->setRules([ '@PSR2' => true, + '@Symfony' => true, + '@Symfony:risky' => true, 'array_syntax' => ['syntax' => 'short'], + 'concat_space' => ['spacing' => 'one'], + 'ordered_imports' => true, + 'random_api_migration' => true, ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php index f4760517b..ad96189b3 100644 --- a/examples/vanilla/index.php +++ b/examples/vanilla/index.php @@ -15,8 +15,8 @@ function setupSentry() function createCrumbs() { - echo($undefined['foobar']); - echo($undefined['bizbaz']); + echo $undefined['foobar']; + echo $undefined['bizbaz']; } function createError() @@ -24,7 +24,6 @@ function createError() 1 / 0; } - function createException() { throw new Exception('example exception'); diff --git a/lib/Raven/Breadcrumbs/Breadcrumb.php b/lib/Raven/Breadcrumbs/Breadcrumb.php index 4b5bde52f..e64738e42 100644 --- a/lib/Raven/Breadcrumbs/Breadcrumb.php +++ b/lib/Raven/Breadcrumbs/Breadcrumb.php @@ -121,7 +121,7 @@ public function getType() } /** - * Sets the type of the breadcrumb + * Sets the type of the breadcrumb. * * @param string $type The type * @@ -295,7 +295,7 @@ public function getTimestamp() /** * Sets the breadcrumb timestamp. * - * @param float $timestamp The timestamp. + * @param float $timestamp the timestamp * * @return static */ diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index 1f8ef41b0..0e07e0a55 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -39,6 +39,7 @@ public function handleError($code, $message, $file = '', $line = 0, $context = [ public function install() { $this->existingHandler = set_error_handler([$this, 'handleError'], E_ALL); + return $this; } } diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 2a2b6fa12..3d295c334 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -12,13 +12,13 @@ class MonologHandler extends AbstractProcessingHandler * Translates Monolog log levels to Raven log levels. */ protected $logLevels = [ - Logger::DEBUG => Client::LEVEL_DEBUG, - Logger::INFO => Client::LEVEL_INFO, - Logger::NOTICE => Client::LEVEL_INFO, - Logger::WARNING => Client::LEVEL_WARNING, - Logger::ERROR => Client::LEVEL_ERROR, - Logger::CRITICAL => Client::LEVEL_FATAL, - Logger::ALERT => Client::LEVEL_FATAL, + Logger::DEBUG => Client::LEVEL_DEBUG, + Logger::INFO => Client::LEVEL_INFO, + Logger::NOTICE => Client::LEVEL_INFO, + Logger::WARNING => Client::LEVEL_WARNING, + Logger::ERROR => Client::LEVEL_ERROR, + Logger::CRITICAL => Client::LEVEL_FATAL, + Logger::ALERT => Client::LEVEL_FATAL, Logger::EMERGENCY => Client::LEVEL_FATAL, ]; @@ -43,6 +43,7 @@ public function __construct(Client $ravenClient, $level = Logger::DEBUG, $bubble /** * @param string $message + * * @return array|null */ protected function parseException($message) @@ -66,7 +67,7 @@ protected function write(array $record) if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { /** - * @var \Exception $exc + * @var \Exception */ $exc = $record['context']['exception']; diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index c95c4e95e..37c556e2b 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -21,12 +21,10 @@ use Raven\Util\JSON; /** - * Raven PHP Client + * Raven PHP Client. * - * @package raven * @doc https://docs.sentry.io/clients/php/config/ */ - class Client { const VERSION = '2.0.x-dev'; @@ -59,7 +57,7 @@ class Client const LEVEL_FATAL = 'fatal'; /** - * Default message limit + * Default message limit. */ const MESSAGE_LIMIT = 1024; @@ -69,7 +67,7 @@ class Client protected $recorder; /** - * This constant defines the client's user-agent string + * This constant defines the client's user-agent string. */ const USER_AGENT = 'sentry-php/' . self::VERSION; @@ -90,21 +88,21 @@ class Client public $store_errors_for_bulk_send = false; /** - * @var \Raven\ErrorHandler $error_handler + * @var \Raven\ErrorHandler */ protected $error_handler; /** - * @var \Raven\Serializer $serializer + * @var \Raven\Serializer */ protected $serializer; /** - * @var \Raven\Serializer $serializer + * @var \Raven\Serializer */ protected $reprSerializer; /** - * @var \Raven\Processor[] $processors An array of classes to use to process data before it is sent to Sentry + * @var \Raven\Processor[] An array of classes to use to process data before it is sent to Sentry */ protected $processors = []; @@ -120,7 +118,7 @@ class Client public $_user; /** - * @var array[] $_pending_events + * @var array[] */ public $_pending_events = []; @@ -186,7 +184,7 @@ public function __construct(Configuration $config, HttpAsyncClient $httpClient, } /** - * Destruct all objects contain link to this object + * Destruct all objects contain link to this object. * * This method can not delete shutdown handler */ @@ -213,9 +211,6 @@ public function clearBreadcrumbs() $this->recorder->clear(); } - /** - * {@inheritdoc} - */ public function getConfig() { return $this->config; @@ -253,6 +248,7 @@ public function install() $this->error_handler->registerExceptionHandler(); $this->error_handler->registerErrorHandler(); $this->error_handler->registerShutdownFunction(); + return $this; } @@ -268,7 +264,7 @@ public function createProcessors() $processorsOptions = $this->config->getProcessorsOptions(); foreach ($this->config->getProcessors() as $processor) { - /** @var Processor $processorInstance */ + /** @var Processor $processorInstance */ $processorInstance = new $processor($this); if (isset($processorsOptions[$processor])) { @@ -292,6 +288,7 @@ public function getLastError() * Given an identifier, returns a Sentry searchable string. * * @param mixed $ident + * * @return mixed * @codeCoverageIgnore */ @@ -302,12 +299,14 @@ public function getIdent($ident) } /** - * @param string $message The message (primary description) for the event. - * @param array $params params to use when formatting the message. + * @param string $message the message (primary description) for the event + * @param array $params params to use when formatting the message * @param string $level Log level group * @param bool|array $stack * @param mixed $vars + * * @return string|null + * * @deprecated * @codeCoverageIgnore */ @@ -323,7 +322,9 @@ public function message( /** * @param Exception $exception + * * @return string|null + * * @deprecated * @codeCoverageIgnore */ @@ -333,13 +334,14 @@ public function exception($exception) } /** - * Log a message to sentry + * Log a message to sentry. * - * @param string $message The message (primary description) for the event. - * @param array $params params to use when formatting the message. - * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param string $message the message (primary description) for the event + * @param array $params params to use when formatting the message + * @param array $data additional attributes to pass with this event (see Sentry docs) * @param bool|array $stack * @param mixed $vars + * * @return string|null */ public function captureMessage( @@ -377,12 +379,13 @@ public function captureMessage( } /** - * Log an exception to sentry + * Log an exception to sentry. * - * @param \Exception $exception The Exception object. - * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param \Exception $exception the Exception object + * @param array $data additional attributes to pass with this event (see Sentry docs) * @param mixed $logger * @param mixed $vars + * * @return string|null */ public function captureException($exception, $data = null, $logger = null, $vars = null) @@ -451,9 +454,9 @@ public function captureException($exception, $data = null, $logger = null, $vars return $this->capture($data, $trace, $vars); } - /** * Capture the most recent error (obtained with ``error_get_last``). + * * @return string|null */ public function captureLastError() @@ -476,7 +479,7 @@ public function captureLastError() } /** - * Log an query to sentry + * Log an query to sentry. * * @param string|null $query * @param string $level @@ -488,13 +491,14 @@ public function captureQuery($query, $level = self::LEVEL_INFO, $engine = '') 'message' => $query, 'level' => $level, 'sentry.interfaces.Query' => [ - 'query' => $query - ] + 'query' => $query, + ], ]; if ($engine !== '') { $data['sentry.interfaces.Query']['engine'] = $engine; } + return $this->capture($data, false); } @@ -584,6 +588,7 @@ protected function get_user_data() $user['data'] = $_SESSION; } } + return [ 'user' => $user, ]; @@ -726,7 +731,7 @@ public function sanitize(&$data) } if (!empty($data['tags'])) { foreach ($data['tags'] as $key => $value) { - $data['tags'][$key] = @(string)$value; + $data['tags'][$key] = @(string) $value; } } if (!empty($data['contexts'])) { @@ -735,7 +740,7 @@ public function sanitize(&$data) } /** - * Process data through all defined \Raven\Processor sub-classes + * Process data through all defined \Raven\Processor sub-classes. * * @param array $data Associative array of data to log */ @@ -777,6 +782,7 @@ public function send(&$data) if ($this->config->getTransport()) { call_user_func($this->getConfig()->getTransport(), $this, $data); + return; } @@ -822,7 +828,7 @@ public function send(&$data) } /** - * Generate an uuid4 value + * Generate an uuid4 value. * * @return string */ @@ -856,7 +862,7 @@ protected static function uuid4() } /** - * Return the URL for the current request + * Return the URL for the current request. * * @return string|null */ @@ -869,10 +875,11 @@ protected function get_current_url() // HTTP_HOST is a client-supplied header that is optional in HTTP 1.0 $host = (!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] - : (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR'] + : (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR'] : (!empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''))); $httpS = $this->isHttps() ? 's' : ''; + return "http{$httpS}://{$host}{$_SERVER['REQUEST_URI']}"; } @@ -901,10 +908,11 @@ protected function isHttps() } /** - * Get the value of a key from $_SERVER + * Get the value of a key from $_SERVER. * * @param string $key Key whose value you wish to obtain - * @return string Key's value + * + * @return string Key's value */ private static function _server_variable($key) { @@ -916,10 +924,11 @@ private static function _server_variable($key) } /** - * Translate a PHP Error constant into a Sentry log level group + * Translate a PHP Error constant into a Sentry log level group. * * @param string $severity PHP E_$x error constant - * @return string Sentry log level group + * + * @return string Sentry log level group */ public function translateSeverity($severity) { @@ -943,12 +952,13 @@ public function translateSeverity($severity) case E_STRICT: return \Raven\Client::LEVEL_INFO; case E_RECOVERABLE_ERROR: return \Raven\Client::LEVEL_ERROR; } + return \Raven\Client::LEVEL_ERROR; } /** * Provide a map of PHP Error constants to Sentry logging groups to use instead - * of the defaults in translateSeverity() + * of the defaults in translateSeverity(). * * @param string[] $map */ @@ -958,9 +968,10 @@ public function registerSeverityMap($map) } /** - * Convenience function for setting a user's ID and Email + * Convenience function for setting a user's ID and Email. * * @deprecated + * * @param string $id User's ID * @param string|null $email User's email * @param array $data Additional user data diff --git a/lib/Raven/Context.php b/lib/Raven/Context.php index bbb5ea44e..c93224dd3 100644 --- a/lib/Raven/Context.php +++ b/lib/Raven/Context.php @@ -4,8 +4,6 @@ /** * Storage for additional client context. - * - * @package raven */ class Context { diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index a3b05c076..de27e9ff6 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -12,15 +12,13 @@ */ /** - * Event handlers for exceptions and errors + * Event handlers for exceptions and errors. * * $client = new \Raven\Client('http://public:secret/example.com/1'); * $error_handler = new \Raven\ErrorHandler($client); * $error_handler->registerExceptionHandler(); * $error_handler->registerErrorHandler(); * $error_handler->registerShutdownFunction(); - * - * @package raven */ // TODO(dcramer): deprecate default error types in favor of runtime configuration @@ -48,8 +46,8 @@ class ErrorHandler /** * @var array - * Error types which should be processed by the handler. - * A 'null' value implies "whatever error_reporting is at time of error". + * Error types which should be processed by the handler. + * A 'null' value implies "whatever error_reporting is at time of error". */ protected $error_types = null; @@ -123,6 +121,7 @@ public function handleError($type, $message, $file = '', $line = 0, $context = [ return false; } } + return true; } @@ -155,14 +154,15 @@ public function shouldCaptureFatalError($type) * Register a handler which will intercept unhandled exceptions and report them to the * associated Sentry client. * - * @param bool $call_existing Call any existing exception handlers after processing - * this instance. + * @param bool $call_existing Call any existing exception handlers after processing this instance + * * @return \Raven\ErrorHandler */ public function registerExceptionHandler($call_existing = true) { $this->old_exception_handler = set_exception_handler([$this, 'handleException']); $this->call_existing_exception_handler = $call_existing; + return $this; } @@ -170,9 +170,9 @@ public function registerExceptionHandler($call_existing = true) * Register a handler which will intercept standard PHP errors and report them to the * associated Sentry client. * - * @param bool $call_existing Call any existing errors handlers after processing - * this instance. - * @param array $error_types All error types that should be sent. + * @param bool $call_existing Call any existing errors handlers after processing this instance + * @param array $error_types All error types that should be sent + * * @return \Raven\ErrorHandler */ public function registerErrorHandler($call_existing = true, $error_types = null) @@ -182,6 +182,7 @@ public function registerErrorHandler($call_existing = true, $error_types = null) } $this->old_error_handler = set_error_handler([$this, 'handleError'], E_ALL); $this->call_existing_error_handler = $call_existing; + return $this; } @@ -190,7 +191,8 @@ public function registerErrorHandler($call_existing = true, $error_types = null) * shutdown the PHP process. These are commonly things like OOM or timeouts. * * @param int $reservedMemorySize Number of kilobytes memory space to reserve, - * which is utilized when handling fatal errors. + * which is utilized when handling fatal errors + * * @return \Raven\ErrorHandler */ public function registerShutdownFunction($reservedMemorySize = 10) @@ -198,6 +200,7 @@ public function registerShutdownFunction($reservedMemorySize = 10) register_shutdown_function([$this, 'handleFatalError']); $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); + return $this; } } diff --git a/lib/Raven/Exception.php b/lib/Raven/Exception.php index b88ec3849..3afc627c8 100644 --- a/lib/Raven/Exception.php +++ b/lib/Raven/Exception.php @@ -1,4 +1,5 @@ serializeValue($value); } /** * @param object $object - * @param integer $max_depth - * @param integer $_depth + * @param int $max_depth + * @param int $_depth * @param string[] $hashes * * @return array|string @@ -142,16 +142,17 @@ protected function serializeString($value) /** * @param mixed $value - * @return string|bool|double|int|null + * + * @return string|bool|float|int|null */ protected function serializeValue($value) { - if (is_null($value) || is_bool($value) || is_float($value) || is_integer($value)) { + if (null === $value || is_bool($value) || is_float($value) || is_int($value)) { return $value; } elseif (is_object($value) || gettype($value) == 'object') { - return 'Object '.get_class($value); + return 'Object ' . get_class($value); } elseif (is_resource($value)) { - return 'Resource '.get_resource_type($value); + return 'Resource ' . get_resource_type($value); } elseif (is_array($value)) { return 'Array of length ' . count($value); } else { @@ -159,7 +160,6 @@ protected function serializeValue($value) } } - /** * @return string * @codeCoverageIgnore @@ -183,7 +183,7 @@ public function setMbDetectOrder($mb_detect_order) } /** - * @param boolean $value + * @param bool $value */ public function setAllObjectSerialize($value) { @@ -191,7 +191,7 @@ public function setAllObjectSerialize($value) } /** - * @return boolean + * @return bool */ public function getAllObjectSerialize() { diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index b50d387d8..6acbc88c7 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -7,12 +7,13 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + namespace Raven; class TransactionStack { /** - * @var array $stack + * @var array */ public $stack; @@ -32,6 +33,7 @@ public function peek() if ($len === 0) { return null; } + return $this->stack[$len - 1]; } @@ -42,6 +44,7 @@ public function push($context) /** @noinspection PhpInconsistentReturnPointsInspection * @param string|null $context + * * @return mixed */ public function pop($context = null) @@ -56,5 +59,6 @@ public function pop($context = null) } // @codeCoverageIgnoreStart } + // @codeCoverageIgnoreEnd } diff --git a/lib/Raven/Util.php b/lib/Raven/Util.php index 4fe8e570f..240cf87ec 100644 --- a/lib/Raven/Util.php +++ b/lib/Raven/Util.php @@ -11,12 +11,6 @@ namespace Raven; -/** - * Utilities - * - * @package raven - */ - class Util { /** @@ -27,6 +21,7 @@ class Util * @param array $array * @param string $var * @param mixed $default + * * @return mixed */ public static function get($array, $var, $default = null) diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php index 6a4bdf4a1..39c2e8d79 100644 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -13,8 +13,8 @@ use Monolog\Logger; use PHPUnit\Framework\TestCase; -use Raven\Breadcrumbs\MonologHandler; use Raven\Breadcrumbs\Breadcrumb; +use Raven\Breadcrumbs\MonologHandler; use Raven\Client; use Raven\ClientBuilder; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index a21da5176..fb1dbf2ff 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -20,7 +20,6 @@ use Raven\Client; use Raven\ClientBuilder; use Raven\Configuration; -use Raven\HttpClient\HttpClientFactoryInterface; use Raven\Processor\SanitizeDataProcessor; function simple_function($a = null, $b = null, $c = null) @@ -83,7 +82,7 @@ public function registerShutdownFunction() } /** - * Expose the current url method to test it + * Expose the current url method to test it. * * @return string */ @@ -110,9 +109,9 @@ class Dummy_Raven_Client_With_Sync_Override extends \Raven\Client public static function get_test_data() { - if (is_null(self::$_test_data)) { + if (null === self::$_test_data) { self::$_test_data = ''; - for ($i = 0; $i < 128; $i++) { + for ($i = 0; $i < 128; ++$i) { self::$_test_data .= chr(mt_rand(ord('a'), ord('z'))); } } @@ -122,7 +121,7 @@ public static function get_test_data() public static function test_filename() { - return sys_get_temp_dir().'/clientraven.tmp'; + return sys_get_temp_dir() . '/clientraven.tmp'; } } @@ -133,7 +132,7 @@ class ClientTest extends TestCase public static function setUpBeforeClass() { parent::setUpBeforeClass(); - self::$_folder = sys_get_temp_dir().'/sentry_server_'.microtime(true); + self::$_folder = sys_get_temp_dir() . '/sentry_server_' . microtime(true); mkdir(self::$_folder); } @@ -312,7 +311,7 @@ public function testCaptureMessageHandlesOptionsAsThirdArg() $client->captureMessage('Test Message %s', ['foo'], [ 'level' => Dummy_Raven_Client::LEVEL_WARNING, - 'extra' => ['foo' => 'bar'] + 'extra' => ['foo' => 'bar'], ]); $this->assertCount(1, $client->_pending_events); @@ -338,7 +337,7 @@ public function testCaptureMessageHandlesLevelAsThirdArg() */ public function testCaptureExceptionSetsInterfaces() { - # TODO: it'd be nice if we could mock the stacktrace extraction function here + // TODO: it'd be nice if we could mock the stacktrace extraction function here $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; @@ -358,13 +357,13 @@ public function testCaptureExceptionSetsInterfaces() $this->assertEquals('Raven\Tests\ClientTest::create_exception', $frame['function']); $this->assertFalse(isset($frame['vars'])); $this->assertEquals(' throw new \Exception(\'Foo bar\');', $frame['context_line']); - $this->assertFalse(empty($frame['pre_context'])); - $this->assertFalse(empty($frame['post_context'])); + $this->assertNotEmpty($frame['pre_context']); + $this->assertNotEmpty($frame['post_context']); } public function testCaptureExceptionChainedException() { - # TODO: it'd be nice if we could mock the stacktrace extraction function here + // TODO: it'd be nice if we could mock the stacktrace extraction function here $client = ClientBuilder::create()->getClient(); $client->store_errors_for_bulk_send = true; @@ -490,19 +489,19 @@ public function testGetDefaultData() public function testGetHttpData() { $_SERVER = [ - 'REDIRECT_STATUS' => '200', - 'CONTENT_TYPE' => 'text/xml', - 'CONTENT_LENGTH' => '99', - 'HTTP_HOST' => 'getsentry.com', - 'HTTP_ACCEPT' => 'text/html', + 'REDIRECT_STATUS' => '200', + 'CONTENT_TYPE' => 'text/xml', + 'CONTENT_LENGTH' => '99', + 'HTTP_HOST' => 'getsentry.com', + 'HTTP_ACCEPT' => 'text/html', 'HTTP_ACCEPT_CHARSET' => 'utf-8', - 'HTTP_COOKIE' => 'cupcake: strawberry', - 'SERVER_PORT' => '443', - 'SERVER_PROTOCOL' => 'HTTP/1.1', - 'REQUEST_METHOD' => 'PATCH', - 'QUERY_STRING' => 'q=bitch&l=en', - 'REQUEST_URI' => '/welcome/', - 'SCRIPT_NAME' => '/index.php', + 'HTTP_COOKIE' => 'cupcake: strawberry', + 'SERVER_PORT' => '443', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_METHOD' => 'PATCH', + 'QUERY_STRING' => 'q=bitch&l=en', + 'REQUEST_URI' => '/welcome/', + 'SCRIPT_NAME' => '/index.php', ]; $_POST = [ 'stamp' => '1c', @@ -517,20 +516,20 @@ public function testGetHttpData() 'url' => 'https://getsentry.com/welcome/', 'query_string' => 'q=bitch&l=en', 'data' => [ - 'stamp' => '1c', + 'stamp' => '1c', ], 'cookies' => [ - 'donut' => 'chocolat', + 'donut' => 'chocolat', ], 'headers' => [ - 'Host' => 'getsentry.com', - 'Accept' => 'text/html', - 'Accept-Charset' => 'utf-8', - 'Cookie' => 'cupcake: strawberry', - 'Content-Type' => 'text/xml', - 'Content-Length' => '99', + 'Host' => 'getsentry.com', + 'Accept' => 'text/html', + 'Accept-Charset' => 'utf-8', + 'Cookie' => 'cupcake: strawberry', + 'Content-Type' => 'text/xml', + 'Content-Length' => '99', ], - ] + ], ]; $config = new Configuration([ @@ -559,7 +558,7 @@ public function testCaptureMessageWithUserData() $client->user_context([ 'id' => 'unique_id', 'email' => 'foo@example.com', - 'username' => 'my_user' + 'username' => 'my_user', ]); $client->captureMessage('foo'); @@ -649,11 +648,11 @@ public function testCaptureExceptionInLatin1File() $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])->getClient(); $client->store_errors_for_bulk_send = true; - require_once(__DIR__ . '/Fixtures/code/Latin1File.php'); + require_once __DIR__ . '/Fixtures/code/Latin1File.php'; $frames = $client->_pending_events[0]['exception']['values'][0]['stacktrace']['frames']; - $utf8String = "// äöü"; + $utf8String = '// äöü'; $found = false; foreach ($frames as $frame) { @@ -683,8 +682,8 @@ public function testCaptureLastError() $this->assertNull($client->captureLastError()); $this->assertEmpty($client->_pending_events); - /** @var $undefined */ - /** @noinspection PhpExpressionResultUnusedInspection */ + /* @var $undefined */ + /* @noinspection PhpExpressionResultUnusedInspection */ @$undefined; $client->captureLastError(); @@ -700,7 +699,7 @@ public function testGetLastEventID() $client->capture([ 'message' => 'test', - 'event_id' => 'abc' + 'event_id' => 'abc', ]); $this->assertEquals('abc', $client->getLastEventID()); @@ -817,7 +816,7 @@ public function testSendCallback() $this->assertEquals('test', $data['message']); return false; - } + }, ]); /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ @@ -863,7 +862,7 @@ public function testSanitizeExtra() 'context' => [ 'line' => 1216, 'stack' => [ - 1, [2], 3 + 1, [2], 3, ], ], ]]; @@ -873,7 +872,7 @@ public function testSanitizeExtra() 'context' => [ 'line' => 1216, 'stack' => [ - 1, 'Array of length 1', 3 + 1, 'Array of length 1', 3, ], ], ]], $data); @@ -908,7 +907,7 @@ public function testSanitizeObjects() foreach ($reflection2->getProperties(\ReflectionProperty::IS_PUBLIC) as $property2) { $sub_value = $property2->getValue($value); if (is_array($sub_value)) { - $new_value[$property2->getName()] = 'Array of length '.count($sub_value); + $new_value[$property2->getName()] = 'Array of length ' . count($sub_value); continue; } if (is_object($sub_value)) { @@ -971,7 +970,7 @@ public function testSanitizeRequest() 'context' => [ 'line' => 1216, 'stack' => [ - 1, [2], 3 + 1, [2], 3, ], ], ]]; @@ -981,7 +980,7 @@ public function testSanitizeRequest() 'context' => [ 'line' => 1216, 'stack' => [ - 1, 'Array of length 1', 3 + 1, 'Array of length 1', 3, ], ], ]], $data); @@ -997,7 +996,7 @@ public function testSanitizeContexts() 1, [ 'foo' => 'bar', 'level4' => [['level5', 'level5 a'], 2], - ], 3 + ], 3, ], ], ]]; @@ -1010,7 +1009,7 @@ public function testSanitizeContexts() 1, [ 'foo' => 'bar', 'level4' => ['Array of length 2', 2], - ], 3 + ], 3, ], ], ]], $data); @@ -1047,13 +1046,14 @@ public function testUserContextWithMergeAndNull() } /** - * Set the server array to the test values, check the current url + * Set the server array to the test values, check the current url. * * @dataProvider currentUrlProvider - * @param array $serverVars - * @param array $options - * @param string $expected - the url expected - * @param string $message - fail message + * + * @param array $serverVars + * @param array $options + * @param string $expected - the url expected + * @param string $message - fail message * @covers \Raven\Client::get_current_url * @covers \Raven\Client::isHttps */ @@ -1080,7 +1080,7 @@ public function testCurrentUrl($serverVars, $options, $expected, $message) * $_SERVER data * config * expected url - * Fail message + * Fail message. * * @return array */ @@ -1091,7 +1091,7 @@ public function currentUrlProvider() [], [], null, - 'No url expected for empty REQUEST_URI' + 'No url expected for empty REQUEST_URI', ], [ [ @@ -1100,48 +1100,48 @@ public function currentUrlProvider() ], [], 'http://example.com/', - 'The url is expected to be http with the request uri' + 'The url is expected to be http with the request uri', ], [ [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', - 'HTTPS' => 'on' + 'HTTPS' => 'on', ], [], 'https://example.com/', - 'The url is expected to be https because of HTTPS on' + 'The url is expected to be https because of HTTPS on', ], [ [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', - 'SERVER_PORT' => '443' + 'SERVER_PORT' => '443', ], [], 'https://example.com/', - 'The url is expected to be https because of the server port' + 'The url is expected to be https because of the server port', ], [ [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', - 'X-FORWARDED-PROTO' => 'https' + 'X-FORWARDED-PROTO' => 'https', ], [], 'http://example.com/', - 'The url is expected to be http because the X-Forwarded header is ignored' + 'The url is expected to be http because the X-Forwarded header is ignored', ], [ [ 'REQUEST_URI' => '/', 'HTTP_HOST' => 'example.com', - 'X-FORWARDED-PROTO' => 'https' + 'X-FORWARDED-PROTO' => 'https', ], ['trust_x_forwarded_proto' => true], 'https://example.com/', - 'The url is expected to be https because the X-Forwarded header is trusted' - ] + 'The url is expected to be https because the X-Forwarded header is trusted', + ], ]; } @@ -1152,7 +1152,7 @@ public function testUuid4() { $method = new \ReflectionMethod('\\Raven\\Client', 'uuid4'); $method->setAccessible(true); - for ($i = 0; $i < 1000; $i++) { + for ($i = 0; $i < 1000; ++$i) { $this->assertRegExp('/^[0-9a-z-]+$/', $method->invoke(null)); } } @@ -1169,12 +1169,12 @@ public function testGettersAndSetters() $callable = [$this, 'stabClosureVoid']; $data = [ - ['_lasterror', null, null,], - ['_lasterror', null, 'value',], - ['_lasterror', null, mt_rand(100, 999),], - ['_last_sentry_error', null, (object)['error' => 'test',],], - ['_last_event_id', null, mt_rand(100, 999),], - ['_last_event_id', null, 'value',], + ['_lasterror', null, null], + ['_lasterror', null, 'value'], + ['_lasterror', null, mt_rand(100, 999)], + ['_last_sentry_error', null, (object) ['error' => 'test']], + ['_last_event_id', null, mt_rand(100, 999)], + ['_last_event_id', null, 'value'], ['_shutdown_function_has_been_set', null, true], ['_shutdown_function_has_been_set', null, false], ]; @@ -1195,18 +1195,18 @@ private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) } else { list($property_name, $function_name, $value_in, $value_out) = $datum; } - if (is_null($function_name)) { + if (null === $function_name) { $function_name = str_replace('_', '', $property_name); } - $method_get_name = 'get'.$function_name; - $method_set_name = 'set'.$function_name; + $method_get_name = 'get' . $function_name; + $method_set_name = 'set' . $function_name; $property = new \ReflectionProperty('\\Raven\\Client', $property_name); $property->setAccessible(true); if (method_exists($client, $method_set_name)) { $setter_output = $client->$method_set_name($value_in); - if (!is_null($setter_output) and is_object($setter_output)) { + if (null !== $setter_output and is_object($setter_output)) { // chaining call test $this->assertEquals(spl_object_hash($client), spl_object_hash($setter_output)); } @@ -1226,13 +1226,13 @@ private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) private function assertMixedValueAndArray($expected_value, $actual_value) { - if (is_null($expected_value)) { + if (null === $expected_value) { $this->assertNull($actual_value); } elseif ($expected_value === true) { $this->assertTrue($actual_value); } elseif ($expected_value === false) { $this->assertFalse($actual_value); - } elseif (is_string($expected_value) or is_integer($expected_value) or is_double($expected_value)) { + } elseif (is_string($expected_value) or is_int($expected_value) or is_float($expected_value)) { $this->assertEquals($expected_value, $actual_value); } elseif (is_array($expected_value)) { $this->assertInternalType('array', $actual_value); @@ -1261,7 +1261,7 @@ public function testTranslateSeverity() E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR, ]; $predefined[] = E_DEPRECATED; $predefined[] = E_USER_DEPRECATED; - $predefined_values = ['debug', 'info', 'warning', 'warning', 'error', 'fatal', ]; + $predefined_values = ['debug', 'info', 'warning', 'warning', 'error', 'fatal']; // step 1 foreach ($predefined as &$key) { @@ -1277,7 +1277,7 @@ public function testTranslateSeverity() $this->assertEquals('error', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123456)); // step 3 - $client->registerSeverityMap([123456 => 'foo', ]); + $client->registerSeverityMap([123456 => 'foo']); $this->assertMixedValueAndArray([123456 => 'foo'], $reflection->getValue($client)); foreach ($predefined as &$key) { $this->assertContains($client->translateSeverity($key), $predefined_values); @@ -1285,12 +1285,12 @@ public function testTranslateSeverity() $this->assertEquals('foo', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); // step 4 - $client->registerSeverityMap([E_USER_ERROR => 'bar', ]); + $client->registerSeverityMap([E_USER_ERROR => 'bar']); $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); $this->assertEquals('error', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); // step 5 - $client->registerSeverityMap([E_USER_ERROR => 'bar', 123456 => 'foo', ]); + $client->registerSeverityMap([E_USER_ERROR => 'bar', 123456 => 'foo']); $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); $this->assertEquals('foo', $client->translateSeverity(123456)); $this->assertEquals('error', $client->translateSeverity(123457)); @@ -1524,7 +1524,7 @@ public function testGet_user_data() // step 3 session_id($session_id); - @session_start(['use_cookies' => false, ]); + @session_start(['use_cookies' => false]); $_SESSION = ['foo' => 'bar']; $output = $client->get_user_data(); $this->assertInternalType('array', $output); @@ -1549,7 +1549,7 @@ public function testCaptureLevel() foreach ([Client::MESSAGE_LIMIT * 3, 100] as $length) { $message = ''; - for ($i = 0; $i < $length; $i++) { + for ($i = 0; $i < $length; ++$i) { $message .= chr($i % 256); } @@ -1663,7 +1663,7 @@ public function testSampleRateAbsolute($options) ->setHttpClient($httpClient) ->getClient(); - for ($i = 0; $i < 10; $i++) { + for ($i = 0; $i < 10; ++$i) { $client->captureMessage('foobar'); } diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 418f9eba9..1a1c25699 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -114,7 +114,7 @@ public function testExceptionHandlerPropagatesToNative() ->with($this->isInstanceOf('Exception')); $handler = new \Raven\ErrorHandler($client); - + set_exception_handler(null); $handler->registerExceptionHandler(false); diff --git a/tests/Fixtures/code/Latin1File.php b/tests/Fixtures/code/Latin1File.php index 3fabbd698..9f0f951be 100644 --- a/tests/Fixtures/code/Latin1File.php +++ b/tests/Fixtures/code/Latin1File.php @@ -1,4 +1,5 @@ captureException(new \Exception()); diff --git a/tests/Fixtures/code/LongFile.php b/tests/Fixtures/code/LongFile.php index 9821d143c..f2c9ff75d 100644 --- a/tests/Fixtures/code/LongFile.php +++ b/tests/Fixtures/code/LongFile.php @@ -6,7 +6,6 @@ $d = 4; throw new Exception('Foo'); - $e = 5; $f = 6; $g = 7; diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 360d44762..2e3e627ad 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -12,7 +12,6 @@ use PHPUnit\Framework\TestCase; use Raven\ClientBuilder; -use Raven\Configuration; class DummyIntegration_Raven_Client extends \Raven\Client { @@ -22,6 +21,7 @@ public function getSentEvents() { return $this->__sent_events; } + public function send(&$data) { if (false === $this->config->shouldCapture($data)) { @@ -30,10 +30,12 @@ public function send(&$data) } $this->__sent_events[] = $data; } + public static function is_http_request() { return true; } + // short circuit breadcrumbs public function registerDefaultBreadcrumbHandlers() { diff --git a/tests/Processor/RemoveCookiesProcessorTest.php b/tests/Processor/RemoveCookiesProcessorTest.php index 5f0481e43..6677f900d 100644 --- a/tests/Processor/RemoveCookiesProcessorTest.php +++ b/tests/Processor/RemoveCookiesProcessorTest.php @@ -58,19 +58,19 @@ public function processDataProvider() ], [ [ 'request' => [ - 'foo' => 'bar', + 'foo' => 'bar', 'cookies' => 'baz', 'headers' => [ - 'Cookie' => 'bar', + 'Cookie' => 'bar', 'AnotherHeader' => 'foo', ], ], ], [ 'request' => [ - 'foo' => 'bar', + 'foo' => 'bar', 'cookies' => RemoveCookiesProcessor::STRING_MASK, 'headers' => [ - 'Cookie' => RemoveCookiesProcessor::STRING_MASK, + 'Cookie' => RemoveCookiesProcessor::STRING_MASK, 'AnotherHeader' => 'foo', ], ], diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index 4cb9b9add..b761e6544 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -32,10 +32,10 @@ public function testDoesFilterHttpData() '1111', '2222', '3333', - '4444' - ] + '4444', + ], ], - ] + ], ]; $client = ClientBuilder::create()->getClient(); @@ -62,7 +62,7 @@ public function testDoesFilterSessionId() 'cookies' => [ ini_get('session.name') => 'abc', ], - ] + ], ]; $client = ClientBuilder::create()->getClient(); @@ -98,7 +98,7 @@ public function testSettingProcessorOptions() $options = [ 'fields_re' => '/(api_token)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/' + 'values_re' => '/^(?:\d[ -]*?){15,16}$/', ]; $processor->setProcessorOptions($options); @@ -141,9 +141,9 @@ public function testOverridenSanitize($processorOptions, $clientOptions) 'card_number' => [ '1111111111111111', '2222', - ] + ], ], - ] + ], ]; $client = ClientBuilder::create($clientOptions)->getClient(); @@ -182,11 +182,11 @@ public static function overrideDataProvider() $client_options = [ 'processors' => [SanitizeDataProcessor::class], - 'processors_options' => $processorOptions + 'processors_options' => $processorOptions, ]; return [ - [$processorOptions, $client_options] + [$processorOptions, $client_options], ]; } } diff --git a/tests/ReprSerializerTest.php b/tests/ReprSerializerTest.php index 6ba39f7c2..7af36ce37 100644 --- a/tests/ReprSerializerTest.php +++ b/tests/ReprSerializerTest.php @@ -24,7 +24,7 @@ protected static function get_test_class() } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testIntsAreInts($serialize_all_objects) @@ -40,7 +40,7 @@ public function testIntsAreInts($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testFloats($serialize_all_objects) @@ -56,7 +56,7 @@ public function testFloats($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testBooleans($serialize_all_objects) @@ -76,7 +76,7 @@ public function testBooleans($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testNull($serialize_all_objects) @@ -92,7 +92,7 @@ public function testNull($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam * @covers \Raven\ReprSerializer::serializeValue */ @@ -103,15 +103,15 @@ public function testSerializeRoundedFloat($serialize_all_objects) $serializer->setAllObjectSerialize(true); } - $result = $serializer->serialize((double)1); + $result = $serializer->serialize((float) 1); $this->assertInternalType('string', $result); $this->assertEquals('1.0', $result); - $result = $serializer->serialize((double)floor(5 / 2)); + $result = $serializer->serialize((float) floor(5 / 2)); $this->assertInternalType('string', $result); $this->assertEquals('2.0', $result); - $result = $serializer->serialize((double)floor(12345.678901234)); + $result = $serializer->serialize((float) floor(12345.678901234)); $this->assertInternalType('string', $result); $this->assertEquals('12345.0', $result); } diff --git a/tests/SerializerAbstractTest.php b/tests/SerializerAbstractTest.php index 044e42430..4b2d904e5 100644 --- a/tests/SerializerAbstractTest.php +++ b/tests/SerializerAbstractTest.php @@ -32,13 +32,13 @@ public function dataGetBaseParam() } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testArraysAreArrays($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer * */ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -49,13 +49,13 @@ public function testArraysAreArrays($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testStdClassAreArrays($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -69,7 +69,7 @@ public function testStdClassAreArrays($serialize_all_objects) public function testObjectsAreStrings() { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer * */ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); $input = new \Raven\Tests\SerializerTestObject(); $result = $serializer->serialize($input); @@ -79,7 +79,7 @@ public function testObjectsAreStrings() public function testObjectsAreNotStrings() { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer * */ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); $serializer->setAllObjectSerialize(true); $input = new \Raven\Tests\SerializerTestObject(); @@ -88,13 +88,13 @@ public function testObjectsAreNotStrings() } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testIntsAreInts($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -106,13 +106,13 @@ public function testIntsAreInts($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testFloats($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -124,13 +124,13 @@ public function testFloats($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testBooleans($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -145,13 +145,13 @@ public function testBooleans($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testNull($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -162,13 +162,13 @@ public function testNull($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testRecursionMaxDepth($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -195,74 +195,73 @@ public function dataRecursionInObjects() { $data = []; // case 1 - $object = new SerializerTestObject; + $object = new SerializerTestObject(); $object->key = $object; $data[] = [ - 'object' => $object, + 'object' => $object, 'result_serialize' => ['key' => 'Object Raven\Tests\SerializerTestObject'], ]; // case 2 - $object = new SerializerTestObject; - $object2 = new SerializerTestObject; + $object = new SerializerTestObject(); + $object2 = new SerializerTestObject(); $object2->key = $object; $object->key = $object2; $data[] = [ - 'object' => $object, + 'object' => $object, 'result_serialize' => ['key' => ['key' => 'Object Raven\Tests\SerializerTestObject']], ]; // case 3 - $object = new SerializerTestObject; - $object2 = new SerializerTestObject; + $object = new SerializerTestObject(); + $object2 = new SerializerTestObject(); $object2->key = 'foobar'; $object->key = $object2; $data[] = [ - 'object' => $object, + 'object' => $object, 'result_serialize' => ['key' => ['key' => 'foobar']], ]; // case 4 - $object3 = new SerializerTestObject; + $object3 = new SerializerTestObject(); $object3->key = 'foobar'; - $object2 = new SerializerTestObject; + $object2 = new SerializerTestObject(); $object2->key = $object3; - $object = new SerializerTestObject; + $object = new SerializerTestObject(); $object->key = $object2; $data[] = [ - 'object' => $object, + 'object' => $object, 'result_serialize' => ['key' => ['key' => ['key' => 'foobar']]], ]; // case 5 - $object4 = new SerializerTestObject; + $object4 = new SerializerTestObject(); $object4->key = 'foobar'; - $object3 = new SerializerTestObject; + $object3 = new SerializerTestObject(); $object3->key = $object4; - $object2 = new SerializerTestObject; + $object2 = new SerializerTestObject(); $object2->key = $object3; - $object = new SerializerTestObject; + $object = new SerializerTestObject(); $object->key = $object2; $data[] = [ - 'object' => $object, + 'object' => $object, 'result_serialize' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject']]], ]; // case 6 - $object3 = new SerializerTestObject; - $object2 = new SerializerTestObject; + $object3 = new SerializerTestObject(); + $object2 = new SerializerTestObject(); $object2->key = $object3; $object2->keys = 'keys'; - $object = new SerializerTestObject; + $object = new SerializerTestObject(); $object->key = $object2; $object3->key = $object2; $data[] = [ - 'object' => $object, - 'result_serialize' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject'], - 'keys' => 'keys']], + 'object' => $object, + 'result_serialize' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject'], + 'keys' => 'keys', ]], ]; - // foreach ($data as &$datum) { if (!isset($datum['result_serialize_object'])) { $datum['result_serialize_object'] = $datum['result_serialize']; @@ -282,7 +281,7 @@ public function dataRecursionInObjects() public function testRecursionInObjects($object, $result_serialize, $result_serialize_object) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); $serializer->setAllObjectSerialize(true); @@ -295,18 +294,18 @@ public function testRecursionInObjects($object, $result_serialize, $result_seria public function testRecursionMaxDepthForObject() { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); $serializer->setAllObjectSerialize(true); - $result = $serializer->serialize((object)['key' => (object)['key' => 12345]], 3); + $result = $serializer->serialize((object) ['key' => (object) ['key' => 12345]], 3); $this->assertEquals(['key' => ['key' => 12345]], $result); - $result = $serializer->serialize((object)['key' => (object)['key' => (object)['key' => 12345]]], 3); + $result = $serializer->serialize((object) ['key' => (object) ['key' => (object) ['key' => 12345]]], 3); $this->assertEquals(['key' => ['key' => ['key' => 12345]]], $result); $result = $serializer->serialize( - (object)['key' => (object)['key' => (object)['key' => (object)['key' => 12345]]]], + (object) ['key' => (object) ['key' => (object) ['key' => (object) ['key' => 12345]]]], 3 ); $this->assertEquals(['key' => ['key' => ['key' => 'Object stdClass']]], $result); @@ -315,7 +314,7 @@ public function testRecursionMaxDepthForObject() public function testObjectInArray() { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); $input = ['foo' => new \Raven\Tests\SerializerTestObject()]; $result = $serializer->serialize($input); @@ -325,7 +324,7 @@ public function testObjectInArray() public function testObjectInArraySerializeAll() { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); $serializer->setAllObjectSerialize(true); $input = ['foo' => new \Raven\Tests\SerializerTestObject()]; @@ -334,13 +333,13 @@ public function testObjectInArraySerializeAll() } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testBrokenEncoding($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -356,21 +355,21 @@ public function testBrokenEncoding($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testLongString($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } - for ($i = 0; $i < 100; $i++) { + for ($i = 0; $i < 100; ++$i) { foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { $input = ''; - for ($i = 0; $i < $length; $i++) { + for ($i = 0; $i < $length; ++$i) { $input .= chr(mt_rand(0, 255)); } $result = $serializer->serialize($input); @@ -381,13 +380,13 @@ public function testLongString($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam */ public function testSerializeValueResource($serialize_all_objects) { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); @@ -403,7 +402,7 @@ public function testSerializeValueResource($serialize_all_objects) public function testSetAllObjectSerialize() { $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer **/ + /** @var \Raven\Serializer $serializer */ $serializer = new $class_name(); $serializer->setAllObjectSerialize(true); $this->assertTrue($serializer->getAllObjectSerialize()); @@ -413,9 +412,8 @@ public function testSetAllObjectSerialize() } /** - * Class SerializerTestObject + * Class SerializerTestObject. * - * @package Raven\Tests * @property mixed $keys */ class SerializerTestObject diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index c4f4dba8c..fc9fbc9e0 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -15,7 +15,7 @@ protected static function get_test_class() } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam * @covers \Raven\Serializer::serializeString */ @@ -25,7 +25,7 @@ public function testBrokenEncoding($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam * @covers \Raven\Serializer::serializeString */ @@ -35,7 +35,7 @@ public function testLongString($serialize_all_objects) } /** - * @param boolean $serialize_all_objects + * @param bool $serialize_all_objects * @dataProvider dataGetBaseParam * @covers \Raven\Serializer::serializeValue */ diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index cbe0e6544..b4343b767 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -83,7 +83,7 @@ public function testAddFrameSerializesMethodArguments() 'file' => 'path/to/file', 'line' => 12, 'function' => 'test_function', - 'args' => [1, 'foo'] + 'args' => [1, 'foo'], ]); $frames = $stacktrace->getFrames(); @@ -249,13 +249,13 @@ public function testGetFrameArgumentsDoesNotModifyCapturedArgs() ]; $frame = [ - "file" => dirname(__FILE__) . "/resources/a.php", - "line" => 9, - "args"=> [ + 'file' => __DIR__ . '/resources/a.php', + 'line' => 9, + 'args' => [ &$newFoo, &$nestedArray, ], - "function" => "a_test", + 'function' => 'a_test', ]; $result = Stacktrace::getFrameArguments($frame, 5); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 462dba31a..1fb223f0e 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -13,4 +13,4 @@ session_start(); -require_once __DIR__.'/../vendor/autoload.php'; +require_once __DIR__ . '/../vendor/autoload.php'; From dcc287f6579b765c7935e0e60fa642ae029b834f Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 11 Sep 2017 16:33:51 +0200 Subject: [PATCH 0308/1161] Improve Client and refactor Context (#495) * Improve the Client code style and use camelCase * Refactor Context into a proper class with encapsulation and own responsibilities * Rebase and fix code style according to newly merged rules --- docs/config.rst | 2 +- docs/index.rst | 4 +- docs/integrations/laravel.rst | 10 +- lib/Raven/Client.php | 290 +++++--------- lib/Raven/Context.php | 78 +++- lib/Raven/ErrorHandler.php | 2 +- tests/ClientTest.php | 354 ++++++++---------- tests/ContextTest.php | 69 ++++ tests/IntegrationTest.php | 6 +- .../SanitizeStacktraceProcessorTest.php | 10 +- 10 files changed, 420 insertions(+), 405 deletions(-) create mode 100644 tests/ContextTest.php diff --git a/docs/config.rst b/docs/config.rst index eaf2725b3..b97e8de53 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -259,7 +259,7 @@ There are three primary methods for providing request context: .. code-block:: php // bind the logged in user - $client->user_context(array('email' => 'foo@example.com')); + $client->setUserContext(array('email' => 'foo@example.com')); // tag the request with something interesting $client->tags_context(array('interesting' => 'yes')); diff --git a/docs/index.rst b/docs/index.rst index a08806abb..cac1a0ff0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,11 +48,11 @@ Much of the usefulness of Sentry comes from additional context data with the events. The PHP client makes this very convenient by providing methods to set thread local context data that is then submitted automatically with all events. For instance you can use the -``user_context`` method to add information about the current user: +``setUserContext`` method to add information about the current user: .. sourcecode:: php - $client->user_context(array( + $client->setUserContext(array( 'email' => $USER->getEmail() )); diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 76ebf4e15..ecc16eb12 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -244,9 +244,9 @@ In the following example, we'll use a middleware: // Add user context if (auth()->check()) { - $sentry->user_context([...]); + $sentry->setUserContext([...]); } else { - $sentry->user_context(['id' => null]); + $sentry->setUserContext(['id' => null]); } // Add tags context @@ -290,13 +290,13 @@ The following settings are available for the client: 'breadcrumbs.sql_bindings' => false, -.. describe:: user_context +.. describe:: setUserContext - Capture user_context automatically. + Capture setUserContext automatically. Defaults to ``true``. .. code-block:: php - 'user_context' => false, + 'setUserContext' => false, diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 37c556e2b..051bd8f95 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -32,28 +32,12 @@ class Client const PROTOCOL = '6'; /** - * This constant defines the debug log level. + * Debug log levels. */ const LEVEL_DEBUG = 'debug'; - - /** - * This constant defines the info log level. - */ const LEVEL_INFO = 'info'; - - /** - * This constant defines the warning log level. - */ const LEVEL_WARNING = 'warning'; - - /** - * This constant defines the error log level. - */ const LEVEL_ERROR = 'error'; - - /** - * This constant defines the fatal log level. - */ const LEVEL_FATAL = 'fatal'; /** @@ -84,13 +68,13 @@ class Client /** * @var string[]|null */ - public $severity_map; - public $store_errors_for_bulk_send = false; + public $severityMap; + public $storeErrorsForBulkSend = false; /** - * @var \Raven\ErrorHandler + * @var ErrorHandler */ - protected $error_handler; + protected $errorHandler; /** * @var \Raven\Serializer @@ -109,23 +93,19 @@ class Client /** * @var string|int|null */ - public $_lasterror; - /** - * @var object|null - */ - protected $_last_sentry_error; - public $_last_event_id; - public $_user; + private $lastError; + + private $lastEventId; /** * @var array[] */ - public $_pending_events = []; + public $pendingEvents = []; /** * @var bool */ - protected $_shutdown_function_has_been_set = false; + protected $shutdownFunctionHasBeenSet = false; /** * @var Configuration The client configuration @@ -166,7 +146,7 @@ public function __construct(Configuration $config, HttpAsyncClient $httpClient, $this->reprSerializer = new ReprSerializer($this->config->getMbDetectOrder()); $this->processors = $this->createProcessors(); - if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) { + if (static::isHttpRequest() && isset($_SERVER['PATH_INFO'])) { $this->transaction->push($_SERVER['PATH_INFO']); } @@ -238,16 +218,19 @@ public function getSerializer() /** * Installs any available automated hooks (such as error_reporting). + * + * @throws \Raven\Exception */ public function install() { - if ($this->error_handler) { - throw new \Raven\Exception(sprintf('%s->install() must only be called once', get_class($this))); + if ($this->errorHandler) { + throw new \Raven\Exception(__CLASS__ . '->install() must only be called once'); } - $this->error_handler = new \Raven\ErrorHandler($this, false, $this->getConfig()->getErrorTypes()); - $this->error_handler->registerExceptionHandler(); - $this->error_handler->registerErrorHandler(); - $this->error_handler->registerShutdownFunction(); + + $this->errorHandler = new ErrorHandler($this, false, $this->getConfig()->getErrorTypes()); + $this->errorHandler->registerExceptionHandler(); + $this->errorHandler->registerErrorHandler(); + $this->errorHandler->registerShutdownFunction(); return $this; } @@ -281,7 +264,7 @@ public function createProcessors() public function getLastError() { - return $this->_lasterror; + return $this->lastError; } /** @@ -314,25 +297,12 @@ public function message( $message, $params = [], $level = self::LEVEL_INFO, - $stack = false, + $stack = false, $vars = null ) { return $this->captureMessage($message, $params, $level, $stack, $vars); } - /** - * @param Exception $exception - * - * @return string|null - * - * @deprecated - * @codeCoverageIgnore - */ - public function exception($exception) - { - return $this->captureException($exception); - } - /** * Log a message to sentry. * @@ -348,7 +318,7 @@ public function captureMessage( $message, $params = [], $data = [], - $stack = false, + $stack = false, $vars = null ) { // Gracefully handle messages which contain formatting characters, but were not @@ -398,33 +368,28 @@ public function captureException($exception, $data = null, $logger = null, $vars $data = []; } - $exc = $exception; + $currentException = $exception; do { - $exc_data = [ - 'value' => $this->serializer->serialize($exc->getMessage()), - 'type' => get_class($exc), + $exceptionData = [ + 'value' => $this->serializer->serialize($currentException->getMessage()), + 'type' => get_class($currentException), ]; - /**'exception' + /** * Exception::getTrace doesn't store the point at where the exception * was thrown, so we have to stuff it in ourselves. Ugh. */ - $trace = $exc->getTrace(); - $frame_where_exception_thrown = [ - 'file' => $exc->getFile(), - 'line' => $exc->getLine(), + $trace = $currentException->getTrace(); + $frameWhereExceptionWasThrown = [ + 'file' => $currentException->getFile(), + 'line' => $currentException->getLine(), ]; - array_unshift($trace, $frame_where_exception_thrown); + array_unshift($trace, $frameWhereExceptionWasThrown); - // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('\\Raven\\Stacktrace')) { - // @codeCoverageIgnoreStart - spl_autoload_call('\\Raven\\Stacktrace'); - // @codeCoverageIgnoreEnd - } + $this->autoloadRavenStacktrace(); - $exc_data['stacktrace'] = [ + $exceptionData['stacktrace'] = [ 'frames' => Stacktrace::fromBacktrace( $this, $exception->getTrace(), @@ -433,8 +398,8 @@ public function captureException($exception, $data = null, $logger = null, $vars )->getFrames(), ]; - $exceptions[] = $exc_data; - } while ($exc = $exc->getPrevious()); + $exceptions[] = $exceptionData; + } while ($currentException = $currentException->getPrevious()); $data['exception'] = [ 'values' => array_reverse($exceptions), @@ -505,21 +470,21 @@ public function captureQuery($query, $level = self::LEVEL_INFO, $engine = '') /** * Return the last captured event's ID or null if none available. */ - public function getLastEventID() + public function getLastEventId() { - return $this->_last_event_id; + return $this->lastEventId; } protected function registerDefaultBreadcrumbHandlers() { - $handler = new \Raven\Breadcrumbs\ErrorHandler($this); + $handler = new Breadcrumbs\ErrorHandler($this); $handler->install(); } protected function registerShutdownFunction() { - if (!$this->_shutdown_function_has_been_set) { - $this->_shutdown_function_has_been_set = true; + if (!$this->shutdownFunctionHasBeenSet) { + $this->shutdownFunctionHasBeenSet = true; register_shutdown_function([$this, 'onShutdown']); } } @@ -528,12 +493,12 @@ protected function registerShutdownFunction() * @return bool * @codeCoverageIgnore */ - protected static function is_http_request() + protected static function isHttpRequest() { return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; } - protected function get_http_data() + protected function getHttpData() { $headers = []; @@ -550,7 +515,7 @@ protected function get_http_data() $result = [ 'method' => self::_server_variable('REQUEST_METHOD'), - 'url' => $this->get_current_url(), + 'url' => $this->getCurrentUrl(), 'query_string' => self::_server_variable('QUERY_STRING'), ]; @@ -571,10 +536,10 @@ protected function get_http_data() ]; } - protected function get_user_data() + protected function getUserData() { - $user = $this->context->user; - if ($user === null) { + $user = $this->context->getUserData(); + if (empty($user)) { if (!function_exists('session_id') || !session_id()) { return []; } @@ -594,7 +559,7 @@ protected function get_user_data() ]; } - public function get_default_data() + public function getDefaultData() { return [ 'server_name' => $this->config->getServerName(), @@ -632,13 +597,13 @@ public function capture($data, $stack = null, $vars = null) $data['message'] = substr($data['message'], 0, self::MESSAGE_LIMIT); } - $data = array_merge($this->get_default_data(), $data); + $data = array_merge($this->getDefaultData(), $data); - if (static::is_http_request()) { - $data = array_merge($this->get_http_data(), $data); + if (static::isHttpRequest()) { + $data = array_merge($this->getHttpData(), $data); } - $data = array_merge($this->get_user_data(), $data); + $data = array_merge($this->getUserData(), $data); if (!empty($this->config->getRelease())) { $data['release'] = $this->config->getRelease(); @@ -650,12 +615,12 @@ public function capture($data, $stack = null, $vars = null) $data['tags'] = array_merge( $this->config->getTags(), - $this->context->tags, + $this->context->getTags(), $data['tags'] ); $data['extra'] = array_merge( - $this->context->extra, + $this->context->getExtraData(), $data['extra'] ); @@ -672,7 +637,7 @@ public function capture($data, $stack = null, $vars = null) unset($data['request']); } - if (!empty($this->recorder)) { + if (null !== $this->recorder) { $data['breadcrumbs'] = iterator_to_array($this->recorder); } @@ -684,12 +649,7 @@ public function capture($data, $stack = null, $vars = null) } if (!empty($stack)) { - // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('\\Raven\\Stacktrace')) { - // @codeCoverageIgnoreStart - spl_autoload_call('\\Raven\\Stacktrace'); - // @codeCoverageIgnoreEnd - } + $this->autoloadRavenStacktrace(); if (!isset($data['stacktrace']) && !isset($data['exception'])) { $data['stacktrace'] = [ @@ -706,13 +666,13 @@ public function capture($data, $stack = null, $vars = null) $this->sanitize($data); $this->process($data); - if (!$this->store_errors_for_bulk_send) { + if (!$this->storeErrorsForBulkSend) { $this->send($data); } else { - $this->_pending_events[] = $data; + $this->pendingEvents[] = $data; } - $this->_last_event_id = $data['event_id']; + $this->lastEventId = $data['event_id']; return $data['event_id']; } @@ -753,15 +713,15 @@ public function process(&$data) public function sendUnsentErrors() { - foreach ($this->_pending_events as $data) { + foreach ($this->pendingEvents as $data) { $this->send($data); } - $this->_pending_events = []; + $this->pendingEvents = []; - if ($this->store_errors_for_bulk_send) { + if ($this->storeErrorsForBulkSend) { //in case an error occurs after this is called, on shutdown, send any new errors. - $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED'); + $this->storeErrorsForBulkSend = !defined('RAVEN_CLIENT_END_REACHED'); } foreach ($this->pendingRequests as $pendingRequest) { @@ -866,7 +826,7 @@ protected static function uuid4() * * @return string|null */ - protected function get_current_url() + protected function getCurrentUrl() { // When running from commandline the REQUEST_URI is missing. if (!isset($_SERVER['REQUEST_URI'])) { @@ -932,28 +892,32 @@ private static function _server_variable($key) */ public function translateSeverity($severity) { - if (is_array($this->severity_map) && isset($this->severity_map[$severity])) { - return $this->severity_map[$severity]; + if (is_array($this->severityMap) && isset($this->severityMap[$severity])) { + return $this->severityMap[$severity]; } + switch ($severity) { - case E_DEPRECATED: return \Raven\Client::LEVEL_WARNING; - case E_USER_DEPRECATED: return \Raven\Client::LEVEL_WARNING; - case E_ERROR: return \Raven\Client::LEVEL_ERROR; - case E_WARNING: return \Raven\Client::LEVEL_WARNING; - case E_PARSE: return \Raven\Client::LEVEL_ERROR; - case E_NOTICE: return \Raven\Client::LEVEL_INFO; - case E_CORE_ERROR: return \Raven\Client::LEVEL_ERROR; - case E_CORE_WARNING: return \Raven\Client::LEVEL_WARNING; - case E_COMPILE_ERROR: return \Raven\Client::LEVEL_ERROR; - case E_COMPILE_WARNING: return \Raven\Client::LEVEL_WARNING; - case E_USER_ERROR: return \Raven\Client::LEVEL_ERROR; - case E_USER_WARNING: return \Raven\Client::LEVEL_WARNING; - case E_USER_NOTICE: return \Raven\Client::LEVEL_INFO; - case E_STRICT: return \Raven\Client::LEVEL_INFO; - case E_RECOVERABLE_ERROR: return \Raven\Client::LEVEL_ERROR; + case E_DEPRECATED: + case E_USER_DEPRECATED: + case E_WARNING: + case E_CORE_WARNING: + case E_COMPILE_WARNING: + case E_USER_WARNING: + case E_RECOVERABLE_ERROR: + return self::LEVEL_WARNING; + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + return self::LEVEL_ERROR; + case E_NOTICE: + case E_USER_NOTICE: + case E_STRICT: + return self::LEVEL_INFO; + default: + return self::LEVEL_ERROR; } - - return \Raven\Client::LEVEL_ERROR; } /** @@ -964,26 +928,7 @@ public function translateSeverity($severity) */ public function registerSeverityMap($map) { - $this->severity_map = $map; - } - - /** - * Convenience function for setting a user's ID and Email. - * - * @deprecated - * - * @param string $id User's ID - * @param string|null $email User's email - * @param array $data Additional user data - * @codeCoverageIgnore - */ - public function set_user_data($id, $email = null, $data = []) - { - $user = ['id' => $id]; - if (isset($email)) { - $user['email'] = $email; - } - $this->user_context(array_merge($user, $data)); + $this->severityMap = $map; } public function onShutdown() @@ -995,42 +940,11 @@ public function onShutdown() } /** - * Sets user context. - * - * @param array $data Associative array of user data - * @param bool $merge Merge existing context with new context + * @return Context */ - public function user_context($data, $merge = true) + public function getContext() { - if ($merge && $this->context->user !== null) { - // bail if data is null - if (!$data) { - return; - } - $this->context->user = array_merge($this->context->user, $data); - } else { - $this->context->user = $data; - } - } - - /** - * Appends tags context. - * - * @param array $data Associative array of tags - */ - public function tags_context($data) - { - $this->context->tags = array_merge($this->context->tags, $data); - } - - /** - * Appends additional context. - * - * @param array $data Associative array of extra data - */ - public function extra_context($data) - { - $this->context->extra = array_merge($this->context->extra, $data); + return $this->context; } /** @@ -1041,20 +955,12 @@ public function setProcessors(array $processors) $this->processors = $processors; } - /** - * @return object|null - */ - public function getLastSentryError() - { - return $this->_last_sentry_error; - } - /** * @return bool */ public function getShutdownFunctionHasBeenSet() { - return $this->_shutdown_function_has_been_set; + return $this->shutdownFunctionHasBeenSet; } public function setAllObjectSerialize($value) @@ -1072,4 +978,14 @@ private function isEncodingCompressed() { return 'gzip' === $this->config->getEncoding(); } + + private function autoloadRavenStacktrace() + { + // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) + if (!class_exists('\\Raven\\Stacktrace')) { + // @codeCoverageIgnoreStart + spl_autoload_call('\\Raven\\Stacktrace'); + // @codeCoverageIgnoreEnd + } + } } diff --git a/lib/Raven/Context.php b/lib/Raven/Context.php index c93224dd3..1e7e20404 100644 --- a/lib/Raven/Context.php +++ b/lib/Raven/Context.php @@ -10,15 +10,17 @@ class Context /** * @var array */ - public $tags; + private $tags; + /** * @var array */ - public $extra; + private $userData; + /** - * @var array|null + * @var array */ - public $user; + private $extraData; public function __construct() { @@ -31,7 +33,71 @@ public function __construct() public function clear() { $this->tags = []; - $this->extra = []; - $this->user = null; + $this->extraData = []; + $this->userData = []; + } + + public function setTag($name, $value) + { + if ( + !is_string($name) + || '' === $name + ) { + throw new \InvalidArgumentException('Invalid tag name'); + } + + $this->tags[$name] = $value; + } + + public function setUserId($userId) + { + $this->userData['id'] = $userId; + } + + public function setUserEmail($userEmail) + { + $this->userData['email'] = $userEmail; + } + + /** + * @param array $data + */ + public function setUserData(array $data) + { + $this->userData = $data; + } + + public function mergeUserData(array $data = []) + { + $this->userData = array_merge($this->userData, $data); + } + + public function mergeExtraData(array $data = []) + { + $this->extraData = array_merge($this->extraData, $data); + } + + /** + * @return array + */ + public function getTags() + { + return $this->tags; + } + + /** + * @return array + */ + public function getUserData() + { + return $this->userData; + } + + /** + * @return array + */ + public function getExtraData() + { + return $this->extraData; } } diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index de27e9ff6..fe4a8ccc2 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -67,7 +67,7 @@ public function __construct( $this->fatal_error_types = array_reduce($this->fatal_error_types, [$this, 'bitwiseOr']); if ($send_errors_last) { $this->send_errors_last = true; - $this->client->store_errors_for_bulk_send = true; + $this->client->storeErrorsForBulkSend = true; } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index fb1dbf2ff..ddc561026 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -55,19 +55,19 @@ public function send(&$data) $this->__sent_events[] = $data; } - public static function is_http_request() + public static function isHttpRequest() { return true; } - public function get_http_data() + public function getHttpData() { - return parent::get_http_data(); + return parent::getHttpData(); } - public function get_user_data() + public function getUserData() { - return parent::get_user_data(); + return parent::getUserData(); } // short circuit breadcrumbs @@ -88,7 +88,7 @@ public function registerShutdownFunction() */ public function test_get_current_url() { - return $this->get_current_url(); + return $this->getCurrentUrl(); } } @@ -97,7 +97,7 @@ class Dummy_Raven_Client_No_Http extends Dummy_Raven_Client /** * @return bool */ - public static function is_http_request() + public static function isHttpRequest() { return false; } @@ -219,117 +219,117 @@ public function testDestructor() public function testOptionsExtraData() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; - $client->extra_context(['foo' => 'bar']); + $client->getContext()->mergeExtraData(['foo' => 'bar']); $client->captureMessage('Test Message %s', ['foo']); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('bar', $client->_pending_events[0]['extra']['foo']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('bar', $client->pendingEvents[0]['extra']['foo']); } public function testOptionsExtraDataWithNull() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; - $client->extra_context(['foo' => 'bar']); + $client->getContext()->mergeExtraData(['foo' => 'bar']); $client->captureMessage('Test Message %s', ['foo'], null); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('bar', $client->_pending_events[0]['extra']['foo']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('bar', $client->pendingEvents[0]['extra']['foo']); } public function testEmptyExtraData() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureMessage('Test Message %s', ['foo']); - $this->assertCount(1, $client->_pending_events); - $this->assertArrayNotHasKey('extra', $client->_pending_events[0]); + $this->assertCount(1, $client->pendingEvents); + $this->assertArrayNotHasKey('extra', $client->pendingEvents[0]); } public function testCaptureMessageDoesHandleUninterpolatedMessage() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureMessage('foo %s'); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('foo %s', $client->_pending_events[0]['message']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('foo %s', $client->pendingEvents[0]['message']); } public function testCaptureMessageDoesHandleInterpolatedMessage() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureMessage('foo %s', ['bar']); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('foo bar', $client->_pending_events[0]['message']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('foo bar', $client->pendingEvents[0]['message']); } public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() { $client = ClientBuilder::create(['release' => '1.2.3'])->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $this->assertEquals('1.2.3', $client->getConfig()->getRelease()); $client->captureMessage('foo %s', ['bar']); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('foo bar', $client->_pending_events[0]['message']); - $this->assertEquals('1.2.3', $client->_pending_events[0]['release']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('foo bar', $client->pendingEvents[0]['message']); + $this->assertEquals('1.2.3', $client->pendingEvents[0]['release']); } public function testCaptureMessageSetsInterface() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureMessage('foo %s', ['bar']); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('foo bar', $client->_pending_events[0]['message']); - $this->assertArrayHasKey('sentry.interfaces.Message', $client->_pending_events[0]); - $this->assertEquals('foo bar', $client->_pending_events[0]['sentry.interfaces.Message']['formatted']); - $this->assertEquals('foo %s', $client->_pending_events[0]['sentry.interfaces.Message']['message']); - $this->assertEquals(['bar'], $client->_pending_events[0]['sentry.interfaces.Message']['params']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('foo bar', $client->pendingEvents[0]['message']); + $this->assertArrayHasKey('sentry.interfaces.Message', $client->pendingEvents[0]); + $this->assertEquals('foo bar', $client->pendingEvents[0]['sentry.interfaces.Message']['formatted']); + $this->assertEquals('foo %s', $client->pendingEvents[0]['sentry.interfaces.Message']['message']); + $this->assertEquals(['bar'], $client->pendingEvents[0]['sentry.interfaces.Message']['params']); } public function testCaptureMessageHandlesOptionsAsThirdArg() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureMessage('Test Message %s', ['foo'], [ 'level' => Dummy_Raven_Client::LEVEL_WARNING, 'extra' => ['foo' => 'bar'], ]); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals(Dummy_Raven_Client::LEVEL_WARNING, $client->_pending_events[0]['level']); - $this->assertEquals('bar', $client->_pending_events[0]['extra']['foo']); - $this->assertEquals('Test Message foo', $client->_pending_events[0]['message']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals(Dummy_Raven_Client::LEVEL_WARNING, $client->pendingEvents[0]['level']); + $this->assertEquals('bar', $client->pendingEvents[0]['extra']['foo']); + $this->assertEquals('Test Message foo', $client->pendingEvents[0]['message']); } public function testCaptureMessageHandlesLevelAsThirdArg() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureMessage('Test Message %s', ['foo'], Dummy_Raven_Client::LEVEL_WARNING); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals(Dummy_Raven_Client::LEVEL_WARNING, $client->_pending_events[0]['level']); - $this->assertEquals('Test Message foo', $client->_pending_events[0]['message']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals(Dummy_Raven_Client::LEVEL_WARNING, $client->pendingEvents[0]['level']); + $this->assertEquals('Test Message foo', $client->pendingEvents[0]['message']); } /** @@ -339,18 +339,18 @@ public function testCaptureExceptionSetsInterfaces() { // TODO: it'd be nice if we could mock the stacktrace extraction function here $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureException($this->create_exception()); - $this->assertCount(1, $client->_pending_events); + $this->assertCount(1, $client->pendingEvents); - $this->assertCount(1, $client->_pending_events[0]['exception']['values']); - $this->assertEquals('Foo bar', $client->_pending_events[0]['exception']['values'][0]['value']); - $this->assertEquals('Exception', $client->_pending_events[0]['exception']['values'][0]['type']); - $this->assertNotEmpty($client->_pending_events[0]['exception']['values'][0]['stacktrace']['frames']); + $this->assertCount(1, $client->pendingEvents[0]['exception']['values']); + $this->assertEquals('Foo bar', $client->pendingEvents[0]['exception']['values'][0]['value']); + $this->assertEquals('Exception', $client->pendingEvents[0]['exception']['values'][0]['type']); + $this->assertNotEmpty($client->pendingEvents[0]['exception']['values'][0]['stacktrace']['frames']); - $frames = $client->_pending_events[0]['exception']['values'][0]['stacktrace']['frames']; + $frames = $client->pendingEvents[0]['exception']['values'][0]['stacktrace']['frames']; $frame = $frames[count($frames) - 1]; $this->assertTrue($frame['lineno'] > 0); @@ -365,20 +365,20 @@ public function testCaptureExceptionChainedException() { // TODO: it'd be nice if we could mock the stacktrace extraction function here $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureException($this->create_chained_exception()); - $this->assertCount(1, $client->_pending_events); - $this->assertCount(2, $client->_pending_events[0]['exception']['values']); - $this->assertEquals('Foo bar', $client->_pending_events[0]['exception']['values'][0]['value']); - $this->assertEquals('Child exc', $client->_pending_events[0]['exception']['values'][1]['value']); + $this->assertCount(1, $client->pendingEvents); + $this->assertCount(2, $client->pendingEvents[0]['exception']['values']); + $this->assertEquals('Foo bar', $client->pendingEvents[0]['exception']['values'][0]['value']); + $this->assertEquals('Child exc', $client->pendingEvents[0]['exception']['values'][1]['value']); } public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $e1 = new \ErrorException('First', 0, E_DEPRECATED); $e2 = new \ErrorException('Second', 0, E_NOTICE, __FILE__, __LINE__, $e1); @@ -388,37 +388,37 @@ public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() $client->captureException($e2); $client->captureException($e3); - $this->assertCount(3, $client->_pending_events); - $this->assertEquals(Client::LEVEL_WARNING, $client->_pending_events[0]['level']); - $this->assertEquals(Client::LEVEL_INFO, $client->_pending_events[1]['level']); - $this->assertEquals(Client::LEVEL_ERROR, $client->_pending_events[2]['level']); + $this->assertCount(3, $client->pendingEvents); + $this->assertEquals(Client::LEVEL_WARNING, $client->pendingEvents[0]['level']); + $this->assertEquals(Client::LEVEL_INFO, $client->pendingEvents[1]['level']); + $this->assertEquals(Client::LEVEL_ERROR, $client->pendingEvents[2]['level']); } public function testCaptureExceptionHandlesOptionsAsSecondArg() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureException($this->create_exception(), ['culprit' => 'test']); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('test', $client->_pending_events[0]['culprit']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('test', $client->pendingEvents[0]['culprit']); } public function testCaptureExceptionHandlesExcludeOption() { $client = ClientBuilder::create(['excluded_exceptions' => ['Exception']])->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureException($this->create_exception(), 'test'); - $this->assertEmpty($client->_pending_events); + $this->assertEmpty($client->pendingEvents); } public function testCaptureExceptionInvalidUTF8() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; try { invalid_encoding(); @@ -426,10 +426,10 @@ public function testCaptureExceptionInvalidUTF8() $client->captureException($ex); } - $this->assertCount(1, $client->_pending_events); + $this->assertCount(1, $client->pendingEvents); try { - $client->send($client->_pending_events[0]); + $client->send($client->pendingEvents[0]); } catch (\Exception $ex) { $this->fail(); } @@ -480,7 +480,7 @@ public function testGetDefaultData() ], ]; - $this->assertEquals($expected, $client->get_default_data()); + $this->assertEquals($expected, $client->getDefaultData()); } /** @@ -547,90 +547,91 @@ public function testGetHttpData() $client = new Dummy_Raven_Client($config, $httpClient, $requestFactory); - $this->assertEquals($expected, $client->get_http_data()); + $this->assertEquals($expected, $client->getHttpData()); } public function testCaptureMessageWithUserData() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; - $client->user_context([ - 'id' => 'unique_id', - 'email' => 'foo@example.com', + $context = $client->getContext(); + $context->setUserId('unique_id'); + $context->setUserEmail('foo@example.com'); + $context->mergeUserData([ 'username' => 'my_user', ]); $client->captureMessage('foo'); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('unique_id', $client->_pending_events[0]['user']['id']); - $this->assertEquals('my_user', $client->_pending_events[0]['user']['username']); - $this->assertEquals('foo@example.com', $client->_pending_events[0]['user']['email']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('unique_id', $client->pendingEvents[0]['user']['id']); + $this->assertEquals('my_user', $client->pendingEvents[0]['user']['username']); + $this->assertEquals('foo@example.com', $client->pendingEvents[0]['user']['email']); } public function testCaptureMessageWithNoUserData() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureMessage('foo'); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals(session_id(), $client->_pending_events[0]['user']['id']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals(session_id(), $client->pendingEvents[0]['user']['id']); } public function testCaptureMessageWithUnserializableUserData() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; - $client->user_context([ - 'email' => 'foo@example.com', - 'data' => [ - 'error' => new \Exception('test'), - ], + $context = $client->getContext(); + $context->setUserEmail('foo@example.com'); + $context->mergeUserData([ + 'error' => new \Exception('test'), ]); $client->captureMessage('test'); - $this->assertCount(1, $client->_pending_events); + $this->assertCount(1, $client->pendingEvents); } public function testCaptureMessageWithTagsContext() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; + $context = $client->getContext(); - $client->tags_context(['foo' => 'bar']); - $client->tags_context(['biz' => 'boz']); - $client->tags_context(['biz' => 'baz']); + $context->setTag('foo', 'bar'); + $context->setTag('biz', 'boz'); + $context->setTag('biz', 'baz'); $client->captureMessage('test'); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals(['foo' => 'bar', 'biz' => 'baz'], $client->_pending_events[0]['tags']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals(['foo' => 'bar', 'biz' => 'baz'], $client->pendingEvents[0]['tags']); } public function testCaptureMessageWithExtraContext() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; - $client->extra_context(['foo' => 'bar']); - $client->extra_context(['biz' => 'boz']); - $client->extra_context(['biz' => 'baz']); + $client->getContext()->mergeExtraData(['foo' => 'bar']); + $client->getContext()->mergeExtraData(['biz' => 'boz']); + $client->getContext()->mergeExtraData(['biz' => 'baz']); $client->captureMessage('test'); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals(['foo' => 'bar', 'biz' => 'baz'], $client->_pending_events[0]['extra']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals(['foo' => 'bar', 'biz' => 'baz'], $client->pendingEvents[0]['extra']); } public function testCaptureExceptionContainingLatin1() { $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; // we need a non-utf8 string here. // nobody writes non-utf8 in exceptions, but it is the easiest way to test. @@ -639,18 +640,18 @@ public function testCaptureExceptionContainingLatin1() $latin1String = utf8_decode($utf8String); $client->captureException(new \Exception($latin1String)); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals($utf8String, $client->_pending_events[0]['exception']['values'][0]['value']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals($utf8String, $client->pendingEvents[0]['exception']['values'][0]['value']); } public function testCaptureExceptionInLatin1File() { $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; require_once __DIR__ . '/Fixtures/code/Latin1File.php'; - $frames = $client->_pending_events[0]['exception']['values'][0]['stacktrace']['frames']; + $frames = $client->pendingEvents[0]['exception']['values'][0]['stacktrace']['frames']; $utf8String = '// äöü'; $found = false; @@ -677,10 +678,10 @@ public function testCaptureLastError() } $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $this->assertNull($client->captureLastError()); - $this->assertEmpty($client->_pending_events); + $this->assertEmpty($client->pendingEvents); /* @var $undefined */ /* @noinspection PhpExpressionResultUnusedInspection */ @@ -688,21 +689,21 @@ public function testCaptureLastError() $client->captureLastError(); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('Undefined variable: undefined', $client->_pending_events[0]['exception']['values'][0]['value']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('Undefined variable: undefined', $client->pendingEvents[0]['exception']['values'][0]['value']); } public function testGetLastEventID() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->capture([ 'message' => 'test', 'event_id' => 'abc', ]); - $this->assertEquals('abc', $client->getLastEventID()); + $this->assertEquals('abc', $client->getLastEventId()); } public function testCustomTransport() @@ -1015,36 +1016,6 @@ public function testSanitizeContexts() ]], $data); } - public function testUserContextWithoutMerge() - { - $client = ClientBuilder::create()->getClient(); - - $client->user_context(['foo' => 'bar'], false); - $client->user_context(['baz' => 'bar'], false); - - $this->assertEquals(['baz' => 'bar'], $client->context->user); - } - - public function testUserContextWithMerge() - { - $client = ClientBuilder::create()->getClient(); - - $client->user_context(['foo' => 'bar'], true); - $client->user_context(['baz' => 'bar'], true); - - $this->assertEquals(['foo' => 'bar', 'baz' => 'bar'], $client->context->user); - } - - public function testUserContextWithMergeAndNull() - { - $client = ClientBuilder::create()->getClient(); - - $client->user_context(['foo' => 'bar'], true); - $client->user_context(null, true); - - $this->assertEquals(['foo' => 'bar'], $client->context->user); - } - /** * Set the server array to the test values, check the current url. * @@ -1054,7 +1025,7 @@ public function testUserContextWithMergeAndNull() * @param array $options * @param string $expected - the url expected * @param string $message - fail message - * @covers \Raven\Client::get_current_url + * @covers \Raven\Client::getCurrentUrl * @covers \Raven\Client::isHttps */ public function testCurrentUrl($serverVars, $options, $expected, $message) @@ -1159,32 +1130,25 @@ public function testUuid4() /** * @covers \Raven\Client::getLastError - * @covers \Raven\Client::getLastEventID - * @covers \Raven\Client::getLastSentryError + * @covers \Raven\Client::getLastEventId * @covers \Raven\Client::getShutdownFunctionHasBeenSet */ public function testGettersAndSetters() { $client = ClientBuilder::create()->getClient(); - $callable = [$this, 'stabClosureVoid']; $data = [ - ['_lasterror', null, null], - ['_lasterror', null, 'value'], - ['_lasterror', null, mt_rand(100, 999)], - ['_last_sentry_error', null, (object) ['error' => 'test']], - ['_last_event_id', null, mt_rand(100, 999)], - ['_last_event_id', null, 'value'], - ['_shutdown_function_has_been_set', null, true], - ['_shutdown_function_has_been_set', null, false], + ['lastError', null, null], + ['lastError', null, 'value'], + ['lastError', null, mt_rand(100, 999)], + ['lastEventId', null, mt_rand(100, 999)], + ['lastEventId', null, 'value'], + ['shutdownFunctionHasBeenSet', null, true], + ['shutdownFunctionHasBeenSet', null, false], ]; foreach ($data as &$datum) { $this->subTestGettersAndSettersDatum($client, $datum); } - foreach ($data as &$datum) { - $client = ClientBuilder::create()->getClient(); - $this->subTestGettersAndSettersDatum($client, $datum); - } } private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) @@ -1252,7 +1216,7 @@ private function assertMixedValueAndArray($expected_value, $actual_value) */ public function testTranslateSeverity() { - $reflection = new \ReflectionProperty('\\Raven\Client', 'severity_map'); + $reflection = new \ReflectionProperty(Client::class, 'severityMap'); $reflection->setAccessible(true); $client = ClientBuilder::create()->getClient(); @@ -1299,12 +1263,12 @@ public function testTranslateSeverity() public function testCaptureExceptionWithLogger() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureException(new \Exception(), null, 'foobar'); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('foobar', $client->_pending_events[0]['logger']); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('foobar', $client->pendingEvents[0]['logger']); } /** @@ -1402,20 +1366,20 @@ public function testOnShutdown() ->setHttpClient($httpClient) ->getClient(); - $this->assertEquals(0, count($client->_pending_events)); - $client->_pending_events[] = ['foo' => 'bar']; + $this->assertEquals(0, count($client->pendingEvents)); + $client->pendingEvents[] = ['foo' => 'bar']; $client->sendUnsentErrors(); $this->assertCount(1, $httpClient->getRequests()); - $this->assertEquals(0, count($client->_pending_events)); + $this->assertEquals(0, count($client->pendingEvents)); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->captureMessage('foobar'); - $this->assertEquals(1, count($client->_pending_events)); + $this->assertEquals(1, count($client->pendingEvents)); // step 3 $client->onShutdown(); $this->assertCount(2, $httpClient->getRequests()); - $this->assertEquals(0, count($client->_pending_events)); + $this->assertEquals(0, count($client->pendingEvents)); } public function testSendChecksShouldCaptureOption() @@ -1508,7 +1472,7 @@ public function testGet_user_data() // step 1 $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); - $output = $client->get_user_data(); + $output = $client->getUserData(); $this->assertInternalType('array', $output); $this->assertArrayHasKey('user', $output); $this->assertArrayHasKey('id', $output['user']); @@ -1518,7 +1482,7 @@ public function testGet_user_data() $session_id = session_id(); session_write_close(); session_id(''); - $output = $client->get_user_data(); + $output = $client->getUserData(); $this->assertInternalType('array', $output); $this->assertEquals(0, count($output)); @@ -1526,7 +1490,7 @@ public function testGet_user_data() session_id($session_id); @session_start(['use_cookies' => false]); $_SESSION = ['foo' => 'bar']; - $output = $client->get_user_data(); + $output = $client->getUserData(); $this->assertInternalType('array', $output); $this->assertArrayHasKey('user', $output); $this->assertArrayHasKey('id', $output['user']); @@ -1554,33 +1518,33 @@ public function testCaptureLevel() } $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->capture(['message' => $message]); - $this->assertCount(1, $client->_pending_events); - $this->assertEquals('error', $client->_pending_events[0]['level']); - $this->assertEquals(substr($message, 0, min(\Raven\Client::MESSAGE_LIMIT, $length)), $client->_pending_events[0]['message']); - $this->assertArrayNotHasKey('release', $client->_pending_events[0]); + $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('error', $client->pendingEvents[0]['level']); + $this->assertEquals(substr($message, 0, min(\Raven\Client::MESSAGE_LIMIT, $length)), $client->pendingEvents[0]['message']); + $this->assertArrayNotHasKey('release', $client->pendingEvents[0]); } $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->capture(['message' => 'foobar']); - $input = $client->get_http_data(); + $input = $client->getHttpData(); - $this->assertEquals($input['request'], $client->_pending_events[0]['request']); - $this->assertArrayNotHasKey('release', $client->_pending_events[0]); + $this->assertEquals($input['request'], $client->pendingEvents[0]['request']); + $this->assertArrayNotHasKey('release', $client->pendingEvents[0]); $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->capture(['message' => 'foobar', 'request' => ['foo' => 'bar']]); - $this->assertEquals(['foo' => 'bar'], $client->_pending_events[0]['request']); - $this->assertArrayNotHasKey('release', $client->_pending_events[0]); + $this->assertEquals(['foo' => 'bar'], $client->pendingEvents[0]['request']); + $this->assertArrayNotHasKey('release', $client->pendingEvents[0]); foreach ([false, true] as $u1) { foreach ([false, true] as $u2) { @@ -1595,18 +1559,18 @@ public function testCaptureLevel() } $client = new Client(new Configuration($options), $httpClient, $requestFactory); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->capture(['message' => 'foobar']); if ($u1) { - $this->assertEquals('foo', $client->_pending_events[0]['release']); + $this->assertEquals('foo', $client->pendingEvents[0]['release']); } else { - $this->assertArrayNotHasKey('release', $client->_pending_events[0]); + $this->assertArrayNotHasKey('release', $client->pendingEvents[0]); } if ($u2) { - $this->assertEquals('bar', $client->_pending_events[0]['environment']); + $this->assertEquals('bar', $client->pendingEvents[0]['environment']); } } } @@ -1623,7 +1587,7 @@ public function testCaptureNoUserAndRequest() ->getMock(); $client = new Dummy_Raven_Client_No_Http(new Configuration(['install_default_breadcrumb_handlers' => false]), $httpClient, $requestFactory); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $session_id = session_id(); @@ -1632,9 +1596,9 @@ public function testCaptureNoUserAndRequest() $client->capture(['user' => '', 'request' => '']); - $this->assertCount(1, $client->_pending_events); - $this->assertArrayNotHasKey('user', $client->_pending_events[0]); - $this->assertArrayNotHasKey('request', $client->_pending_events[0]); + $this->assertCount(1, $client->pendingEvents); + $this->assertArrayNotHasKey('user', $client->pendingEvents[0]); + $this->assertArrayNotHasKey('request', $client->pendingEvents[0]); session_id($session_id); @session_start(['use_cookies' => false]); @@ -1643,13 +1607,13 @@ public function testCaptureNoUserAndRequest() public function testCaptureAutoLogStacks() { $client = ClientBuilder::create()->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; $client->capture(['auto_log_stacks' => true], true); - $this->assertCount(1, $client->_pending_events); - $this->assertArrayHasKey('stacktrace', $client->_pending_events[0]); - $this->assertInternalType('array', $client->_pending_events[0]['stacktrace']['frames']); + $this->assertCount(1, $client->pendingEvents); + $this->assertArrayHasKey('stacktrace', $client->pendingEvents[0]); + $this->assertInternalType('array', $client->pendingEvents[0]['stacktrace']['frames']); } /** diff --git a/tests/ContextTest.php b/tests/ContextTest.php new file mode 100644 index 000000000..94b11ad7d --- /dev/null +++ b/tests/ContextTest.php @@ -0,0 +1,69 @@ +setTag('foo', 'bar'); + $context->setTag('foo', 'baz'); + + $this->assertEquals(['foo' => 'baz'], $context->getTags()); + } + + public function testMergeUserData() + { + $context = new Context(); + + $context->mergeUserData(['foo' => 'bar']); + $context->mergeUserData(['baz' => 'bar']); + + $this->assertEquals(['foo' => 'bar', 'baz' => 'bar'], $context->getUserData()); + } + + public function testMergeUserDataWithSameKey() + { + $context = new Context(); + + $context->mergeUserData(['foo' => 'bar']); + $context->mergeUserData(['foo' => 'baz']); + + $this->assertEquals(['foo' => 'baz'], $context->getUserData()); + } + + public function testSetUserData() + { + $context = new Context(); + + $context->setUserData(['foo' => 'bar']); + $context->setUserData(['bar' => 'baz']); + + $this->assertEquals(['bar' => 'baz'], $context->getUserData()); + } + + public function testMergeExtraData() + { + $context = new Context(); + + $context->mergeExtraData(['foo' => 'bar']); + $context->mergeExtraData(['baz' => 'bar']); + + $this->assertEquals(['foo' => 'bar', 'baz' => 'bar'], $context->getExtraData()); + } + + public function testMergeExtraDataWithSameKey() + { + $context = new Context(); + + $context->mergeExtraData(['foo' => 'bar']); + $context->mergeExtraData(['foo' => 'baz']); + + $this->assertEquals(['foo' => 'baz'], $context->getExtraData()); + } +} diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 2e3e627ad..94c2e33e8 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -31,7 +31,7 @@ public function send(&$data) $this->__sent_events[] = $data; } - public static function is_http_request() + public static function isHttpRequest() { return true; } @@ -60,13 +60,13 @@ private function create_chained_exception() public function testCaptureSimpleError() { $client = ClientBuilder::create([])->getClient(); - $client->store_errors_for_bulk_send = true; + $client->storeErrorsForBulkSend = true; @mkdir('/no/way'); $client->captureLastError(); - $event = $client->_pending_events[0]['exception']['values'][0]; + $event = $client->pendingEvents[0]['exception']['values'][0]; $this->assertEquals($event['value'], 'mkdir(): No such file or directory'); $this->assertEquals($event['stacktrace']['frames'][count($event['stacktrace']['frames']) - 1]['filename'], 'tests/IntegrationTest.php'); diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php index f181db93f..62ceda188 100644 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -31,7 +31,7 @@ class SanitizeStacktraceProcessorTest extends TestCase protected function setUp() { $this->client = ClientBuilder::create()->getClient(); - $this->client->store_errors_for_bulk_send = true; + $this->client->storeErrorsForBulkSend = true; $this->processor = new SanitizeStacktraceProcessor($this->client); } @@ -44,9 +44,9 @@ public function testProcess() $this->client->captureException($exception); } - $this->processor->process($this->client->_pending_events[0]); + $this->processor->process($this->client->pendingEvents[0]); - foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($this->client->pendingEvents[0]['exception']['values'] as $exceptionValue) { foreach ($exceptionValue['stacktrace']['frames'] as $frame) { $this->assertArrayNotHasKey('pre_context', $frame); $this->assertArrayNotHasKey('context_line', $frame); @@ -67,9 +67,9 @@ public function testProcessWithPreviousException() $this->client->captureException($exception); } - $this->processor->process($this->client->_pending_events[0]); + $this->processor->process($this->client->pendingEvents[0]); - foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($this->client->pendingEvents[0]['exception']['values'] as $exceptionValue) { foreach ($exceptionValue['stacktrace']['frames'] as $frame) { $this->assertArrayNotHasKey('pre_context', $frame); $this->assertArrayNotHasKey('context_line', $frame); From cad94761152d9ed7d3421fc48645137f6aff2740 Mon Sep 17 00:00:00 2001 From: Nikita Vetrov Date: Mon, 20 Nov 2017 19:58:13 +0300 Subject: [PATCH 0309/1161] [2.0] Yoda Style, Small fixes in PHP Doc & Code coverage (#511) * PHP CS: Explicit enable"yoda style" * Small PHP doc fixes and fixes for Intellij Idea * Code Coverage --- .php_cs | 1 + lib/Raven/Breadcrumbs/ErrorHandler.php | 2 +- lib/Raven/Breadcrumbs/MonologHandler.php | 1 + lib/Raven/Client.php | 20 +++++++------- lib/Raven/ClientBuilder.php | 1 - lib/Raven/ErrorHandler.php | 12 ++++----- lib/Raven/ReprSerializer.php | 8 +++--- lib/Raven/Serializer.php | 8 +++--- lib/Raven/Stacktrace.php | 4 ++- lib/Raven/TransactionStack.php | 2 +- tests/ClientTest.php | 34 ++++++++++++++++++------ 11 files changed, 57 insertions(+), 36 deletions(-) diff --git a/.php_cs b/.php_cs index d0609d1ed..34df9cfdb 100644 --- a/.php_cs +++ b/.php_cs @@ -9,6 +9,7 @@ return PhpCsFixer\Config::create() 'concat_space' => ['spacing' => 'one'], 'ordered_imports' => true, 'random_api_migration' => true, + 'yoda_style' => true, ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php index 0e07e0a55..0cd02be5e 100644 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ b/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -29,7 +29,7 @@ public function handleError($code, $message, $file = '', $line = 0, $context = [ ]) ); - if ($this->existingHandler !== null) { + if (null !== $this->existingHandler) { return call_user_func($this->existingHandler, $code, $message, $file, $line, $context); } else { return false; diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 3d295c334..6e19bb32a 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -71,6 +71,7 @@ protected function write(array $record) */ $exc = $record['context']['exception']; + /** @noinspection PhpUndefinedMethodInspection */ $breadcrumb = new Breadcrumb($this->logLevels[$record['level']], Breadcrumb::TYPE_ERROR, $record['channel'], null, [ 'type' => get_class($exc), 'value' => $exc->getMessage(), diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 051bd8f95..9783d27e2 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -46,7 +46,7 @@ class Client const MESSAGE_LIMIT = 1024; /** - * @var Recorder The bredcrumbs recorder + * @var Recorder The breadcrumbs recorder */ protected $recorder; @@ -329,7 +329,7 @@ public function captureMessage( $formatted_message = $message; } - if ($data === null) { + if (null === $data) { $data = []; // support legacy method of passing in a level name as the third arg } elseif (!is_array($data)) { @@ -364,7 +364,7 @@ public function captureException($exception, $data = null, $logger = null, $vars return null; } - if ($data === null) { + if (null === $data) { $data = []; } @@ -404,7 +404,7 @@ public function captureException($exception, $data = null, $logger = null, $vars $data['exception'] = [ 'values' => array_reverse($exceptions), ]; - if ($logger !== null) { + if (null !== $logger) { $data['logger'] = $logger; } @@ -460,7 +460,7 @@ public function captureQuery($query, $level = self::LEVEL_INFO, $engine = '') ], ]; - if ($engine !== '') { + if (!empty($engine)) { $data['sentry.interfaces.Query']['engine'] = $engine; } @@ -507,7 +507,7 @@ protected function getHttpData() $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); $headers[$header_key] = $value; - } elseif (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH']) && $value !== '') { + } elseif (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH']) && !empty($value)) { $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); $headers[$header_key] = $value; } @@ -641,7 +641,7 @@ public function capture($data, $stack = null, $vars = null) $data['breadcrumbs'] = iterator_to_array($this->recorder); } - if ((!$stack && $this->config->getAutoLogStacks()) || $stack === true) { + if ((!$stack && $this->config->getAutoLogStacks()) || (true === $stack)) { $stack = debug_backtrace(); // Drop last stack @@ -850,17 +850,17 @@ protected function getCurrentUrl() */ protected function isHttps() { - if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { + if (!empty($_SERVER['HTTPS']) && ('off' !== $_SERVER['HTTPS'])) { return true; } - if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) { + if (!empty($_SERVER['SERVER_PORT']) && (443 == $_SERVER['SERVER_PORT'])) { return true; } if (!empty($this->config->isTrustXForwardedProto()) && !empty($_SERVER['X-FORWARDED-PROTO']) && - $_SERVER['X-FORWARDED-PROTO'] === 'https') { + ('https' === $_SERVER['X-FORWARDED-PROTO'])) { return true; } diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index a408ae1f6..26178531b 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -187,7 +187,6 @@ public function getClient() { $this->messageFactory = $this->messageFactory ?: MessageFactoryDiscovery::find(); $this->uriFactory = $this->uriFactory ?: UriFactoryDiscovery::find(); - $this->messageFactory = $this->messageFactory ?: MessageFactoryDiscovery::find(); $this->httpClient = $this->httpClient ?: HttpAsyncClientDiscovery::find(); return new Client($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index fe4a8ccc2..e17d43828 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -58,7 +58,7 @@ public function __construct( $__error_types = null ) { // support legacy fourth argument for error types - if ($error_types === null) { + if (null === $error_types) { $error_types = $__error_types; } @@ -81,7 +81,7 @@ public function handleException($e, $isError = false, $vars = null) $e->event_id = $this->client->captureException($e, null, null, $vars); if (!$isError && $this->call_existing_exception_handler) { - if ($this->old_exception_handler !== null) { + if (null !== $this->old_exception_handler) { call_user_func($this->old_exception_handler, $e); } else { throw $e; @@ -96,9 +96,9 @@ public function handleError($type, $message, $file = '', $line = 0, $context = [ // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and // most of E_STRICT raised in the file where set_error_handler() is called. - if (error_reporting() !== 0) { + if (0 !== error_reporting()) { $error_types = $this->error_types; - if ($error_types === null) { + if (null === $error_types) { $error_types = error_reporting(); } if ($error_types & $type) { @@ -108,7 +108,7 @@ public function handleError($type, $message, $file = '', $line = 0, $context = [ } if ($this->call_existing_error_handler) { - if ($this->old_error_handler !== null) { + if (null !== $this->old_error_handler) { return call_user_func( $this->old_error_handler, $type, @@ -177,7 +177,7 @@ public function registerExceptionHandler($call_existing = true) */ public function registerErrorHandler($call_existing = true, $error_types = null) { - if ($error_types !== null) { + if (null !== $error_types) { $this->error_types = $error_types; } $this->old_error_handler = set_error_handler([$this, 'handleError'], E_ALL); diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php index 8ff0e9ad3..f9de0ba46 100644 --- a/lib/Raven/ReprSerializer.php +++ b/lib/Raven/ReprSerializer.php @@ -19,17 +19,17 @@ class ReprSerializer extends \Raven\Serializer { protected function serializeValue($value) { - if ($value === null) { + if (null === $value) { return 'null'; - } elseif ($value === false) { + } elseif (false === $value) { return 'false'; - } elseif ($value === true) { + } elseif (true === $value) { return 'true'; } elseif (is_float($value) && (int) $value == $value) { return $value . '.0'; } elseif (is_int($value) || is_float($value)) { return (string) $value; - } elseif (is_object($value) || gettype($value) == 'object') { + } elseif (is_object($value) || ('object' == gettype($value))) { return 'Object ' . get_class($value); } elseif (is_resource($value)) { return 'Resource ' . get_resource_type($value); diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 8633e7e3d..7095843b2 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -55,7 +55,7 @@ class Serializer */ public function __construct($mb_detect_order = null) { - if ($mb_detect_order != null) { + if (null != $mb_detect_order) { $this->mb_detect_order = $mb_detect_order; } } @@ -83,7 +83,7 @@ public function serialize($value, $max_depth = 3, $_depth = 0) } if (is_object($value)) { - if ((get_class($value) == 'stdClass') or $this->_all_object_serialize) { + if (('stdClass' == get_class($value)) or $this->_all_object_serialize) { return $this->serializeObject($value, $max_depth, $_depth, []); } } @@ -147,9 +147,9 @@ protected function serializeString($value) */ protected function serializeValue($value) { - if (null === $value || is_bool($value) || is_float($value) || is_int($value)) { + if ((null === $value) || is_bool($value) || is_float($value) || is_int($value)) { return $value; - } elseif (is_object($value) || gettype($value) == 'object') { + } elseif (is_object($value) || ('object' == gettype($value))) { return 'Object ' . get_class($value); } elseif (is_resource($value)) { return 'Resource ' . get_resource_type($value); diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 0b0402cd8..a1c9340ac 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -246,8 +246,10 @@ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) $file->next(); } + // @codeCoverageIgnoreStart } catch (\Exception $ex) { } + // @codeCoverageIgnoreEnd $frame['pre_context'] = $this->serializer->serialize($frame['pre_context']); $frame['context_line'] = $this->serializer->serialize($frame['context_line']); @@ -343,7 +345,7 @@ public static function getFrameArguments($frame, $maxValueLength = Client::MESSA if (isset($frame['class'])) { if (method_exists($frame['class'], $frame['function'])) { $reflection = new \ReflectionMethod($frame['class'], $frame['function']); - } elseif ($frame['type'] === '::') { + } elseif ('::' === $frame['type']) { $reflection = new \ReflectionMethod($frame['class'], '__callStatic'); } else { $reflection = new \ReflectionMethod($frame['class'], '__call'); diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index 6acbc88c7..8cbf82eae 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -30,7 +30,7 @@ public function clear() public function peek() { $len = count($this->stack); - if ($len === 0) { + if (0 === $len) { return null; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index ddc561026..5f11faed7 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1153,7 +1153,7 @@ public function testGettersAndSetters() private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) { - if (count($datum) == 3) { + if (3 == count($datum)) { list($property_name, $function_name, $value_in) = $datum; $value_out = $value_in; } else { @@ -1192,9 +1192,9 @@ private function assertMixedValueAndArray($expected_value, $actual_value) { if (null === $expected_value) { $this->assertNull($actual_value); - } elseif ($expected_value === true) { + } elseif (true === $expected_value) { $this->assertTrue($actual_value); - } elseif ($expected_value === false) { + } elseif (false === $expected_value) { $this->assertFalse($actual_value); } elseif (is_string($expected_value) or is_int($expected_value) or is_float($expected_value)) { $this->assertEquals($expected_value, $actual_value); @@ -1299,7 +1299,7 @@ public function test_server_variable() public function testRegisterDefaultBreadcrumbHandlers() { - if (isset($_ENV['HHVM']) and ($_ENV['HHVM'] == 1)) { + if (isset($_ENV['HHVM']) and (1 == $_ENV['HHVM'])) { $this->markTestSkipped('HHVM stacktrace behaviour'); return; @@ -1673,6 +1673,26 @@ public function testSetAllObjectSerialize() $this->assertFalse($client->getSerializer()->getAllObjectSerialize()); $this->assertFalse($client->getReprSerializer()->getAllObjectSerialize()); } + + public function testClearBreadcrumb() + { + $client = ClientBuilder::create()->getClient(); + $client->leaveBreadcrumb( + new \Raven\Breadcrumbs\Breadcrumb( + 'warning', \Raven\Breadcrumbs\Breadcrumb::TYPE_ERROR, 'error_reporting', 'message', [ + 'code' => 127, + 'line' => 10, + 'file' => '/tmp/delme.php', + ] + ) + ); + $reflection = new \ReflectionProperty($client, 'recorder'); + $reflection->setAccessible(true); + $this->assertNotEmpty(iterator_to_array($reflection->getValue($client))); + + $client->clearBreadcrumbs(); + $this->assertEmpty(iterator_to_array($reflection->getValue($client))); + } } class PromiseMock implements Promise @@ -1712,20 +1732,18 @@ public function getState() public function wait($unwrap = true) { switch ($this->state) { - case self::FULFILLED: { + case self::FULFILLED: foreach ($this->onFullfilledCallbacks as $onFullfilledCallback) { $onFullfilledCallback($this->result); } break; - } - case self::REJECTED: { + case self::REJECTED: foreach ($this->onRejectedCallbacks as $onRejectedCallback) { $onRejectedCallback($this->result); } break; - } } if ($unwrap) { From 873ab3c9915dba04e37a74b5cb3f9b5d3101e9cf Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 9 Dec 2017 20:47:40 +0100 Subject: [PATCH 0310/1161] Refactor how the events are captured and sent by the client --- UPGRADE-2.0.md | 78 ++ composer.json | 7 +- lib/Raven/Client.php | 586 ++++-------- lib/Raven/Configuration.php | 21 +- lib/Raven/ErrorHandler.php | 2 +- lib/Raven/Event.php | 835 ++++++++++++++++++ .../BreadcrumbInterfaceMiddleware.php | 60 ++ .../Middleware/ContextInterfaceMiddleware.php | 68 ++ .../ExceptionInterfaceMiddleware.php | 87 ++ .../Middleware/MessageInterfaceMiddleware.php | 47 + .../Middleware/RequestInterfaceMiddleware.php | 60 ++ .../Middleware/UserInterfaceMiddleware.php | 47 + lib/Raven/Processor/SanitizeDataProcessor.php | 4 + lib/Raven/ReprSerializer.php | 2 +- lib/Raven/Serializer.php | 2 +- lib/Raven/Stacktrace.php | 16 +- lib/Raven/Util.php | 35 - tests/ClientTest.php | 812 ++--------------- tests/ConfigurationTest.php | 26 + tests/EventTest.php | 204 +++++ tests/Fixtures/code/Latin1File.php | 2 +- tests/IntegrationTest.php | 2 +- .../BreadcrumbInterfaceMiddlewareTest.php | 49 + .../ContextInterfaceMiddlewareTest.php | 55 ++ .../ExceptionInterfaceMiddlewareTest.php | 261 ++++++ .../MessageInterfaceMiddlewareTest.php | 80 ++ .../RequestInterfaceMiddlewareTest.php | 113 +++ .../UserInterfaceMiddlewareTest.php | 61 ++ .../SanitizeStacktraceProcessorTest.php | 2 +- tests/StacktraceTest.php | 4 +- tests/UtilTest.php | 31 - 31 files changed, 2411 insertions(+), 1248 deletions(-) create mode 100644 lib/Raven/Event.php create mode 100644 lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php create mode 100644 lib/Raven/Middleware/ContextInterfaceMiddleware.php create mode 100644 lib/Raven/Middleware/ExceptionInterfaceMiddleware.php create mode 100644 lib/Raven/Middleware/MessageInterfaceMiddleware.php create mode 100644 lib/Raven/Middleware/RequestInterfaceMiddleware.php create mode 100644 lib/Raven/Middleware/UserInterfaceMiddleware.php delete mode 100644 lib/Raven/Util.php create mode 100644 tests/EventTest.php create mode 100644 tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php create mode 100644 tests/Middleware/ContextInterfaceMiddlewareTest.php create mode 100644 tests/Middleware/ExceptionInterfaceMiddlewareTest.php create mode 100644 tests/Middleware/MessageInterfaceMiddlewareTest.php create mode 100644 tests/Middleware/RequestInterfaceMiddlewareTest.php create mode 100644 tests/Middleware/UserInterfaceMiddlewareTest.php delete mode 100644 tests/UtilTest.php diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 7dcc892ec..2709aa0e6 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -208,6 +208,84 @@ - The `Client::getDefaultProcessors` method has been removed. +- The `Client::message` method has been removed. + +- The `Client::captureQuery` method has been removed. + +- The `Client::captureMessage` method has changed its signature by removing the + `$stack` and `$vars` arguments. + + Before: + + ```php + public function captureMessage($message, $params = array(), $data = array(), $stack = false, $vars = null) + { + // ... + } + ``` + + After: + + ```php + public function captureMessage($message, array $params = [], array $payload = []) + { + // ... + } + ``` + +- The `Client::captureException` method has changed its signature by removing the + `$logger` and `$vars` arguments. + + Before: + + ```php + public function captureException($exception, $data = null, $logger = null, $vars = null) + { + // ... + } + ``` + + After: + + ```php + public function captureException($exception, array $payload = []) + { + // ... + } + ``` + +- The `$vars` argument of the `Client::captureException`, `Client::captureMessage` and + `Client::captureQuery` methods accepted some values that were setting additional data + in the event like the tags or the user data. Some of them have changed name. + + Before: + + ```php + $vars = array( + 'tags' => array(...), + 'extra' => array(...), + 'user' => array(...), + ); + + $client->captureException(new Exception(), null, null, $vars); + ``` + + After: + + ```php + $payload = array( + 'tags_context' => array(...), + 'extra_context' => array(...), + 'user_context' => array(...), + ); + + $client->captureException(new Exception(), $payload); + ``` + +- If an exception implemented the `getSeverity()` method its value was used as error + level of the event. This has been changed so that only the `ErrorException` or its + derivated classes are considered for this behavior. + ### Client builder - To simplify the creation of a `Client` object instance, a new builder class diff --git a/composer.json b/composer.json index 344bae29b..1b558f5c5 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,9 @@ "php-http/discovery": "~1.2", "php-http/httplug": "~1.1", "psr/http-message-implementation": "~1.0", - "symfony/options-resolver": "~2.7|~3.0" + "ramsey/uuid": "~3.3", + "symfony/options-resolver": "~2.7|~3.0", + "zendframework/zend-diactoros": "~1.4" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.1", @@ -28,8 +30,7 @@ "php-http/curl-client": "~1.7", "php-http/mock-client": "~1.0", "phpunit/phpunit": "^5.7|^6.0", - "symfony/phpunit-bridge": "~2.7|~3.0", - "zendframework/zend-diactoros": "~1.4" + "symfony/phpunit-bridge": "~2.7|~3.0" }, "suggest": { "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 9783d27e2..2208e2c7e 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -1,4 +1,5 @@ serializer = new Serializer($this->config->getMbDetectOrder()); $this->reprSerializer = new ReprSerializer($this->config->getMbDetectOrder()); $this->processors = $this->createProcessors(); + $this->middlewareStackTip = function (Event $event) { + return $event; + }; + + $this->addMiddleware(new MessageInterfaceMiddleware()); + $this->addMiddleware(new RequestInterfaceMiddleware()); + $this->addMiddleware(new UserInterfaceMiddleware()); + $this->addMiddleware(new ContextInterfaceMiddleware($this->context)); + $this->addMiddleware(new BreadcrumbInterfaceMiddleware($this->recorder)); + $this->addMiddleware(new ExceptionInterfaceMiddleware($this)); if (static::isHttpRequest() && isset($_SERVER['PATH_INFO'])) { $this->transaction->push($_SERVER['PATH_INFO']); @@ -191,11 +218,42 @@ public function clearBreadcrumbs() $this->recorder->clear(); } + /** + * Gets the configuration of the client. + * + * @return Configuration + */ public function getConfig() { return $this->config; } + /** + * Adds a new middleware to the end of the stack. + * + * @param callable $callable The middleware + * + * @throws \RuntimeException If this method is called while the stack is dequeuing + */ + public function addMiddleware(callable $callable) + { + if ($this->stackLocked) { + throw new \RuntimeException('Middleware can\'t be added once the stack is dequeuing'); + } + + $next = $this->middlewareStackTip; + + $this->middlewareStackTip = function (Event $event, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) use ($callable, $next) { + $result = $callable($event, $next, $request, $exception, $payload); + + if (!$result instanceof Event) { + throw new \UnexpectedValueException(sprintf('Middleware must return an instance of the "%s" class.', Event::class)); + } + + return $result; + }; + } + /** * Gets the representation serialier. * @@ -262,167 +320,42 @@ public function createProcessors() return $processors; } - public function getLastError() - { - return $this->lastError; - } - /** - * Given an identifier, returns a Sentry searchable string. + * Logs a message. * - * @param mixed $ident + * @param string $message The message (primary description) for the event + * @param array $params Params to use when formatting the message + * @param array $payload Additional attributes to pass with this event * - * @return mixed - * @codeCoverageIgnore + * @return string */ - public function getIdent($ident) + public function captureMessage($message, array $params = [], array $payload = []) { - // XXX: We don't calculate checksums yet, so we only have the ident. - return $ident; - } - - /** - * @param string $message the message (primary description) for the event - * @param array $params params to use when formatting the message - * @param string $level Log level group - * @param bool|array $stack - * @param mixed $vars - * - * @return string|null - * - * @deprecated - * @codeCoverageIgnore - */ - public function message( - $message, - $params = [], - $level = self::LEVEL_INFO, - $stack = false, - $vars = null - ) { - return $this->captureMessage($message, $params, $level, $stack, $vars); - } + $payload['message'] = $message; + $payload['message_params'] = $params; - /** - * Log a message to sentry. - * - * @param string $message the message (primary description) for the event - * @param array $params params to use when formatting the message - * @param array $data additional attributes to pass with this event (see Sentry docs) - * @param bool|array $stack - * @param mixed $vars - * - * @return string|null - */ - public function captureMessage( - $message, - $params = [], - $data = [], - $stack = false, - $vars = null - ) { - // Gracefully handle messages which contain formatting characters, but were not - // intended to be used with formatting. - if (!empty($params)) { - $formatted_message = vsprintf($message, $params); - } else { - $formatted_message = $message; - } - - if (null === $data) { - $data = []; - // support legacy method of passing in a level name as the third arg - } elseif (!is_array($data)) { - $data = [ - 'level' => $data, - ]; - } - - $data['message'] = $formatted_message; - $data['sentry.interfaces.Message'] = [ - 'message' => $message, - 'params' => $params, - 'formatted' => $formatted_message, - ]; - - return $this->capture($data, $stack, $vars); + return $this->capture($payload); } /** - * Log an exception to sentry. + * Logs an exception. * - * @param \Exception $exception the Exception object - * @param array $data additional attributes to pass with this event (see Sentry docs) - * @param mixed $logger - * @param mixed $vars + * @param \Throwable|\Exception $exception The exception object + * @param array $payload Additional attributes to pass with this event * - * @return string|null + * @return string */ - public function captureException($exception, $data = null, $logger = null, $vars = null) + public function captureException($exception, array $payload = []) { - if (in_array(get_class($exception), $this->config->getExcludedExceptions())) { - return null; - } - - if (null === $data) { - $data = []; - } - - $currentException = $exception; - do { - $exceptionData = [ - 'value' => $this->serializer->serialize($currentException->getMessage()), - 'type' => get_class($currentException), - ]; - - /** - * Exception::getTrace doesn't store the point at where the exception - * was thrown, so we have to stuff it in ourselves. Ugh. - */ - $trace = $currentException->getTrace(); - $frameWhereExceptionWasThrown = [ - 'file' => $currentException->getFile(), - 'line' => $currentException->getLine(), - ]; - - array_unshift($trace, $frameWhereExceptionWasThrown); - - $this->autoloadRavenStacktrace(); - - $exceptionData['stacktrace'] = [ - 'frames' => Stacktrace::fromBacktrace( - $this, - $exception->getTrace(), - $exception->getFile(), - $exception->getLine() - )->getFrames(), - ]; - - $exceptions[] = $exceptionData; - } while ($currentException = $currentException->getPrevious()); - - $data['exception'] = [ - 'values' => array_reverse($exceptions), - ]; - if (null !== $logger) { - $data['logger'] = $logger; - } - - if (empty($data['level'])) { - if (method_exists($exception, 'getSeverity')) { - $data['level'] = $this->translateSeverity($exception->getSeverity()); - } else { - $data['level'] = self::LEVEL_ERROR; - } - } + $payload['exception'] = $exception; - return $this->capture($data, $trace, $vars); + return $this->capture($payload); } /** - * Capture the most recent error (obtained with ``error_get_last``). + * Logs the most recent error (obtained with {@link error_get_last}). * - * @return string|null + * @return string */ public function captureLastError() { @@ -432,47 +365,36 @@ public function captureLastError() return null; } - $e = new \ErrorException( - @$error['message'], - 0, - @$error['type'], - @$error['file'], - @$error['line'] - ); + $exception = new \ErrorException(@$error['message'], 0, @$error['type'], @$error['file'], @$error['line']); - return $this->captureException($e); + return $this->captureException($exception); } /** - * Log an query to sentry. + * Gets the last event that was captured by the client. However, it could + * have been sent or still sit in the queue of pending events. * - * @param string|null $query - * @param string $level - * @param string $engine + * @return Event */ - public function captureQuery($query, $level = self::LEVEL_INFO, $engine = '') + public function getLastEvent() { - $data = [ - 'message' => $query, - 'level' => $level, - 'sentry.interfaces.Query' => [ - 'query' => $query, - ], - ]; - - if (!empty($engine)) { - $data['sentry.interfaces.Query']['engine'] = $engine; - } - - return $this->capture($data, false); + return $this->lastEvent; } /** * Return the last captured event's ID or null if none available. + * + * @deprecated since version 2.0, to be removed in 3.0. Use getLastEvent() instead. */ public function getLastEventId() { - return $this->lastEventId; + @trigger_error(sprintf('The %s() method is deprecated since version 2.0. Use getLastEvent() instead.', __METHOD__), E_USER_DEPRECATED); + + if (null === $this->lastEvent) { + return null; + } + + return str_replace('-', '', $this->lastEvent->getId()->toString()); } protected function registerDefaultBreadcrumbHandlers() @@ -498,183 +420,63 @@ protected static function isHttpRequest() return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; } - protected function getHttpData() - { - $headers = []; - - foreach ($_SERVER as $key => $value) { - if (0 === strpos($key, 'HTTP_')) { - $header_key = - str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); - $headers[$header_key] = $value; - } elseif (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH']) && !empty($value)) { - $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); - $headers[$header_key] = $value; - } - } - - $result = [ - 'method' => self::_server_variable('REQUEST_METHOD'), - 'url' => $this->getCurrentUrl(), - 'query_string' => self::_server_variable('QUERY_STRING'), - ]; - - // dont set this as an empty array as PHP will treat it as a numeric array - // instead of a mapping which goes against the defined Sentry spec - if (!empty($_POST)) { - $result['data'] = $_POST; - } - if (!empty($_COOKIE)) { - $result['cookies'] = $_COOKIE; - } - if (!empty($headers)) { - $result['headers'] = $headers; - } - - return [ - 'request' => $result, - ]; - } - - protected function getUserData() - { - $user = $this->context->getUserData(); - if (empty($user)) { - if (!function_exists('session_id') || !session_id()) { - return []; - } - $user = [ - 'id' => session_id(), - ]; - if (!empty($_SERVER['REMOTE_ADDR'])) { - $user['ip_address'] = $_SERVER['REMOTE_ADDR']; - } - if (!empty($_SESSION)) { - $user['data'] = $_SESSION; - } - } - - return [ - 'user' => $user, - ]; - } - - public function getDefaultData() - { - return [ - 'server_name' => $this->config->getServerName(), - 'project' => $this->config->getProjectId(), - 'logger' => $this->config->getLogger(), - 'tags' => $this->config->getTags(), - 'platform' => 'php', - 'culprit' => $this->transaction->peek(), - 'sdk' => [ - 'name' => 'sentry-php', - 'version' => self::VERSION, - ], - ]; - } - - public function capture($data, $stack = null, $vars = null) + /** + * Captures a new event using the provided data. + * + * @param array $payload The data of the event being captured + * + * @return string + */ + public function capture(array $payload) { - if (!isset($data['timestamp'])) { - $data['timestamp'] = gmdate('Y-m-d\TH:i:s\Z'); - } - if (!isset($data['level'])) { - $data['level'] = self::LEVEL_ERROR; - } - if (!isset($data['tags'])) { - $data['tags'] = []; - } - if (!isset($data['extra'])) { - $data['extra'] = []; - } - if (!isset($data['event_id'])) { - $data['event_id'] = static::uuid4(); - } + $event = new Event($this->config); - if (isset($data['message'])) { - $data['message'] = substr($data['message'], 0, self::MESSAGE_LIMIT); + if (isset($payload['culprit'])) { + $event = $event->withCulprit($payload['culprit']); + } else { + $event = $event->withCulprit($this->transaction->peek()); } - $data = array_merge($this->getDefaultData(), $data); - - if (static::isHttpRequest()) { - $data = array_merge($this->getHttpData(), $data); + if (isset($payload['level'])) { + $event = $event->withLevel($payload['level']); } - $data = array_merge($this->getUserData(), $data); - - if (!empty($this->config->getRelease())) { - $data['release'] = $this->config->getRelease(); + if (isset($payload['logger'])) { + $event = $event->withLogger($payload['logger']); } - if (!empty($this->config->getCurrentEnvironment())) { - $data['environment'] = $this->config->getCurrentEnvironment(); + if (isset($payload['tags_context'])) { + $event = $event->withTagsContext($payload['tags_context']); } - $data['tags'] = array_merge( - $this->config->getTags(), - $this->context->getTags(), - $data['tags'] - ); - - $data['extra'] = array_merge( - $this->context->getExtraData(), - $data['extra'] - ); - - if (empty($data['extra'])) { - unset($data['extra']); - } - if (empty($data['tags'])) { - unset($data['tags']); - } - if (empty($data['user'])) { - unset($data['user']); - } - if (empty($data['request'])) { - unset($data['request']); + if (isset($payload['extra_context'])) { + $event = $event->withExtraContext($payload['extra_context']); } - if (null !== $this->recorder) { - $data['breadcrumbs'] = iterator_to_array($this->recorder); + if (isset($payload['user_context'])) { + $event = $event->withUserContext($payload['user_context']); } - if ((!$stack && $this->config->getAutoLogStacks()) || (true === $stack)) { - $stack = debug_backtrace(); - - // Drop last stack - array_shift($stack); + if (isset($payload['message'])) { + $payload['message'] = substr($payload['message'], 0, static::MESSAGE_LIMIT); } - if (!empty($stack)) { - $this->autoloadRavenStacktrace(); + $event = $this->callMiddlewareStack($event, static::isHttpRequest() ? ServerRequestFactory::fromGlobals() : null, isset($payload['exception']) ? $payload['exception'] : null, $payload); - if (!isset($data['stacktrace']) && !isset($data['exception'])) { - $data['stacktrace'] = [ - 'frames' => Stacktrace::fromBacktrace( - $this, - $stack, - isset($stack['file']) ? $stack['file'] : __FILE__, - isset($stack['line']) ? $stack['line'] : __LINE__ - 2 - )->getFrames(), - ]; - } - } + $payload = $event->toArray(); - $this->sanitize($data); - $this->process($data); + $this->sanitize($payload); + $this->process($payload); if (!$this->storeErrorsForBulkSend) { - $this->send($data); + $this->send($payload); } else { - $this->pendingEvents[] = $data; + $this->pendingEvents[] = $payload; } - $this->lastEventId = $data['event_id']; + $this->lastEvent = $event; - return $data['event_id']; + return $payload['event_id']; } public function sanitize(&$data) @@ -787,102 +589,6 @@ public function send(&$data) $this->pendingRequests[] = $promise; } - /** - * Generate an uuid4 value. - * - * @return string - */ - protected static function uuid4() - { - $uuid = sprintf( - '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', - // 32 bits for "time_low" - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - - // 16 bits for "time_mid" - mt_rand(0, 0xffff), - - // 16 bits for "time_hi_and_version", - // four most significant bits holds version number 4 - mt_rand(0, 0x0fff) | 0x4000, - - // 16 bits, 8 bits for "clk_seq_hi_res", - // 8 bits for "clk_seq_low", - // two most significant bits holds zero and one for variant DCE1.1 - mt_rand(0, 0x3fff) | 0x8000, - - // 48 bits for "node" - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - mt_rand(0, 0xffff) - ); - - return str_replace('-', '', $uuid); - } - - /** - * Return the URL for the current request. - * - * @return string|null - */ - protected function getCurrentUrl() - { - // When running from commandline the REQUEST_URI is missing. - if (!isset($_SERVER['REQUEST_URI'])) { - return null; - } - - // HTTP_HOST is a client-supplied header that is optional in HTTP 1.0 - $host = (!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] - : (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR'] - : (!empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''))); - - $httpS = $this->isHttps() ? 's' : ''; - - return "http{$httpS}://{$host}{$_SERVER['REQUEST_URI']}"; - } - - /** - * Was the current request made over https? - * - * @return bool - */ - protected function isHttps() - { - if (!empty($_SERVER['HTTPS']) && ('off' !== $_SERVER['HTTPS'])) { - return true; - } - - if (!empty($_SERVER['SERVER_PORT']) && (443 == $_SERVER['SERVER_PORT'])) { - return true; - } - - if (!empty($this->config->isTrustXForwardedProto()) && - !empty($_SERVER['X-FORWARDED-PROTO']) && - ('https' === $_SERVER['X-FORWARDED-PROTO'])) { - return true; - } - - return false; - } - - /** - * Get the value of a key from $_SERVER. - * - * @param string $key Key whose value you wish to obtain - * - * @return string Key's value - */ - private static function _server_variable($key) - { - if (isset($_SERVER[$key])) { - return $_SERVER[$key]; - } - - return ''; - } - /** * Translate a PHP Error constant into a Sentry log level group. * @@ -979,13 +685,27 @@ private function isEncodingCompressed() return 'gzip' === $this->config->getEncoding(); } - private function autoloadRavenStacktrace() + /** + * Calls the middleware stack. + * + * @param Event $event The event object + * @param ServerRequestInterface|null $request The request object, if available + * @param \Exception|null $exception The thrown exception, if any + * @param array $payload Additional payload data + * + * @return Event + */ + private function callMiddlewareStack(Event $event, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) { - // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) - if (!class_exists('\\Raven\\Stacktrace')) { - // @codeCoverageIgnoreStart - spl_autoload_call('\\Raven\\Stacktrace'); - // @codeCoverageIgnoreEnd - } + $start = $this->middlewareStackTip; + + $this->stackLocked = true; + + /** @var Event $event */ + $event = $start($event, $request, $exception, $payload); + + $this->stackLocked = false; + + return $event; } } diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index a69919d87..59e4ed2ad 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -407,6 +407,25 @@ public function setExcludedExceptions(array $exceptions) $this->options = $this->resolver->resolve($options); } + /** + * Checks whether the given exception should be ignored when sending events + * to Sentry. + * + * @param \Throwable|\Exception The exception + * + * @return bool + */ + public function isExcludedException($exception) + { + foreach ($this->options['excluded_exceptions'] as $exceptionClass) { + if ($exception instanceof $exceptionClass) { + return true; + } + } + + return false; + } + /** * Gets the list of paths to exclude from app_path detection. * @@ -741,7 +760,7 @@ private function configureOptions(OptionsResolver $resolver) 'install_default_breadcrumb_handlers' => true, 'install_shutdown_handler' => true, 'mb_detect_order' => null, - 'auto_log_stacks' => false, + 'auto_log_stacks' => true, 'context_lines' => 3, 'encoding' => 'gzip', 'current_environment' => 'default', diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index e17d43828..0162d0545 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -78,7 +78,7 @@ public function bitwiseOr($a, $b) public function handleException($e, $isError = false, $vars = null) { - $e->event_id = $this->client->captureException($e, null, null, $vars); + $e->event_id = $this->client->captureException($e, $vars); if (!$isError && $this->call_existing_exception_handler) { if (null !== $this->old_exception_handler) { diff --git a/lib/Raven/Event.php b/lib/Raven/Event.php new file mode 100644 index 000000000..5efc6821b --- /dev/null +++ b/lib/Raven/Event.php @@ -0,0 +1,835 @@ + + */ +final class Event implements \JsonSerializable +{ + /** + * @var UuidInterface The UUID + */ + private $id; + + /** + * @var string The date and time of when this event was generated + */ + private $timestamp; + + /** + * @var string The severity of this event + */ + private $level; + + /** + * @var string The name of the logger which created the record + */ + private $logger; + + /** + * @var string the name of the transaction (or culprit) which caused this exception + */ + private $culprit; + + /** + * @var string The name of the server (e.g. the host name) + */ + private $serverName; + + /** + * @var string The release of the program + */ + private $release; + + /** + * @var string The error message + */ + private $message; + + /** + * @var array The parameters to use to format the message + */ + private $messageParams = []; + + /** + * @var string The environment where this event generated (e.g. production) + */ + private $environment; + + /** + * @var array A list of relevant modules and their versions + */ + private $modules = []; + + /** + * @var array The request data + */ + private $request = []; + + /** + * @var array The server OS context data + */ + private $serverOsContext = []; + + /** + * @var array The runtime context data + */ + private $runtimeContext = []; + + /** + * @var array The user context data + */ + private $userContext = []; + + /** + * @var array An arbitrary mapping of additional metadata + */ + private $extraContext = []; + + /** + * @var string[] A List of tags associated to this event + */ + private $tagsContext = []; + + /** + * @var string[] An array of strings used to dictate the deduplication of this event + */ + private $fingerprint = []; + + /** + * @var Breadcrumb[] The associated breadcrumbs + */ + private $breadcrumbs = []; + + /** + * @var array The exception + */ + private $exception; + + /** + * @var Stacktrace The stacktrace that generated this event + */ + private $stacktrace; + + /** + * Class constructor. + * + * @param Configuration $config The client configuration + */ + public function __construct(Configuration $config) + { + $this->id = Uuid::uuid4(); + $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); + $this->level = Client::LEVEL_ERROR; + $this->serverName = $config->getServerName(); + $this->release = $config->getRelease(); + $this->environment = $config->getCurrentEnvironment(); + } + + /** + * Creates a new instance of this class. + * + * @param Configuration $config The client configuration + * + * @return static + */ + public static function create(Configuration $config) + { + return new static($config); + } + + /** + * Gets the UUID of this event. + * + * @return UuidInterface + */ + public function getId() + { + return $this->id; + } + + /** + * Gets the timestamp of when this event was generated. + * + * @return string + */ + public function getTimestamp() + { + return $this->timestamp; + } + + /** + * Gets the severity of this event. + * + * @return string + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the severity of this event. + * + * @param string $level The severity + * + * @return static + */ + public function withLevel($level) + { + if ($level === $this->level) { + return $this; + } + + $new = clone $this; + $new->level = $level; + + return $new; + } + + /** + * Gets the name of the logger which created the event. + * + * @return string + */ + public function getLogger() + { + return $this->logger; + } + + /** + * Sets the name of the logger which created the event. + * + * @param string $logger The logger name + * + * @return static + */ + public function withLogger($logger) + { + if ($logger === $this->logger) { + return $this; + } + + $new = clone $this; + $new->logger = $logger; + + return $new; + } + + /** + * Gets the name of the transaction (or culprit) which caused this + * exception. + * + * @return string + */ + public function getCulprit() + { + return $this->culprit; + } + + /** + * Sets the name of the transaction (or culprit) which caused this + * exception. + * + * @param string $culprit The transaction name + * + * @return static + */ + public function withCulprit($culprit) + { + if ($culprit === $this->culprit) { + return $this; + } + + $new = clone $this; + $new->culprit = $culprit; + + return $new; + } + + /** + * Gets the name of the server. + * + * @return string + */ + public function getServerName() + { + return $this->serverName; + } + + /** + * Sets the name of the server. + * + * @param string $serverName The server name + * + * @return static + */ + public function withServerName($serverName) + { + if ($serverName === $this->serverName) { + return $this; + } + + $new = clone $this; + $new->serverName = $serverName; + + return $new; + } + + /** + * Gets the release of the program. + * + * @return string + */ + public function getRelease() + { + return $this->release; + } + + /** + * Sets the release of the program. + * + * @param string $release The release + * + * @return static + */ + public function withRelease($release) + { + if ($release === $this->release) { + return $this; + } + + $new = clone $this; + $new->release = $release; + + return $new; + } + + /** + * Gets the error message. + * + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Gets the parameters to use to format the message. + * + * @return string[] + */ + public function getMessageParams() + { + return $this->messageParams; + } + + /** + * Sets the error message. + * + * @param string $message The message + * @param array $params The parameters to use to format the message + * + * @return static + */ + public function withMessage($message, array $params = []) + { + if ($message === $this->message && $params === $this->messageParams) { + return $this; + } + + $new = clone $this; + $new->message = $message; + $new->messageParams = $params; + + return $new; + } + + /** + * Gets a list of relevant modules and their versions. + * + * @return array + */ + public function getModules() + { + return $this->modules; + } + + /** + * Sets a list of relevant modules and their versions. + * + * @param array $modules + * + * @return static + */ + public function withModules(array $modules) + { + if ($modules === $this->modules) { + return $this; + } + + $new = clone $this; + $new->modules = $modules; + + return $new; + } + + /** + * Gets the request data. + * + * @return array + */ + public function getRequest() + { + return $this->request; + } + + /** + * Sets the request data. + * + * @param array $request The request data + * + * @return static + */ + public function withRequest(array $request) + { + if ($request === $this->request) { + return $this; + } + + $new = clone $this; + $new->request = $request; + + return $new; + } + + /** + * Gets an arbitrary mapping of additional metadata. + * + * @return array + */ + public function getExtraContext() + { + return $this->extraContext; + } + + /** + * Sets an arbitrary mapping of additional metadata. + * + * @param array $extraContext Additional metadata + * @param bool $replace Whether to merge the old data with the new one or replace entirely it + * + * @return static + */ + public function withExtraContext(array $extraContext, $replace = true) + { + if ($extraContext === $this->extraContext) { + return $this; + } + + $new = clone $this; + $new->extraContext = $replace ? $extraContext : array_merge($this->extraContext, $extraContext); + + return $new; + } + + /** + * Gets a list of tags. + * + * @return string[] + */ + public function getTagsContext() + { + return $this->tagsContext; + } + + /** + * Sets a list of tags. + * + * @param string[] $tagsContext The tags + * @param bool $replace Whether to merge the old data with the new one or replace entirely it + * + * @return static + */ + public function withTagsContext(array $tagsContext, $replace = true) + { + if ($tagsContext === $this->tagsContext) { + return $this; + } + + $new = clone $this; + $new->tagsContext = $replace ? $tagsContext : array_merge($this->tagsContext, $tagsContext); + + return $new; + } + + /** + * Gets the user context. + * + * @return array + */ + public function getUserContext() + { + return $this->userContext; + } + + /** + * Sets the user context. + * + * @param array $userContext The context data + * @param bool $replace Whether to merge the old data with the new one or replace entirely it + * + * @return static + */ + public function withUserContext(array $userContext, $replace = true) + { + if ($userContext === $this->userContext) { + return $this; + } + + $new = clone $this; + $new->userContext = $replace ? $userContext : array_merge($this->userContext, $userContext); + + return $new; + } + + /** + * Gets the server OS context. + * + * @return array + */ + public function getServerOsContext() + { + return $this->serverOsContext; + } + + /** + * Gets the server OS context. + * + * @param array $serverOsContext The context data + * @param bool $replace Whether to merge the old data with the new one or replace entirely it + * + * @return static + */ + public function withServerOsContext(array $serverOsContext, $replace = true) + { + if ($serverOsContext === $this->serverOsContext) { + return $this; + } + + $new = clone $this; + $new->serverOsContext = $replace ? $serverOsContext : array_merge($this->serverOsContext, $serverOsContext); + + return $new; + } + + /** + * Gets the runtime context data. + * + * @return array + */ + public function getRuntimeContext() + { + return $this->runtimeContext; + } + + /** + * Sets the runtime context data. + * + * @param array $runtimeContext The context data + * @param bool $replace Whether to merge the old data with the new one or replace entirely it + * + * @return static + */ + public function withRuntimeContext(array $runtimeContext, $replace = true) + { + if ($runtimeContext === $this->runtimeContext) { + return $this; + } + + $new = clone $this; + $new->runtimeContext = $replace ? $runtimeContext : array_merge($this->runtimeContext, $runtimeContext); + + return $new; + } + + /** + * Gets an array of strings used to dictate the deduplication of this + * event. + * + * @return string[] + */ + public function getFingerprint() + { + return $this->fingerprint; + } + + /** + * Sets an array of strings used to dictate the deduplication of this + * event. + * + * @param string[] $fingerprint The strings + * + * @return static + */ + public function withFingerprint(array $fingerprint) + { + if ($fingerprint === $this->fingerprint) { + return $this; + } + + $new = clone $this; + $new->fingerprint = $fingerprint; + + return $new; + } + + /** + * Gets the environment in which this event was generated. + * + * @return string + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * Sets the environment in which this event was generated. + * + * @param string $environment The name of the environment + * + * @return static + */ + public function withEnvironment($environment) + { + if ($environment === $this->environment) { + return $this; + } + + $new = clone $this; + $new->environment = $environment; + + return $new; + } + + /** + * Gets the breadcrumbs. + * + * @return array + */ + public function getBreadcrumbs() + { + return $this->breadcrumbs; + } + + /** + * Adds a new breadcrumb to the event. + * + * @param Breadcrumb $breadcrumb The breadcrumb + * + * @return static + */ + public function withBreadcrumb(Breadcrumb $breadcrumb) + { + $new = clone $this; + $new->breadcrumbs[] = $breadcrumb; + + return $new; + } + + /** + * Gets the exception. + * + * @return array + */ + public function getException() + { + return $this->exception; + } + + /** + * Sets the exception. + * + * @param array $exception The exception + * + * @return static + */ + public function withException(array $exception) + { + if ($exception === $this->exception) { + return $this; + } + + $new = clone $this; + $new->exception = $exception; + + return $new; + } + + /** + * Gets the stacktrace that generated this event. + * + * @return Stacktrace + */ + public function getStacktrace() + { + return $this->stacktrace; + } + + /** + * Sets the stacktrace that generated this event. + * + * @param Stacktrace $stacktrace The stacktrace instance + * + * @return static + */ + public function withStacktrace(Stacktrace $stacktrace) + { + if ($stacktrace === $this->stacktrace) { + return $this; + } + + $new = clone $this; + $new->stacktrace = $stacktrace; + + return $new; + } + + /** + * Gets the event as an array. + * + * @return array + */ + public function toArray() + { + $data = [ + 'event_id' => str_replace('-', '', $this->id->toString()), + 'timestamp' => $this->timestamp, + 'level' => $this->level, + 'platform' => 'php', + 'sdk' => [ + 'name' => 'sentry-php', + 'version' => Client::VERSION, + ], + ]; + + if (null !== $this->logger) { + $data['logger'] = $this->logger; + } + + if (null !== $this->culprit) { + $data['culprit'] = $this->culprit; + } + + if (null !== $this->serverName) { + $data['server_name'] = $this->serverName; + } + + if (null !== $this->release) { + $data['release'] = $this->release; + } + + if (null !== $this->environment) { + $data['environment'] = $this->environment; + } + + if (!empty($this->fingerprint)) { + $data['fingerprint'] = $this->fingerprint; + } + + if (!empty($this->modules)) { + $data['modules'] = $this->modules; + } + + if (!empty($this->extraContext)) { + $data['extra'] = $this->extraContext; + } + + if (!empty($this->tagsContext)) { + $data['tags'] = $this->tagsContext; + } + + if (!empty($this->userContext)) { + $data['user'] = $this->userContext; + } + + if (!empty($this->serverOsContext)) { + $data['server_os'] = $this->serverOsContext; + } + + if (!empty($this->runtimeContext)) { + $data['runtime'] = $this->runtimeContext; + } + + if (!empty($this->breadcrumbs)) { + $data['breadcrumbs'] = $this->breadcrumbs; + } + + if (!empty($this->exception)) { + foreach ($this->exception as $exception) { + $exceptionData = [ + 'type' => $exception['type'], + 'value' => $exception['value'], + ]; + + if (isset($exception['stacktrace'])) { + $exceptionData['stacktrace'] = [ + 'frames' => $exception['stacktrace']->toArray(), + ]; + } + + $data['exception']['values'][] = $exceptionData; + } + } + + if (null !== $this->stacktrace) { + $data['stacktrace'] = [ + 'frames' => $this->stacktrace->toArray(), + ]; + } + + if (!empty($this->request)) { + $data['request'] = $this->request; + } + + if (null !== $this->message) { + if (empty($this->messageParams)) { + $data['message'] = $this->message; + } else { + $data['message'] = [ + 'message' => $this->message, + 'params' => $this->messageParams, + 'formatted' => vsprintf($this->message, $this->messageParams), + ]; + } + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->toArray(); + } +} diff --git a/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php b/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php new file mode 100644 index 000000000..e18af7af9 --- /dev/null +++ b/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php @@ -0,0 +1,60 @@ + + */ +final class BreadcrumbInterfaceMiddleware +{ + /** + * @var Recorder The breadcrumbs recorder + */ + private $recorder; + + /** + * Constructor. + * + * @param Recorder $recorder The breadcrumbs recorder + */ + public function __construct(Recorder $recorder) + { + $this->recorder = $recorder; + } + + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + { + foreach ($this->recorder as $breadcrumb) { + $event = $event->withBreadcrumb($breadcrumb); + } + + return $next($event, $request, $exception, $payload); + } +} diff --git a/lib/Raven/Middleware/ContextInterfaceMiddleware.php b/lib/Raven/Middleware/ContextInterfaceMiddleware.php new file mode 100644 index 000000000..d132de7da --- /dev/null +++ b/lib/Raven/Middleware/ContextInterfaceMiddleware.php @@ -0,0 +1,68 @@ + + */ +final class ContextInterfaceMiddleware +{ + /** + * @var Context The context storage + */ + private $context; + + /** + * Constructor. + * + * @param Context $context The context storage + */ + public function __construct(Context $context) + { + $this->context = $context; + } + + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + { + $tagsContext = isset($payload['tags_context']) ? $payload['tags_context'] : []; + $extraContext = isset($payload['extra_context']) ? $payload['extra_context'] : []; + $serverOsContext = isset($payload['server_os_context']) ? $payload['server_os_context'] : []; + $runtimeContext = isset($payload['runtime_context']) ? $payload['runtime_context'] : []; + $userContext = isset($payload['user_context']) ? $payload['user_context'] : []; + + $event = $event->withTagsContext(array_merge($this->context->getTags(), $tagsContext), false) + ->withExtraContext(array_merge($this->context->getExtraData(), $extraContext), false) + ->withUserContext(array_merge($this->context->getUserData(), $userContext), false) + ->withServerOsContext($serverOsContext, false) + ->withRuntimeContext($runtimeContext, false); + + return $next($event, $request, $exception, $payload); + } +} diff --git a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php new file mode 100644 index 000000000..995c22a58 --- /dev/null +++ b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php @@ -0,0 +1,87 @@ + + */ +final class ExceptionInterfaceMiddleware +{ + /** + * @var Client The Raven client + */ + private $client; + + /** + * Constructor. + * + * @param Client $client The Raven client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + { + // Do not override the level if it was set explicitly by the user + if (!isset($payload['level']) && $exception instanceof \ErrorException) { + $event = $event->withLevel($this->client->translateSeverity($exception->getSeverity())); + } + + if (null !== $exception) { + $exceptions = []; + $currentException = $exception; + + do { + if ($this->client->getConfig()->isExcludedException($currentException)) { + continue; + } + + $data = [ + 'type' => get_class($currentException), + 'value' => $this->client->getSerializer()->serialize($currentException->getMessage()), + ]; + + if ($this->client->getConfig()->getAutoLogStacks()) { + $data['stacktrace'] = Stacktrace::createFromBacktrace($this->client, $currentException->getTrace(), $currentException->getFile(), $currentException->getLine()); + } + + $exceptions[] = $data; + } while ($currentException = $currentException->getPrevious()); + + $exceptions = array_reverse($exceptions); + + $event = $event->withException($exceptions); + } + + return $next($event, $request, $exception, $payload); + } +} diff --git a/lib/Raven/Middleware/MessageInterfaceMiddleware.php b/lib/Raven/Middleware/MessageInterfaceMiddleware.php new file mode 100644 index 000000000..f251d1df7 --- /dev/null +++ b/lib/Raven/Middleware/MessageInterfaceMiddleware.php @@ -0,0 +1,47 @@ + + */ +class MessageInterfaceMiddleware +{ + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + { + $message = isset($payload['message']) ? $payload['message'] : null; + $messageParams = isset($payload['message_params']) ? $payload['message_params'] : []; + + if (null !== $message) { + $event = $event->withMessage($message, $messageParams); + } + + return $next($event, $request, $exception, $payload); + } +} diff --git a/lib/Raven/Middleware/RequestInterfaceMiddleware.php b/lib/Raven/Middleware/RequestInterfaceMiddleware.php new file mode 100644 index 000000000..ec67d22a8 --- /dev/null +++ b/lib/Raven/Middleware/RequestInterfaceMiddleware.php @@ -0,0 +1,60 @@ + + */ +class RequestInterfaceMiddleware +{ + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + { + if (null === $request) { + return $next($event, $request, $exception, $payload); + } + + $requestData = [ + 'url' => (string) $request->getUri(), + 'method' => $request->getMethod(), + 'headers' => $request->getHeaders(), + ]; + + if ('' !== $request->getUri()->getQuery()) { + $requestData['query_string'] = $request->getUri()->getQuery(); + } + + if ($request->hasHeader('REMOTE_ADDR')) { + $requestData['env']['REMOTE_ADDR'] = $request->getHeaderLine('REMOTE_ADDR'); + } + + $event = $event->withRequest($requestData); + + return $next($event, $request, $exception, $payload); + } +} diff --git a/lib/Raven/Middleware/UserInterfaceMiddleware.php b/lib/Raven/Middleware/UserInterfaceMiddleware.php new file mode 100644 index 000000000..1f425ce72 --- /dev/null +++ b/lib/Raven/Middleware/UserInterfaceMiddleware.php @@ -0,0 +1,47 @@ + + */ +final class UserInterfaceMiddleware +{ + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + { + $userContext = $event->getUserContext(); + + if (!isset($userContext['ip_address']) && null !== $request && $request->hasHeader('REMOTE_ADDR')) { + $userContext['ip_address'] = $request->getHeaderLine('REMOTE_ADDR'); + } + + $event = $event->withUserContext($userContext); + + return $next($event, $request, $exception, $payload); + } +} diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php index 48e5ada97..1e83ba31c 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -80,6 +80,10 @@ public function sanitize(&$item, $key) public function sanitizeException(&$data) { foreach ($data['exception']['values'] as &$value) { + if (!isset($value['stacktrace'])) { + continue; + } + $this->sanitizeStacktrace($value['stacktrace']); } } diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php index f9de0ba46..aa68a0ca5 100644 --- a/lib/Raven/ReprSerializer.php +++ b/lib/Raven/ReprSerializer.php @@ -29,7 +29,7 @@ protected function serializeValue($value) return $value . '.0'; } elseif (is_int($value) || is_float($value)) { return (string) $value; - } elseif (is_object($value) || ('object' == gettype($value))) { + } elseif (is_object($value) || 'object' == gettype($value)) { return 'Object ' . get_class($value); } elseif (is_resource($value)) { return 'Resource ' . get_resource_type($value); diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 7095843b2..7d8cdadca 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -149,7 +149,7 @@ protected function serializeValue($value) { if ((null === $value) || is_bool($value) || is_float($value) || is_int($value)) { return $value; - } elseif (is_object($value) || ('object' == gettype($value))) { + } elseif (is_object($value) || 'object' == gettype($value)) { return 'Object ' . get_class($value); } elseif (is_resource($value)) { return 'Resource ' . get_resource_type($value); diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index a1c9340ac..fb24f23fc 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -65,6 +65,20 @@ public function __construct(Client $client) $this->reprSerializer = $client->getReprSerializer(); } + /** + * Creates a new instance of this class using the current backtrace data. + * + * @param Client $client The Raven client + * + * @return static + */ + public static function create(Client $client) + { + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + return static::createFromBacktrace($client, $backtrace, __FILE__, __LINE__); + } + /** * Creates a new instance of this class from the given backtrace. * @@ -75,7 +89,7 @@ public function __construct(Client $client) * * @return static */ - public static function fromBacktrace(Client $client, array $backtrace, $file, $line) + public static function createFromBacktrace(Client $client, array $backtrace, $file, $line) { $stacktrace = new static($client); diff --git a/lib/Raven/Util.php b/lib/Raven/Util.php deleted file mode 100644 index 240cf87ec..000000000 --- a/lib/Raven/Util.php +++ /dev/null @@ -1,35 +0,0 @@ -dummy_shutdown_handlers_has_set = true; } - - /** - * Expose the current url method to test it. - * - * @return string - */ - public function test_get_current_url() - { - return $this->getCurrentUrl(); - } } class Dummy_Raven_Client_No_Http extends Dummy_Raven_Client @@ -158,19 +141,6 @@ private function create_exception() } } - private function create_chained_exception() - { - try { - throw new \Exception('Foo bar'); - } catch (\Exception $ex) { - try { - throw new \Exception('Child exc', 0, $ex); - } catch (\Exception $ex2) { - return $ex2; - } - } - } - public function testDestructor() { $waitCalled = false; @@ -216,182 +186,56 @@ public function testDestructor() $this->assertTrue($waitCalled); } - public function testOptionsExtraData() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->getContext()->mergeExtraData(['foo' => 'bar']); - $client->captureMessage('Test Message %s', ['foo']); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('bar', $client->pendingEvents[0]['extra']['foo']); - } - - public function testOptionsExtraDataWithNull() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->getContext()->mergeExtraData(['foo' => 'bar']); - $client->captureMessage('Test Message %s', ['foo'], null); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('bar', $client->pendingEvents[0]['extra']['foo']); - } - - public function testEmptyExtraData() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->captureMessage('Test Message %s', ['foo']); - - $this->assertCount(1, $client->pendingEvents); - $this->assertArrayNotHasKey('extra', $client->pendingEvents[0]); - } - - public function testCaptureMessageDoesHandleUninterpolatedMessage() - { - $client = ClientBuilder::create()->getClient(); - - $client->storeErrorsForBulkSend = true; - - $client->captureMessage('foo %s'); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('foo %s', $client->pendingEvents[0]['message']); - } - - public function testCaptureMessageDoesHandleInterpolatedMessage() + public function testMiddlewareStackIsSeeded() { $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; + $firstMiddleware = $this->getObjectAttribute($client, 'middlewareStackTip'); + $lastMiddleware = null; - $client->captureMessage('foo %s', ['bar']); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('foo bar', $client->pendingEvents[0]['message']); - } + $client->addMiddleware(function (Event $event, callable $next) use (&$lastMiddleware) { + $lastMiddleware = $next; - public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() - { - $client = ClientBuilder::create(['release' => '1.2.3'])->getClient(); - $client->storeErrorsForBulkSend = true; - - $this->assertEquals('1.2.3', $client->getConfig()->getRelease()); - - $client->captureMessage('foo %s', ['bar']); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('foo bar', $client->pendingEvents[0]['message']); - $this->assertEquals('1.2.3', $client->pendingEvents[0]['release']); - } - - public function testCaptureMessageSetsInterface() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->captureMessage('foo %s', ['bar']); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('foo bar', $client->pendingEvents[0]['message']); - $this->assertArrayHasKey('sentry.interfaces.Message', $client->pendingEvents[0]); - $this->assertEquals('foo bar', $client->pendingEvents[0]['sentry.interfaces.Message']['formatted']); - $this->assertEquals('foo %s', $client->pendingEvents[0]['sentry.interfaces.Message']['message']); - $this->assertEquals(['bar'], $client->pendingEvents[0]['sentry.interfaces.Message']['params']); - } - - public function testCaptureMessageHandlesOptionsAsThirdArg() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->captureMessage('Test Message %s', ['foo'], [ - 'level' => Dummy_Raven_Client::LEVEL_WARNING, - 'extra' => ['foo' => 'bar'], - ]); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals(Dummy_Raven_Client::LEVEL_WARNING, $client->pendingEvents[0]['level']); - $this->assertEquals('bar', $client->pendingEvents[0]['extra']['foo']); - $this->assertEquals('Test Message foo', $client->pendingEvents[0]['message']); - } - - public function testCaptureMessageHandlesLevelAsThirdArg() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; + return $event; + }); - $client->captureMessage('Test Message %s', ['foo'], Dummy_Raven_Client::LEVEL_WARNING); + $client->capture([]); - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals(Dummy_Raven_Client::LEVEL_WARNING, $client->pendingEvents[0]['level']); - $this->assertEquals('Test Message foo', $client->pendingEvents[0]['message']); + $this->assertSame($firstMiddleware, $lastMiddleware); } /** - * @covers \Raven\Client::captureException + * @expectedException \RuntimeException + * @expectedExceptionMessage Middleware can't be added once the stack is dequeuing */ - public function testCaptureExceptionSetsInterfaces() + public function testAddMiddlewareThrowsWhileStackIsRunning() { - // TODO: it'd be nice if we could mock the stacktrace extraction function here $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->captureException($this->create_exception()); - - $this->assertCount(1, $client->pendingEvents); - $this->assertCount(1, $client->pendingEvents[0]['exception']['values']); - $this->assertEquals('Foo bar', $client->pendingEvents[0]['exception']['values'][0]['value']); - $this->assertEquals('Exception', $client->pendingEvents[0]['exception']['values'][0]['type']); - $this->assertNotEmpty($client->pendingEvents[0]['exception']['values'][0]['stacktrace']['frames']); + $client->addMiddleware(function (Event $event) use ($client) { + $client->addMiddleware(function () { + // Do nothing, it's just a middleware added to trigger the exception + }); - $frames = $client->pendingEvents[0]['exception']['values'][0]['stacktrace']['frames']; - $frame = $frames[count($frames) - 1]; + return $event; + }); - $this->assertTrue($frame['lineno'] > 0); - $this->assertEquals('Raven\Tests\ClientTest::create_exception', $frame['function']); - $this->assertFalse(isset($frame['vars'])); - $this->assertEquals(' throw new \Exception(\'Foo bar\');', $frame['context_line']); - $this->assertNotEmpty($frame['pre_context']); - $this->assertNotEmpty($frame['post_context']); + $client->capture([]); } - public function testCaptureExceptionChainedException() - { - // TODO: it'd be nice if we could mock the stacktrace extraction function here - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->captureException($this->create_chained_exception()); - - $this->assertCount(1, $client->pendingEvents); - $this->assertCount(2, $client->pendingEvents[0]['exception']['values']); - $this->assertEquals('Foo bar', $client->pendingEvents[0]['exception']['values'][0]['value']); - $this->assertEquals('Child exc', $client->pendingEvents[0]['exception']['values'][1]['value']); - } - - public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Middleware must return an instance of the "Raven\Event" class. + */ + public function testMiddlewareThrowsWhenBadValueIsReturned() { $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - $e1 = new \ErrorException('First', 0, E_DEPRECATED); - $e2 = new \ErrorException('Second', 0, E_NOTICE, __FILE__, __LINE__, $e1); - $e3 = new \ErrorException('Third', 0, E_ERROR, __FILE__, __LINE__, $e2); - - $client->captureException($e1); - $client->captureException($e2); - $client->captureException($e3); + $client->addMiddleware(function () { + // Do nothing, it's just a middleware added to trigger the exception + }); - $this->assertCount(3, $client->pendingEvents); - $this->assertEquals(Client::LEVEL_WARNING, $client->pendingEvents[0]['level']); - $this->assertEquals(Client::LEVEL_INFO, $client->pendingEvents[1]['level']); - $this->assertEquals(Client::LEVEL_ERROR, $client->pendingEvents[2]['level']); + $client->capture([]); } public function testCaptureExceptionHandlesOptionsAsSecondArg() @@ -405,34 +249,25 @@ public function testCaptureExceptionHandlesOptionsAsSecondArg() $this->assertEquals('test', $client->pendingEvents[0]['culprit']); } - public function testCaptureExceptionHandlesExcludeOption() - { - $client = ClientBuilder::create(['excluded_exceptions' => ['Exception']])->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->captureException($this->create_exception(), 'test'); - - $this->assertEmpty($client->pendingEvents); - } - - public function testCaptureExceptionInvalidUTF8() + public function testCapture() { $client = ClientBuilder::create()->getClient(); $client->storeErrorsForBulkSend = true; - try { - invalid_encoding(); - } catch (\Exception $ex) { - $client->captureException($ex); - } + $client->capture([ + 'level' => Client::LEVEL_DEBUG, + 'logger' => 'foo', + 'tags_context' => ['foo', 'bar'], + 'extra_context' => ['foo' => 'bar'], + 'user_context' => ['bar' => 'foo'], + ]); $this->assertCount(1, $client->pendingEvents); - - try { - $client->send($client->pendingEvents[0]); - } catch (\Exception $ex) { - $this->fail(); - } + $this->assertEquals(Client::LEVEL_DEBUG, $client->pendingEvents[0]['level']); + $this->assertEquals('foo', $client->pendingEvents[0]['logger']); + $this->assertEquals(['foo', 'bar'], $client->pendingEvents[0]['tags']); + $this->assertEquals(['foo' => 'bar'], $client->pendingEvents[0]['extra']); + $this->assertEquals(['bar' => 'foo'], $client->pendingEvents[0]['user']); } public function testDoesRegisterProcessors() @@ -460,250 +295,67 @@ public function testProcessDoesCallProcessors() $client->process($data); } - public function testGetDefaultData() - { - $client = ClientBuilder::create()->getClient(); - $config = $client->getConfig(); - - $client->transaction->push('test'); - - $expected = [ - 'platform' => 'php', - 'project' => $config->getProjectId(), - 'server_name' => $config->getServerName(), - 'logger' => $config->getLogger(), - 'tags' => $config->getTags(), - 'culprit' => 'test', - 'sdk' => [ - 'name' => 'sentry-php', - 'version' => $client::VERSION, - ], - ]; - - $this->assertEquals($expected, $client->getDefaultData()); - } - - /** - * @backupGlobals - */ - public function testGetHttpData() - { - $_SERVER = [ - 'REDIRECT_STATUS' => '200', - 'CONTENT_TYPE' => 'text/xml', - 'CONTENT_LENGTH' => '99', - 'HTTP_HOST' => 'getsentry.com', - 'HTTP_ACCEPT' => 'text/html', - 'HTTP_ACCEPT_CHARSET' => 'utf-8', - 'HTTP_COOKIE' => 'cupcake: strawberry', - 'SERVER_PORT' => '443', - 'SERVER_PROTOCOL' => 'HTTP/1.1', - 'REQUEST_METHOD' => 'PATCH', - 'QUERY_STRING' => 'q=bitch&l=en', - 'REQUEST_URI' => '/welcome/', - 'SCRIPT_NAME' => '/index.php', - ]; - $_POST = [ - 'stamp' => '1c', - ]; - $_COOKIE = [ - 'donut' => 'chocolat', - ]; - - $expected = [ - 'request' => [ - 'method' => 'PATCH', - 'url' => 'https://getsentry.com/welcome/', - 'query_string' => 'q=bitch&l=en', - 'data' => [ - 'stamp' => '1c', - ], - 'cookies' => [ - 'donut' => 'chocolat', - ], - 'headers' => [ - 'Host' => 'getsentry.com', - 'Accept' => 'text/html', - 'Accept-Charset' => 'utf-8', - 'Cookie' => 'cupcake: strawberry', - 'Content-Type' => 'text/xml', - 'Content-Length' => '99', - ], - ], - ]; - - $config = new Configuration([ - 'install_default_breadcrumb_handlers' => false, - 'install_shutdown_handler' => false, - ]); - - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); - - $client = new Dummy_Raven_Client($config, $httpClient, $requestFactory); - - $this->assertEquals($expected, $client->getHttpData()); - } - - public function testCaptureMessageWithUserData() + public function testCaptureLastError() { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $context = $client->getContext(); - $context->setUserId('unique_id'); - $context->setUserEmail('foo@example.com'); - $context->mergeUserData([ - 'username' => 'my_user', - ]); - - $client->captureMessage('foo'); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('unique_id', $client->pendingEvents[0]['user']['id']); - $this->assertEquals('my_user', $client->pendingEvents[0]['user']['username']); - $this->assertEquals('foo@example.com', $client->pendingEvents[0]['user']['email']); - } + if (function_exists('error_clear_last')) { + error_clear_last(); + } - public function testCaptureMessageWithNoUserData() - { $client = ClientBuilder::create()->getClient(); $client->storeErrorsForBulkSend = true; - $client->captureMessage('foo'); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals(session_id(), $client->pendingEvents[0]['user']['id']); - } - - public function testCaptureMessageWithUnserializableUserData() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; + $this->assertNull($client->captureLastError()); + $this->assertEmpty($client->pendingEvents); - $context = $client->getContext(); - $context->setUserEmail('foo@example.com'); - $context->mergeUserData([ - 'error' => new \Exception('test'), - ]); + /* @var $undefined */ + /* @noinspection PhpExpressionResultUnusedInspection */ + @$undefined; - $client->captureMessage('test'); + $client->captureLastError(); $this->assertCount(1, $client->pendingEvents); + $this->assertEquals('Undefined variable: undefined', $client->pendingEvents[0]['exception']['values'][0]['value']); } - public function testCaptureMessageWithTagsContext() + public function testGetLastEvent() { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - $context = $client->getContext(); - - $context->setTag('foo', 'bar'); - $context->setTag('biz', 'boz'); - $context->setTag('biz', 'baz'); - - $client->captureMessage('test'); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals(['foo' => 'bar', 'biz' => 'baz'], $client->pendingEvents[0]['tags']); - } + $lastEvent = null; - public function testCaptureMessageWithExtraContext() - { $client = ClientBuilder::create()->getClient(); $client->storeErrorsForBulkSend = true; - $client->getContext()->mergeExtraData(['foo' => 'bar']); - $client->getContext()->mergeExtraData(['biz' => 'boz']); - $client->getContext()->mergeExtraData(['biz' => 'baz']); + $client->addMiddleware(function (Event $event) use (&$lastEvent) { + $lastEvent = $event; - $client->captureMessage('test'); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals(['foo' => 'bar', 'biz' => 'baz'], $client->pendingEvents[0]['extra']); - } - - public function testCaptureExceptionContainingLatin1() - { - $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])->getClient(); - $client->storeErrorsForBulkSend = true; + return $event; + }); - // we need a non-utf8 string here. - // nobody writes non-utf8 in exceptions, but it is the easiest way to test. - // in real live non-utf8 may be somewhere in the exception's stacktrace - $utf8String = 'äöü'; - $latin1String = utf8_decode($utf8String); - $client->captureException(new \Exception($latin1String)); + $client->capture(['message' => 'foo']); - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals($utf8String, $client->pendingEvents[0]['exception']['values'][0]['value']); + $this->assertSame($lastEvent, $client->getLastEvent()); } - public function testCaptureExceptionInLatin1File() + /** + * @group legacy + */ + public function testGetLastEventId() { - $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']])->getClient(); - $client->storeErrorsForBulkSend = true; - - require_once __DIR__ . '/Fixtures/code/Latin1File.php'; - - $frames = $client->pendingEvents[0]['exception']['values'][0]['stacktrace']['frames']; + /** @var UuidFactory|\PHPUnit_Framework_MockObject_MockObject $uuidFactory */ + $uuidFactory = $this->createMock(UuidFactory::class); + $uuidFactory->expects($this->once()) + ->method('uuid4') + ->willReturn(Uuid::fromString('ddbd643a-5190-4cce-a6ce-3098506f9d33')); - $utf8String = '// äöü'; - $found = false; - - foreach ($frames as $frame) { - if (!isset($frame['pre_context'])) { - continue; - } - - if (in_array($utf8String, $frame['pre_context'])) { - $found = true; - - break; - } - } - - $this->assertTrue($found); - } - - public function testCaptureLastError() - { - if (function_exists('error_clear_last')) { - error_clear_last(); - } + Uuid::setFactory($uuidFactory); $client = ClientBuilder::create()->getClient(); $client->storeErrorsForBulkSend = true; - $this->assertNull($client->captureLastError()); - $this->assertEmpty($client->pendingEvents); - - /* @var $undefined */ - /* @noinspection PhpExpressionResultUnusedInspection */ - @$undefined; - - $client->captureLastError(); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('Undefined variable: undefined', $client->pendingEvents[0]['exception']['values'][0]['value']); - } - - public function testGetLastEventID() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; + $client->capture(['message' => 'test']); - $client->capture([ - 'message' => 'test', - 'event_id' => 'abc', - ]); + $this->assertEquals('ddbd643a51904ccea6ce3098506f9d33', $client->getLastEventID()); - $this->assertEquals('abc', $client->getLastEventId()); + Uuid::setFactory(new UuidFactory()); } public function testCustomTransport() @@ -1017,120 +669,6 @@ public function testSanitizeContexts() } /** - * Set the server array to the test values, check the current url. - * - * @dataProvider currentUrlProvider - * - * @param array $serverVars - * @param array $options - * @param string $expected - the url expected - * @param string $message - fail message - * @covers \Raven\Client::getCurrentUrl - * @covers \Raven\Client::isHttps - */ - public function testCurrentUrl($serverVars, $options, $expected, $message) - { - $_SERVER = $serverVars; - - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); - - $client = new Dummy_Raven_Client(new Configuration($options), $httpClient, $requestFactory); - $result = $client->test_get_current_url(); - - $this->assertSame($expected, $result, $message); - } - - /** - * Arrays of: - * $_SERVER data - * config - * expected url - * Fail message. - * - * @return array - */ - public function currentUrlProvider() - { - return [ - [ - [], - [], - null, - 'No url expected for empty REQUEST_URI', - ], - [ - [ - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - ], - [], - 'http://example.com/', - 'The url is expected to be http with the request uri', - ], - [ - [ - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - 'HTTPS' => 'on', - ], - [], - 'https://example.com/', - 'The url is expected to be https because of HTTPS on', - ], - [ - [ - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - 'SERVER_PORT' => '443', - ], - [], - 'https://example.com/', - 'The url is expected to be https because of the server port', - ], - [ - [ - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - 'X-FORWARDED-PROTO' => 'https', - ], - [], - 'http://example.com/', - 'The url is expected to be http because the X-Forwarded header is ignored', - ], - [ - [ - 'REQUEST_URI' => '/', - 'HTTP_HOST' => 'example.com', - 'X-FORWARDED-PROTO' => 'https', - ], - ['trust_x_forwarded_proto' => true], - 'https://example.com/', - 'The url is expected to be https because the X-Forwarded header is trusted', - ], - ]; - } - - /** - * @covers \Raven\Client::uuid4() - */ - public function testUuid4() - { - $method = new \ReflectionMethod('\\Raven\\Client', 'uuid4'); - $method->setAccessible(true); - for ($i = 0; $i < 1000; ++$i) { - $this->assertRegExp('/^[0-9a-z-]+$/', $method->invoke(null)); - } - } - - /** - * @covers \Raven\Client::getLastError - * @covers \Raven\Client::getLastEventId * @covers \Raven\Client::getShutdownFunctionHasBeenSet */ public function testGettersAndSetters() @@ -1138,11 +676,6 @@ public function testGettersAndSetters() $client = ClientBuilder::create()->getClient(); $data = [ - ['lastError', null, null], - ['lastError', null, 'value'], - ['lastError', null, mt_rand(100, 999)], - ['lastEventId', null, mt_rand(100, 999)], - ['lastEventId', null, 'value'], ['shutdownFunctionHasBeenSet', null, true], ['shutdownFunctionHasBeenSet', null, false], ]; @@ -1260,43 +793,6 @@ public function testTranslateSeverity() $this->assertEquals('error', $client->translateSeverity(123457)); } - public function testCaptureExceptionWithLogger() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->captureException(new \Exception(), null, 'foobar'); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('foobar', $client->pendingEvents[0]['logger']); - } - - /** - * @backupGlobals - * @covers \Raven\Client::_server_variable - */ - public function test_server_variable() - { - $method = new \ReflectionMethod('\\Raven\Client', '_server_variable'); - $method->setAccessible(true); - foreach ($_SERVER as $key => $value) { - $actual = $method->invoke(null, $key); - $this->assertNotNull($actual); - $this->assertEquals($value, $actual); - } - foreach (['foo', 'bar', 'foobar', '123456', 'SomeLongNonExistedKey'] as $key => $value) { - if (!isset($_SERVER[$key])) { - $actual = $method->invoke(null, $key); - $this->assertNotNull($actual); - $this->assertEquals('', $actual); - } - unset($_SERVER[$key]); - $actual = $method->invoke(null, $key); - $this->assertNotNull($actual); - $this->assertEquals('', $actual); - } - } - public function testRegisterDefaultBreadcrumbHandlers() { if (isset($_ENV['HHVM']) and (1 == $_ENV['HHVM'])) { @@ -1460,162 +956,6 @@ public function test__construct_handlers() } } - public function testGet_user_data() - { - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); - - // step 1 - $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); - $output = $client->getUserData(); - $this->assertInternalType('array', $output); - $this->assertArrayHasKey('user', $output); - $this->assertArrayHasKey('id', $output['user']); - $session_old = $_SESSION; - - // step 2 - $session_id = session_id(); - session_write_close(); - session_id(''); - $output = $client->getUserData(); - $this->assertInternalType('array', $output); - $this->assertEquals(0, count($output)); - - // step 3 - session_id($session_id); - @session_start(['use_cookies' => false]); - $_SESSION = ['foo' => 'bar']; - $output = $client->getUserData(); - $this->assertInternalType('array', $output); - $this->assertArrayHasKey('user', $output); - $this->assertArrayHasKey('id', $output['user']); - $this->assertArrayHasKey('data', $output['user']); - $this->assertArrayHasKey('foo', $output['user']['data']); - $this->assertEquals('bar', $output['user']['data']['foo']); - $_SESSION = $session_old; - } - - public function testCaptureLevel() - { - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); - - foreach ([Client::MESSAGE_LIMIT * 3, 100] as $length) { - $message = ''; - - for ($i = 0; $i < $length; ++$i) { - $message .= chr($i % 256); - } - - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->capture(['message' => $message]); - - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('error', $client->pendingEvents[0]['level']); - $this->assertEquals(substr($message, 0, min(\Raven\Client::MESSAGE_LIMIT, $length)), $client->pendingEvents[0]['message']); - $this->assertArrayNotHasKey('release', $client->pendingEvents[0]); - } - - $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); - $client->storeErrorsForBulkSend = true; - - $client->capture(['message' => 'foobar']); - - $input = $client->getHttpData(); - - $this->assertEquals($input['request'], $client->pendingEvents[0]['request']); - $this->assertArrayNotHasKey('release', $client->pendingEvents[0]); - - $client = new Dummy_Raven_Client(new Configuration(), $httpClient, $requestFactory); - $client->storeErrorsForBulkSend = true; - - $client->capture(['message' => 'foobar', 'request' => ['foo' => 'bar']]); - - $this->assertEquals(['foo' => 'bar'], $client->pendingEvents[0]['request']); - $this->assertArrayNotHasKey('release', $client->pendingEvents[0]); - - foreach ([false, true] as $u1) { - foreach ([false, true] as $u2) { - $options = []; - - if ($u1) { - $options['release'] = 'foo'; - } - - if ($u2) { - $options['current_environment'] = 'bar'; - } - - $client = new Client(new Configuration($options), $httpClient, $requestFactory); - $client->storeErrorsForBulkSend = true; - - $client->capture(['message' => 'foobar']); - - if ($u1) { - $this->assertEquals('foo', $client->pendingEvents[0]['release']); - } else { - $this->assertArrayNotHasKey('release', $client->pendingEvents[0]); - } - - if ($u2) { - $this->assertEquals('bar', $client->pendingEvents[0]['environment']); - } - } - } - } - - public function testCaptureNoUserAndRequest() - { - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); - - $client = new Dummy_Raven_Client_No_Http(new Configuration(['install_default_breadcrumb_handlers' => false]), $httpClient, $requestFactory); - $client->storeErrorsForBulkSend = true; - - $session_id = session_id(); - - session_write_close(); - session_id(''); - - $client->capture(['user' => '', 'request' => '']); - - $this->assertCount(1, $client->pendingEvents); - $this->assertArrayNotHasKey('user', $client->pendingEvents[0]); - $this->assertArrayNotHasKey('request', $client->pendingEvents[0]); - - session_id($session_id); - @session_start(['use_cookies' => false]); - } - - public function testCaptureAutoLogStacks() - { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - - $client->capture(['auto_log_stacks' => true], true); - - $this->assertCount(1, $client->pendingEvents); - $this->assertArrayHasKey('stacktrace', $client->pendingEvents[0]); - $this->assertInternalType('array', $client->pendingEvents[0]['stacktrace']['frames']); - } - /** * @dataProvider sampleRateAbsoluteDataProvider */ diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index cd17b67e8..70f3d0798 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -200,4 +200,30 @@ public function testShouldCapture() $this->assertFalse($configuration->shouldCapture($data)); } + + /** + * @dataProvider excludedExceptionsDataProvider + */ + public function testIsExcludedException($excludedExceptions, $exception, $result) + { + $configuration = new Configuration(['excluded_exceptions' => $excludedExceptions]); + + $this->assertSame($result, $configuration->isExcludedException($exception)); + } + + public function excludedExceptionsDataProvider() + { + return [ + [ + [\BadFunctionCallException::class, \BadMethodCallException::class], + new \BadMethodCallException(), + true, + ], + [ + [\BadFunctionCallException::class], + new \Exception(), + false, + ], + ]; + } } diff --git a/tests/EventTest.php b/tests/EventTest.php new file mode 100644 index 000000000..2497ba6cc --- /dev/null +++ b/tests/EventTest.php @@ -0,0 +1,204 @@ +uuidGeneratorInvokationCount = 0; + $this->originalUuidFactory = new UuidFactory(); + $this->client = ClientBuilder::create()->getClient(); + $this->configuration = $this->client->getConfig(); + + /** @var UuidFactoryInterface|\PHPUnit_Framework_MockObject_MockObject $uuidFactory */ + $uuidFactory = $this->getMockBuilder(UuidFactoryInterface::class) + ->getMock(); + + $uuidFactory->expects($this->any()) + ->method('uuid4') + ->willReturnCallback(function () { + $uuid = static::GENERATED_UUID[$this->uuidGeneratorInvokationCount++]; + + return $this->originalUuidFactory->fromString($uuid); + }); + + Uuid::setFactory($uuidFactory); + } + + protected function tearDown() + { + Uuid::setFactory($this->originalUuidFactory); + } + + public function testEventIsGeneratedWithUniqueIdentifier() + { + $event1 = new Event($this->configuration); + $event2 = new Event($this->configuration); + + $this->assertEquals(static::GENERATED_UUID[0], $event1->getId()->toString()); + $this->assertEquals(static::GENERATED_UUID[1], $event2->getId()->toString()); + } + + public function testToArray() + { + $this->configuration->setRelease('1.2.3-dev'); + + $expected = [ + 'event_id' => str_replace('-', '', static::GENERATED_UUID[0]), + 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), + 'level' => 'error', + 'platform' => 'php', + 'sdk' => [ + 'name' => 'sentry-php', + 'version' => Client::VERSION, + ], + 'server_name' => $this->configuration->getServerName(), + 'release' => $this->configuration->getRelease(), + 'environment' => $this->configuration->getCurrentEnvironment(), + ]; + + $event = new Event($this->configuration); + + $this->assertEquals($expected, $event->toArray()); + } + + public function testToArrayWithMessage() + { + $event = Event::create($this->configuration) + ->withMessage('foo bar'); + + $data = $event->toArray(); + + $this->assertArrayHasKey('message', $data); + $this->assertEquals('foo bar', $data['message']); + } + + public function testToArrayWithMessageWithParams() + { + $expected = [ + 'message' => 'foo %s', + 'params' => ['bar'], + 'formatted' => 'foo bar', + ]; + + $event = Event::create($this->configuration) + ->withMessage('foo %s', ['bar']); + + $data = $event->toArray(); + + $this->assertArrayHasKey('message', $data); + $this->assertEquals($expected, $data['message']); + } + + public function testToArrayWithBreadcrumbs() + { + $breadcrumbs = [ + new Breadcrumb(Client::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'foo'), + new Breadcrumb(Client::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'bar'), + ]; + + $event = new Event($this->configuration); + + foreach ($breadcrumbs as $breadcrumb) { + $event = $event->withBreadcrumb($breadcrumb); + } + + $this->assertSame($breadcrumbs, $event->getBreadcrumbs()); + + $data = $event->toArray(); + + $this->assertArrayHasKey('breadcrumbs', $data); + $this->assertSame($breadcrumbs, $data['breadcrumbs']); + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters($propertyName, $propertyValue, $serializedPropertyName) + { + $getterMethod = 'get' . ucfirst($propertyName); + $setterMethod = 'with' . ucfirst($propertyName); + + $event = new Event($this->configuration); + $newEvent = call_user_func([$event, $setterMethod], call_user_func([$event, $getterMethod])); + + $this->assertSame($event, $newEvent); + + $newEvent = call_user_func([$event, $setterMethod], $propertyValue); + + $this->assertNotSame($event, $newEvent); + + $value = call_user_func([$event, $getterMethod]); + $newValue = call_user_func([$newEvent, $getterMethod]); + + $this->assertNotSame($value, $newValue); + $this->assertSame($newValue, $propertyValue); + + $data = $newEvent->toArray(); + + $this->assertArrayHasKey($serializedPropertyName, $data); + $this->assertSame($propertyValue, $data[$serializedPropertyName]); + } + + public function gettersAndSettersDataProvider() + { + return [ + ['level', 'info', 'level'], + ['logger', 'ruby', 'logger'], + ['culprit', '', 'culprit'], + ['serverName', 'local.host', 'server_name'], + ['release', '0.0.1', 'release'], + ['modules', ['foo' => '0.0.1', 'bar' => '0.0.2'], 'modules'], + ['extraContext', ['foo' => 'bar'], 'extra'], + ['tagsContext', ['foo' => 'bar'], 'tags'], + ['userContext', ['foo' => 'bar'], 'user'], + ['serverOsContext', ['foo' => 'bar'], 'server_os'], + ['runtimeContext', ['foo' => 'bar'], 'runtime'], + ['fingerprint', ['foo', 'bar'], 'fingerprint'], + ['environment', 'foo', 'environment'], + ]; + } + + public function testEventJsonSerialization() + { + $event = new Event($this->configuration); + + $this->assertJsonStringEqualsJsonString(json_encode($event->toArray()), json_encode($event)); + } +} diff --git a/tests/Fixtures/code/Latin1File.php b/tests/Fixtures/code/Latin1File.php index 9f0f951be..b1c8d6edc 100644 --- a/tests/Fixtures/code/Latin1File.php +++ b/tests/Fixtures/code/Latin1File.php @@ -2,4 +2,4 @@ // This file is encoded in latin1 // äöü -$client->captureException(new \Exception()); +return new \Exception('foo'); diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 94c2e33e8..8afefe38d 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -59,7 +59,7 @@ private function create_chained_exception() public function testCaptureSimpleError() { - $client = ClientBuilder::create([])->getClient(); + $client = ClientBuilder::create(['auto_log_stacks' => true])->getClient(); $client->storeErrorsForBulkSend = true; @mkdir('/no/way'); diff --git a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php new file mode 100644 index 000000000..b338fed21 --- /dev/null +++ b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php @@ -0,0 +1,49 @@ +record($breadcrumb); + $recorder->record($breadcrumb2); + + $configuration = new Configuration(); + $event = new Event($configuration); + + $invokationCount = 0; + $callback = function (Event $eventArg) use ($event, $breadcrumb, $breadcrumb2, &$invokationCount) { + $this->assertNotSame($event, $eventArg); + $this->assertEquals([$breadcrumb, $breadcrumb2], $eventArg->getBreadcrumbs()); + + ++$invokationCount; + }; + + $middleware = new BreadcrumbInterfaceMiddleware($recorder); + $middleware($event, $callback); + + $this->assertEquals(1, $invokationCount); + } +} diff --git a/tests/Middleware/ContextInterfaceMiddlewareTest.php b/tests/Middleware/ContextInterfaceMiddlewareTest.php new file mode 100644 index 000000000..8b0b99893 --- /dev/null +++ b/tests/Middleware/ContextInterfaceMiddlewareTest.php @@ -0,0 +1,55 @@ +setTag('bar', 'foo'); + $context->mergeUserData(['foo' => 'bar']); + $context->mergeExtraData(['bar' => 'baz']); + + $configuration = new Configuration(); + $event = new Event($configuration); + + $invokationCount = 0; + $callback = function (Event $eventArg) use ($event, &$invokationCount) { + $this->assertNotSame($event, $eventArg); + $this->assertEquals(['bar' => 'foo', 'foobar' => 'barfoo'], $eventArg->getTagsContext()); + $this->assertEquals(['foo' => 'bar', 'baz' => 'foo'], $eventArg->getUserContext()); + $this->assertEquals(['bar' => 'baz', 'barbaz' => 'bazbar'], $eventArg->getExtraContext()); + $this->assertEquals(['foo' => 'bar'], $eventArg->getServerOsContext()); + $this->assertEquals(['bar' => 'foo'], $eventArg->getRuntimeContext()); + + ++$invokationCount; + }; + + $middleware = new ContextInterfaceMiddleware($context); + $middleware($event, $callback, null, null, [ + 'tags_context' => ['foobar' => 'barfoo'], + 'extra_context' => ['barbaz' => 'bazbar'], + 'server_os_context' => ['foo' => 'bar'], + 'runtime_context' => ['bar' => 'foo'], + 'user_context' => ['baz' => 'foo'], + ]); + + $this->assertEquals(1, $invokationCount); + } +} diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php new file mode 100644 index 000000000..9c09e2db7 --- /dev/null +++ b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php @@ -0,0 +1,261 @@ +getClient(); + $assertHasStacktrace = $client->getConfig()->getAutoLogStacks(); + + $event = new Event($client->getConfig()); + + $invokationCount = 0; + $callback = function (Event $eventArg) use ($event, $assertHasStacktrace, $expectedResult, &$invokationCount) { + $this->assertNotSame($event, $eventArg); + $this->assertArraySubset($expectedResult, $eventArg->toArray()); + + foreach ($eventArg->getException() as $exception) { + if ($assertHasStacktrace) { + $this->assertArrayHasKey('stacktrace', $exception); + $this->assertInstanceOf(Stacktrace::class, $exception['stacktrace']); + } else { + $this->assertArrayNotHasKey('stacktrace', $exception); + } + } + + ++$invokationCount; + }; + + $middleware = new ExceptionInterfaceMiddleware($client); + $middleware($event, $callback, null, $exception, $payload); + + $this->assertEquals(1, $invokationCount); + } + + public function invokeDataProvider() + { + return [ + [ + new \RuntimeException('foo'), + [], + [], + [ + 'level' => Client::LEVEL_ERROR, + 'exception' => [ + 'values' => [ + [ + 'type' => \RuntimeException::class, + 'value' => 'foo', + ], + ], + ], + ], + ], + [ + new \RuntimeException('foo'), + [ + 'auto_log_stacks' => false, + ], + [], + [ + 'level' => Client::LEVEL_ERROR, + 'exception' => [ + 'values' => [ + [ + 'type' => \RuntimeException::class, + 'value' => 'foo', + ], + ], + ], + ], + ], + [ + new \ErrorException('foo', 0, E_USER_WARNING), + [], + [], + [ + 'level' => Client::LEVEL_WARNING, + 'exception' => [ + 'values' => [ + [ + 'type' => \ErrorException::class, + 'value' => 'foo', + ], + ], + ], + ], + ], + [ + new \BadMethodCallException('baz', 0, new \BadFunctionCallException('bar', 0, new \LogicException('foo', 0))), + [ + 'excluded_exceptions' => [\BadMethodCallException::class], + ], + [], + [ + 'level' => Client::LEVEL_ERROR, + 'exception' => [ + 'values' => [ + [ + 'type' => \LogicException::class, + 'value' => 'foo', + ], + [ + 'type' => \BadFunctionCallException::class, + 'value' => 'bar', + ], + ], + ], + ], + ], + ]; + } + + public function testInvokeWithExceptionContainingLatin1Characters() + { + $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']]) + ->getClient(); + + $event = new Event($client->getConfig()); + $utf8String = 'äöü'; + $latin1String = utf8_decode($utf8String); + $invokationCount = 0; + + $callback = function (Event $eventArg) use ($event, &$invokationCount, $utf8String) { + $this->assertNotSame($event, $eventArg); + + $expectedValue = [ + [ + 'type' => \Exception::class, + 'value' => $utf8String, + ], + ]; + + $this->assertArraySubset($expectedValue, $eventArg->getException()); + + ++$invokationCount; + }; + + $middleware = new ExceptionInterfaceMiddleware($client); + $middleware($event, $callback, null, new \Exception($latin1String)); + + $this->assertEquals(1, $invokationCount); + } + + public function testInvokeWithExceptionContainingInvalidUtf8Characters() + { + $client = ClientBuilder::create()->getClient(); + $event = new Event($client->getConfig()); + $invokationCount = 0; + + $callback = function (Event $eventArg) use ($event, &$invokationCount) { + $this->assertNotSame($event, $eventArg); + + $expectedValue = [ + [ + 'type' => \Exception::class, + 'value' => "\xC2\xA2\x3F", + ], + ]; + + $this->assertArraySubset($expectedValue, $eventArg->getException()); + + ++$invokationCount; + }; + + $middleware = new ExceptionInterfaceMiddleware($client); + $middleware($event, $callback, null, new \Exception("\xC2\xA2\xC2")); // ill-formed 2-byte character U+00A2 (CENT SIGN) + + $this->assertEquals(1, $invokationCount); + } + + public function testInvokeWithExceptionThrownInLatin1File() + { + $client = ClientBuilder::create([ + 'auto_log_stacks' => true, + 'mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8'], + ])->getClient(); + + $event = new Event($client->getConfig()); + + $callback = function (Event $eventArg) use ($event, &$invokationCount) { + $this->assertNotSame($event, $eventArg); + + $result = $eventArg->getException(); + + $expectedValue = [ + [ + 'type' => \Exception::class, + 'value' => 'foo', + ], + ]; + + $this->assertArraySubset($expectedValue, $result); + + $latin1StringFound = false; + + foreach ($result[0]['stacktrace']->toArray() as $frame) { + if (isset($frame['pre_context']) && in_array('// äöü', $frame['pre_context'], true)) { + $latin1StringFound = true; + + break; + } + } + + $this->assertTrue($latin1StringFound); + + ++$invokationCount; + }; + + $middleware = new ExceptionInterfaceMiddleware($client); + $middleware($event, $callback, null, require_once __DIR__ . '/../Fixtures/code/Latin1File.php'); + + $this->assertEquals(1, $invokationCount); + } + + public function testInvokeWithAutoLogStacksDisabled() + { + $client = ClientBuilder::create(['auto_log_stacks' => false])->getClient(); + $event = new Event($client->getConfig()); + + $invokationCount = 0; + $callback = function (Event $eventArg) use ($event, &$invokationCount) { + $this->assertNotSame($event, $eventArg); + + $result = $eventArg->getException(); + + $this->assertNotEmpty($result); + $this->assertInternalType('array', $result[0]); + $this->assertEquals(\Exception::class, $result[0]['type']); + $this->assertEquals('foo', $result[0]['value']); + $this->assertArrayNotHasKey('stacktrace', $result[0]); + + ++$invokationCount; + }; + + $middleware = new ExceptionInterfaceMiddleware($client); + $middleware($event, $callback, null, new \Exception('foo')); + + $this->assertEquals(1, $invokationCount); + } +} diff --git a/tests/Middleware/MessageInterfaceMiddlewareTest.php b/tests/Middleware/MessageInterfaceMiddlewareTest.php new file mode 100644 index 000000000..10b43f8e7 --- /dev/null +++ b/tests/Middleware/MessageInterfaceMiddlewareTest.php @@ -0,0 +1,80 @@ +assertSame($event, $eventArg); + + ++$invokationCount; + }; + + $middleware = new MessageInterfaceMiddleware(); + $middleware($event, $callback); + + $this->assertEquals(1, $invokationCount); + } + + /** + * @dataProvider invokeDataProvider + */ + public function testInvoke($payload) + { + $configuration = new Configuration(); + $event = new Event($configuration); + + $invokationCount = 0; + $callback = function (Event $eventArg) use ($event, $payload, &$invokationCount) { + $this->assertNotSame($event, $eventArg); + + $this->assertEquals($payload['message'], $eventArg->getMessage()); + $this->assertEquals($payload['message_params'], $eventArg->getMessageParams()); + + ++$invokationCount; + }; + + $middleware = new MessageInterfaceMiddleware(); + $middleware($event, $callback, null, null, $payload); + + $this->assertEquals(1, $invokationCount); + } + + public function invokeDataProvider() + { + return [ + [ + [ + 'message' => 'foo %s', + 'message_params' => [], + ], + ], + [ + [ + 'message' => 'foo %s', + 'message_params' => ['bar'], + ], + ], + ]; + } +} diff --git a/tests/Middleware/RequestInterfaceMiddlewareTest.php b/tests/Middleware/RequestInterfaceMiddlewareTest.php new file mode 100644 index 000000000..9c9d270a2 --- /dev/null +++ b/tests/Middleware/RequestInterfaceMiddlewareTest.php @@ -0,0 +1,113 @@ +assertSame($event, $eventArg); + + ++$invokationCount; + }; + + $middleware = new RequestInterfaceMiddleware(); + $middleware($event, $callback); + + $this->assertEquals(1, $invokationCount); + } + + /** + * @dataProvider invokeDataProvider + */ + public function testInvoke($requestData, $expectedValue) + { + $configuration = new Configuration(); + $event = new Event($configuration); + + $request = new ServerRequest(); + $request = $request->withUri(new Uri($requestData['uri'])); + $request = $request->withMethod($requestData['method']); + + foreach ($requestData['headers'] as $name => $value) { + $request = $request->withHeader($name, $value); + } + + $invokationCount = 0; + $callback = function (Event $eventArg, ServerRequestInterface $requestArg) use ($event, $request, $expectedValue, &$invokationCount) { + $this->assertSame($request, $requestArg); + $this->assertEquals($expectedValue, $eventArg->getRequest()); + + ++$invokationCount; + }; + + $middleware = new RequestInterfaceMiddleware(); + $middleware($event, $callback, $request); + + $this->assertEquals(1, $invokationCount); + } + + public function invokeDataProvider() + { + return [ + [ + [ + 'uri' => 'http://www.example.com/foo', + 'method' => 'GET', + 'headers' => [], + ], + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'GET', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + ], + ], + [ + [ + 'uri' => 'http://www.example.com/foo?foo=bar&bar=baz', + 'method' => 'GET', + 'headers' => [ + 'Host' => ['www.example.com'], + 'REMOTE_ADDR' => ['127.0.0.1'], + ], + ], + [ + 'url' => 'http://www.example.com/foo?foo=bar&bar=baz', + 'method' => 'GET', + 'query_string' => 'foo=bar&bar=baz', + 'headers' => [ + 'Host' => ['www.example.com'], + 'REMOTE_ADDR' => ['127.0.0.1'], + ], + 'env' => [ + 'REMOTE_ADDR' => '127.0.0.1', + ], + ], + ], + ]; + } +} diff --git a/tests/Middleware/UserInterfaceMiddlewareTest.php b/tests/Middleware/UserInterfaceMiddlewareTest.php new file mode 100644 index 000000000..0d47a2895 --- /dev/null +++ b/tests/Middleware/UserInterfaceMiddlewareTest.php @@ -0,0 +1,61 @@ +withUserContext(['foo' => 'bar']); + + $invokationCount = 0; + $callback = function (Event $eventArg) use ($event, &$invokationCount) { + $this->assertSame($event, $eventArg); + + ++$invokationCount; + }; + + $middleware = new UserInterfaceMiddleware(); + $middleware($event, $callback); + + $this->assertEquals(1, $invokationCount); + } + + public function testInvokeWithRequest() + { + $event = new Event(new Configuration()); + $event = $event->withUserContext(['foo' => 'bar']); + + $request = new ServerRequest(); + $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); + + $invokationCount = 0; + $callback = function (Event $eventArg) use ($event, &$invokationCount) { + $this->assertNotSame($event, $eventArg); + $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $eventArg->getUserContext()); + + ++$invokationCount; + }; + + $middleware = new UserInterfaceMiddleware(); + $middleware($event, $callback, $request); + + $this->assertEquals(1, $invokationCount); + } +} diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php index 62ceda188..37e8c8447 100644 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -30,7 +30,7 @@ class SanitizeStacktraceProcessorTest extends TestCase protected function setUp() { - $this->client = ClientBuilder::create()->getClient(); + $this->client = ClientBuilder::create(['auto_log_stacks' => true])->getClient(); $this->client->storeErrorsForBulkSend = true; $this->processor = new SanitizeStacktraceProcessor($this->client); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index b4343b767..892507231 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -221,7 +221,7 @@ public function removeFrameDataProvider() public function testFromBacktrace() { $fixture = $this->getJsonFixture('backtraces/exception.json'); - $frames = Stacktrace::fromBacktrace($this->client, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + $frames = Stacktrace::createFromBacktrace($this->client, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); $this->assertFrameEquals($frames[0], null, 'path/to/file', 16); $this->assertFrameEquals($frames[1], 'TestClass::crashyFunction', 'path/to/file', 7); @@ -231,7 +231,7 @@ public function testFromBacktrace() public function testFromBacktraceWithAnonymousFrame() { $fixture = $this->getJsonFixture('backtraces/anonymous_frame.json'); - $frames = Stacktrace::fromBacktrace($this->client, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + $frames = Stacktrace::createFromBacktrace($this->client, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); $this->assertFrameEquals($frames[0], null, 'path/to/file', 7); $this->assertFrameEquals($frames[1], 'call_user_func', '[internal]', 0); diff --git a/tests/UtilTest.php b/tests/UtilTest.php deleted file mode 100644 index 3fd3f1ae2..000000000 --- a/tests/UtilTest.php +++ /dev/null @@ -1,31 +0,0 @@ - 'bar']; - $result = \Raven\Util::get($input, 'baz', 'foo'); - $this->assertEquals('foo', $result); - } - - public function testGetReturnsPresentValuesEvenWhenEmpty() - { - $input = ['foo' => '']; - $result = \Raven\Util::get($input, 'foo', 'bar'); - $this->assertEquals('', $result); - } -} From 5da84aaf8c8ce5b27f3907bf0b152026f6c86ea8 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 2 Jan 2018 10:44:10 +0100 Subject: [PATCH 0311/1161] Prevent segmentation fault in Reflection->getParameters() when an native php function has been disabled via disabled_functions. (#523) Rebased version of #505, which is te porting of #504 to the 2.0 branch --- lib/Raven/Stacktrace.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index fb24f23fc..517da2945 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -364,8 +364,10 @@ public static function getFrameArguments($frame, $maxValueLength = Client::MESSA } else { $reflection = new \ReflectionMethod($frame['class'], '__call'); } - } else { + } elseif (function_exists($frame['function'])) { $reflection = new \ReflectionFunction($frame['function']); + } else { + return self::getFrameArgumentsValues($frame, $maxValueLength); } } catch (\ReflectionException $ex) { return self::getFrameArgumentsValues($frame, $maxValueLength); From 7c126ad71b43ba21b34db8c46ba032aaca0f1cc6 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 22 Dec 2017 18:04:18 +0100 Subject: [PATCH 0312/1161] Realign text documents --- CHANGELOG.md | 29 ++++++++++- README.md | 92 +++++++++++++++++++++++++++++++++-- docs/integrations/laravel.rst | 87 +++++++++++++++++++-------------- 3 files changed, 167 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f86e0ad68..80756057b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,33 @@ # CHANGELOG -## 1.7.0 +## Unreleased + +- ... + +## 1.8.2 (2017-12-21) + +- Improve handling DSN with "null" like values (#522) +- Prevent warning in Raven_Stacktrace (#493) + +## 1.8.1 (2017-11-09) + +- Add setters for the serializers on the `Raven_Client` (#515) +- Avoid to capture `E_ERROR` in PHP 7+, because it's also a `Throwable` that gets captured and duplicates the error (#514) + +## 1.8.0 (2017-10-29) + +- Use namespaced classes in test for PHPUnit (#506) +- Prevent segmentation fault on PHP `<5.6` (#504) +- Remove `ini_set` call for unneeded functionality (#501) +- Exclude single `.php` files from the app path (#500) +- Start testing PHP 7.2 (#489) +- Exclude anonymous frames from app path (#482) + +## 1.7.1 (2017-08-02) + +- Fix of filtering sensitive data when there is an exception with multiple 'values' (#483) + +## 1.7.0 (2017-06-07) - Corrected some issues with argument serialization in stacktraces (#399). - The default exception handler will now re-raise exceptions when `call_existing` is true and no exception handler is registered (#421). diff --git a/README.md b/README.md index fa59d3543..cec8d54cd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@

- +

# Sentry for PHP -[![Build Status](https://img.shields.io/travis/getsentry/sentry-php.png?style=flat-square)](http://travis-ci.org/getsentry/sentry-php) +[![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master)](http://travis-ci.org/getsentry/sentry-php) [![Total Downloads](https://img.shields.io/packagist/dt/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) [![Downloads per month](https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) [![Latest stable version](https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) @@ -75,9 +75,23 @@ For more information, see our [documentation](https://docs.getsentry.com/hosted/ Other packages exists to integrate this SDK into the most common frameworks. +### Official integrations + +The following integrations are fully supported and maintained by the Sentry team. + - [Symfony](https://github.com/getsentry/sentry-symfony) - [Laravel](https://github.com/getsentry/sentry-laravel) +### 3rd party integrations + +The following integrations are available and maintained by members of the Sentry community. + +- [Nette](https://github.com/Salamek/raven-nette) +- [ZendFramework](https://github.com/facile-it/sentry-module) +- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) +- [Drupal](https://www.drupal.org/project/raven) +- [OpenCart](https://github.com/BurdaPraha/oc_sentry) +- ... feel free to be famous, create a port to your favourite platform! ## Community @@ -101,4 +115,76 @@ Tests can then be run via phpunit: ``` $ vendor/bin/phpunit -``` \ No newline at end of file +``` + + +Tagging a Release +----------------- + +1. Make sure ``CHANGES`` is up to date (add the release date) and ``master`` is green. + +2. Create a new branch for the minor version (if not present): + +``` +$ git checkout -b releases/1.9.x +``` + +3. Update the hardcoded version tag in ``Client.php``: + +``` +class Raven_Client +{ + const VERSION = '1.9.0'; +} +``` + +4. Commit the change: + +``` +$ git commit -a -m "1.9.0" +``` + +5. Tag the branch: + +``` +git tag 1.9.0 +``` + +6. Push the tag: + +``` +git push --tags +``` + +7. Switch back to ``master``: + +``` +git checkout master +``` + +8. Add the next minor release to the ``CHANGES`` file: + +``` +## 1.10.0 (unreleased) +``` + +9. Update the version in ``Client.php``: + +``` +class Raven_Client +{ + const VERSION = '1.10.x-dev'; +} +``` + +10. Lastly, update the composer version in ``composer.json``: + +``` + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + } +``` + +All done! Composer will pick up the tag and configuration automatically. diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index ecc16eb12..acd72a337 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -1,7 +1,7 @@ Laravel ======= -Laravel is supported via a native extension, `sentry-laravel `_. +Laravel is supported via a native package, `sentry-laravel `_. Laravel 5.x ----------- @@ -12,7 +12,7 @@ Install the ``sentry/sentry-laravel`` package: $ composer require sentry/sentry-laravel -Add the Sentry service provider and facade in ``config/app.php``: +If you're on Laravel 5.4 or earlier, you'll need to add the following to your ``config/app.php`` (for Laravel 5.5+ these will be auto-discovered by Laravel): .. code-block:: php @@ -31,12 +31,13 @@ Add Sentry reporting to ``App/Exceptions/Handler.php``: .. code-block:: php - public function report(Exception $e) + public function report(Exception $exception) { - if ($this->shouldReport($e)) { - app('sentry')->captureException($e); + if (app()->bound('sentry') && $this->shouldReport($exception)) { + app('sentry')->captureException($exception); } - parent::report($e); + + parent::report($exception); } Create the Sentry configuration file (``config/sentry.php``): @@ -53,8 +54,11 @@ Add your DSN to ``.env``: SENTRY_DSN=___DSN___ Finally, if you wish to wire up User Feedback, you can do so by creating a custom -error response. To do this, open up ``App/Exceptions/Handler.php`` and except the -``render`` method: +error view in `resources/views/errors/500.blade.php`. + +For Laravel 5 up to 5.4 you need to open up ``App/Exceptions/Handler.php`` and extend the +``render`` method to make sure the 500 error is rendered as a view correctly, in 5.5+ this +step is not required anymore an you can skip ahead to the next one: .. code-block:: php @@ -62,23 +66,25 @@ error response. To do this, open up ``App/Exceptions/Handler.php`` and except th class Handler extends ExceptionHandler { - private $sentryID; - - public function report(Exception $e) + public function report(Exception $exception) { - if ($this->shouldReport($e)) { - // bind the event ID for Feedback - $this->sentryID = app('sentry')->captureException($e); + if (app()->bound('sentry') && $this->shouldReport($exception)) { + app('sentry')->captureException($exception); } - parent::report($e); + + parent::report($exception); } - // ... - public function render($request, Exception $e) + public function render($request, Exception $exception) { - return response()->view('errors.500', [ - 'sentryID' => $this->sentryID, - ], 500); + // Convert all non-http exceptions to a proper 500 http exception + // if we don't do this exceptions are shown as a default template + // instead of our own view in resources/views/errors/500.blade.php + if ($this->shouldReport($exception) && !$this->isHttpException($exception) && !config('app.debug')) { + $exception = new HttpException(500, 'Whoops!'); + } + + return parent::render($request, $exception); } } @@ -88,19 +94,25 @@ Next, create ``resources/views/errors/500.blade.php``, and embed the feedback co
Something went wrong.
- @unless(empty($sentryID)) + + @if(app()->bound('sentry') && !empty(Sentry::getLastEventID())) +
Error ID: {{ Sentry::getLastEventID() }}
+ - @endunless + @endif
That's it! @@ -178,9 +190,10 @@ Add Sentry reporting to ``app/Exceptions/Handler.php``: public function report(Exception $e) { - if ($this->shouldReport($e)) { + if (app()->bound('sentry') && $this->shouldReport($e)) { app('sentry')->captureException($e); } + parent::report($e); } @@ -193,8 +206,8 @@ Create the Sentry configuration file (``config/sentry.php``): return array( 'dsn' => '___DSN___', - // capture release as git sha - // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), + // capture release as git sha + // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), ); Testing with Artisan @@ -239,14 +252,14 @@ In the following example, we'll use a middleware: public function handle($request, Closure $next) { if (app()->bound('sentry')) { - /** @var \Raven\Client $sentry */ + /** @var \Raven_Client $sentry */ $sentry = app('sentry'); // Add user context if (auth()->check()) { - $sentry->setUserContext([...]); + $sentry->user_context([...]); } else { - $sentry->setUserContext(['id' => null]); + $sentry->user_context(['id' => null]); } // Add tags context @@ -290,13 +303,13 @@ The following settings are available for the client: 'breadcrumbs.sql_bindings' => false, -.. describe:: setUserContext +.. describe:: user_context - Capture setUserContext automatically. + Capture user_context automatically. Defaults to ``true``. .. code-block:: php - 'setUserContext' => false, + 'user_context' => false, From 0bfea95c107d29512d170d1cd958048cd667e7a7 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 22 Dec 2017 18:23:13 +0100 Subject: [PATCH 0313/1161] Add PHP 7.2 in CI (like in 5958fdf39f278128e23e9f612a41f8d15d6e53b1) --- .scrutinizer.yml | 2 +- .travis.yml | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 1a7fcfcf5..94d403d5c 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,7 +5,7 @@ tools: php_code_coverage: true external_code_coverage: timeout: 2400 # There can be another pull request in progress - runs: 3 # PHP 5.6 + PHP 7.0 + PHP 7.1 + runs: 3 # PHP 5.6 + PHP 7.0 + PHP 7.1 + PHP 7.2 build: environment: diff --git a/.travis.yml b/.travis.yml index bde000397..5734a80cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,9 @@ php: - 5.6 - 7.0 - 7.1 - - nightly + - 7.2 matrix: - allow_failures: - - php: nightly fast_finish: true exclude: - php: nightly @@ -37,3 +35,4 @@ after_script: - if [ $(phpenv version-name) = "5.6" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi - if [ $(phpenv version-name) = "7.0" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi - if [ $(phpenv version-name) = "7.1" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "7.2" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi From aad776de983fc913e5a7936a3d158c0208816722 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 2 Jan 2018 14:16:11 +0100 Subject: [PATCH 0314/1161] Allow single files in the excluded paths option (port of #500 onto 2.x) --- lib/Raven/Configuration.php | 8 ++++++-- tests/ConfigurationTest.php | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 59e4ed2ad..c3360f235 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -899,8 +899,12 @@ private function normalizeAbsolutePath($value) $path = $value; } - if (DIRECTORY_SEPARATOR === substr($path, 0, 1) && DIRECTORY_SEPARATOR !== substr($path, -1)) { - $path = $path . DIRECTORY_SEPARATOR; + if ( + DIRECTORY_SEPARATOR === substr($path, 0, 1) + && DIRECTORY_SEPARATOR !== substr($path, -1) + && '.php' !== substr($path, -4) + ) { + $path .= DIRECTORY_SEPARATOR; } return $path; diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 70f3d0798..8406303d5 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -226,4 +226,26 @@ public function excludedExceptionsDataProvider() ], ]; } + + /** + * @dataProvider excludedPathProviders + * @param string $value + * @param string $expected + */ + public function testExcludedAppPathsPathRegressionWithFileName($value, $expected) + { + $configuration = new Configuration(['excluded_app_paths' => [$value]]); + + $this->assertSame([$expected], $configuration->getExcludedProjectPaths()); + } + + public function excludedPathProviders() + { + return [ + ['some/path', 'some/path'], + ['some/specific/file.php', 'some/specific/file.php'], + [__DIR__, __DIR__ . '/'], + [__FILE__, __FILE__], + ]; + } } From 20c79ed48de91533f85b35becc7f6518525b306c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 2 Jan 2018 14:22:10 +0100 Subject: [PATCH 0315/1161] Do not capture fatal errors under PHP 7+ (port of #514 onto 2.x) --- lib/Raven/ErrorHandler.php | 7 +++++++ tests/ErrorHandlerTest.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 0162d0545..594e6ab7a 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -147,6 +147,13 @@ public function handleFatalError() public function shouldCaptureFatalError($type) { + // Do not capture E_ERROR since those can be caught by userland since PHP 7.0 + // E_ERROR should already be handled by the exception handler + // This prevents duplicated exceptions in PHP 7.0+ + if (PHP_VERSION_ID >= 70000 && $type === E_ERROR) { + return false; + } + return $type & $this->fatal_error_types; } diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 1a1c25699..6034319c6 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -211,7 +211,7 @@ public function testShouldCaptureFatalErrorBehavior() ->getMock(); $handler = new \Raven\ErrorHandler($client); - $this->assertEquals($handler->shouldCaptureFatalError(E_ERROR), true); + $this->assertEquals($handler->shouldCaptureFatalError(E_ERROR), PHP_VERSION_ID < 70000); $this->assertEquals($handler->shouldCaptureFatalError(E_WARNING), false); } From d604f051fea1930a510199d8347944a2cbde81f8 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 2 Jan 2018 14:32:45 +0100 Subject: [PATCH 0316/1161] Add setters for Serializers (port of #515 onto 2.x) --- lib/Raven/Client.php | 10 ++++++++++ tests/ClientTest.php | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 2208e2c7e..8f1e418e5 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -264,6 +264,11 @@ public function getReprSerializer() return $this->reprSerializer; } + public function setReprSerializer(Serializer $reprSerializer) + { + $this->reprSerializer = $reprSerializer; + } + /** * Gets the serializer. * @@ -274,6 +279,11 @@ public function getSerializer() return $this->serializer; } + public function setSerializer(Serializer $serializer) + { + $this->serializer = $serializer; + } + /** * Installs any available automated hooks (such as error_reporting). * diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f75308d96..61f82bdc2 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -24,6 +24,7 @@ use Raven\Configuration; use Raven\Event; use Raven\Processor\SanitizeDataProcessor; +use Raven\Serializer; function simple_function($a = null, $b = null, $c = null) { @@ -1033,6 +1034,26 @@ public function testClearBreadcrumb() $client->clearBreadcrumbs(); $this->assertEmpty(iterator_to_array($reflection->getValue($client))); } + + public function testSetSerializer() + { + $client = ClientBuilder::create()->getClient(); + $serializer = $this->prophesize(Serializer::class)->reveal(); + + $client->setSerializer($serializer); + + $this->assertSame($serializer, $client->getSerializer()); + } + + public function testSetReprSerializer() + { + $client = ClientBuilder::create()->getClient(); + $serializer = $this->prophesize(Serializer::class)->reveal(); + + $client->setReprSerializer($serializer); + + $this->assertSame($serializer, $client->getReprSerializer()); + } } class PromiseMock implements Promise From f844b5dc09e3e2dd728309aebbe810dea0ca1937 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 2 Jan 2018 15:09:43 +0100 Subject: [PATCH 0317/1161] Handle nullable value for DSN (porting #522 to 2.x branch) --- lib/Raven/Configuration.php | 27 ++++++++++++++++++++++----- tests/ConfigurationTest.php | 25 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index c3360f235..9f27b2b81 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -748,6 +748,8 @@ public function setProcessorsOptions(array $options) * Configures the options for this processor. * * @param OptionsResolver $resolver The resolver for the options + * @throws \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException + * @throws \Symfony\Component\OptionsResolver\Exception\AccessException */ private function configureOptions(OptionsResolver $resolver) { @@ -808,7 +810,7 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('logger', 'string'); $resolver->setAllowedTypes('proxy', ['null', 'string']); $resolver->setAllowedTypes('release', ['null', 'string']); - $resolver->setAllowedTypes('server', ['null', 'string']); + $resolver->setAllowedTypes('server', ['null', 'boolean', 'string']); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('should_capture', ['null', 'callable']); $resolver->setAllowedTypes('tags', 'array'); @@ -818,8 +820,15 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedValues('encoding', ['gzip', 'json']); $resolver->setAllowedValues('server', function ($value) { - if (null === $value) { - return true; + switch (strtolower($value)) { + case '': + case 'false': + case '(false)': + case 'empty': + case '(empty)': + case 'null': + case '(null)': + return true; } $parsed = @parse_url($value); @@ -844,8 +853,16 @@ private function configureOptions(OptionsResolver $resolver) }); $resolver->setNormalizer('server', function (Options $options, $value) { - if (null === $value) { - return $value; + switch (strtolower($value)) { + case '': + case 'false': + case '(false)': + case 'empty': + case '(empty)': + case 'null': + case '(null)': + $this->server = null; + return null; } $parsed = @parse_url($value); diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 8406303d5..dcff8e888 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -170,6 +170,31 @@ public function invalidServerOptionDataProvider() ]; } + /** + * @dataProvider disabledDsnProvider + */ + public function testParseDSNWithDisabledValue($dsn) + { + $configuration = new Configuration(['server' => $dsn]); + + $this->assertNull($configuration->getProjectId()); + $this->assertNull($configuration->getPublicKey()); + $this->assertNull($configuration->getSecretKey()); + $this->assertNull($configuration->getServer()); + } + + public function disabledDsnProvider() + { + return array( + array(null), + array('null'), + array(false), + array('false'), + array(''), + array('empty'), + ); + } + public function testShouldCapture() { $configuration = new Configuration(); From 0537ffc9cd9767b139062da99c1eb7f37644f0cf Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 2 Jan 2018 15:22:34 +0100 Subject: [PATCH 0318/1161] Fix CS --- lib/Raven/Configuration.php | 4 +++- lib/Raven/ErrorHandler.php | 2 +- tests/ClientTest.php | 8 ++++---- tests/ConfigurationTest.php | 17 +++++++++-------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 9f27b2b81..cbf1c55e4 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -748,6 +748,7 @@ public function setProcessorsOptions(array $options) * Configures the options for this processor. * * @param OptionsResolver $resolver The resolver for the options + * * @throws \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException * @throws \Symfony\Component\OptionsResolver\Exception\AccessException */ @@ -862,6 +863,7 @@ private function configureOptions(OptionsResolver $resolver) case 'null': case '(null)': $this->server = null; + return null; } @@ -917,7 +919,7 @@ private function normalizeAbsolutePath($value) } if ( - DIRECTORY_SEPARATOR === substr($path, 0, 1) + DIRECTORY_SEPARATOR === substr($path, 0, 1) && DIRECTORY_SEPARATOR !== substr($path, -1) && '.php' !== substr($path, -4) ) { diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index 594e6ab7a..ec0cb0c79 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -150,7 +150,7 @@ public function shouldCaptureFatalError($type) // Do not capture E_ERROR since those can be caught by userland since PHP 7.0 // E_ERROR should already be handled by the exception handler // This prevents duplicated exceptions in PHP 7.0+ - if (PHP_VERSION_ID >= 70000 && $type === E_ERROR) { + if (PHP_VERSION_ID >= 70000 && E_ERROR === $type) { return false; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 61f82bdc2..558872c61 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1039,9 +1039,9 @@ public function testSetSerializer() { $client = ClientBuilder::create()->getClient(); $serializer = $this->prophesize(Serializer::class)->reveal(); - + $client->setSerializer($serializer); - + $this->assertSame($serializer, $client->getSerializer()); } @@ -1049,9 +1049,9 @@ public function testSetReprSerializer() { $client = ClientBuilder::create()->getClient(); $serializer = $this->prophesize(Serializer::class)->reveal(); - + $client->setReprSerializer($serializer); - + $this->assertSame($serializer, $client->getReprSerializer()); } } diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index dcff8e888..d0bcca49d 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -185,14 +185,14 @@ public function testParseDSNWithDisabledValue($dsn) public function disabledDsnProvider() { - return array( - array(null), - array('null'), - array(false), - array('false'), - array(''), - array('empty'), - ); + return [ + [null], + ['null'], + [false], + ['false'], + [''], + ['empty'], + ]; } public function testShouldCapture() @@ -254,6 +254,7 @@ public function excludedExceptionsDataProvider() /** * @dataProvider excludedPathProviders + * * @param string $value * @param string $expected */ From 1fec8919e33e635426d165f947765988664366d3 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 2 Jan 2018 15:48:19 +0100 Subject: [PATCH 0319/1161] Fix scrutinizer coverage count setting --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 94d403d5c..e9150c0d9 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,7 +5,7 @@ tools: php_code_coverage: true external_code_coverage: timeout: 2400 # There can be another pull request in progress - runs: 3 # PHP 5.6 + PHP 7.0 + PHP 7.1 + PHP 7.2 + runs: 4 # PHP 5.6 + PHP 7.0 + PHP 7.1 + PHP 7.2 build: environment: From 454410111a7c04f2694c364fa383898731e9fe7d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 18 Jan 2018 21:56:53 +0100 Subject: [PATCH 0320/1161] Refactor how the processors are configured and instantiated (#472) * Refactor how the processors are registered and executed by the client * Encapsulate the stacktrace frames inside objects to ease edits of their properties (such as sanitization) * Apply changes as requested in the code review --- .php_cs | 3 + UPGRADE-2.0.md | 42 +++ lib/Raven/Client.php | 87 ++--- lib/Raven/ClientBuilder.php | 72 +++- lib/Raven/ClientBuilderInterface.php | 30 ++ lib/Raven/Configuration.php | 55 +--- lib/Raven/Event.php | 4 +- lib/Raven/Frame.php | 257 +++++++++++++++ .../ExceptionInterfaceMiddleware.php | 4 +- lib/Raven/Middleware/ProcessorMiddleware.php | 64 ++++ lib/Raven/Processor.php | 45 --- lib/Raven/Processor/ProcessorInterface.php | 38 +++ lib/Raven/Processor/ProcessorRegistry.php | 80 +++++ .../Processor/RemoveCookiesProcessor.php | 22 +- .../Processor/RemoveHttpBodyProcessor.php | 16 +- lib/Raven/Processor/SanitizeDataProcessor.php | 157 ++++----- .../SanitizeHttpHeadersProcessor.php | 59 ++-- .../Processor/SanitizeStacktraceProcessor.php | 28 +- lib/Raven/Stacktrace.php | 40 ++- tests/ClientBuilderTest.php | 39 ++- tests/ClientTest.php | 26 -- tests/ConfigurationTest.php | 6 - tests/FrameTest.php | 79 +++++ tests/IntegrationTest.php | 2 +- .../ExceptionInterfaceMiddlewareTest.php | 39 ++- tests/Middleware/ProcessorMiddlewareTest.php | 64 ++++ tests/Processor/ProcessorRegistryTest.php | 69 ++++ .../Processor/RemoveCookiesProcessorTest.php | 57 ++-- .../Processor/RemoveHttpBodyProcessorTest.php | 108 +++--- tests/Processor/SanitizeDataProcessorTest.php | 308 +++++++++--------- .../SanitizeHttpHeadersProcessorTest.php | 56 ++-- .../SanitizeStacktraceProcessorTest.php | 71 ++-- tests/StacktraceTest.php | 36 +- 33 files changed, 1378 insertions(+), 685 deletions(-) create mode 100644 lib/Raven/Frame.php create mode 100644 lib/Raven/Middleware/ProcessorMiddleware.php delete mode 100644 lib/Raven/Processor.php create mode 100644 lib/Raven/Processor/ProcessorInterface.php create mode 100644 lib/Raven/Processor/ProcessorRegistry.php create mode 100644 tests/FrameTest.php create mode 100644 tests/Middleware/ProcessorMiddlewareTest.php create mode 100644 tests/Processor/ProcessorRegistryTest.php diff --git a/.php_cs b/.php_cs index 34df9cfdb..da318ed84 100644 --- a/.php_cs +++ b/.php_cs @@ -10,6 +10,9 @@ return PhpCsFixer\Config::create() 'ordered_imports' => true, 'random_api_migration' => true, 'yoda_style' => true, + 'phpdoc_align' => [ + 'tags' => ['param', 'return', 'throws', 'type', 'var'], + ], ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 2709aa0e6..d03f30494 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -19,6 +19,11 @@ - The `curl_ssl_version` option has been removed. - The `verify_ssl` option has been removed. - The `ca_cert` option has been removed. +- The `processors` option has been removed in favour of leaving to the user the + choice of which processors add or remove using the appropriate methods of the + `Client` and `ClientBuilder` classes. +- The `processors_options` option has been removed in favour of leaving to the + user the burden of adding an already configured processor instance to the client. - The `http_client_options` has been added to set the options that applies to the HTTP client chosen by the user as underlying transport method. - The `open_timeout` option has been added to set the maximum number of seconds @@ -286,6 +291,43 @@ level of the event. This has been changed so that only the `ErrorException` or its derivated classes are considered for this behavior. +- The method `Client::createProcessors` has been removed as there is no need to create + instances of the processors from outside the `Client` class. + +- The method `Client::setProcessors` has been removed. You should use `Client::addProcessor` + and `Client::removeProcessor` instead to manage the processors that will be executed. + + Before: + + ```php + $processor1 = new Processor(); + $processor2 = new Processor(); + + $client->setProcessors(array($processor2, $processor1)); + ``` + + After: + + ```php + $processor1 = new Processor(); + $processor2 = new Processor(); + + $client->addProcessor($processor2); + $client->addProcessor($processor1); + + // or + + $client->addProcessor($processor1); + $client->addProcessor($processor2, 255); // Note the priority: higher the priority earlier a processor will be executed. This is equivalent to adding first $processor2 and then $processor1 + ``` + +- The method `Client::process` has been removed as there is no need to process event data + from outside the `Client` class. + +- The `Raven_Processor` class has been removed. There is not anymore a base + abstract class for the processors, but a `ProcessorInterface` interface has + been introduced. + ### Client builder - To simplify the creation of a `Client` object instance, a new builder class diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 2208e2c7e..6cebc3fc1 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -24,8 +24,11 @@ use Raven\Middleware\ContextInterfaceMiddleware; use Raven\Middleware\ExceptionInterfaceMiddleware; use Raven\Middleware\MessageInterfaceMiddleware; +use Raven\Middleware\ProcessorMiddleware; use Raven\Middleware\RequestInterfaceMiddleware; use Raven\Middleware\UserInterfaceMiddleware; +use Raven\Processor\ProcessorInterface; +use Raven\Processor\ProcessorRegistry; use Raven\Util\JSON; use Zend\Diactoros\ServerRequestFactory; @@ -94,11 +97,6 @@ class Client */ protected $reprSerializer; - /** - * @var \Raven\Processor[] An array of classes to use to process data before it is sent to Sentry - */ - protected $processors = []; - /** * @var array[] */ @@ -124,6 +122,11 @@ class Client */ private $requestFactory; + /** + * @var ProcessorRegistry The registry of processors + */ + private $processorRegistry; + /** * @var Promise[] The list of pending requests */ @@ -156,16 +159,17 @@ public function __construct(Configuration $config, HttpAsyncClient $httpClient, $this->config = $config; $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; + $this->processorRegistry = new ProcessorRegistry(); $this->context = new Context(); $this->recorder = new Recorder(); $this->transaction = new TransactionStack(); $this->serializer = new Serializer($this->config->getMbDetectOrder()); $this->reprSerializer = new ReprSerializer($this->config->getMbDetectOrder()); - $this->processors = $this->createProcessors(); $this->middlewareStackTip = function (Event $event) { return $event; }; + $this->addMiddleware(new ProcessorMiddleware($this->processorRegistry)); $this->addMiddleware(new MessageInterfaceMiddleware()); $this->addMiddleware(new RequestInterfaceMiddleware()); $this->addMiddleware(new UserInterfaceMiddleware()); @@ -254,6 +258,29 @@ public function addMiddleware(callable $callable) }; } + /** + * Adds a new processor to the processors chain with the specified priority. + * + * @param ProcessorInterface $processor The processor instance + * @param int $priority The priority. The higher this value, + * the earlier a processor will be + * executed in the chain (defaults to 0) + */ + public function addProcessor(ProcessorInterface $processor, $priority = 0) + { + $this->processorRegistry->addProcessor($processor, $priority); + } + + /** + * Removes the given processor from the list. + * + * @param ProcessorInterface $processor The processor instance + */ + public function removeProcessor(ProcessorInterface $processor) + { + $this->processorRegistry->removeProcessor($processor); + } + /** * Gets the representation serialier. * @@ -293,33 +320,6 @@ public function install() return $this; } - /** - * Sets the \Raven\Processor sub-classes to be used when data is processed before being - * sent to Sentry. - * - * @return \Raven\Processor[] - */ - public function createProcessors() - { - $processors = []; - $processorsOptions = $this->config->getProcessorsOptions(); - - foreach ($this->config->getProcessors() as $processor) { - /** @var Processor $processorInstance */ - $processorInstance = new $processor($this); - - if (isset($processorsOptions[$processor])) { - if (method_exists($processor, 'setProcessorOptions')) { - $processorInstance->setProcessorOptions($processorsOptions[$processor]); - } - } - - $processors[] = $processorInstance; - } - - return $processors; - } - /** * Logs a message. * @@ -466,7 +466,6 @@ public function capture(array $payload) $payload = $event->toArray(); $this->sanitize($payload); - $this->process($payload); if (!$this->storeErrorsForBulkSend) { $this->send($payload); @@ -501,18 +500,6 @@ public function sanitize(&$data) } } - /** - * Process data through all defined \Raven\Processor sub-classes. - * - * @param array $data Associative array of data to log - */ - public function process(&$data) - { - foreach ($this->processors as $processor) { - $processor->process($data); - } - } - public function sendUnsentErrors() { foreach ($this->pendingEvents as $data) { @@ -653,14 +640,6 @@ public function getContext() return $this->context; } - /** - * @param array $processors - */ - public function setProcessors(array $processors) - { - $this->processors = $processors; - } - /** * @return bool */ diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index 26178531b..0d855cb3b 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -25,6 +25,11 @@ use Http\Message\MessageFactory; use Http\Message\UriFactory; use Raven\HttpClient\Authentication\SentryAuth; +use Raven\Processor\ProcessorInterface; +use Raven\Processor\RemoveCookiesProcessor; +use Raven\Processor\RemoveHttpBodyProcessor; +use Raven\Processor\SanitizeDataProcessor; +use Raven\Processor\SanitizeHttpHeadersProcessor; /** * The default implementation of {@link ClientBuilderInterface}. @@ -74,10 +79,6 @@ * @method setServerName(string $serverName) * @method string[] getTags() * @method setTags(string[] $tags) - * @method string[] getProcessors() - * @method setProcessors(string[] $processors) - * @method array getProcessorsOptions() - * @method setProcessorsOptions(array $options) */ final class ClientBuilder implements ClientBuilderInterface { @@ -106,6 +107,11 @@ final class ClientBuilder implements ClientBuilderInterface */ private $httpClientPlugins = []; + /** + * @var array List of processors and their priorities + */ + private $processors = []; + /** * Class constructor. * @@ -114,6 +120,7 @@ final class ClientBuilder implements ClientBuilderInterface public function __construct(array $options = []) { $this->configuration = new Configuration($options); + $this->processors = self::getDefaultProcessors(); } /** @@ -180,6 +187,40 @@ public function removeHttpClientPlugin($className) return $this; } + /** + * {@inheritdoc} + */ + public function addProcessor(ProcessorInterface $processor, $priority = 0) + { + $this->processors[] = [$processor, $priority]; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function removeProcessor(ProcessorInterface $processor) + { + foreach ($this->processors as $key => $value) { + if ($value[0] !== $processor) { + continue; + } + + unset($this->processors[$key]); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getProcessors() + { + return $this->processors; + } + /** * {@inheritdoc} */ @@ -189,7 +230,13 @@ public function getClient() $this->uriFactory = $this->uriFactory ?: UriFactoryDiscovery::find(); $this->httpClient = $this->httpClient ?: HttpAsyncClientDiscovery::find(); - return new Client($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); + $client = new Client($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); + + foreach ($this->processors as $value) { + $client->addProcessor($value[0], $value[1]); + } + + return $client; } /** @@ -231,4 +278,19 @@ private function createHttpClientInstance() return new PluginClient($this->httpClient, $this->httpClientPlugins); } + + /** + * Returns a list of processors that are enabled by default. + * + * @return array + */ + private static function getDefaultProcessors() + { + return [ + [new RemoveCookiesProcessor(), 0], + [new RemoveHttpBodyProcessor(), 0], + [new SanitizeHttpHeadersProcessor(), 0], + [new SanitizeDataProcessor(), -255], + ]; + } } diff --git a/lib/Raven/ClientBuilderInterface.php b/lib/Raven/ClientBuilderInterface.php index bbe27eca4..5c0cfd439 100644 --- a/lib/Raven/ClientBuilderInterface.php +++ b/lib/Raven/ClientBuilderInterface.php @@ -15,6 +15,7 @@ use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory; use Http\Message\UriFactory; +use Raven\Processor\ProcessorInterface; /** * A configurable builder for Client objects. @@ -77,6 +78,35 @@ public function addHttpClientPlugin(Plugin $plugin); */ public function removeHttpClientPlugin($className); + /** + * Adds a new processor to the processors chain with the specified priority. + * + * @param ProcessorInterface $processor The processor instance + * @param int $priority The priority. The higher this value, + * the earlier a processor will be + * executed in the chain (defaults to 0) + * + * @return $this + */ + public function addProcessor(ProcessorInterface $processor, $priority = 0); + + /** + * Removes the given processor from the list. + * + * @param ProcessorInterface $processor The processor instance + * + * @return $this + */ + public function removeProcessor(ProcessorInterface $processor); + + /** + * Gets a list of processors that will be added to the client at the + * given priority. + * + * @return array + */ + public function getProcessors(); + /** * Gets the instance of the client built using the configured options. * diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 59e4ed2ad..c0040a676 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -11,10 +11,6 @@ namespace Raven; -use Raven\Processor\RemoveCookiesProcessor; -use Raven\Processor\RemoveHttpBodyProcessor; -use Raven\Processor\SanitizeDataProcessor; -use Raven\Processor\SanitizeHttpHeadersProcessor; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -411,7 +407,7 @@ public function setExcludedExceptions(array $exceptions) * Checks whether the given exception should be ignored when sending events * to Sentry. * - * @param \Throwable|\Exception The exception + * @param \Throwable|\Exception $exception The exception * * @return bool */ @@ -704,46 +700,6 @@ public function setErrorTypes($errorTypes) $this->options = $this->resolver->resolve($options); } - /** - * Gets the list of enabled processors. - * - * @return string[] - */ - public function getProcessors() - { - return $this->options['processors']; - } - - /** - * Sets the list of enabled processors. - * - * @param string[] $processors A list of FCQN - */ - public function setProcessors(array $processors) - { - $this->options = $this->resolver->resolve(['processors' => $processors]); - } - - /** - * Gets the options to configure the processors. - * - * @return array - */ - public function getProcessorsOptions() - { - return $this->options['processors_options']; - } - - /** - * Sets the options to configure the processors. - * - * @param array $options The options - */ - public function setProcessorsOptions(array $options) - { - $this->options = $this->resolver->resolve(['processors_options' => $options]); - } - /** * Configures the options for this processor. * @@ -778,13 +734,6 @@ private function configureOptions(OptionsResolver $resolver) 'should_capture' => null, 'tags' => [], 'error_types' => null, - 'processors_options' => [], - 'processors' => [ - SanitizeDataProcessor::class, - RemoveCookiesProcessor::class, - RemoveHttpBodyProcessor::class, - SanitizeHttpHeadersProcessor::class, - ], ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -813,8 +762,6 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('should_capture', ['null', 'callable']); $resolver->setAllowedTypes('tags', 'array'); $resolver->setAllowedTypes('error_types', ['null', 'int']); - $resolver->setAllowedTypes('processors_options', 'array'); - $resolver->setAllowedTypes('processors', 'array'); $resolver->setAllowedValues('encoding', ['gzip', 'json']); $resolver->setAllowedValues('server', function ($value) { diff --git a/lib/Raven/Event.php b/lib/Raven/Event.php index 5efc6821b..df566ce51 100644 --- a/lib/Raven/Event.php +++ b/lib/Raven/Event.php @@ -783,8 +783,8 @@ public function toArray() $data['breadcrumbs'] = $this->breadcrumbs; } - if (!empty($this->exception)) { - foreach ($this->exception as $exception) { + if (null !== $this->exception && isset($this->exception['values'])) { + foreach ($this->exception['values'] as $exception) { $exceptionData = [ 'type' => $exception['type'], 'value' => $exception['value'], diff --git a/lib/Raven/Frame.php b/lib/Raven/Frame.php new file mode 100644 index 000000000..42154636b --- /dev/null +++ b/lib/Raven/Frame.php @@ -0,0 +1,257 @@ + + */ +final class Frame implements \JsonSerializable +{ + /** + * @var string The name of the function being called + */ + private $functionName; + + /** + * @var string The file where the frame originated + */ + private $file; + + /** + * @var int The line at which the frame originated + */ + private $line; + + /** + * @var string[] A list of source code lines before the one where the frame + * originated + */ + private $preContext; + + /** + * @var string[] The source code written at the line number of the file that + * originated this frame + */ + private $contextLine; + + /** + * @var string[] A list of source code lines after the one where the frame + * originated + */ + private $postContext; + + /** + * @var bool Flag telling whether the frame is related to the execution of + * the relevant code in this stacktrace + */ + private $inApp = false; + + /** + * @var array A mapping of variables which were available within this + * frame (usually context-locals) + */ + private $vars = []; + + /** + * Initializes a new instance of this class using the provided information. + * + * @param string $functionName The name of the function being called + * @param string $file The file where the frame originated + * @param int $line The line at which the frame originated + */ + public function __construct($functionName, $file, $line) + { + $this->functionName = $functionName; + $this->file = $file; + $this->line = $line; + } + + /** + * Gets the name of the function being called. + * + * @return string + */ + public function getFunctionName() + { + return $this->functionName; + } + + /** + * Gets the file where the frame originated. + * + * @return string + */ + public function getFile() + { + return $this->file; + } + + /** + * Gets the line at which the frame originated. + * + * @return int + */ + public function getLine() + { + return $this->line; + } + + /** + * Gets a list of source code lines before the one where the frame originated. + * + * @return string[]|null + */ + public function getPreContext() + { + return $this->preContext; + } + + /** + * Sets a list of source code lines before the one where the frame originated. + * + * @param string[]|null $preContext The source code lines + */ + public function setPreContext(array $preContext = null) + { + $this->preContext = $preContext; + } + + /** + * Gets the source code written at the line number of the file that originated + * this frame. + * + * @return string[]|null + */ + public function getContextLine() + { + return $this->contextLine; + } + + /** + * Sets the source code written at the line number of the file that originated + * this frame. + * + * @param string|null $contextLine The source code line + */ + public function setContextLine($contextLine) + { + $this->contextLine = $contextLine; + } + + /** + * Gets a list of source code lines after the one where the frame originated. + * + * @return string[]|null + */ + public function getPostContext() + { + return $this->postContext; + } + + /** + * Sets a list of source code lines after the one where the frame originated. + * + * @param string[]|null $postContext The source code lines + */ + public function setPostContext(array $postContext = null) + { + $this->postContext = $postContext; + } + + /** + * Gets whether the frame is related to the execution of the relevant code + * in this stacktrace. + * + * @return bool + */ + public function isInApp() + { + return $this->inApp; + } + + /** + * Sets whether the frame is related to the execution of the relevant code + * in this stacktrace. + * + * @param bool $inApp flag indicating whether the frame is application-related + */ + public function setIsInApp($inApp) + { + $this->inApp = (bool) $inApp; + } + + /** + * Gets a mapping of variables which were available within this frame + * (usually context-locals). + * + * @return array + */ + public function getVars() + { + return $this->vars; + } + + /** + * Sets a mapping of variables which were available within this frame + * (usually context-locals). + * + * @param array $vars The variables + */ + public function setVars(array $vars) + { + $this->vars = $vars; + } + + /** + * Returns an array representation of the data of this frame modeled according + * to the specifications of the Sentry SDK Stacktrace Interface. + * + * @return array + */ + public function toArray() + { + $result = [ + 'function' => $this->functionName, + 'filename' => $this->file, + 'lineno' => $this->line, + 'in_app' => $this->inApp, + ]; + + if (null !== $this->preContext) { + $result['pre_context'] = $this->preContext; + } + + if (null !== $this->contextLine) { + $result['context_line'] = $this->contextLine; + } + + if (null !== $this->postContext) { + $result['post_context'] = $this->postContext; + } + + if (!empty($this->vars)) { + $result['vars'] = $this->vars; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->toArray(); + } +} diff --git a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php index 995c22a58..1fef44e4c 100644 --- a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php @@ -77,7 +77,9 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $exceptions[] = $data; } while ($currentException = $currentException->getPrevious()); - $exceptions = array_reverse($exceptions); + $exceptions = [ + 'values' => array_reverse($exceptions), + ]; $event = $event->withException($exceptions); } diff --git a/lib/Raven/Middleware/ProcessorMiddleware.php b/lib/Raven/Middleware/ProcessorMiddleware.php new file mode 100644 index 000000000..d17f88800 --- /dev/null +++ b/lib/Raven/Middleware/ProcessorMiddleware.php @@ -0,0 +1,64 @@ + + */ +final class ProcessorMiddleware +{ + /** + * @var ProcessorRegistry The registry of processors + */ + private $processorRegistry; + + /** + * Constructor. + * + * @param ProcessorRegistry $processorRegistry The registry of processors + */ + public function __construct(ProcessorRegistry $processorRegistry) + { + $this->processorRegistry = $processorRegistry; + } + + /** + * Invokes all the processors to process the event before it's sent. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + { + foreach ($this->processorRegistry->getProcessors() as $processor) { + $event = $processor->process($event); + + if (!$event instanceof Event) { + throw new \UnexpectedValueException(sprintf('The processor must return an instance of the "%s" class.', Event::class)); + } + } + + return $next($event, $request, $exception, $payload); + } +} diff --git a/lib/Raven/Processor.php b/lib/Raven/Processor.php deleted file mode 100644 index 9f74f0a1d..000000000 --- a/lib/Raven/Processor.php +++ /dev/null @@ -1,45 +0,0 @@ -client = $client; - } - - /** - * Override the default processor options. - * - * @param array $options Associative array of processor options - */ - public function setProcessorOptions(array $options) - { - } - - /** - * Process and sanitize data, modifying the existing value if necessary. - * - * @param array $data Array of log data - */ - abstract public function process(&$data); -} diff --git a/lib/Raven/Processor/ProcessorInterface.php b/lib/Raven/Processor/ProcessorInterface.php new file mode 100644 index 000000000..c203a0816 --- /dev/null +++ b/lib/Raven/Processor/ProcessorInterface.php @@ -0,0 +1,38 @@ + + */ +interface ProcessorInterface +{ + /** + * This constant defines the mask string used to strip sensitive informations. + */ + const STRING_MASK = '********'; + + /** + * Process and sanitize data, modifying the existing value if necessary. + * + * @param Event $event The event object + * + * @return Event + */ + public function process(Event $event); +} diff --git a/lib/Raven/Processor/ProcessorRegistry.php b/lib/Raven/Processor/ProcessorRegistry.php new file mode 100644 index 000000000..2f54240d3 --- /dev/null +++ b/lib/Raven/Processor/ProcessorRegistry.php @@ -0,0 +1,80 @@ + + */ +final class ProcessorRegistry +{ + /** + * @var array List of processors sorted by priority + */ + private $processors = []; + + /** + * @var array + */ + private $sortedProcessors = []; + + /** + * Registers the given processor. + * + * @param ProcessorInterface $processor The processor instance + * @param int $priority The priority at which the processor must run + */ + public function addProcessor(ProcessorInterface $processor, $priority = 0) + { + $this->processors[$priority][] = $processor; + + unset($this->sortedProcessors); + } + + /** + * Removes the given processor from the list of available ones. + * + * @param ProcessorInterface $processor The processor instance + */ + public function removeProcessor(ProcessorInterface $processor) + { + foreach ($this->processors as $priority => $processors) { + foreach ($processors as $key => $value) { + if ($value === $processor) { + unset($this->processors[$priority][$key], $this->sortedProcessors); + } + } + } + } + + /** + * Gets the processors sorted by priority. + * + * @return ProcessorInterface[] + */ + public function getProcessors() + { + if (empty($this->processors)) { + return []; + } + + if (empty($this->sortedProcessors)) { + krsort($this->processors); + + $this->sortedProcessors = array_merge(...$this->processors); + } + + return $this->sortedProcessors; + } +} diff --git a/lib/Raven/Processor/RemoveCookiesProcessor.php b/lib/Raven/Processor/RemoveCookiesProcessor.php index f70f8f610..0a7ebb19b 100644 --- a/lib/Raven/Processor/RemoveCookiesProcessor.php +++ b/lib/Raven/Processor/RemoveCookiesProcessor.php @@ -11,7 +11,7 @@ namespace Raven\Processor; -use Raven\Processor; +use Raven\Event; /** * This processor removes all the cookies from the request to ensure no sensitive @@ -19,21 +19,23 @@ * * @author Stefano Arlandini */ -final class RemoveCookiesProcessor extends Processor +final class RemoveCookiesProcessor implements ProcessorInterface { /** * {@inheritdoc} */ - public function process(&$data) + public function process(Event $event) { - if (isset($data['request'])) { - if (isset($data['request']['cookies'])) { - $data['request']['cookies'] = self::STRING_MASK; - } + $request = $event->getRequest(); - if (isset($data['request']['headers']) && isset($data['request']['headers']['Cookie'])) { - $data['request']['headers']['Cookie'] = self::STRING_MASK; - } + if (isset($request['cookies'])) { + $request['cookies'] = self::STRING_MASK; } + + if (isset($request['headers'], $request['headers']['cookie'])) { + $request['headers']['cookie'] = self::STRING_MASK; + } + + return $event->withRequest($request); } } diff --git a/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/lib/Raven/Processor/RemoveHttpBodyProcessor.php index 9421f894e..9ec86b316 100644 --- a/lib/Raven/Processor/RemoveHttpBodyProcessor.php +++ b/lib/Raven/Processor/RemoveHttpBodyProcessor.php @@ -11,7 +11,7 @@ namespace Raven\Processor; -use Raven\Processor; +use Raven\Event; /** * This processor removes all the data of the HTTP body to ensure no sensitive @@ -20,17 +20,19 @@ * * @author Stefano Arlandini */ -final class RemoveHttpBodyProcessor extends Processor +final class RemoveHttpBodyProcessor implements ProcessorInterface { /** * {@inheritdoc} */ - public function process(&$data) + public function process(Event $event) { - if (isset($data['request'], $data['request']['method']) - && in_array(strtoupper($data['request']['method']), ['POST', 'PUT', 'PATCH', 'DELETE']) - ) { - $data['request']['data'] = self::STRING_MASK; + $request = $event->getRequest(); + + if (isset($request['method']) && in_array(strtoupper($request['method']), ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { + $request['data'] = self::STRING_MASK; } + + return $event->withRequest($request); } } diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php index 1e83ba31c..268f482ae 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -13,43 +13,53 @@ namespace Raven\Processor; -use Raven\Client; -use Raven\Processor; +use Raven\Event; +use Symfony\Component\OptionsResolver\OptionsResolver; -class SanitizeDataProcessor extends Processor +/** + * Asterisk out passwords from password fields in frames, http and basic extra + * data. + * + * @author David Cramer + * @author Stefano Arlandini + */ +final class SanitizeDataProcessor implements ProcessorInterface { - const MASK = self::STRING_MASK; - const FIELDS_RE = '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i'; - const VALUES_RE = '/^(?:\d[ -]*?){13,16}$/'; - - protected $fields_re; - protected $values_re; - protected $session_cookie_name; + /** + * @var array The configuration options + */ + private $options; /** - * {@inheritdoc} + * Class constructor. + * + * @param array $options An optional array of configuration options */ - public function __construct(Client $client) + public function __construct(array $options = []) { - parent::__construct($client); + $resolver = new OptionsResolver(); + + $this->configureOptions($resolver); - $this->fields_re = self::FIELDS_RE; - $this->values_re = self::VALUES_RE; - $this->session_cookie_name = ini_get('session.name'); + $this->options = $resolver->resolve($options); } /** - * {@inheritdoc} + * Configures the options for this processor. + * + * @param OptionsResolver $resolver The resolver for the options */ - public function setProcessorOptions(array $options) + private function configureOptions(OptionsResolver $resolver) { - if (isset($options['fields_re'])) { - $this->fields_re = $options['fields_re']; - } - - if (isset($options['values_re'])) { - $this->values_re = $options['values_re']; - } + $resolver->setDefaults([ + 'fields_re' => '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', + 'values_re' => '/^(?:\d[ -]*?){13,16}$/', + 'session_cookie_name' => ini_get('session.name'), + ]); + + $resolver->setAllowedTypes('fields_re', 'string'); + $resolver->setAllowedTypes('values_re', 'string'); + $resolver->setAllowedTypes('session_cookie_name', 'string'); } /** @@ -64,7 +74,7 @@ public function sanitize(&$item, $key) return; } - if (preg_match($this->values_re, $item)) { + if (preg_match($this->options['values_re'], $item)) { $item = self::STRING_MASK; } @@ -72,94 +82,85 @@ public function sanitize(&$item, $key) return; } - if (preg_match($this->fields_re, $key)) { + if (preg_match($this->options['fields_re'], $key)) { $item = self::STRING_MASK; } } public function sanitizeException(&$data) { - foreach ($data['exception']['values'] as &$value) { + foreach ($data['values'] as &$value) { if (!isset($value['stacktrace'])) { continue; } $this->sanitizeStacktrace($value['stacktrace']); } + + return $data; } public function sanitizeHttp(&$data) { - $http = &$data['request']; - if (!empty($http['cookies']) && is_array($http['cookies'])) { - $cookies = &$http['cookies']; - if (!empty($cookies[$this->session_cookie_name])) { - $cookies[$this->session_cookie_name] = self::STRING_MASK; + if (!empty($data['cookies']) && is_array($data['cookies'])) { + $cookies = &$data['cookies']; + if (!empty($cookies[$this->options['session_cookie_name']])) { + $cookies[$this->options['session_cookie_name']] = self::STRING_MASK; } } - if (!empty($http['data']) && is_array($http['data'])) { - array_walk_recursive($http['data'], [$this, 'sanitize']); + + if (!empty($data['data']) && is_array($data['data'])) { + array_walk_recursive($data['data'], [$this, 'sanitize']); } + + return $data; } - public function sanitizeStacktrace(&$data) + public function sanitizeStacktrace($data) { - foreach ($data['frames'] as &$frame) { - if (empty($frame['vars'])) { + foreach ($data->getFrames() as &$frame) { + if (empty($frame->getVars())) { continue; } - array_walk_recursive($frame['vars'], [$this, 'sanitize']); + + $vars = $frame->getVars(); + + array_walk_recursive($vars, [$this, 'sanitize']); + + $frame->setVars($vars); } + + return $data; } /** * {@inheritdoc} */ - public function process(&$data) + public function process(Event $event) { - if (!empty($data['exception'])) { - $this->sanitizeException($data); - } - if (!empty($data['stacktrace'])) { - $this->sanitizeStacktrace($data['stacktrace']); + $exception = $event->getException(); + $stacktrace = $event->getStacktrace(); + $request = $event->getRequest(); + $extraContext = $event->getExtraContext(); + + if (!empty($exception)) { + $event = $event->withException($this->sanitizeException($exception)); } - if (!empty($data['request'])) { - $this->sanitizeHttp($data); + + if (!empty($stacktrace)) { + $event = $event->withStacktrace($this->sanitizeStacktrace($stacktrace)); } - if (!empty($data['extra'])) { - array_walk_recursive($data['extra'], [$this, 'sanitize']); + + if (!empty($request)) { + $event = $event->withRequest($this->sanitizeHttp($request)); } - } - /** - * @return string - */ - public function getFieldsRe() - { - return $this->fields_re; - } + if (!empty($extraContext)) { + array_walk_recursive($extraContext, [$this, 'sanitize']); - /** - * @param string $fields_re - */ - public function setFieldsRe($fields_re) - { - $this->fields_re = $fields_re; - } - - /** - * @return string - */ - public function getValuesRe() - { - return $this->values_re; - } + $event = $event->withExtraContext($extraContext); + } - /** - * @param string $values_re - */ - public function setValuesRe($values_re) - { - $this->values_re = $values_re; + return $event; } } diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php index 96c6b393c..dde9cd8b3 100644 --- a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php +++ b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -11,8 +11,8 @@ namespace Raven\Processor; -use Raven\Client; -use Raven\Processor; +use Raven\Event; +use Symfony\Component\OptionsResolver\OptionsResolver; /** * This processor sanitizes the configured HTTP headers to ensure no sensitive @@ -20,53 +20,54 @@ * * @author Stefano Arlandini */ -final class SanitizeHttpHeadersProcessor extends Processor +final class SanitizeHttpHeadersProcessor implements ProcessorInterface { /** - * @var string[] The list of HTTP headers to sanitize + * @var array The configuration options */ - private $httpHeadersToSanitize = []; + private $options; /** - * {@inheritdoc} + * Class constructor. + * + * @param array $options An optional array of configuration options */ - public function __construct(Client $client) + public function __construct(array $options = []) { - parent::__construct($client); - } + $resolver = new OptionsResolver(); - /** - * {@inheritdoc} - */ - public function setProcessorOptions(array $options) - { - $this->httpHeadersToSanitize = array_merge( - $this->getDefaultHeaders(), - isset($options['sanitize_http_headers']) ? $options['sanitize_http_headers'] : [] - ); + $this->configureOptions($resolver); + + $this->options = $resolver->resolve($options); } /** * {@inheritdoc} */ - public function process(&$data) + public function process(Event $event) { - if (isset($data['request']) && isset($data['request']['headers'])) { - foreach ($data['request']['headers'] as $header => &$value) { - if (in_array($header, $this->httpHeadersToSanitize)) { - $value = self::STRING_MASK; - } + $request = $event->getRequest(); + + if (!isset($request['headers'])) { + return $event; + } + + foreach ($request['headers'] as $header => &$value) { + if (in_array($header, $this->options['sanitize_http_headers'], true)) { + $value = self::STRING_MASK; } } + + return $event->withRequest($request); } /** - * Gets the list of default headers that must be sanitized. - * - * @return string[] + * {@inheritdoc} */ - private function getDefaultHeaders() + private function configureOptions(OptionsResolver $resolver) { - return ['Authorization', 'Proxy-Authorization', 'X-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN']; + $resolver->setDefault('sanitize_http_headers', ['Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN']); + + $resolver->setAllowedTypes('sanitize_http_headers', 'array'); } } diff --git a/lib/Raven/Processor/SanitizeStacktraceProcessor.php b/lib/Raven/Processor/SanitizeStacktraceProcessor.php index 748e8d67d..76fa9f0ad 100644 --- a/lib/Raven/Processor/SanitizeStacktraceProcessor.php +++ b/lib/Raven/Processor/SanitizeStacktraceProcessor.php @@ -11,33 +11,33 @@ namespace Raven\Processor; -use Raven\Processor; +use Raven\Event; /** * This processor removes the `pre_context`, `context_line` and `post_context` - * informations from all exceptions captured by an event. + * information from all exceptions captured by an event. * * @author Stefano Arlandini */ -class SanitizeStacktraceProcessor extends Processor +final class SanitizeStacktraceProcessor implements ProcessorInterface { /** * {@inheritdoc} */ - public function process(&$data) + public function process(Event $event) { - if (!isset($data['exception'], $data['exception']['values'])) { - return; - } + $stacktrace = $event->getStacktrace(); - foreach ($data['exception']['values'] as &$exception) { - if (!isset($exception['stacktrace'])) { - continue; - } + if (null === $stacktrace) { + return $event; + } - foreach ($exception['stacktrace']['frames'] as &$frame) { - unset($frame['pre_context'], $frame['context_line'], $frame['post_context']); - } + foreach ($stacktrace->getFrames() as $frame) { + $frame->setPreContext(null); + $frame->setContextLine(null); + $frame->setPostContext(null); } + + return $event->withStacktrace($stacktrace); } } diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 517da2945..68a4f02c5 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -39,7 +39,7 @@ class Stacktrace implements \JsonSerializable protected $reprSerializer; /** - * @var array The frames that compose the stacktrace + * @var Frame[] The frames that compose the stacktrace */ protected $frames = []; @@ -109,7 +109,7 @@ public static function createFromBacktrace(Client $client, array $backtrace, $fi /** * Gets the stacktrace frames. * - * @return array[] + * @return Frame[] */ public function getFrames() { @@ -134,14 +134,28 @@ public function addFrame($file, $line, array $backtraceFrame) $line = $matches[2]; } - $frame = array_merge( - [ - 'filename' => $this->stripPrefixFromFilePath($file), - 'lineno' => (int) $line, - 'function' => isset($backtraceFrame['class']) ? sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']) : (isset($backtraceFrame['function']) ? $backtraceFrame['function'] : null), - ], - self::getSourceCodeExcerpt($file, $line, self::CONTEXT_NUM_LINES) - ); + if (isset($backtraceFrame['class'])) { + $functionName = sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); + } elseif (isset($backtraceFrame['function'])) { + $functionName = $backtraceFrame['function']; + } else { + $functionName = null; + } + + $frame = new Frame($functionName, $this->stripPrefixFromFilePath($file), (int) $line); + $sourceCodeExcerpt = self::getSourceCodeExcerpt($file, $line, self::CONTEXT_NUM_LINES); + + if (isset($sourceCodeExcerpt['pre_context'])) { + $frame->setPreContext($sourceCodeExcerpt['pre_context']); + } + + if (isset($sourceCodeExcerpt['context_line'])) { + $frame->setContextLine($sourceCodeExcerpt['context_line']); + } + + if (isset($sourceCodeExcerpt['post_context'])) { + $frame->setPostContext($sourceCodeExcerpt['post_context']); + } if (null !== $this->client->getConfig()->getProjectRoot()) { $excludedAppPaths = $this->client->getConfig()->getExcludedProjectPaths(); @@ -151,7 +165,7 @@ public function addFrame($file, $line, array $backtraceFrame) if ($isApplicationFile && !empty($excludedAppPaths)) { foreach ($excludedAppPaths as $path) { if (0 === strpos($absoluteFilePath, $path)) { - $frame['in_app'] = $isApplicationFile; + $frame->setIsInApp($isApplicationFile); break; } @@ -172,7 +186,7 @@ public function addFrame($file, $line, array $backtraceFrame) } } - $frame['vars'] = $frameArguments; + $frame->setVars($frameArguments); } array_unshift($this->frames, $frame); @@ -198,7 +212,7 @@ public function removeFrame($index) * Gets the stacktrace frames (this is the same as calling the getFrames * method). * - * @return array + * @return Frame[] */ public function toArray() { diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index e746c8c16..c7b21929d 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -20,6 +20,7 @@ use Raven\Client; use Raven\ClientBuilder; use Raven\Configuration; +use Raven\Processor\ProcessorInterface; class ClientBuilderTest extends TestCase { @@ -109,6 +110,42 @@ public function testRemoveHttpClientPlugin() $this->assertSame($plugin2, reset($plugins)); } + public function testAddProcessor() + { + /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ + $processor = $this->createMock(ProcessorInterface::class); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->addProcessor($processor, -10); + + $this->assertAttributeContains([$processor, -10], 'processors', $clientBuilder); + } + + public function testRemoveProcessor() + { + /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ + $processor = $this->createMock(ProcessorInterface::class); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->addProcessor($processor, -10); + $clientBuilder->removeProcessor($processor); + + $this->assertAttributeNotContains([$processor, -10], 'processors', $clientBuilder); + } + + public function testGetProcessors() + { + /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ + $processor = $this->createMock(ProcessorInterface::class); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->addProcessor($processor, -10); + $clientBuilder->addProcessor($processor, 10); + + $this->assertContains([$processor, -10], $clientBuilder->getProcessors()); + $this->assertContains([$processor, 10], $clientBuilder->getProcessors()); + } + public function testGetClient() { $clientBuilder = new ClientBuilder(); @@ -174,8 +211,6 @@ public function optionsDataProvider() ['setServerName', 'example.com'], ['setTags', ['foo', 'bar']], ['setErrorTypes', 0], - ['setProcessors', ['foo']], - ['setProcessorsOptions', ['foo']], ]; } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f75308d96..9afdf4087 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -23,7 +23,6 @@ use Raven\ClientBuilder; use Raven\Configuration; use Raven\Event; -use Raven\Processor\SanitizeDataProcessor; function simple_function($a = null, $b = null, $c = null) { @@ -270,31 +269,6 @@ public function testCapture() $this->assertEquals(['bar' => 'foo'], $client->pendingEvents[0]['user']); } - public function testDoesRegisterProcessors() - { - $client = ClientBuilder::create(['processors' => [SanitizeDataProcessor::class]])->getClient(); - - $this->assertInstanceOf(SanitizeDataProcessor::class, $this->getObjectAttribute($client, 'processors')[0]); - } - - public function testProcessDoesCallProcessors() - { - $data = ['key' => 'value']; - - $processor = $this->getMockBuilder('Processor') - ->setMethods(['process']) - ->getMock(); - - $processor->expects($this->once()) - ->method('process') - ->with($data); - - $client = ClientBuilder::create()->getClient(); - - $client->setProcessors([$processor]); - $client->process($data); - } - public function testCaptureLastError() { if (function_exists('error_clear_last')) { diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 70f3d0798..b35516123 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -13,10 +13,6 @@ use PHPUnit\Framework\TestCase; use Raven\Configuration; -use Raven\Processor\RemoveCookiesProcessor; -use Raven\Processor\RemoveHttpBodyProcessor; -use Raven\Processor\SanitizeDataProcessor; -use Raven\Processor\SanitizeHttpHeadersProcessor; class ConfigurationTest extends TestCase { @@ -70,8 +66,6 @@ public function optionsDataProvider() ['server_name', 'foo', 'getServerName', 'setServerName'], ['tags', ['foo', 'bar'], 'getTags', 'setTags'], ['error_types', 0, 'getErrorTypes', 'setErrorTypes'], - ['processors', [SanitizeDataProcessor::class, RemoveCookiesProcessor::class, RemoveHttpBodyProcessor::class, SanitizeHttpHeadersProcessor::class], 'getProcessors', 'setProcessors'], - ['processors_options', ['foo' => 'bar'], 'getProcessorsOptions', 'setProcessorsOptions'], ]; } diff --git a/tests/FrameTest.php b/tests/FrameTest.php new file mode 100644 index 000000000..e60b42bff --- /dev/null +++ b/tests/FrameTest.php @@ -0,0 +1,79 @@ +assertEquals('foo', $frame->getFunctionName()); + $this->assertEquals('bar', $frame->getFile()); + $this->assertEquals(10, $frame->getLine()); + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters($getterMethod, $setterMethod, $expectedData) + { + $frame = new Frame('foo', 'bar', 10); + $frame->$setterMethod($expectedData); + + $this->assertEquals($expectedData, $frame->$getterMethod()); + } + + public function gettersAndSettersDataProvider() + { + return [ + ['getPreContext', 'setPreContext', ['foo' => 'bar', 'bar' => 'baz']], + ['getContextLine', 'setContextLine', 'foo bar'], + ['getPostContext', 'setPostContext', ['bar' => 'foo', 'baz' => 'bar']], + ['isInApp', 'setIsInApp', true], + ['getVars', 'setVars', ['foo' => 'bar']], + ]; + } + + /** + * @dataProvider toArrayAndJsonSerializeDataProvider + */ + public function testToArrayAndJsonSerialize($setterMethod, $expectedDataKey, $expectedData) + { + $frame = new Frame('foo', 'bar', 10); + $frame->$setterMethod($expectedData); + + $expectedResult = [ + 'function' => 'foo', + 'filename' => 'bar', + 'lineno' => 10, + $expectedDataKey => $expectedData, + ]; + + $this->assertArraySubset($expectedResult, $frame->toArray()); + $this->assertArraySubset($expectedResult, $frame->jsonSerialize()); + } + + public function toArrayAndJsonSerializeDataProvider() + { + return [ + ['setPreContext', 'pre_context', ['foo' => 'bar']], + ['setContextLine', 'context_line', 'foo bar'], + ['setPostContext', 'post_context', ['bar' => 'foo']], + ['setIsInApp', 'in_app', true], + ['setVars', 'vars', ['baz' => 'bar']], + ]; + } +} diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 8afefe38d..bccf798b9 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -69,6 +69,6 @@ public function testCaptureSimpleError() $event = $client->pendingEvents[0]['exception']['values'][0]; $this->assertEquals($event['value'], 'mkdir(): No such file or directory'); - $this->assertEquals($event['stacktrace']['frames'][count($event['stacktrace']['frames']) - 1]['filename'], 'tests/IntegrationTest.php'); + $this->assertEquals($event['stacktrace']['frames'][count($event['stacktrace']['frames']) - 1]->getFile(), 'tests/IntegrationTest.php'); } } diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php index 9c09e2db7..11632f854 100644 --- a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php +++ b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php @@ -35,7 +35,7 @@ public function testInvoke($exception, $clientConfig, $payload, $expectedResult) $this->assertNotSame($event, $eventArg); $this->assertArraySubset($expectedResult, $eventArg->toArray()); - foreach ($eventArg->getException() as $exception) { + foreach ($eventArg->getException()['values'] as $exception) { if ($assertHasStacktrace) { $this->assertArrayHasKey('stacktrace', $exception); $this->assertInstanceOf(Stacktrace::class, $exception['stacktrace']); @@ -145,9 +145,11 @@ public function testInvokeWithExceptionContainingLatin1Characters() $this->assertNotSame($event, $eventArg); $expectedValue = [ - [ - 'type' => \Exception::class, - 'value' => $utf8String, + 'values' => [ + [ + 'type' => \Exception::class, + 'value' => $utf8String, + ], ], ]; @@ -172,9 +174,11 @@ public function testInvokeWithExceptionContainingInvalidUtf8Characters() $this->assertNotSame($event, $eventArg); $expectedValue = [ - [ - 'type' => \Exception::class, - 'value' => "\xC2\xA2\x3F", + 'values' => [ + [ + 'type' => \Exception::class, + 'value' => "\xC2\xA2\x3F", + ], ], ]; @@ -204,9 +208,11 @@ public function testInvokeWithExceptionThrownInLatin1File() $result = $eventArg->getException(); $expectedValue = [ - [ - 'type' => \Exception::class, - 'value' => 'foo', + 'values' => [ + [ + 'type' => \Exception::class, + 'value' => 'foo', + ], ], ]; @@ -214,8 +220,9 @@ public function testInvokeWithExceptionThrownInLatin1File() $latin1StringFound = false; - foreach ($result[0]['stacktrace']->toArray() as $frame) { - if (isset($frame['pre_context']) && in_array('// äöü', $frame['pre_context'], true)) { + /** @var \Raven\Frame $frame */ + foreach ($result['values'][0]['stacktrace']->getFrames() as $frame) { + if (null !== $frame->getPreContext() && in_array('// äöü', $frame->getPreContext(), true)) { $latin1StringFound = true; break; @@ -245,10 +252,10 @@ public function testInvokeWithAutoLogStacksDisabled() $result = $eventArg->getException(); $this->assertNotEmpty($result); - $this->assertInternalType('array', $result[0]); - $this->assertEquals(\Exception::class, $result[0]['type']); - $this->assertEquals('foo', $result[0]['value']); - $this->assertArrayNotHasKey('stacktrace', $result[0]); + $this->assertInternalType('array', $result['values'][0]); + $this->assertEquals(\Exception::class, $result['values'][0]['type']); + $this->assertEquals('foo', $result['values'][0]['value']); + $this->assertArrayNotHasKey('stacktrace', $result['values'][0]); ++$invokationCount; }; diff --git a/tests/Middleware/ProcessorMiddlewareTest.php b/tests/Middleware/ProcessorMiddlewareTest.php new file mode 100644 index 000000000..32e91659c --- /dev/null +++ b/tests/Middleware/ProcessorMiddlewareTest.php @@ -0,0 +1,64 @@ +getClient(); + $event = new Event($client->getConfig()); + $processorRegistry = $this->getObjectAttribute($client, 'processorRegistry'); + + /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ + $processor = $this->createMock(ProcessorInterface::class); + $processor->expects($this->once()) + ->method('process') + ->willReturnArgument(0); + + $client->addProcessor($processor); + + $middleware = new ProcessorMiddleware($processorRegistry); + $middleware($event, function () { + // Do nothing, it's just a middleware added to end the chain + }); + } + + /** + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage The processor must return an instance of the "Raven\Event" class. + */ + public function testInvokeProcessorThatReturnsNothingThrows() + { + $client = ClientBuilder::create([])->getClient(); + $event = new Event($client->getConfig()); + $processorRegistry = $this->getObjectAttribute($client, 'processorRegistry'); + + /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ + $processor = $this->createMock(ProcessorInterface::class); + $processor->expects($this->once()) + ->method('process'); + + $client->addProcessor($processor); + + $middleware = new ProcessorMiddleware($processorRegistry); + $middleware($event, function () { + // Do nothing, it's just a middleware added to end the chain + }); + } +} diff --git a/tests/Processor/ProcessorRegistryTest.php b/tests/Processor/ProcessorRegistryTest.php new file mode 100644 index 000000000..ee11200c1 --- /dev/null +++ b/tests/Processor/ProcessorRegistryTest.php @@ -0,0 +1,69 @@ +processorRegistry = new ProcessorRegistry(); + } + + public function testAddProcessor() + { + /** @var ProcessorInterface $processor1 */ + $processor1 = $this->createMock(ProcessorInterface::class); + + /** @var ProcessorInterface $processor2 */ + $processor2 = $this->createMock(ProcessorInterface::class); + + /** @var ProcessorInterface $processor3 */ + $processor3 = $this->createMock(ProcessorInterface::class); + + $this->processorRegistry->addProcessor($processor1, -10); + $this->processorRegistry->addProcessor($processor2, 10); + $this->processorRegistry->addProcessor($processor3); + + $processors = $this->processorRegistry->getProcessors(); + + $this->assertCount(3, $processors); + $this->assertSame($processor2, $processors[0]); + $this->assertSame($processor3, $processors[1]); + $this->assertSame($processor1, $processors[2]); + } + + public function testRemoveProcessor() + { + /** @var ProcessorInterface $processor1 */ + $processor1 = $this->createMock(ProcessorInterface::class); + + /** @var ProcessorInterface $processor2 */ + $processor2 = $this->createMock(ProcessorInterface::class); + + /** @var ProcessorInterface $processor3 */ + $processor3 = $this->createMock(ProcessorInterface::class); + + $this->processorRegistry->addProcessor($processor1, -10); + $this->processorRegistry->addProcessor($processor2, 10); + $this->processorRegistry->addProcessor($processor3); + + $this->assertCount(3, $this->processorRegistry->getProcessors()); + + $this->processorRegistry->removeProcessor($processor1); + + $processors = $this->processorRegistry->getProcessors(); + + $this->assertCount(2, $processors); + $this->assertSame($processor2, $processors[0]); + $this->assertSame($processor3, $processors[1]); + } +} diff --git a/tests/Processor/RemoveCookiesProcessorTest.php b/tests/Processor/RemoveCookiesProcessorTest.php index 6677f900d..e43520fd8 100644 --- a/tests/Processor/RemoveCookiesProcessorTest.php +++ b/tests/Processor/RemoveCookiesProcessorTest.php @@ -13,23 +13,26 @@ use PHPUnit\Framework\TestCase; use Raven\Client; +use Raven\ClientBuilder; +use Raven\Event; use Raven\Processor\RemoveCookiesProcessor; class RemoveCookiesProcessorTest extends TestCase { /** - * @var RemoveCookiesProcessor|\PHPUnit_Framework_MockObject_MockObject + * @var Client + */ + protected $client; + + /** + * @var RemoveCookiesProcessor */ protected $processor; protected function setUp() { - /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->processor = new RemoveCookiesProcessor($client); + $this->client = ClientBuilder::create()->getClient(); + $this->processor = new RemoveCookiesProcessor(); } /** @@ -37,9 +40,12 @@ protected function setUp() */ public function testProcess($inputData, $expectedData) { - $this->processor->process($inputData); + $event = new Event($this->client->getConfig()); + $event = $event->withRequest($inputData); - $this->assertArraySubset($expectedData, $inputData); + $event = $this->processor->process($event); + + $this->assertArraySubset($expectedData, $event->getRequest()); } public function processDataProvider() @@ -47,32 +53,19 @@ public function processDataProvider() return [ [ [ - 'request' => [ - 'foo' => 'bar', - ], - ], [ - 'request' => [ - 'foo' => 'bar', + 'foo' => 'bar', + 'cookies' => 'baz', + 'headers' => [ + 'cookie' => 'bar', + 'another-header' => 'foo', ], ], - ], [ [ - 'request' => [ - 'foo' => 'bar', - 'cookies' => 'baz', - 'headers' => [ - 'Cookie' => 'bar', - 'AnotherHeader' => 'foo', - ], - ], - ], [ - 'request' => [ - 'foo' => 'bar', - 'cookies' => RemoveCookiesProcessor::STRING_MASK, - 'headers' => [ - 'Cookie' => RemoveCookiesProcessor::STRING_MASK, - 'AnotherHeader' => 'foo', - ], + 'foo' => 'bar', + 'cookies' => RemoveCookiesProcessor::STRING_MASK, + 'headers' => [ + 'cookie' => RemoveCookiesProcessor::STRING_MASK, + 'another-header' => 'foo', ], ], ], diff --git a/tests/Processor/RemoveHttpBodyProcessorTest.php b/tests/Processor/RemoveHttpBodyProcessorTest.php index 71a958a4e..0c98bc737 100644 --- a/tests/Processor/RemoveHttpBodyProcessorTest.php +++ b/tests/Processor/RemoveHttpBodyProcessorTest.php @@ -13,10 +13,17 @@ use PHPUnit\Framework\TestCase; use Raven\Client; +use Raven\ClientBuilder; +use Raven\Event; use Raven\Processor\RemoveHttpBodyProcessor; class RemoveHttpBodyProcessorTest extends TestCase { + /** + * @var Client + */ + protected $client; + /** * @var RemoveHttpBodyProcessor|\PHPUnit_Framework_MockObject_MockObject */ @@ -24,12 +31,8 @@ class RemoveHttpBodyProcessorTest extends TestCase protected function setUp() { - /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->processor = new RemoveHttpBodyProcessor($client); + $this->client = ClientBuilder::create()->getClient(); + $this->processor = new RemoveHttpBodyProcessor(); } /** @@ -37,9 +40,12 @@ protected function setUp() */ public function testProcess($inputData, $expectedData) { - $this->processor->process($inputData); + $event = new Event($this->client->getConfig()); + $event = $event->withRequest($inputData); - $this->assertArraySubset($expectedData, $inputData); + $event = $this->processor->process($event); + + $this->assertArraySubset($expectedData, $event->getRequest()); } public function processDataProvider() @@ -47,70 +53,58 @@ public function processDataProvider() return [ [ [ - 'request' => [ - 'method' => 'POST', - 'data' => [ - 'foo' => 'bar', - ], - ], - ], [ - 'request' => [ - 'data' => RemoveHttpBodyProcessor::STRING_MASK, + 'method' => 'POST', + 'data' => [ + 'foo' => 'bar', ], ], - ], [ [ - 'request' => [ - 'method' => 'PUT', - 'data' => [ - 'foo' => 'bar', - ], - ], - ], [ - 'request' => [ - 'data' => RemoveHttpBodyProcessor::STRING_MASK, - ], + 'data' => RemoveHttpBodyProcessor::STRING_MASK, ], - ], [ + ], + [ [ - 'request' => [ - 'method' => 'PATCH', - 'data' => [ - 'foo' => 'bar', - ], + 'method' => 'PUT', + 'data' => [ + 'foo' => 'bar', ], ], [ - 'request' => [ - 'data' => RemoveHttpBodyProcessor::STRING_MASK, - ], + 'data' => RemoveHttpBodyProcessor::STRING_MASK, ], - ], [ + ], + [ [ - 'request' => [ - 'method' => 'DELETE', - 'data' => [ - 'foo' => 'bar', - ], + 'method' => 'PATCH', + 'data' => [ + 'foo' => 'bar', ], - ], [ - 'request' => [ - 'data' => RemoveHttpBodyProcessor::STRING_MASK, + ], + [ + 'data' => RemoveHttpBodyProcessor::STRING_MASK, + ], + ], + [ + [ + 'method' => 'DELETE', + 'data' => [ + 'foo' => 'bar', ], ], - ], [ [ - 'request' => [ - 'method' => 'GET', - 'data' => [ - 'foo' => 'bar', - ], + 'data' => RemoveHttpBodyProcessor::STRING_MASK, + ], + ], + [ + [ + 'method' => 'GET', + 'data' => [ + 'foo' => 'bar', ], - ], [ - 'request' => [ - 'data' => [ - 'foo' => 'bar', - ], + ], + [ + 'data' => [ + 'foo' => 'bar', ], ], ], diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index b761e6544..5b4534033 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -12,181 +12,199 @@ namespace Raven\Tests\Processor; use PHPUnit\Framework\TestCase; +use Raven\Client; use Raven\ClientBuilder; +use Raven\Event; use Raven\Processor\SanitizeDataProcessor; +use Raven\Stacktrace; class SanitizeDataProcessorTest extends TestCase { - public function testDoesFilterHttpData() - { - $data = [ - 'request' => [ - 'data' => [ - 'foo' => 'bar', - 'password' => 'hello', - 'the_secret' => 'hello', - 'a_password_here' => 'hello', - 'mypasswd' => 'hello', - 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', - 'card_number' => [ - '1111', - '2222', - '3333', - '4444', - ], - ], - ], - ]; - - $client = ClientBuilder::create()->getClient(); - $processor = new SanitizeDataProcessor($client); - $processor->process($data); - - $vars = $data['request']['data']; - $this->assertEquals($vars['foo'], 'bar'); - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['password']); - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['the_secret']); - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['a_password_here']); - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['mypasswd']); - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['authorization']); - - $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); + /** + * @var Client + */ + protected $client; - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0']); - } + /** + * @var SanitizeDataProcessor + */ + protected $processor; - public function testDoesFilterSessionId() + protected function setUp() { - $data = [ - 'request' => [ - 'cookies' => [ - ini_get('session.name') => 'abc', - ], - ], - ]; - - $client = ClientBuilder::create()->getClient(); - $processor = new SanitizeDataProcessor($client); - $processor->process($data); - - $cookies = $data['request']['cookies']; - $this->assertEquals($cookies[ini_get('session.name')], SanitizeDataProcessor::STRING_MASK); + $this->client = ClientBuilder::create()->getClient(); + $this->processor = new SanitizeDataProcessor(); } - public function testDoesFilterCreditCard() + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) { - $data = [ - 'extra' => [ - 'ccnumba' => '4242424242424242', - ], - ]; - - $client = ClientBuilder::create()->getClient(); - $processor = new SanitizeDataProcessor($client); - $processor->process($data); - - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $data['extra']['ccnumba']); - } + $event = new Event($this->client->getConfig()); - public function testSettingProcessorOptions() - { - $client = ClientBuilder::create()->getClient(); - $processor = new SanitizeDataProcessor($client); + if (isset($inputData['request'])) { + $event = $event->withRequest($inputData['request']); + } - $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); - $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); + if (isset($inputData['extra_context'])) { + $event = $event->withExtraContext($inputData['extra_context']); + } - $options = [ - 'fields_re' => '/(api_token)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/', - ]; + if (isset($inputData['exception'])) { + // We must convert the backtrace to a Stacktrace instance here because + // PHPUnit executes the data provider before the setUp method and so + // the client instance cannot be accessed from there + $event = $event->withException($this->convertExceptionValuesToStacktrace($expectedData['exception'])); + } - $processor->setProcessorOptions($options); + $event = $this->processor->process($event); - $this->assertEquals($processor->getFieldsRe(), '/(api_token)/i', 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){15,16}$/', 'overwrote values'); - } + if (isset($expectedData['request'])) { + $this->assertArraySubset($expectedData['request'], $event->getRequest()); + } - /** - * @dataProvider overrideDataProvider - */ - public function testOverrideOptions($processorOptions, $clientOptions) - { - $client = ClientBuilder::create($clientOptions)->getClient(); + if (isset($expectedData['extra_context'])) { + $this->assertArraySubset($expectedData['extra_context'], $event->getExtraContext()); + } - /** @var SanitizeDataProcessor $processor */ - $processor = $this->getObjectAttribute($client, 'processors')[0]; + if (isset($expectedData['exception'])) { + // We must convert the backtrace to a Stacktrace instance here because + // PHPUnit executes the data provider before the setUp method and so + // the client instance cannot be accessed from there + $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), $event->getException()); + } - $this->assertInstanceOf(SanitizeDataProcessor::class, $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions[SanitizeDataProcessor::class]['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions[SanitizeDataProcessor::class]['values_re'], 'overwrote values'); + $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); } - /** - * @depends testOverrideOptions - * @dataProvider overrideDataProvider - */ - public function testOverridenSanitize($processorOptions, $clientOptions) + public function processDataProvider() { - $data = [ - 'request' => [ - 'data' => [ - 'foo' => 'bar', - 'password' => 'hello', - 'the_secret' => 'hello', - 'a_password_here' => 'hello', - 'mypasswd' => 'hello', - 'api_token' => 'nioenio3nrio3jfny89nby9bhr#RML#R', - 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', - 'card_number' => [ - '1111111111111111', - '2222', + return [ + [ + [ + 'request' => [ + 'data' => [ + 'foo' => 'bar', + 'password' => 'hello', + 'the_secret' => 'hello', + 'a_password_here' => 'hello', + 'mypasswd' => 'hello', + 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', + ], + ], + ], + [ + 'request' => [ + 'data' => [ + 'foo' => 'bar', + 'password' => SanitizeDataProcessor::STRING_MASK, + 'the_secret' => SanitizeDataProcessor::STRING_MASK, + 'a_password_here' => SanitizeDataProcessor::STRING_MASK, + 'mypasswd' => SanitizeDataProcessor::STRING_MASK, + 'authorization' => SanitizeDataProcessor::STRING_MASK, + ], + ], + ], + ], + [ + [ + 'request' => [ + 'cookies' => [ + ini_get('session.name') => 'abc', + ], + ], + ], + [ + 'request' => [ + 'cookies' => [ + ini_get('session.name') => SanitizeDataProcessor::STRING_MASK, + ], + ], + ], + ], + [ + [ + 'extra_context' => [ + 'ccnumba' => '4242424242424242', + ], + ], + [ + 'extra_context' => [ + 'ccnumba' => SanitizeDataProcessor::STRING_MASK, + ], + ], + ], + [ + [ + 'exception' => [ + 'values' => [ + [ + 'stacktrace' => [ + [ + 'args' => [ + [ + 'password' => 'foo', + ], + ], + ], + ], + ], + [ + 'stacktrace' => [ + [ + 'args' => [ + [ + 'password' => 'foo', + ], + ], + ], + ], + ], + ], + ], + ], + [ + 'exception' => [ + 'values' => [ + [ + 'stacktrace' => [ + [ + 'args' => [ + [ + 'password' => SanitizeDataProcessor::STRING_MASK, + ], + ], + ], + ], + ], + [ + 'stacktrace' => [ + [ + 'args' => [ + [ + 'password' => SanitizeDataProcessor::STRING_MASK, + ], + ], + ], + ], + ], + ], ], ], ], ]; - - $client = ClientBuilder::create($clientOptions)->getClient(); - - /** @var SanitizeDataProcessor $processor */ - $processor = $this->getObjectAttribute($client, 'processors')[0]; - - $this->assertInstanceOf(SanitizeDataProcessor::class, $processor); - $this->assertEquals($processor->getFieldsRe(), $processorOptions[SanitizeDataProcessor::class]['fields_re'], 'overwrote fields'); - $this->assertEquals($processor->getValuesRe(), $processorOptions[SanitizeDataProcessor::class]['values_re'], 'overwrote values'); - - $processor->process($data); - - $vars = $data['request']['data']; - - $this->assertEquals($vars['foo'], 'bar', 'did not alter foo'); - $this->assertEquals($vars['password'], 'hello', 'did not alter password'); - $this->assertEquals($vars['the_secret'], 'hello', 'did not alter the_secret'); - $this->assertEquals($vars['a_password_here'], 'hello', 'did not alter a_password_here'); - $this->assertEquals($vars['mypasswd'], 'hello', 'did not alter mypasswd'); - $this->assertEquals($vars['authorization'], 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', 'did not alter authorization'); - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['api_token'], 'masked api_token'); - - $this->assertEquals(SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0'], 'masked card_number[0]'); - $this->assertEquals($vars['card_number']['1'], $vars['card_number']['1'], 'did not alter card_number[1]'); } - public static function overrideDataProvider() + private function convertExceptionValuesToStacktrace($exceptionValues) { - $processorOptions = [ - SanitizeDataProcessor::class => [ - 'fields_re' => '/(api_token)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/', - ], - ]; + foreach ($exceptionValues['values'] as &$exceptionValue) { + $exceptionValue['stacktrace'] = Stacktrace::createFromBacktrace($this->client, $exceptionValue['stacktrace'], 'foo', 1); + } - $client_options = [ - 'processors' => [SanitizeDataProcessor::class], - 'processors_options' => $processorOptions, - ]; + // Free the memory from the reference + unset($exceptionValue); - return [ - [$processorOptions, $client_options], - ]; + return $exceptionValues; } } diff --git a/tests/Processor/SanitizeHttpHeadersProcessorTest.php b/tests/Processor/SanitizeHttpHeadersProcessorTest.php index acd23dd15..dbe0b2e53 100644 --- a/tests/Processor/SanitizeHttpHeadersProcessorTest.php +++ b/tests/Processor/SanitizeHttpHeadersProcessorTest.php @@ -13,10 +13,17 @@ use PHPUnit\Framework\TestCase; use Raven\Client; +use Raven\ClientBuilder; +use Raven\Event; use Raven\Processor\SanitizeHttpHeadersProcessor; class SanitizeHttpHeadersProcessorTest extends TestCase { + /** + * @var Client + */ + protected $client; + /** * @var SanitizeHttpHeadersProcessor|\PHPUnit_Framework_MockObject_MockObject */ @@ -24,13 +31,8 @@ class SanitizeHttpHeadersProcessorTest extends TestCase protected function setUp() { - /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->processor = new SanitizeHttpHeadersProcessor($client); - $this->processor->setProcessorOptions([ + $this->client = ClientBuilder::create()->getClient(); + $this->processor = new SanitizeHttpHeadersProcessor([ 'sanitize_http_headers' => ['User-Defined-Header'], ]); } @@ -40,9 +42,12 @@ protected function setUp() */ public function testProcess($inputData, $expectedData) { - $this->processor->process($inputData); + $event = new Event($this->client->getConfig()); + $event = $event->withRequest($inputData); - $this->assertArraySubset($expectedData, $inputData); + $event = $this->processor->process($event); + + $this->assertArraySubset($expectedData, $event->getRequest()); } public function processDataProvider() @@ -50,34 +55,17 @@ public function processDataProvider() return [ [ [ - 'request' => [ - 'headers' => [ - 'Authorization' => 'foo', - 'AnotherHeader' => 'bar', - ], - ], - ], [ - 'request' => [ - 'headers' => [ - 'Authorization' => SanitizeHttpHeadersProcessor::STRING_MASK, - 'AnotherHeader' => 'bar', - ], + 'headers' => [ + 'Authorization' => 'foo', + 'AnotherHeader' => 'bar', + 'User-Defined-Header' => 'baz', ], ], - ], [ [ - 'request' => [ - 'headers' => [ - 'User-Defined-Header' => 'foo', - 'AnotherHeader' => 'bar', - ], - ], - ], [ - 'request' => [ - 'headers' => [ - 'User-Defined-Header' => SanitizeHttpHeadersProcessor::STRING_MASK, - 'AnotherHeader' => 'bar', - ], + 'headers' => [ + 'Authorization' => 'foo', + 'AnotherHeader' => 'bar', + 'User-Defined-Header' => SanitizeHttpHeadersProcessor::STRING_MASK, ], ], ], diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php index 37e8c8447..2abfc8853 100644 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -14,67 +14,66 @@ use PHPUnit\Framework\TestCase; use Raven\Client; use Raven\ClientBuilder; +use Raven\Event; use Raven\Processor\SanitizeStacktraceProcessor; +use Raven\Stacktrace; class SanitizeStacktraceProcessorTest extends TestCase { /** - * @var Client + * @var SanitizeStacktraceProcessor */ - protected $client; + protected $processor; /** - * @var SanitizeStacktraceProcessor + * @var Client */ - protected $processor; + protected $client; protected function setUp() { - $this->client = ClientBuilder::create(['auto_log_stacks' => true])->getClient(); - $this->client->storeErrorsForBulkSend = true; - - $this->processor = new SanitizeStacktraceProcessor($this->client); + $this->processor = new SanitizeStacktraceProcessor(); + $this->client = ClientBuilder::create(['auto_log_stacks' => true]) + ->getClient(); } public function testProcess() { - try { - throw new \Exception(); - } catch (\Exception $exception) { - $this->client->captureException($exception); - } + $exception = new \Exception(); + + $event = new Event($this->client->getConfig()); + $event = $event->withStacktrace(Stacktrace::createFromBacktrace($this->client, $exception->getTrace(), $exception->getFile(), $exception->getLine())); - $this->processor->process($this->client->pendingEvents[0]); + $event = $this->processor->process($event); - foreach ($this->client->pendingEvents[0]['exception']['values'] as $exceptionValue) { - foreach ($exceptionValue['stacktrace']['frames'] as $frame) { - $this->assertArrayNotHasKey('pre_context', $frame); - $this->assertArrayNotHasKey('context_line', $frame); - $this->assertArrayNotHasKey('post_context', $frame); - } + foreach ($event->getStacktrace()->getFrames() as $frame) { + $this->assertNull($frame->getPreContext()); + $this->assertNull($frame->getContextLine()); + $this->assertNull($frame->getPostContext()); } } public function testProcessWithPreviousException() { - try { - try { - throw new \Exception('foo'); - } catch (\Exception $exception) { - throw new \Exception('bar', 0, $exception); - } - } catch (\Exception $exception) { - $this->client->captureException($exception); - } + $exception1 = new \Exception(); + $exception2 = new \Exception('', 0, $exception1); + + $event = new Event($this->client->getConfig()); + $event = $event->withStacktrace(Stacktrace::createFromBacktrace($this->client, $exception2->getTrace(), $exception2->getFile(), $exception2->getLine())); - $this->processor->process($this->client->pendingEvents[0]); + $event = $this->processor->process($event); - foreach ($this->client->pendingEvents[0]['exception']['values'] as $exceptionValue) { - foreach ($exceptionValue['stacktrace']['frames'] as $frame) { - $this->assertArrayNotHasKey('pre_context', $frame); - $this->assertArrayNotHasKey('context_line', $frame); - $this->assertArrayNotHasKey('post_context', $frame); - } + foreach ($event->getStacktrace()->toArray() as $frame) { + $this->assertNull($frame->getPreContext()); + $this->assertNull($frame->getContextLine()); + $this->assertNull($frame->getPostContext()); } } + + public function testProcessWithNoStacktrace() + { + $event = new Event($this->client->getConfig()); + + $this->assertSame($event, $this->processor->process($event)); + } } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 892507231..1def591b3 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Raven\Client; use Raven\ClientBuilder; +use Raven\Frame; use Raven\Stacktrace; class StacktraceTest extends TestCase @@ -90,7 +91,7 @@ public function testAddFrameSerializesMethodArguments() $this->assertCount(1, $frames); $this->assertFrameEquals($frames[0], 'test_function', 'path/to/file', 12); - $this->assertEquals(['param1' => 1, 'param2' => 'foo'], $frames[0]['vars']); + $this->assertEquals(['param1' => 1, 'param2' => 'foo'], $frames[0]->getVars()); } public function testAddFrameStripsPath() @@ -124,9 +125,8 @@ public function testAddFrameMarksAsInApp() $frames = $stacktrace->getFrames(); - $this->assertArrayHasKey('in_app', $frames[0]); - $this->assertTrue($frames[0]['in_app']); - $this->assertArrayNotHasKey('in_app', $frames[1]); + $this->assertTrue($frames[0]->isInApp()); + $this->assertFalse($frames[1]->isInApp()); } public function testAddFrameReadsCodeFromShortFile() @@ -139,17 +139,17 @@ public function testAddFrameReadsCodeFromShortFile() $frames = $stacktrace->getFrames(); $this->assertCount(1, $frames); - $this->assertCount(2, $frames[0]['pre_context']); - $this->assertCount(2, $frames[0]['post_context']); + $this->assertCount(2, $frames[0]->getPreContext()); + $this->assertCount(2, $frames[0]->getPostContext()); for ($i = 0; $i < 2; ++$i) { - $this->assertEquals(rtrim($fileContent[$i]), $frames[0]['pre_context'][$i]); + $this->assertEquals(rtrim($fileContent[$i]), $frames[0]->getPreContext()[$i]); } - $this->assertEquals(rtrim($fileContent[2]), $frames[0]['context_line']); + $this->assertEquals(rtrim($fileContent[2]), $frames[0]->getContextLine()); for ($i = 0; $i < 2; ++$i) { - $this->assertEquals(rtrim($fileContent[$i + 3]), $frames[0]['post_context'][$i]); + $this->assertEquals(rtrim($fileContent[$i + 3]), $frames[0]->getPostContext()[$i]); } } @@ -165,17 +165,17 @@ public function testAddFrameReadsCodeFromLongFile() $frames = $stacktrace->getFrames(); $this->assertCount(1, $frames); - $this->assertCount(5, $frames[0]['pre_context']); - $this->assertCount(5, $frames[0]['post_context']); + $this->assertCount(5, $frames[0]->getPreContext()); + $this->assertCount(5, $frames[0]->getPostContext()); for ($i = 0; $i < 5; ++$i) { - $this->assertEquals(rtrim($fileContent[$i + 2]), $frames[0]['pre_context'][$i]); + $this->assertEquals(rtrim($fileContent[$i + 2]), $frames[0]->getPreContext()[$i]); } - $this->assertEquals(rtrim($fileContent[7]), $frames[0]['context_line']); + $this->assertEquals(rtrim($fileContent[7]), $frames[0]->getContextLine()); for ($i = 0; $i < 5; ++$i) { - $this->assertEquals(rtrim($fileContent[$i + 8]), $frames[0]['post_context'][$i]); + $this->assertEquals(rtrim($fileContent[$i + 8]), $frames[0]->getPostContext()[$i]); } } @@ -284,10 +284,10 @@ protected function getJsonFixture($file) return json_decode($this->getFixture($file), true); } - protected function assertFrameEquals($frame, $method, $file, $line) + protected function assertFrameEquals(Frame $frame, $method, $file, $line) { - $this->assertSame($method, $frame['function']); - $this->assertSame($file, $frame['filename']); - $this->assertSame($line, $frame['lineno']); + $this->assertSame($method, $frame->getFunctionName()); + $this->assertSame($file, $frame->getFile()); + $this->assertSame($line, $frame->getLine()); } } From 7c353247a9dcd9006961d63318a4c3a9bf237595 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 19 Jan 2018 13:05:41 +0100 Subject: [PATCH 0321/1161] Fix bad merge (and tests) --- tests/ClientTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 33069fb91..a09b9bf0b 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -23,6 +23,7 @@ use Raven\ClientBuilder; use Raven\Configuration; use Raven\Event; +use Raven\Serializer; function simple_function($a = null, $b = null, $c = null) { From b07dd29dada866217f858739e97c721a8507061a Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 20 Jan 2018 00:00:49 +0100 Subject: [PATCH 0322/1161] Make the behaviour of the RemoveCookiesProcessor configurable (#533) --- UPGRADE-2.0.md | 6 + lib/Raven/ClientBuilder.php | 4 +- .../Middleware/RequestInterfaceMiddleware.php | 1 + .../Processor/RemoveCookiesProcessor.php | 41 ------ .../Processor/SanitizeCookiesProcessor.php | 96 ++++++++++++++ .../RequestInterfaceMiddlewareTest.php | 9 ++ .../Processor/RemoveCookiesProcessorTest.php | 74 ----------- .../SanitizeCookiesProcessorTest.php | 119 ++++++++++++++++++ 8 files changed, 233 insertions(+), 117 deletions(-) delete mode 100644 lib/Raven/Processor/RemoveCookiesProcessor.php create mode 100644 lib/Raven/Processor/SanitizeCookiesProcessor.php delete mode 100644 tests/Processor/RemoveCookiesProcessorTest.php create mode 100644 tests/Processor/SanitizeCookiesProcessorTest.php diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index d03f30494..f93961d99 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -350,3 +350,9 @@ $client = ClientBuilder::create([...])->getClient(); ``` + +### Processors + +- The `RemoveCookiesProessor` class has been renamed to `SanitizeCookiesProcessor` to + better reflect its purpose. The constructor accepts an array of options to make the + behaviour of which cookies to sanitize configurable. diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index 0d855cb3b..c435495cb 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -26,8 +26,8 @@ use Http\Message\UriFactory; use Raven\HttpClient\Authentication\SentryAuth; use Raven\Processor\ProcessorInterface; -use Raven\Processor\RemoveCookiesProcessor; use Raven\Processor\RemoveHttpBodyProcessor; +use Raven\Processor\SanitizeCookiesProcessor; use Raven\Processor\SanitizeDataProcessor; use Raven\Processor\SanitizeHttpHeadersProcessor; @@ -287,7 +287,7 @@ private function createHttpClientInstance() private static function getDefaultProcessors() { return [ - [new RemoveCookiesProcessor(), 0], + [new SanitizeCookiesProcessor(), 0], [new RemoveHttpBodyProcessor(), 0], [new SanitizeHttpHeadersProcessor(), 0], [new SanitizeDataProcessor(), -255], diff --git a/lib/Raven/Middleware/RequestInterfaceMiddleware.php b/lib/Raven/Middleware/RequestInterfaceMiddleware.php index ec67d22a8..6afd6b774 100644 --- a/lib/Raven/Middleware/RequestInterfaceMiddleware.php +++ b/lib/Raven/Middleware/RequestInterfaceMiddleware.php @@ -43,6 +43,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r 'url' => (string) $request->getUri(), 'method' => $request->getMethod(), 'headers' => $request->getHeaders(), + 'cookies' => $request->getCookieParams(), ]; if ('' !== $request->getUri()->getQuery()) { diff --git a/lib/Raven/Processor/RemoveCookiesProcessor.php b/lib/Raven/Processor/RemoveCookiesProcessor.php deleted file mode 100644 index 0a7ebb19b..000000000 --- a/lib/Raven/Processor/RemoveCookiesProcessor.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ -final class RemoveCookiesProcessor implements ProcessorInterface -{ - /** - * {@inheritdoc} - */ - public function process(Event $event) - { - $request = $event->getRequest(); - - if (isset($request['cookies'])) { - $request['cookies'] = self::STRING_MASK; - } - - if (isset($request['headers'], $request['headers']['cookie'])) { - $request['headers']['cookie'] = self::STRING_MASK; - } - - return $event->withRequest($request); - } -} diff --git a/lib/Raven/Processor/SanitizeCookiesProcessor.php b/lib/Raven/Processor/SanitizeCookiesProcessor.php new file mode 100644 index 000000000..fd7666cd8 --- /dev/null +++ b/lib/Raven/Processor/SanitizeCookiesProcessor.php @@ -0,0 +1,96 @@ + + */ +final class SanitizeCookiesProcessor implements ProcessorInterface +{ + /** + * @var array The configuration options + */ + private $options; + + /** + * Class constructor. + * + * @param array $options An optional array of configuration options + */ + public function __construct(array $options = []) + { + $resolver = new OptionsResolver(); + + $this->configureOptions($resolver); + + $this->options = $resolver->resolve($options); + + if (!empty($this->options['only']) && !empty($this->options['except'])) { + throw new InvalidOptionsException('You can configure only one of "only" and "except" options.'); + } + } + + /** + * {@inheritdoc} + */ + public function process(Event $event) + { + $request = $event->getRequest(); + + if (isset($request['cookies'])) { + $cookiesToSanitize = array_keys($request['cookies']); + + if (!empty($this->options['only'])) { + $cookiesToSanitize = $this->options['only']; + } + + if (!empty($this->options['except'])) { + $cookiesToSanitize = array_diff($cookiesToSanitize, $this->options['except']); + } + + foreach ($request['cookies'] as $name => $value) { + if (!in_array($name, $cookiesToSanitize)) { + continue; + } + + $request['cookies'][$name] = self::STRING_MASK; + } + } + + unset($request['headers']['cookie']); + + return $event->withRequest($request); + } + + /** + * Configures the options for this processor. + * + * @param OptionsResolver $resolver The resolver for the options + */ + private function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'only' => [], + 'except' => [], + ]); + + $resolver->setAllowedTypes('only', 'array'); + $resolver->setAllowedTypes('except', 'array'); + } +} diff --git a/tests/Middleware/RequestInterfaceMiddlewareTest.php b/tests/Middleware/RequestInterfaceMiddlewareTest.php index 9c9d270a2..a4e74875a 100644 --- a/tests/Middleware/RequestInterfaceMiddlewareTest.php +++ b/tests/Middleware/RequestInterfaceMiddlewareTest.php @@ -50,6 +50,7 @@ public function testInvoke($requestData, $expectedValue) $request = new ServerRequest(); $request = $request->withUri(new Uri($requestData['uri'])); $request = $request->withMethod($requestData['method']); + $request = $request->withCookieParams($requestData['cookies']); foreach ($requestData['headers'] as $name => $value) { $request = $request->withHeader($name, $value); @@ -76,11 +77,17 @@ public function invokeDataProvider() [ 'uri' => 'http://www.example.com/foo', 'method' => 'GET', + 'cookies' => [ + 'foo' => 'bar', + ], 'headers' => [], ], [ 'url' => 'http://www.example.com/foo', 'method' => 'GET', + 'cookies' => [ + 'foo' => 'bar', + ], 'headers' => [ 'Host' => ['www.example.com'], ], @@ -90,6 +97,7 @@ public function invokeDataProvider() [ 'uri' => 'http://www.example.com/foo?foo=bar&bar=baz', 'method' => 'GET', + 'cookies' => [], 'headers' => [ 'Host' => ['www.example.com'], 'REMOTE_ADDR' => ['127.0.0.1'], @@ -99,6 +107,7 @@ public function invokeDataProvider() 'url' => 'http://www.example.com/foo?foo=bar&bar=baz', 'method' => 'GET', 'query_string' => 'foo=bar&bar=baz', + 'cookies' => [], 'headers' => [ 'Host' => ['www.example.com'], 'REMOTE_ADDR' => ['127.0.0.1'], diff --git a/tests/Processor/RemoveCookiesProcessorTest.php b/tests/Processor/RemoveCookiesProcessorTest.php deleted file mode 100644 index e43520fd8..000000000 --- a/tests/Processor/RemoveCookiesProcessorTest.php +++ /dev/null @@ -1,74 +0,0 @@ -client = ClientBuilder::create()->getClient(); - $this->processor = new RemoveCookiesProcessor(); - } - - /** - * @dataProvider processDataProvider - */ - public function testProcess($inputData, $expectedData) - { - $event = new Event($this->client->getConfig()); - $event = $event->withRequest($inputData); - - $event = $this->processor->process($event); - - $this->assertArraySubset($expectedData, $event->getRequest()); - } - - public function processDataProvider() - { - return [ - [ - [ - 'foo' => 'bar', - 'cookies' => 'baz', - 'headers' => [ - 'cookie' => 'bar', - 'another-header' => 'foo', - ], - ], - [ - 'foo' => 'bar', - 'cookies' => RemoveCookiesProcessor::STRING_MASK, - 'headers' => [ - 'cookie' => RemoveCookiesProcessor::STRING_MASK, - 'another-header' => 'foo', - ], - ], - ], - ]; - } -} diff --git a/tests/Processor/SanitizeCookiesProcessorTest.php b/tests/Processor/SanitizeCookiesProcessorTest.php new file mode 100644 index 000000000..eb9fafba6 --- /dev/null +++ b/tests/Processor/SanitizeCookiesProcessorTest.php @@ -0,0 +1,119 @@ +client = ClientBuilder::create()->getClient(); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage You can configure only one of "only" and "except" options. + */ + public function testConstructorThrowsIfBothOnlyAndExceptOptionsAreSet() + { + new SanitizeCookiesProcessor([ + 'only' => ['foo'], + 'except' => ['bar'], + ]); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($options, $expectedData) + { + $event = new Event($this->client->getConfig()); + $event = $event->withRequest([ + 'foo' => 'bar', + 'cookies' => [ + 'foo' => 'bar', + 'bar' => 'foo', + ], + 'headers' => [ + 'cookie' => 'bar', + 'another-header' => 'foo', + ], + ]); + + $processor = new SanitizeCookiesProcessor($options); + $event = $processor->process($event); + + $requestData = $event->getRequest(); + + $this->assertArraySubset($expectedData, $requestData); + $this->assertArrayNotHasKey('cookie', $requestData['headers']); + } + + public function processDataProvider() + { + return [ + [ + [], + [ + 'foo' => 'bar', + 'cookies' => [ + 'foo' => SanitizeCookiesProcessor::STRING_MASK, + 'bar' => SanitizeCookiesProcessor::STRING_MASK, + ], + 'headers' => [ + 'another-header' => 'foo', + ], + ], + ], + [ + [ + 'only' => ['foo'], + ], + [ + 'foo' => 'bar', + 'cookies' => [ + 'foo' => SanitizeCookiesProcessor::STRING_MASK, + 'bar' => 'foo', + ], + 'headers' => [ + 'another-header' => 'foo', + ], + ], + ], + [ + [ + 'except' => ['foo'], + ], + [ + 'foo' => 'bar', + 'cookies' => [ + 'foo' => 'bar', + 'bar' => SanitizeCookiesProcessor::STRING_MASK, + ], + 'headers' => [ + 'another-header' => 'foo', + ], + ], + ], + ]; + } +} From 7ba4d7f2250544168162ba106a4ea18529c9fa7e Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 6 Feb 2018 22:41:12 +0100 Subject: [PATCH 0323/1161] Implement transport mechanisms and spooling support (#537) --- UPGRADE-2.0.md | 4 + lib/Raven/Client.php | 198 +----- lib/Raven/ClientBuilder.php | 43 +- lib/Raven/ClientBuilderInterface.php | 10 + lib/Raven/Configuration.php | 88 +-- lib/Raven/Spool/MemorySpool.php | 50 ++ lib/Raven/Spool/SpoolInterface.php | 40 ++ lib/Raven/Transport/HttpTransport.php | 144 +++++ lib/Raven/Transport/NullTransport.php | 30 + lib/Raven/Transport/SpoolTransport.php | 56 ++ lib/Raven/Transport/TransportInterface.php | 32 + tests/ClientBuilderTest.php | 87 ++- tests/ClientTest.php | 696 +++++---------------- tests/ConfigurationTest.php | 7 +- tests/IntegrationTest.php | 74 --- tests/Spool/MemorySpoolTest.php | 69 ++ tests/Transport/HttpTransportTest.php | 224 +++++++ tests/Transport/NullTransportTest.php | 28 + tests/Transport/SpoolTransportTest.php | 53 ++ 19 files changed, 1027 insertions(+), 906 deletions(-) create mode 100644 lib/Raven/Spool/MemorySpool.php create mode 100644 lib/Raven/Spool/SpoolInterface.php create mode 100644 lib/Raven/Transport/HttpTransport.php create mode 100644 lib/Raven/Transport/NullTransport.php create mode 100644 lib/Raven/Transport/SpoolTransport.php create mode 100644 lib/Raven/Transport/TransportInterface.php delete mode 100644 tests/IntegrationTest.php create mode 100644 tests/Spool/MemorySpoolTest.php create mode 100644 tests/Transport/HttpTransportTest.php create mode 100644 tests/Transport/NullTransportTest.php create mode 100644 tests/Transport/SpoolTransportTest.php diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index f93961d99..c246022f3 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -19,11 +19,15 @@ - The `curl_ssl_version` option has been removed. - The `verify_ssl` option has been removed. - The `ca_cert` option has been removed. +- The `proxy` option has been removed in favour of leaving to the user the burden + of configuring the HTTP client options using a custom client. - The `processors` option has been removed in favour of leaving to the user the choice of which processors add or remove using the appropriate methods of the `Client` and `ClientBuilder` classes. - The `processors_options` option has been removed in favour of leaving to the user the burden of adding an already configured processor instance to the client. +- The `transport` option has been removed in favour of setting it using the + client builder. - The `http_client_options` has been added to set the options that applies to the HTTP client chosen by the user as underlying transport method. - The `open_timeout` option has been added to set the maximum number of seconds diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 6cebc3fc1..15f40c99e 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -11,15 +11,9 @@ namespace Raven; -use Http\Client\HttpAsyncClient; -use Http\Message\Encoding\CompressStream; -use Http\Message\RequestFactory; -use Http\Promise\Promise; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Raven\Breadcrumbs\Breadcrumb; use Raven\Breadcrumbs\Recorder; -use Raven\HttpClient\Encoding\Base64EncodingStream; use Raven\Middleware\BreadcrumbInterfaceMiddleware; use Raven\Middleware\ContextInterfaceMiddleware; use Raven\Middleware\ExceptionInterfaceMiddleware; @@ -29,7 +23,7 @@ use Raven\Middleware\UserInterfaceMiddleware; use Raven\Processor\ProcessorInterface; use Raven\Processor\ProcessorRegistry; -use Raven\Util\JSON; +use Raven\Transport\TransportInterface; use Zend\Diactoros\ServerRequestFactory; /** @@ -97,41 +91,21 @@ class Client */ protected $reprSerializer; - /** - * @var array[] - */ - public $pendingEvents = []; - - /** - * @var bool - */ - protected $shutdownFunctionHasBeenSet = false; - /** * @var Configuration The client configuration */ protected $config; /** - * @var HttpAsyncClient The HTTP client - */ - private $httpClient; - - /** - * @var RequestFactory The PSR-7 request factory + * @var TransportInterface The transport */ - private $requestFactory; + private $transport; /** * @var ProcessorRegistry The registry of processors */ private $processorRegistry; - /** - * @var Promise[] The list of pending requests - */ - private $pendingRequests = []; - /** * @var callable The tip of the middleware call stack */ @@ -150,15 +124,13 @@ class Client /** * Constructor. * - * @param Configuration $config The client configuration - * @param HttpAsyncClient $httpClient The HTTP client - * @param RequestFactory $requestFactory The PSR-7 request factory + * @param Configuration $config The client configuration + * @param TransportInterface $transport The transport */ - public function __construct(Configuration $config, HttpAsyncClient $httpClient, RequestFactory $requestFactory) + public function __construct(Configuration $config, TransportInterface $transport) { $this->config = $config; - $this->httpClient = $httpClient; - $this->requestFactory = $requestFactory; + $this->transport = $transport; $this->processorRegistry = new ProcessorRegistry(); $this->context = new Context(); $this->recorder = new Recorder(); @@ -188,20 +160,6 @@ public function __construct(Configuration $config, HttpAsyncClient $httpClient, if ($this->config->shouldInstallDefaultBreadcrumbHandlers()) { $this->registerDefaultBreadcrumbHandlers(); } - - if ($this->config->shouldInstallShutdownHandler()) { - $this->registerShutdownFunction(); - } - } - - /** - * Destruct all objects contain link to this object. - * - * This method can not delete shutdown handler - */ - public function __destruct() - { - $this->sendUnsentErrors(); } /** @@ -355,7 +313,7 @@ public function captureException($exception, array $payload = []) /** * Logs the most recent error (obtained with {@link error_get_last}). * - * @return string + * @return string|null */ public function captureLastError() { @@ -403,14 +361,6 @@ protected function registerDefaultBreadcrumbHandlers() $handler->install(); } - protected function registerShutdownFunction() - { - if (!$this->shutdownFunctionHasBeenSet) { - $this->shutdownFunctionHasBeenSet = true; - register_shutdown_function([$this, 'onShutdown']); - } - } - /** * @return bool * @codeCoverageIgnore @@ -462,76 +412,51 @@ public function capture(array $payload) } $event = $this->callMiddlewareStack($event, static::isHttpRequest() ? ServerRequestFactory::fromGlobals() : null, isset($payload['exception']) ? $payload['exception'] : null, $payload); + $event = $this->sanitize($event); - $payload = $event->toArray(); - - $this->sanitize($payload); - - if (!$this->storeErrorsForBulkSend) { - $this->send($payload); - } else { - $this->pendingEvents[] = $payload; - } + $this->send($event); $this->lastEvent = $event; - return $payload['event_id']; + return str_replace('-', '', $event->getId()->toString()); } - public function sanitize(&$data) + public function sanitize(Event $event) { // attempt to sanitize any user provided data - if (!empty($data['request'])) { - $data['request'] = $this->serializer->serialize($data['request']); + $request = $event->getRequest(); + $userContext = $event->getUserContext(); + $extraContext = $event->getExtraContext(); + $tagsContext = $event->getTagsContext(); + + if (!empty($request)) { + $event = $event->withRequest($this->serializer->serialize($request)); } - if (!empty($data['user'])) { - $data['user'] = $this->serializer->serialize($data['user'], 3); + if (!empty($userContext)) { + $event = $event->withUserContext($this->serializer->serialize($userContext, 3)); } - if (!empty($data['extra'])) { - $data['extra'] = $this->serializer->serialize($data['extra']); + if (!empty($extraContext)) { + $event = $event->withExtraContext($this->serializer->serialize($extraContext)); } - if (!empty($data['tags'])) { - foreach ($data['tags'] as $key => $value) { - $data['tags'][$key] = @(string) $value; + if (!empty($tagsContext)) { + foreach ($tagsContext as $key => $value) { + $tagsContext[$key] = @(string) $value; } - } - if (!empty($data['contexts'])) { - $data['contexts'] = $this->serializer->serialize($data['contexts'], 5); - } - } - - public function sendUnsentErrors() - { - foreach ($this->pendingEvents as $data) { - $this->send($data); - } - - $this->pendingEvents = []; - if ($this->storeErrorsForBulkSend) { - //in case an error occurs after this is called, on shutdown, send any new errors. - $this->storeErrorsForBulkSend = !defined('RAVEN_CLIENT_END_REACHED'); + $event = $event->withTagsContext($tagsContext); } - foreach ($this->pendingRequests as $pendingRequest) { - $pendingRequest->wait(); - } + return $event; } /** * Sends the given event to the Sentry server. * - * @param array $data Associative array of data to log + * @param Event $event The event to send */ - public function send(&$data) + public function send(Event $event) { - if (!$this->config->shouldCapture($data) || !$this->config->getServer()) { - return; - } - - if ($this->config->getTransport()) { - call_user_func($this->getConfig()->getTransport(), $this, $data); - + if (!$this->config->shouldCapture($event)) { return; } @@ -540,40 +465,7 @@ public function send(&$data) return; } - $request = $this->requestFactory->createRequest( - 'POST', - sprintf('api/%d/store/', $this->getConfig()->getProjectId()), - ['Content-Type' => $this->isEncodingCompressed() ? 'application/octet-stream' : 'application/json'], - JSON::encode($data) - ); - - if ($this->isEncodingCompressed()) { - $request = $request->withBody( - new Base64EncodingStream( - new CompressStream($request->getBody()) - ) - ); - } - - $promise = $this->httpClient->sendAsyncRequest($request); - - // This function is defined in-line so it doesn't show up for - // type-hinting on classes that implement this trait. - $cleanupPromiseCallback = function (ResponseInterface $response) use ($promise) { - $index = array_search($promise, $this->pendingRequests, true); - - if (false === $index) { - return $response; - } - - unset($this->pendingRequests[$index]); - - return $response; - }; - - $promise->then($cleanupPromiseCallback, $cleanupPromiseCallback); - - $this->pendingRequests[] = $promise; + $this->transport->send($event); } /** @@ -624,14 +516,6 @@ public function registerSeverityMap($map) $this->severityMap = $map; } - public function onShutdown() - { - if (!defined('RAVEN_CLIENT_END_REACHED')) { - define('RAVEN_CLIENT_END_REACHED', true); - } - $this->sendUnsentErrors(); - } - /** * @return Context */ @@ -640,30 +524,12 @@ public function getContext() return $this->context; } - /** - * @return bool - */ - public function getShutdownFunctionHasBeenSet() - { - return $this->shutdownFunctionHasBeenSet; - } - public function setAllObjectSerialize($value) { $this->serializer->setAllObjectSerialize($value); $this->reprSerializer->setAllObjectSerialize($value); } - /** - * Checks whether the encoding is compressed. - * - * @return bool - */ - private function isEncodingCompressed() - { - return 'gzip' === $this->config->getEncoding(); - } - /** * Calls the middleware stack. * diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index c435495cb..7009b35ac 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -30,6 +30,9 @@ use Raven\Processor\SanitizeCookiesProcessor; use Raven\Processor\SanitizeDataProcessor; use Raven\Processor\SanitizeHttpHeadersProcessor; +use Raven\Transport\HttpTransport; +use Raven\Transport\NullTransport; +use Raven\Transport\TransportInterface; /** * The default implementation of {@link ClientBuilderInterface}. @@ -48,8 +51,6 @@ * @method setSampleRate(float $sampleRate) * @method bool shouldInstallDefaultBreadcrumbHandlers() * @method setInstallDefaultBreadcrumbHandlers($installDefaultBreadcrumbHandlers) - * @method bool shouldInstallShutdownHandler() - * @method setInstallShutdownHandler(bool $installShutdownHandler) * @method string getMbDetectOrder() * @method setMbDetectOrder(string $detectOrder) * @method bool getAutoLogStacks() @@ -70,8 +71,6 @@ * @method setProjectRoot(string $path) * @method string getLogger() * @method setLogger(string $logger) - * @method string getProxy() - * @method setProxy(string $proxy) * @method string getRelease() * @method setRelease(string $release) * @method string getServer() @@ -97,6 +96,11 @@ final class ClientBuilder implements ClientBuilderInterface */ private $messageFactory; + /** + * @var TransportInterface The transport + */ + private $transport; + /** * @var HttpAsyncClient The HTTP client */ @@ -151,6 +155,16 @@ public function setMessageFactory(MessageFactory $messageFactory) return $this; } + /** + * {@inheritdoc} + */ + public function setTransport(TransportInterface $transport) + { + $this->transport = $transport; + + return $this; + } + /** * {@inheritdoc} */ @@ -229,8 +243,9 @@ public function getClient() $this->messageFactory = $this->messageFactory ?: MessageFactoryDiscovery::find(); $this->uriFactory = $this->uriFactory ?: UriFactoryDiscovery::find(); $this->httpClient = $this->httpClient ?: HttpAsyncClientDiscovery::find(); + $this->transport = $this->createTransportInstance(); - $client = new Client($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); + $client = new Client($this->configuration, $this->transport); foreach ($this->processors as $value) { $client->addProcessor($value[0], $value[1]); @@ -279,6 +294,24 @@ private function createHttpClientInstance() return new PluginClient($this->httpClient, $this->httpClientPlugins); } + /** + * Creates a new instance of the transport mechanism. + * + * @return TransportInterface + */ + private function createTransportInstance() + { + if (null !== $this->transport) { + return $this->transport; + } + + if (null !== $this->configuration->getServer()) { + return new HttpTransport($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); + } + + return new NullTransport(); + } + /** * Returns a list of processors that are enabled by default. * diff --git a/lib/Raven/ClientBuilderInterface.php b/lib/Raven/ClientBuilderInterface.php index 5c0cfd439..a4d476f41 100644 --- a/lib/Raven/ClientBuilderInterface.php +++ b/lib/Raven/ClientBuilderInterface.php @@ -16,6 +16,7 @@ use Http\Message\MessageFactory; use Http\Message\UriFactory; use Raven\Processor\ProcessorInterface; +use Raven\Transport\TransportInterface; /** * A configurable builder for Client objects. @@ -51,6 +52,15 @@ public function setUriFactory(UriFactory $uriFactory); */ public function setMessageFactory(MessageFactory $messageFactory); + /** + * Sets the transport that will be used to send events. + * + * @param TransportInterface $transport The transport + * + * @return $this + */ + public function setTransport(TransportInterface $transport); + /** * Sets the HTTP client. * diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index c0040a676..f695346d3 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -201,28 +201,6 @@ public function setInstallDefaultBreadcrumbHandlers($installDefaultBreadcrumbHan $this->options = $this->resolver->resolve($options); } - /** - * Gets whether the shutdown hundler should be installed. - * - * @return bool - */ - public function shouldInstallShutdownHandler() - { - return $this->options['install_shutdown_handler']; - } - - /** - * Sets whether the shutdown hundler should be installed. - * - * @param bool $installShutdownHandler Flag indicating if the shutdown handler should be installed - */ - public function setInstallShutdownHandler($installShutdownHandler) - { - $options = array_merge($this->options, ['install_shutdown_handler' => $installShutdownHandler]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the character encoding detection order. * @@ -444,32 +422,6 @@ public function setExcludedProjectPaths(array $paths) $this->options = $this->resolver->resolve($options); } - /** - * Gets the custom transport set to override how Sentry events are sent - * upstream. - * - * @return callable|null - */ - public function getTransport() - { - return $this->options['transport']; - } - - /** - * Set a custom transport to override how Sentry events are sent upstream. - * The bound function will be called with `$client` and `$data` arguments - * and is responsible for encoding the data, authenticating, and sending - * the data to the upstream Sentry server. - * - * @param callable|null $transport The callable - */ - public function setTransport(callable $transport = null) - { - $options = array_merge($this->options, ['transport' => $transport]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the project ID number to send to the Sentry server. * @@ -544,28 +496,6 @@ public function setLogger($logger) $this->options = $this->resolver->resolve($options); } - /** - * Gets the proxy information to pass to the transport adapter. - * - * @return array - */ - public function getProxy() - { - return $this->options['proxy']; - } - - /** - * Sets the proxy information to pass to the transport adapter. - * - * @param string $proxy The proxy information - */ - public function setProxy($proxy) - { - $options = array_merge($this->options, ['proxy' => $proxy]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the release tag to be passed with every event sent to Sentry. * @@ -621,14 +551,14 @@ public function setServerName($serverName) } /** - * Checks whether all events or a specific exception or event (if provided) - * are allowed to be captured. + * Checks whether all events or a specific event (if provided) are allowed + * to be captured. * - * @param object|\Exception $value An optional event or exception to test + * @param Event|null $event The event object * * @return bool */ - public function shouldCapture(&$value = null) + public function shouldCapture(Event $event = null) { $result = true; @@ -636,8 +566,8 @@ public function shouldCapture(&$value = null) $result = false; } - if (null !== $this->options['should_capture'] && null !== $value) { - $result = $result && $this->options['should_capture']($value); + if (null !== $this->options['should_capture'] && null !== $event) { + $result = $result && $this->options['should_capture']($event); } return $result; @@ -714,7 +644,6 @@ private function configureOptions(OptionsResolver $resolver) 'serialize_all_object' => false, 'sample_rate' => 1, 'install_default_breadcrumb_handlers' => true, - 'install_shutdown_handler' => true, 'mb_detect_order' => null, 'auto_log_stacks' => true, 'context_lines' => 3, @@ -724,10 +653,8 @@ private function configureOptions(OptionsResolver $resolver) 'excluded_loggers' => [], 'excluded_exceptions' => [], 'excluded_app_paths' => [], - 'transport' => null, 'project_root' => null, 'logger' => 'php', - 'proxy' => null, 'release' => null, 'server' => isset($_SERVER['SENTRY_DSN']) ? $_SERVER['SENTRY_DSN'] : null, 'server_name' => gethostname(), @@ -742,7 +669,6 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('serialize_all_object', 'bool'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('install_default_breadcrumb_handlers', 'bool'); - $resolver->setAllowedTypes('install_shutdown_handler', 'bool'); $resolver->setAllowedTypes('mb_detect_order', ['null', 'array']); $resolver->setAllowedTypes('auto_log_stacks', 'bool'); $resolver->setAllowedTypes('context_lines', 'int'); @@ -752,10 +678,8 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('excluded_loggers', 'array'); $resolver->setAllowedTypes('excluded_exceptions', 'array'); $resolver->setAllowedTypes('excluded_app_paths', 'array'); - $resolver->setAllowedTypes('transport', ['null', 'callable']); $resolver->setAllowedTypes('project_root', ['null', 'string']); $resolver->setAllowedTypes('logger', 'string'); - $resolver->setAllowedTypes('proxy', ['null', 'string']); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('server', ['null', 'string']); $resolver->setAllowedTypes('server_name', 'string'); diff --git a/lib/Raven/Spool/MemorySpool.php b/lib/Raven/Spool/MemorySpool.php new file mode 100644 index 000000000..5327dd4a7 --- /dev/null +++ b/lib/Raven/Spool/MemorySpool.php @@ -0,0 +1,50 @@ + + */ +final class MemorySpool implements SpoolInterface +{ + /** + * @var Event[] List of enqueued events + */ + private $events = []; + + /** + * {@inheritdoc} + */ + public function queueEvent(Event $event) + { + $this->events[] = $event; + } + + /** + * {@inheritdoc} + */ + public function flushQueue(TransportInterface $transport) + { + if (empty($this->events)) { + return; + } + + while ($event = array_pop($this->events)) { + $transport->send($event); + } + } +} diff --git a/lib/Raven/Spool/SpoolInterface.php b/lib/Raven/Spool/SpoolInterface.php new file mode 100644 index 000000000..fff6bd7b4 --- /dev/null +++ b/lib/Raven/Spool/SpoolInterface.php @@ -0,0 +1,40 @@ + + */ +interface SpoolInterface +{ + /** + * Queues an event. + * + * @param Event $event The event to store + * + * @return bool Whether the operation has succeeded + */ + public function queueEvent(Event $event); + + /** + * Sends the events that are in the queue using the given transport. + * + * @param TransportInterface $transport The transport instance + */ + public function flushQueue(TransportInterface $transport); +} diff --git a/lib/Raven/Transport/HttpTransport.php b/lib/Raven/Transport/HttpTransport.php new file mode 100644 index 000000000..f1b46b00b --- /dev/null +++ b/lib/Raven/Transport/HttpTransport.php @@ -0,0 +1,144 @@ + + */ +final class HttpTransport implements TransportInterface +{ + /** + * @var Configuration The Raven client configuration + */ + private $config; + + /** + * @var HttpAsyncClient The HTTP client + */ + private $httpClient; + + /** + * @var RequestFactory The PSR-7 request factory + */ + private $requestFactory; + + /** + * @var Promise[] The list of pending requests + */ + private $pendingRequests = []; + + /** + * Constructor. + * + * @param Configuration $config The Raven client configuration + * @param HttpAsyncClient $httpClient The HTTP client + * @param RequestFactory $requestFactory The PSR-7 request factory + */ + public function __construct(Configuration $config, HttpAsyncClient $httpClient, RequestFactory $requestFactory) + { + $this->config = $config; + $this->httpClient = $httpClient; + $this->requestFactory = $requestFactory; + + register_shutdown_function(function () { + // When the library will support PHP 7.1+ only this closure can be + // replaced with a simple call to \Closure::fromCallable + $this->cleanupPendingRequests(); + }); + } + + /** + * Destructor. Ensures that all pending requests ends before destroying this + * object instance. + */ + public function __destruct() + { + $this->cleanupPendingRequests(); + } + + /** + * {@inheritdoc} + */ + public function send(Event $event) + { + $request = $this->requestFactory->createRequest( + 'POST', + sprintf('api/%d/store/', $this->config->getProjectId()), + ['Content-Type' => $this->isEncodingCompressed() ? 'application/octet-stream' : 'application/json'], + JSON::encode($event) + ); + + if ($this->isEncodingCompressed()) { + $request = $request->withBody( + new Base64EncodingStream( + new CompressStream($request->getBody()) + ) + ); + } + + $promise = $this->httpClient->sendAsyncRequest($request); + + // This function is defined in-line so it doesn't show up for type-hinting + $cleanupPromiseCallback = function ($responseOrException) use ($promise) { + $index = array_search($promise, $this->pendingRequests, true); + + if (false !== $index) { + unset($this->pendingRequests[$index]); + } + + return $responseOrException; + }; + + $promise->then($cleanupPromiseCallback, $cleanupPromiseCallback); + + $this->pendingRequests[] = $promise; + + return true; + } + + /** + * Checks whether the encoding is compressed. + * + * @return bool + */ + private function isEncodingCompressed() + { + return 'gzip' === $this->config->getEncoding(); + } + + /** + * Cleanups the pending requests by forcing them to be sent. Any error that + * occurs will be ignored. + */ + private function cleanupPendingRequests() + { + foreach ($this->pendingRequests as $pendingRequest) { + try { + $pendingRequest->wait(); + } catch (\Exception $exception) { + // Do nothing because an exception thrown from a destructor + // can't be catched in PHP (see http://php.net/manual/en/language.oop5.decon.php#language.oop5.decon.destructor) + } + } + } +} diff --git a/lib/Raven/Transport/NullTransport.php b/lib/Raven/Transport/NullTransport.php new file mode 100644 index 000000000..f4126fbc6 --- /dev/null +++ b/lib/Raven/Transport/NullTransport.php @@ -0,0 +1,30 @@ + + */ +class NullTransport implements TransportInterface +{ + /** + * {@inheritdoc} + */ + public function send(Event $event) + { + return true; + } +} diff --git a/lib/Raven/Transport/SpoolTransport.php b/lib/Raven/Transport/SpoolTransport.php new file mode 100644 index 000000000..16de536ea --- /dev/null +++ b/lib/Raven/Transport/SpoolTransport.php @@ -0,0 +1,56 @@ + + */ +final class SpoolTransport implements TransportInterface +{ + /** + * @var SpoolInterface The spool instance + */ + private $spool; + + /** + * Constructor. + * + * @param SpoolInterface $spool The spool instance + */ + public function __construct(SpoolInterface $spool) + { + $this->spool = $spool; + } + + /** + * Gets the spool. + * + * @return SpoolInterface + */ + public function getSpool() + { + return $this->spool; + } + + /** + * {@inheritdoc} + */ + public function send(Event $event) + { + return $this->spool->queueEvent($event); + } +} diff --git a/lib/Raven/Transport/TransportInterface.php b/lib/Raven/Transport/TransportInterface.php new file mode 100644 index 000000000..c7bd093c6 --- /dev/null +++ b/lib/Raven/Transport/TransportInterface.php @@ -0,0 +1,32 @@ + + */ +interface TransportInterface +{ + /** + * Sends the given event. + * + * @param Event $event The event + * + * @return bool Whether the event was sent successfully or not + */ + public function send(Event $event); +} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index c7b21929d..cf3f12574 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -12,6 +12,7 @@ namespace Raven\Tests; use Http\Client\Common\Plugin; +use Http\Client\Common\PluginClient; use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory; use Http\Message\UriFactory; @@ -21,6 +22,9 @@ use Raven\ClientBuilder; use Raven\Configuration; use Raven\Processor\ProcessorInterface; +use Raven\Transport\HttpTransport; +use Raven\Transport\NullTransport; +use Raven\Transport\TransportInterface; class ClientBuilderTest extends TestCase { @@ -31,13 +35,30 @@ public function testCreate() $this->assertInstanceOf(ClientBuilder::class, $clientBuilder); } + public function testHttpTransportIsUsedWhenServeIsConfigured() + { + $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + + $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); + + $this->assertInstanceOf(HttpTransport::class, $transport); + } + + public function testNullTransportIsUsedWhenNoServerIsConfigured() + { + $clientBuilder = new ClientBuilder(); + + $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); + + $this->assertInstanceOf(NullTransport::class, $transport); + } + public function testSetUriFactory() { /** @var UriFactory|\PHPUnit_Framework_MockObject_MockObject $uriFactory */ - $uriFactory = $this->getMockBuilder(UriFactory::class) - ->getMock(); + $uriFactory = $this->createMock(UriFactory::class); - $clientBuilder = new ClientBuilder(); + $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setUriFactory($uriFactory); $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); @@ -46,40 +67,49 @@ public function testSetUriFactory() public function testSetMessageFactory() { /** @var MessageFactory|\PHPUnit_Framework_MockObject_MockObject $messageFactory */ - $messageFactory = $this->getMockBuilder(MessageFactory::class) - ->getMock(); + $messageFactory = $this->createMock(MessageFactory::class); - $clientBuilder = new ClientBuilder(); + $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setMessageFactory($messageFactory); $this->assertAttributeSame($messageFactory, 'messageFactory', $clientBuilder); - $client = $clientBuilder->getClient(); + $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); - $this->assertAttributeSame($messageFactory, 'requestFactory', $client); + $this->assertAttributeSame($messageFactory, 'requestFactory', $transport); + } + + public function testSetTransport() + { + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + + $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder->setTransport($transport); + + $this->assertAttributeSame($transport, 'transport', $clientBuilder); + $this->assertAttributeSame($transport, 'transport', $clientBuilder->getClient()); } public function testSetHttpClient() { /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); + $httpClient = $this->createMock(HttpAsyncClient::class); - $clientBuilder = new ClientBuilder(); + $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setHttpClient($httpClient); $this->assertAttributeSame($httpClient, 'httpClient', $clientBuilder); - $client = $this->getObjectAttribute($clientBuilder->getClient(), 'httpClient'); + $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); - $this->assertAttributeSame($httpClient, 'client', $client); + $this->assertAttributeSame($httpClient, 'client', $this->getObjectAttribute($transport, 'httpClient')); } public function testAddHttpClientPlugin() { /** @var Plugin|\PHPUnit_Framework_MockObject_MockObject $plugin */ - $plugin = $this->getMockBuilder(Plugin::class) - ->getMock(); + $plugin = $this->createMock(Plugin::class); $clientBuilder = new ClientBuilder(); $clientBuilder->addHttpClientPlugin($plugin); @@ -133,24 +163,22 @@ public function testRemoveProcessor() $this->assertAttributeNotContains([$processor, -10], 'processors', $clientBuilder); } - public function testGetProcessors() + public function testGetClient() { - /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ - $processor = $this->createMock(ProcessorInterface::class); + $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + $client = $clientBuilder->getClient(); - $clientBuilder = new ClientBuilder(); - $clientBuilder->addProcessor($processor, -10); - $clientBuilder->addProcessor($processor, 10); + $this->assertInstanceOf(Client::class, $client); + $this->assertAttributeInstanceOf(HttpTransport::class, 'transport', $client); - $this->assertContains([$processor, -10], $clientBuilder->getProcessors()); - $this->assertContains([$processor, 10], $clientBuilder->getProcessors()); - } + $transport = $this->getObjectAttribute($client, 'transport'); - public function testGetClient() - { - $clientBuilder = new ClientBuilder(); + $this->assertAttributeSame($this->getObjectAttribute($clientBuilder, 'messageFactory'), 'requestFactory', $transport); + $this->assertAttributeInstanceOf(PluginClient::class, 'httpClient', $transport); + + $httpClientPlugin = $this->getObjectAttribute($transport, 'httpClient'); - $this->assertInstanceOf(Client::class, $clientBuilder->getClient()); + $this->assertAttributeSame($this->getObjectAttribute($clientBuilder, 'httpClient'), 'client', $httpClientPlugin); } /** @@ -193,7 +221,6 @@ public function optionsDataProvider() ['setSerializeAllObjects', false], ['setSampleRate', 0.5], ['setInstallDefaultBreadcrumbHandlers', false], - ['setInstallShutdownHandler', false], ['setMbDetectOrder', ['foo', 'bar']], ['setAutoLogStacks', false], ['setContextLines', 0], @@ -203,10 +230,8 @@ public function optionsDataProvider() ['setExcludedLoggers', ['foo', 'bar']], ['setExcludedExceptions', ['foo', 'bar']], ['setExcludedProjectPaths', ['foo', 'bar']], - ['setTransport', null], ['setProjectRoot', 'foo'], ['setLogger', 'bar'], - ['setProxy', 'foo'], ['setRelease', 'dev'], ['setServerName', 'example.com'], ['setTags', ['foo', 'bar']], diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 9afdf4087..885518a14 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,12 +10,8 @@ namespace Raven\Tests; -use Http\Client\HttpAsyncClient; -use Http\Message\RequestFactory; use Http\Mock\Client as MockClient; -use Http\Promise\Promise; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ResponseInterface; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidFactory; use Raven\Breadcrumbs\ErrorHandler; @@ -23,18 +19,7 @@ use Raven\ClientBuilder; use Raven\Configuration; use Raven\Event; - -function simple_function($a = null, $b = null, $c = null) -{ - throw new \RuntimeException('This simple function should fail before reaching this line!'); -} - -function invalid_encoding() -{ - $fp = fopen(__DIR__ . '/data/binary', 'r'); - simple_function(fread($fp, 64)); - fclose($fp); -} +use Raven\Transport\TransportInterface; // XXX: Is there a better way to stub the client? class Dummy_Raven_Client extends \Raven\Client @@ -48,13 +33,13 @@ public function getSentEvents() return $this->__sent_events; } - public function send(&$data) + public function send(Event $event) { - if (!$this->config->shouldCapture($data)) { + if (!$this->config->shouldCapture($event)) { return; } - $this->__sent_events[] = $data; + $this->__sent_events[] = $event; } public static function isHttpRequest() @@ -74,117 +59,8 @@ public function registerShutdownFunction() } } -class Dummy_Raven_Client_No_Http extends Dummy_Raven_Client -{ - /** - * @return bool - */ - public static function isHttpRequest() - { - return false; - } -} - -class Dummy_Raven_Client_With_Sync_Override extends \Raven\Client -{ - private static $_test_data = null; - - public static function get_test_data() - { - if (null === self::$_test_data) { - self::$_test_data = ''; - for ($i = 0; $i < 128; ++$i) { - self::$_test_data .= chr(mt_rand(ord('a'), ord('z'))); - } - } - - return self::$_test_data; - } - - public static function test_filename() - { - return sys_get_temp_dir() . '/clientraven.tmp'; - } -} - class ClientTest extends TestCase { - protected static $_folder = null; - - public static function setUpBeforeClass() - { - parent::setUpBeforeClass(); - self::$_folder = sys_get_temp_dir() . '/sentry_server_' . microtime(true); - mkdir(self::$_folder); - } - - public static function tearDownAfterClass() - { - exec(sprintf('rm -rf %s', escapeshellarg(self::$_folder))); - } - - public function tearDown() - { - parent::tearDown(); - if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { - unlink(Dummy_Raven_Client_With_Sync_Override::test_filename()); - } - } - - private function create_exception() - { - try { - throw new \Exception('Foo bar'); - } catch (\Exception $ex) { - return $ex; - } - } - - public function testDestructor() - { - $waitCalled = false; - - /** @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject $response */ - $response = $this->getMockBuilder(ResponseInterface::class) - ->getMock(); - - $promise = new PromiseMock($response); - $promise->then(function (ResponseInterface $response) use (&$waitCalled) { - $waitCalled = true; - - return $response; - }); - - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - $httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn($promise); - - $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1']) - ->setHttpClient($httpClient) - ->getClient(); - - $data = ['foo']; - - $this->assertAttributeEmpty('pendingRequests', $client); - - $client->send($data); - - $this->assertAttributeNotEmpty('pendingRequests', $client); - $this->assertFalse($waitCalled); - - // The destructor should never be called explicitly because it simply - // does nothing in PHP, but it's the only way to assert that the - // $waitCalled variable is true because PHPUnit maintains references - // to all mocks instances - $client->__destruct(); - - $this->assertTrue($waitCalled); - } - public function testMiddlewareStackIsSeeded() { $client = ClientBuilder::create()->getClient(); @@ -237,58 +113,118 @@ public function testMiddlewareThrowsWhenBadValueIsReturned() $client->capture([]); } - public function testCaptureExceptionHandlesOptionsAsSecondArg() + public function testCaptureMessage() { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; + /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(Client::class) + ->disableOriginalConstructor() + ->setMethodsExcept(['captureMessage']) + ->getMock(); - $client->captureException($this->create_exception(), ['culprit' => 'test']); + $client->expects($this->once()) + ->method('capture') + ->with([ + 'message' => 'foo', + 'message_params' => ['bar'], + 'foo' => 'bar', + ]); - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('test', $client->pendingEvents[0]['culprit']); + $client->captureMessage('foo', ['bar'], ['foo' => 'bar']); } - public function testCapture() + public function testCaptureException() { - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; + $exception = new \Exception(); - $client->capture([ - 'level' => Client::LEVEL_DEBUG, - 'logger' => 'foo', - 'tags_context' => ['foo', 'bar'], - 'extra_context' => ['foo' => 'bar'], - 'user_context' => ['bar' => 'foo'], - ]); + /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(Client::class) + ->disableOriginalConstructor() + ->setMethodsExcept(['captureException']) + ->getMock(); - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals(Client::LEVEL_DEBUG, $client->pendingEvents[0]['level']); - $this->assertEquals('foo', $client->pendingEvents[0]['logger']); - $this->assertEquals(['foo', 'bar'], $client->pendingEvents[0]['tags']); - $this->assertEquals(['foo' => 'bar'], $client->pendingEvents[0]['extra']); - $this->assertEquals(['bar' => 'foo'], $client->pendingEvents[0]['user']); + $client->expects($this->once()) + ->method('capture') + ->with([ + 'exception' => $exception, + 'foo' => 'bar', + ]); + + $client->captureException(new \Exception(), ['foo' => 'bar']); } public function testCaptureLastError() { - if (function_exists('error_clear_last')) { - error_clear_last(); - } + /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(Client::class) + ->disableOriginalConstructor() + ->setMethodsExcept(['captureLastError']) + ->getMock(); - $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; + $client->expects($this->once()) + ->method('captureException') + ->with($this->logicalAnd( + $this->isInstanceOf(\ErrorException::class), + $this->attributeEqualTo('message', 'foo'), + $this->attributeEqualTo('code', 0), + $this->attributeEqualTo('severity', E_USER_NOTICE), + $this->attributeEqualTo('file', __FILE__), + $this->attributeEqualTo('line', __LINE__ + 3) + )); + + @trigger_error('foo', E_USER_NOTICE); + + $client->captureLastError(); + + error_clear_last(); + } + + public function testCaptureLastErrorDoesNothingWhenThereIsNoError() + { + /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(Client::class) + ->disableOriginalConstructor() + ->setMethodsExcept(['captureException']) + ->getMock(); - $this->assertNull($client->captureLastError()); - $this->assertEmpty($client->pendingEvents); + $client->expects($this->never()) + ->method('capture'); - /* @var $undefined */ - /* @noinspection PhpExpressionResultUnusedInspection */ - @$undefined; + error_clear_last(); $client->captureLastError(); + } + + public function testCapture() + { + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send'); + + $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1']) + ->setTransport($transport) + ->getClient(); + + $inputData = [ + 'culprit' => 'foo bar', + 'level' => Client::LEVEL_DEBUG, + 'logger' => 'foo', + 'tags_context' => ['foo', 'bar'], + 'extra_context' => ['foo' => 'bar'], + 'user_context' => ['bar' => 'foo'], + ]; - $this->assertCount(1, $client->pendingEvents); - $this->assertEquals('Undefined variable: undefined', $client->pendingEvents[0]['exception']['values'][0]['value']); + $eventId = $client->capture($inputData); + + $event = $client->getLastEvent(); + + $this->assertEquals(str_replace('-', '', $event->getId()->toString()), $eventId); + $this->assertEquals($inputData['culprit'], $event->getCulprit()); + $this->assertEquals($inputData['level'], $event->getLevel()); + $this->assertEquals($inputData['logger'], $event->getLogger()); + $this->assertEquals($inputData['tags_context'], $event->getTagsContext()); + $this->assertEquals($inputData['extra_context'], $event->getExtraContext()); + $this->assertEquals($inputData['user_context'], $event->getUserContext()); } public function testGetLastEvent() @@ -296,8 +232,6 @@ public function testGetLastEvent() $lastEvent = null; $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; - $client->addMiddleware(function (Event $event) use (&$lastEvent) { $lastEvent = $event; @@ -323,31 +257,12 @@ public function testGetLastEventId() Uuid::setFactory($uuidFactory); $client = ClientBuilder::create()->getClient(); - $client->storeErrorsForBulkSend = true; $client->capture(['message' => 'test']); - $this->assertEquals('ddbd643a51904ccea6ce3098506f9d33', $client->getLastEventID()); - Uuid::setFactory(new UuidFactory()); - } - - public function testCustomTransport() - { - $events = []; - - $client = ClientBuilder::create([ - 'server' => 'https://public:secret@sentry.example.com/1', - 'install_default_breadcrumb_handlers' => false, - ])->getClient(); - - $client->getConfig()->setTransport(function ($client, $data) use (&$events) { - $events[] = $data; - }); - $client->capture(['message' => 'test', 'event_id' => 'abc']); - - $this->assertCount(1, $events); + $this->assertEquals('ddbd643a51904ccea6ce3098506f9d33', $client->getLastEventID()); } public function testAppPathLinux() @@ -380,140 +295,41 @@ public function testCannotInstallTwice() $client->install(); } - /** - * @dataProvider sendWithEncodingDataProvider - */ - public function testSendWithEncoding($options, $expectedRequest) - { - $httpClient = new MockClient(); - - $client = ClientBuilder::create($options) - ->setHttpClient($httpClient) - ->getClient(); - - $data = ['foo bar']; - - $client->send($data); - - $requests = $httpClient->getRequests(); - - $this->assertCount(1, $requests); - - $stream = $requests[0]->getBody(); - $stream->rewind(); - - $this->assertEquals($expectedRequest['body'], (string) $stream); - $this->assertArraySubset($expectedRequest['headers'], $requests[0]->getHeaders()); - } - - public function sendWithEncodingDataProvider() - { - return [ - [ - [ - 'server' => 'http://public:secret@example.com/1', - 'encoding' => 'json', - ], - [ - 'body' => '["foo bar"]', - 'headers' => [ - 'Content-Type' => ['application/json'], - ], - ], - ], - [ - [ - 'server' => 'http://public:secret@example.com/1', - 'encoding' => 'gzip', - ], - [ - 'body' => 'eJyLVkrLz1dISixSigUAFYQDlg==', - 'headers' => [ - 'Content-Type' => ['application/octet-stream'], - ], - ], - ], - ]; - } - - public function testSendCallback() - { - $config = new Configuration([ - 'should_capture' => function ($data) { - $this->assertEquals('test', $data['message']); - - return false; - }, - ]); - - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); - - $client = new Dummy_Raven_Client($config, $httpClient, $requestFactory); - - $client->captureMessage('test'); - - $this->assertEmpty($client->getSentEvents()); - - $config->setShouldCapture(function ($data) { - $this->assertEquals('test', $data['message']); - - return true; - }); - - $client->captureMessage('test'); - - $this->assertCount(1, $client->getSentEvents()); - - $config->setShouldCapture(function (&$data) { - unset($data['message']); - - return true; - }); - - $client->captureMessage('test'); - - $this->assertCount(2, $client->getSentEvents()); - $this->assertArrayNotHasKey('message', $client->getSentEvents()[1]); - } - public function testSanitizeExtra() { $client = ClientBuilder::create()->getClient(); - $data = ['extra' => [ - 'context' => [ + + $event = new Event($client->getConfig()); + $event = $event->withExtraContext([ + 'foo' => [ 'line' => 1216, 'stack' => [ 1, [2], 3, ], ], - ]]; - $client->sanitize($data); + ]); - $this->assertEquals(['extra' => [ - 'context' => [ + $event = $client->sanitize($event); + + $this->assertEquals([ + 'foo' => [ 'line' => 1216, 'stack' => [ 1, 'Array of length 1', 3, ], ], - ]], $data); + ], $event->getExtraContext()); } public function testSanitizeObjects() { $client = ClientBuilder::create(['serialize_all_object' => true])->getClient(); $clone = ClientBuilder::create()->getClient(); - $data = [ - 'extra' => [ - 'object' => $clone, - ], - ]; + + $event = new Event($client->getConfig()); + $event = $event->withExtraContext([ + 'object' => $clone, + ]); $reflection = new \ReflectionClass($clone); $expected = []; @@ -551,148 +367,67 @@ public function testSanitizeObjects() unset($reflection, $property, $value, $reflection, $clone); ksort($expected); - $client->sanitize($data); - ksort($data['extra']['object']); - foreach ($data['extra']['object'] as $key => &$value) { - if (is_array($value)) { - ksort($value); - } - } + $event = $client->sanitize($event); - $this->assertEquals(['extra' => ['object' => $expected]], $data); + $this->assertEquals(['object' => $expected], $event->getExtraContext()); } public function testSanitizeTags() { $client = ClientBuilder::create()->getClient(); - $data = ['tags' => [ + + $event = new Event($client->getConfig()); + $event = $event->withTagsContext([ 'foo' => 'bar', 'baz' => ['biz'], - ]]; - $client->sanitize($data); + ]); - $this->assertEquals(['tags' => [ + $event = $client->sanitize($event); + + $this->assertEquals([ 'foo' => 'bar', 'baz' => 'Array', - ]], $data); + ], $event->getTagsContext()); } public function testSanitizeUser() { $client = ClientBuilder::create()->getClient(); - $data = ['user' => [ - 'email' => 'foo@example.com', - ]]; - $client->sanitize($data); - $this->assertEquals(['user' => [ + $event = new Event($client->getConfig()); + $event = $event->withUserContext([ 'email' => 'foo@example.com', - ]], $data); + ]); + + $client->sanitize($event); + + $this->assertEquals(['email' => 'foo@example.com'], $event->getUserContext()); } public function testSanitizeRequest() { $client = ClientBuilder::create()->getClient(); - $data = ['request' => [ - 'context' => [ - 'line' => 1216, - 'stack' => [ - 1, [2], 3, - ], - ], - ]]; - $client->sanitize($data); - $this->assertEquals(['request' => [ + $event = new Event($client->getConfig()); + $event = $event->withRequest([ 'context' => [ 'line' => 1216, 'stack' => [ - 1, 'Array of length 1', 3, + 1, [2], 3, ], ], - ]], $data); - } + ]); - public function testSanitizeContexts() - { - $client = ClientBuilder::create()->getClient(); - $data = ['contexts' => [ - 'context' => [ - 'line' => 1216, - 'stack' => [ - 1, [ - 'foo' => 'bar', - 'level4' => [['level5', 'level5 a'], 2], - ], 3, - ], - ], - ]]; - $client->sanitize($data); + $event = $client->sanitize($event); - $this->assertEquals(['contexts' => [ + $this->assertArraySubset([ 'context' => [ 'line' => 1216, 'stack' => [ - 1, [ - 'foo' => 'bar', - 'level4' => ['Array of length 2', 2], - ], 3, + 1, 'Array of length 1', 3, ], ], - ]], $data); - } - - /** - * @covers \Raven\Client::getShutdownFunctionHasBeenSet - */ - public function testGettersAndSetters() - { - $client = ClientBuilder::create()->getClient(); - - $data = [ - ['shutdownFunctionHasBeenSet', null, true], - ['shutdownFunctionHasBeenSet', null, false], - ]; - foreach ($data as &$datum) { - $this->subTestGettersAndSettersDatum($client, $datum); - } - } - - private function subTestGettersAndSettersDatum(\Raven\Client $client, $datum) - { - if (3 == count($datum)) { - list($property_name, $function_name, $value_in) = $datum; - $value_out = $value_in; - } else { - list($property_name, $function_name, $value_in, $value_out) = $datum; - } - if (null === $function_name) { - $function_name = str_replace('_', '', $property_name); - } - - $method_get_name = 'get' . $function_name; - $method_set_name = 'set' . $function_name; - $property = new \ReflectionProperty('\\Raven\\Client', $property_name); - $property->setAccessible(true); - - if (method_exists($client, $method_set_name)) { - $setter_output = $client->$method_set_name($value_in); - if (null !== $setter_output and is_object($setter_output)) { - // chaining call test - $this->assertEquals(spl_object_hash($client), spl_object_hash($setter_output)); - } - $actual_value = $property->getValue($client); - $this->assertMixedValueAndArray($value_out, $actual_value); - } - - if (method_exists($client, $method_get_name)) { - $property->setValue($client, $value_out); - $reflection = new \ReflectionMethod('\\Raven\Client', $method_get_name); - if ($reflection->isPublic()) { - $actual_value = $client->$method_get_name(); - $this->assertMixedValueAndArray($value_out, $actual_value); - } - } + ], $event->getRequest()); } private function assertMixedValueAndArray($expected_value, $actual_value) @@ -828,44 +563,11 @@ public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, return true; } - public function testOnShutdown() - { - $httpClient = new MockClient(); - - $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1']) - ->setHttpClient($httpClient) - ->getClient(); - - $this->assertEquals(0, count($client->pendingEvents)); - $client->pendingEvents[] = ['foo' => 'bar']; - $client->sendUnsentErrors(); - $this->assertCount(1, $httpClient->getRequests()); - $this->assertEquals(0, count($client->pendingEvents)); - - $client->storeErrorsForBulkSend = true; - $client->captureMessage('foobar'); - $this->assertEquals(1, count($client->pendingEvents)); - - // step 3 - $client->onShutdown(); - $this->assertCount(2, $httpClient->getRequests()); - $this->assertEquals(0, count($client->pendingEvents)); - } - public function testSendChecksShouldCaptureOption() { - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); + $shouldCaptureCalled = false; - $httpClient->expects($this->never()) - ->method('sendAsyncRequest'); - - $config = new Configuration([ + $client = ClientBuilder::create([ 'server' => 'http://public:secret@example.com/1', 'install_default_breadcrumb_handlers' => false, 'should_capture' => function () use (&$shouldCaptureCalled) { @@ -873,60 +575,27 @@ public function testSendChecksShouldCaptureOption() return false; }, - ]); - - $client = new Client($config, $httpClient, $requestFactory); - - $data = ['foo' => 'bar']; + ])->getClient(); - $client->send($data); + $client->capture([]); $this->assertTrue($shouldCaptureCalled); } - public function testSendFailsWhenNoServerIsConfigured() - { - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - $httpClient->expects($this->never()) - ->method('sendAsyncRequest'); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); - - $client = new Client(new Configuration(), $httpClient, $requestFactory); - $data = ['foo' => 'bar']; - - $client->send($data); - } - public function test__construct_handlers() { - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->getMockBuilder(HttpAsyncClient::class) - ->getMock(); - - /** @var RequestFactory|\PHPUnit_Framework_MockObject_MockObject $requestFactory */ - $requestFactory = $this->getMockBuilder(RequestFactory::class) - ->getMock(); + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); foreach ([true, false] as $u1) { - foreach ([true, false] as $u2) { - $client = new Dummy_Raven_Client( - new Configuration([ - 'install_default_breadcrumb_handlers' => $u1, - 'install_shutdown_handler' => $u2, - ]), - $httpClient, - $requestFactory - ); - - $this->assertEquals($u1, $client->dummy_breadcrumbs_handlers_has_set); - $this->assertEquals($u2, $client->dummy_shutdown_handlers_has_set); - } + $client = new Dummy_Raven_Client( + new Configuration([ + 'install_default_breadcrumb_handlers' => $u1, + ]), + $transport + ); + + $this->assertEquals($u1, $client->dummy_breadcrumbs_handlers_has_set); } } @@ -1008,62 +677,3 @@ public function testClearBreadcrumb() $this->assertEmpty(iterator_to_array($reflection->getValue($client))); } } - -class PromiseMock implements Promise -{ - private $result; - - private $state; - - private $onFullfilledCallbacks = []; - - private $onRejectedCallbacks = []; - - public function __construct($result, $state = self::FULFILLED) - { - $this->result = $result; - $this->state = $state; - } - - public function then(callable $onFulfilled = null, callable $onRejected = null) - { - if (null !== $onFulfilled) { - $this->onFullfilledCallbacks[] = $onFulfilled; - } - - if (null !== $onRejected) { - $this->onRejectedCallbacks[] = $onRejected; - } - - return $this; - } - - public function getState() - { - return $this->state; - } - - public function wait($unwrap = true) - { - switch ($this->state) { - case self::FULFILLED: - foreach ($this->onFullfilledCallbacks as $onFullfilledCallback) { - $onFullfilledCallback($this->result); - } - - break; - case self::REJECTED: - foreach ($this->onRejectedCallbacks as $onRejectedCallback) { - $onRejectedCallback($this->result); - } - - break; - } - - if ($unwrap) { - return $this->result; - } - - return null; - } -} diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index b35516123..3fc5425da 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Raven\Configuration; +use Raven\Event; class ConfigurationTest extends TestCase { @@ -49,7 +50,6 @@ public function optionsDataProvider() ['serialize_all_object', false, 'getSerializeAllObjects', 'setSerializeAllObjects'], ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], ['install_default_breadcrumb_handlers', false, 'shouldInstallDefaultBreadcrumbHandlers', 'setInstallDefaultBreadcrumbHandlers'], - ['install_shutdown_handler', false, 'shouldInstallShutdownHandler', 'setInstallShutdownHandler'], ['mb_detect_order', null, 'getMbDetectOrder', 'setMbDetectOrder'], ['auto_log_stacks', false, 'getAutoLogStacks', 'setAutoLogStacks'], ['context_lines', 3, 'getContextLines', 'setContextLines'], @@ -61,7 +61,6 @@ public function optionsDataProvider() ['excluded_app_paths', ['foo', 'bar'], 'getExcludedProjectPaths', 'setExcludedProjectPaths'], ['project_root', 'baz', 'getProjectRoot', 'setProjectRoot'], ['logger', 'foo', 'getLogger', 'setLogger'], - ['proxy', 'tcp://localhost:8125', 'getProxy', 'setProxy'], ['release', 'dev', 'getRelease', 'setRelease'], ['server_name', 'foo', 'getServerName', 'setServerName'], ['tags', ['foo', 'bar'], 'getTags', 'setTags'], @@ -190,9 +189,7 @@ public function testShouldCapture() $this->assertTrue($configuration->shouldCapture()); - $data = 'foo'; - - $this->assertFalse($configuration->shouldCapture($data)); + $this->assertFalse($configuration->shouldCapture(new Event($configuration))); } /** diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php deleted file mode 100644 index bccf798b9..000000000 --- a/tests/IntegrationTest.php +++ /dev/null @@ -1,74 +0,0 @@ -__sent_events; - } - - public function send(&$data) - { - if (false === $this->config->shouldCapture($data)) { - // if send_callback returns falsely, end native send - return; - } - $this->__sent_events[] = $data; - } - - public static function isHttpRequest() - { - return true; - } - - // short circuit breadcrumbs - public function registerDefaultBreadcrumbHandlers() - { - } -} - -class IntegrationTest extends TestCase -{ - private function create_chained_exception() - { - try { - throw new \Exception('Foo bar'); - } catch (\Exception $ex) { - try { - throw new \Exception('Child exc', 0, $ex); - } catch (\Exception $ex2) { - return $ex2; - } - } - } - - public function testCaptureSimpleError() - { - $client = ClientBuilder::create(['auto_log_stacks' => true])->getClient(); - $client->storeErrorsForBulkSend = true; - - @mkdir('/no/way'); - - $client->captureLastError(); - - $event = $client->pendingEvents[0]['exception']['values'][0]; - - $this->assertEquals($event['value'], 'mkdir(): No such file or directory'); - $this->assertEquals($event['stacktrace']['frames'][count($event['stacktrace']['frames']) - 1]->getFile(), 'tests/IntegrationTest.php'); - } -} diff --git a/tests/Spool/MemorySpoolTest.php b/tests/Spool/MemorySpoolTest.php new file mode 100644 index 000000000..f2a69d847 --- /dev/null +++ b/tests/Spool/MemorySpoolTest.php @@ -0,0 +1,69 @@ +spool = new MemorySpool(); + } + + public function testQueueEvent() + { + $this->assertAttributeEmpty('events', $this->spool); + + $this->spool->queueEvent(new Event(new Configuration())); + + $this->assertAttributeNotEmpty('events', $this->spool); + } + + public function testFlushQueue() + { + $event1 = new Event(new Configuration()); + $event2 = new Event(new Configuration()); + + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->exactly(2)) + ->method('send') + ->withConsecutive($event1, $event2); + + $this->spool->queueEvent($event1); + $this->spool->queueEvent($event2); + + $this->spool->flushQueue($transport); + + $this->assertAttributeEmpty('events', $this->spool); + } + + public function testFlushQueueWithEmptyQueue() + { + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->never()) + ->method('send'); + + $this->spool->flushQueue($transport); + } +} diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php new file mode 100644 index 000000000..ddced64af --- /dev/null +++ b/tests/Transport/HttpTransportTest.php @@ -0,0 +1,224 @@ +createMock(Promise::class); + $promise->expects($this->once()) + ->method('wait'); + + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willReturn($promise); + + $config = new Configuration(); + $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); + + $transport->send(new Event($config)); + + // In PHP calling the destructor manually does not destroy the object, + // but for testing we will do it anyway because otherwise we could not + // test the cleanup code of the class if not all references to its + // instance are released + $transport->__destruct(); + } + + public function testCleanupPendingRequests() + { + /** @var Promise|\PHPUnit_Framework_MockObject_MockObject $promise1 */ + $promise1 = $this->createMock(Promise::class); + $promise1->expects($this->once()) + ->method('wait') + ->willThrowException(new \Exception()); + + /** @var Promise|\PHPUnit_Framework_MockObject_MockObject $promise2 */ + $promise2 = $this->createMock(Promise::class); + $promise2->expects($this->once()) + ->method('wait'); + + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $httpClient->expects($this->exactly(2)) + ->method('sendAsyncRequest') + ->willReturnOnConsecutiveCalls($promise1, $promise2); + + $config = new Configuration(); + $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); + + $this->assertAttributeEmpty('pendingRequests', $transport); + + $transport->send(new Event($config)); + $transport->send(new Event($config)); + + $this->assertAttributeNotEmpty('pendingRequests', $transport); + + $reflectionMethod = new \ReflectionMethod(HttpTransport::class, 'cleanupPendingRequests'); + $reflectionMethod->setAccessible(true); + $reflectionMethod->invoke($transport); + $reflectionMethod->setAccessible(false); + } + + public function testSendWithoutCompressedEncoding() + { + $config = new Configuration(['encoding' => 'json']); + $event = new Event($config); + + $promise = $this->createMock(Promise::class); + $promise->expects($this->once()) + ->method('wait'); + + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->with($this->callback(function (RequestInterface $request) use ($event) { + $request->getBody()->rewind(); + + return 'application/json' === $request->getHeaderLine('Content-Type') + && JSON::encode($event) === $request->getBody()->getContents(); + })) + ->willReturn($promise); + + $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); + $transport->send($event); + + $reflectionMethod = new \ReflectionMethod(HttpTransport::class, 'cleanupPendingRequests'); + $reflectionMethod->setAccessible(true); + $reflectionMethod->invoke($transport); + $reflectionMethod->setAccessible(false); + } + + public function testSendWithCompressedEncoding() + { + $config = new Configuration(['encoding' => 'gzip']); + $event = new Event($config); + + $promise = $this->createMock(Promise::class); + $promise->expects($this->once()) + ->method('wait'); + + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->with($this->callback(function (RequestInterface $request) use ($event) { + $request->getBody()->rewind(); + + return 'application/octet-stream' === $request->getHeaderLine('Content-Type') + && base64_encode(gzcompress(JSON::encode($event))) === $request->getBody()->getContents(); + })) + ->willReturn($promise); + + $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); + $transport->send($event); + + $reflectionMethod = new \ReflectionMethod(HttpTransport::class, 'cleanupPendingRequests'); + $reflectionMethod->setAccessible(true); + $reflectionMethod->invoke($transport); + $reflectionMethod->setAccessible(false); + } + + public function testSendFailureCleanupPendingRequests() + { + /** @var HttpException|\PHPUnit_Framework_MockObject_MockObject $exception */ + $exception = $this->createMock(HttpException::class); + + $promise = new PromiseMock($exception, PromiseMock::REJECTED); + + /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willReturn($promise); + + $config = new Configuration(); + $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); + + $transport->send(new Event($config)); + + $this->assertAttributeNotEmpty('pendingRequests', $transport); + $this->assertSame($exception, $promise->wait(true)); + $this->assertAttributeEmpty('pendingRequests', $transport); + } +} + +class PromiseMock implements Promise +{ + private $result; + + private $state; + + private $onFullfilledCallbacks = []; + + private $onRejectedCallbacks = []; + + public function __construct($result, $state = self::FULFILLED) + { + $this->result = $result; + $this->state = $state; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + if (null !== $onFulfilled) { + $this->onFullfilledCallbacks[] = $onFulfilled; + } + + if (null !== $onRejected) { + $this->onRejectedCallbacks[] = $onRejected; + } + + return $this; + } + + public function getState() + { + return $this->state; + } + + public function wait($unwrap = true) + { + switch ($this->state) { + case self::FULFILLED: + foreach ($this->onFullfilledCallbacks as $onFullfilledCallback) { + $this->result = $onFullfilledCallback($this->result); + } + + break; + case self::REJECTED: + foreach ($this->onRejectedCallbacks as $onRejectedCallback) { + $this->result = $onRejectedCallback($this->result); + } + + break; + } + + return $unwrap ? $this->result : null; + } +} diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php new file mode 100644 index 000000000..bf1052a0c --- /dev/null +++ b/tests/Transport/NullTransportTest.php @@ -0,0 +1,28 @@ +assertTrue($transport->send($event)); + } +} diff --git a/tests/Transport/SpoolTransportTest.php b/tests/Transport/SpoolTransportTest.php new file mode 100644 index 000000000..3ecf58375 --- /dev/null +++ b/tests/Transport/SpoolTransportTest.php @@ -0,0 +1,53 @@ +spool = $this->createMock(SpoolInterface::class); + $this->transport = new SpoolTransport($this->spool); + } + + public function testGetSpool() + { + $this->assertSame($this->spool, $this->transport->getSpool()); + } + + public function testSend() + { + $event = new Event(new Configuration()); + + $this->spool->expects($this->once()) + ->method('queueEvent') + ->with($event); + + $this->transport->send($event); + } +} From 6c7893c9c3bb999e441967d183d59b99e5283303 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 7 Feb 2018 23:21:54 +0100 Subject: [PATCH 0324/1161] Porting regression test from master from PR #527 --- tests/StacktraceTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 1def591b3..5b41c9d5e 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -238,6 +238,25 @@ public function testFromBacktraceWithAnonymousFrame() $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); } + + public function testInAppWithEmptyFrame() + { + $stack = [ + [ + 'function' => '{closure}', + ], + null + ]; + + $stacktrace = new Stacktrace($this->client); + $stacktrace->addFrame('/some/file', 123, $stack); + $frames = $stacktrace->getFrames(); + + $this->assertCount(1, $frames); + $this->assertContainsOnlyInstancesOf(Frame::class, $frames); + $this->assertFalse($frames[0]->isInApp()); + } + public function testGetFrameArgumentsDoesNotModifyCapturedArgs() { // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. From 9bdcf522c37246b699aeffed02e3aa68fdfffea8 Mon Sep 17 00:00:00 2001 From: Ryan White Date: Mon, 5 Feb 2018 09:21:23 +0100 Subject: [PATCH 0325/1161] Adding breadcrumbs to sanitize / serialize. (#538) (plus fix CS) (cherry picked from commit 0904031) --- lib/Raven/Client.php | 3 +++ tests/ClientTest.php | 35 +++++++++++++++++++++++++++++++++++ tests/StacktraceTest.php | 3 +-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 23e36beb1..0f7c3e0aa 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -508,6 +508,9 @@ public function sanitize(&$data) if (!empty($data['contexts'])) { $data['contexts'] = $this->serializer->serialize($data['contexts'], 5); } + if (!empty($data['breadcrumbs'])) { + $data['breadcrumbs'] = $this->serializer->serialize($data['breadcrumbs'], 5); + } } public function sendUnsentErrors() diff --git a/tests/ClientTest.php b/tests/ClientTest.php index a09b9bf0b..1db3fd5e7 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -643,6 +643,41 @@ public function testSanitizeContexts() ]], $data); } + public function testSanitizeBreadcrumbs() + { + $client = ClientBuilder::create()->getClient(); + $data = [ + 'breadcrumbs' => [ + [ + 'message' => 'foo', + 'utf8' => pack('NA3CC', 3, 'aBc', 0x0D, 0x0A), + 'data' => [ + 'line' => 1216, + 'bindings' => [ + ['foo', pack('NA3CC', 3, 'aBc', 0x0D, 0x0A)], + ], + ], + ], + ], + ]; + $client->sanitize($data); + + $this->assertEquals([ + 'breadcrumbs' => [ + [ + 'message' => 'foo', + 'utf8' => mb_convert_encoding(pack('NA3CC', 3, 'aBc', 0x0D, 0x0A), 'UTF-8'), + 'data' => [ + 'line' => 1216, + 'bindings' => [ + ['foo', mb_convert_encoding(pack('NA3CC', 3, 'aBc', 0x0D, 0x0A), 'UTF-8')], + ], + ], + ], + ], + ], $data); + } + /** * @covers \Raven\Client::getShutdownFunctionHasBeenSet */ diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 5b41c9d5e..4b511ae45 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -238,14 +238,13 @@ public function testFromBacktraceWithAnonymousFrame() $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); } - public function testInAppWithEmptyFrame() { $stack = [ [ 'function' => '{closure}', ], - null + null, ]; $stacktrace = new Stacktrace($this->client); From 83cc60e951bf09e31cb8eb98be579d1ac0061935 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 7 Feb 2018 09:37:24 +0100 Subject: [PATCH 0326/1161] Update changelog for version 1.8.3 (cherry picked from commit 7a297d0) --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80756057b..11c6ca486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ - ... +## 1.8.3 (2018-02-07) + +- Serialize breadcrumbs to prevent issues with binary data (#538) +- Fix notice array_key_exists() expects parameter 2 to be array, null given (#527) + ## 1.8.2 (2017-12-21) - Improve handling DSN with "null" like values (#522) From e46de58fbaa5d108bdcdcd9f3fa19fbe0e66fb0c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 8 Feb 2018 10:28:33 +0100 Subject: [PATCH 0327/1161] Remove unused variable (#542) --- lib/Raven/Client.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index bf888b2fb..d45864486 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -438,7 +438,6 @@ public function sanitize(Event $event) $userContext = $event->getUserContext(); $extraContext = $event->getExtraContext(); $tagsContext = $event->getTagsContext(); - $breadcrumbs = $event->getBreadcrumbs(); if (!empty($request)) { $event = $event->withRequest($this->serializer->serialize($request)); From 2841bb392de677c15497737e3068d00b36d923ce Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 6 Mar 2018 22:59:43 +0100 Subject: [PATCH 0328/1161] Refactor the Context class (#540) --- UPGRADE-2.0.md | 54 ++++++ lib/Raven/Client.php | 90 +++++++++- lib/Raven/Context.php | 103 ----------- lib/Raven/Context/Context.php | 169 ++++++++++++++++++ lib/Raven/Context/TagsContext.php | 79 ++++++++ lib/Raven/Event.php | 4 +- .../Middleware/ContextInterfaceMiddleware.php | 46 +++-- .../Middleware/MessageInterfaceMiddleware.php | 2 +- .../Middleware/RequestInterfaceMiddleware.php | 2 +- tests/ClientTest.php | 39 ++++ tests/Context/ContextTest.php | 135 ++++++++++++++ tests/Context/TagsContextTest.php | 150 ++++++++++++++++ tests/ContextTest.php | 69 ------- tests/EventTest.php | 31 ++-- .../ContextInterfaceMiddlewareTest.php | 84 +++++++-- 15 files changed, 825 insertions(+), 232 deletions(-) delete mode 100644 lib/Raven/Context.php create mode 100644 lib/Raven/Context/Context.php create mode 100644 lib/Raven/Context/TagsContext.php create mode 100644 tests/Context/ContextTest.php create mode 100644 tests/Context/TagsContextTest.php delete mode 100644 tests/ContextTest.php diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index c246022f3..b8ba2ebf6 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -332,6 +332,51 @@ abstract class for the processors, but a `ProcessorInterface` interface has been introduced. +- The `Client::user_context` method has been removed. You should use `Client::getUserContext` + instead. + + Before: + + ```php + $client->user_context(array('foo' => 'bar')); + ``` + + After: + + ```php + $client->getUserContext()->setData(array('foo' => 'bar')); + ``` + +- The `Client::tags_context` method has been removed. You should use `Client::getTagsContext` + instead. + + Before: + + ```php + $client->tags_context(array('foo', 'bar')); + ``` + + After: + + ```php + $client->getTagsContext()->setData(array('foo', 'bar')); + ``` + +- The `Client::extra_context` method has been removed. You should use `Client::getExtraContext` + instead. + + Before: + + ```php + $client->extra_context(array('foo' => 'bar')); + ``` + + After: + + ```php + $client->getExtraContext()->setData(array('foo' => 'bar')); + ``` + ### Client builder - To simplify the creation of a `Client` object instance, a new builder class @@ -360,3 +405,12 @@ - The `RemoveCookiesProessor` class has been renamed to `SanitizeCookiesProcessor` to better reflect its purpose. The constructor accepts an array of options to make the behaviour of which cookies to sanitize configurable. + +# Context + +- The `Raven_Context` class has been renamed to `Context` and added to the `Raven` + namespace to follow the PSR-4 convention. + +- The `tags`, `extra` and `user` properties of the `Raven_Context` class have + been removed. Each instance of the new class represents now a single context + type only and provides some useful methods to interact with the data. diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index d45864486..bdef9ced1 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -14,6 +14,8 @@ use Psr\Http\Message\ServerRequestInterface; use Raven\Breadcrumbs\Breadcrumb; use Raven\Breadcrumbs\Recorder; +use Raven\Context\Context; +use Raven\Context\TagsContext; use Raven\Middleware\BreadcrumbInterfaceMiddleware; use Raven\Middleware\ContextInterfaceMiddleware; use Raven\Middleware\ExceptionInterfaceMiddleware; @@ -61,11 +63,6 @@ class Client */ const USER_AGENT = 'sentry-php/' . self::VERSION; - /** - * @var Context The context - */ - public $context; - /** * @var TransactionStack The transaction stack */ @@ -106,6 +103,31 @@ class Client */ private $processorRegistry; + /** + * @var TagsContext The tags context + */ + private $tagsContext; + + /** + * @var Context The user context + */ + private $userContext; + + /** + * @var Context The extra context + */ + private $extraContext; + + /** + * @var Context The runtime context + */ + private $runtimeContext; + + /** + * @var Context The server OS context + */ + private $serverOsContext; + /** * @var callable The tip of the middleware call stack */ @@ -132,7 +154,11 @@ public function __construct(Configuration $config, TransportInterface $transport $this->config = $config; $this->transport = $transport; $this->processorRegistry = new ProcessorRegistry(); - $this->context = new Context(); + $this->tagsContext = new TagsContext(); + $this->userContext = new Context(); + $this->extraContext = new Context(); + $this->runtimeContext = new Context(); + $this->serverOsContext = new Context(); $this->recorder = new Recorder(); $this->transaction = new TransactionStack(); $this->serializer = new Serializer($this->config->getMbDetectOrder()); @@ -145,7 +171,11 @@ public function __construct(Configuration $config, TransportInterface $transport $this->addMiddleware(new MessageInterfaceMiddleware()); $this->addMiddleware(new RequestInterfaceMiddleware()); $this->addMiddleware(new UserInterfaceMiddleware()); - $this->addMiddleware(new ContextInterfaceMiddleware($this->context)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->tagsContext, Context::CONTEXT_TAGS)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->userContext, Context::CONTEXT_USER)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->extraContext, Context::CONTEXT_EXTRA)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->runtimeContext, Context::CONTEXT_RUNTIME)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->serverOsContext, Context::CONTEXT_SERVER_OS)); $this->addMiddleware(new BreadcrumbInterfaceMiddleware($this->recorder)); $this->addMiddleware(new ExceptionInterfaceMiddleware($this)); @@ -527,11 +557,53 @@ public function registerSeverityMap($map) } /** + * Gets the user context. + * + * @return Context + */ + public function getUserContext() + { + return $this->userContext; + } + + /** + * Gets the tags context. + * + * @return TagsContext + */ + public function getTagsContext() + { + return $this->tagsContext; + } + + /** + * Gets the extra context. + * + * @return Context + */ + public function getExtraContext() + { + return $this->extraContext; + } + + /** + * Gets the runtime context. + * + * @return Context + */ + public function getRuntimeContext() + { + return $this->runtimeContext; + } + + /** + * Gets the server OS context. + * * @return Context */ - public function getContext() + public function getServerOsContext() { - return $this->context; + return $this->serverOsContext; } public function setAllObjectSerialize($value) diff --git a/lib/Raven/Context.php b/lib/Raven/Context.php deleted file mode 100644 index 1e7e20404..000000000 --- a/lib/Raven/Context.php +++ /dev/null @@ -1,103 +0,0 @@ -clear(); - } - - /** - * Clean up existing context. - */ - public function clear() - { - $this->tags = []; - $this->extraData = []; - $this->userData = []; - } - - public function setTag($name, $value) - { - if ( - !is_string($name) - || '' === $name - ) { - throw new \InvalidArgumentException('Invalid tag name'); - } - - $this->tags[$name] = $value; - } - - public function setUserId($userId) - { - $this->userData['id'] = $userId; - } - - public function setUserEmail($userEmail) - { - $this->userData['email'] = $userEmail; - } - - /** - * @param array $data - */ - public function setUserData(array $data) - { - $this->userData = $data; - } - - public function mergeUserData(array $data = []) - { - $this->userData = array_merge($this->userData, $data); - } - - public function mergeExtraData(array $data = []) - { - $this->extraData = array_merge($this->extraData, $data); - } - - /** - * @return array - */ - public function getTags() - { - return $this->tags; - } - - /** - * @return array - */ - public function getUserData() - { - return $this->userData; - } - - /** - * @return array - */ - public function getExtraData() - { - return $this->extraData; - } -} diff --git a/lib/Raven/Context/Context.php b/lib/Raven/Context/Context.php new file mode 100644 index 000000000..8b4fa30dc --- /dev/null +++ b/lib/Raven/Context/Context.php @@ -0,0 +1,169 @@ + + */ +class Context implements \ArrayAccess, \JsonSerializable, \IteratorAggregate +{ + /** + * This constant defines the alias used for the user context. + */ + const CONTEXT_USER = 'user'; + + /** + * This constant defines the alias used for the runtime context. + */ + const CONTEXT_RUNTIME = 'runtime'; + + /** + * This constant defines the alias used for the tags context. + */ + const CONTEXT_TAGS = 'tags'; + + /** + * This constant defines the alias used for the extra context. + */ + const CONTEXT_EXTRA = 'extra'; + + /** + * This constant defines the alias used for the server OS context. + */ + const CONTEXT_SERVER_OS = 'server_os'; + + /** + * @var array The data stored in this object + */ + protected $data = []; + + /** + * Constructor. + * + * @param array $data The initial data to store + */ + public function __construct(array $data = []) + { + $this->data = $data; + } + + /** + * Merges the given data with the existing one, recursively or not, according + * to the value of the `$recursive` parameter. + * + * @param array $data The data to merge + * @param bool $recursive Whether to merge the data recursively or not + */ + public function merge(array $data, $recursive = false) + { + $this->data = $recursive ? array_merge_recursive($this->data, $data) : array_merge($this->data, $data); + } + + /** + * @param array $data + */ + public function setData(array $data) + { + foreach ($data as $index => $value) { + $this->data[$index] = $value; + } + } + + /** + * Replaces all the data contained in this object with the given one. + * + * @param array $data The data to set + */ + public function replaceData(array $data) + { + $this->data = $data; + } + + /** + * Clears the entire data contained in this object. + */ + public function clear() + { + $this->data = []; + } + + /** + * Checks whether the object is not storing any data. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->data); + } + + /** + * Returns an array representation of the data stored by the object. + * + * @return array + */ + public function toArray() + { + return $this->data; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return isset($this->data[$offset]) || array_key_exists($offset, $this->data); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->data[$offset]; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->data); + } +} diff --git a/lib/Raven/Context/TagsContext.php b/lib/Raven/Context/TagsContext.php new file mode 100644 index 000000000..a0ffcbf7c --- /dev/null +++ b/lib/Raven/Context/TagsContext.php @@ -0,0 +1,79 @@ + + */ +class TagsContext extends Context +{ + /** + * {@inheritdoc} + */ + public function merge(array $data, $recursive = false) + { + if ($recursive) { + throw new \InvalidArgumentException('The tags context does not allow recursive merging of its data.'); + } + + foreach ($data as $value) { + if (!is_string($value)) { + throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); + } + } + + parent::merge($data); + } + + /** + * {@inheritdoc} + */ + public function setData(array $data) + { + foreach ($data as $value) { + if (!is_string($value)) { + throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); + } + } + + parent::setData($data); + } + + /** + * {@inheritdoc} + */ + public function replaceData(array $data) + { + foreach ($data as $value) { + if (!is_string($value)) { + throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); + } + } + + parent::replaceData($data); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + if (!is_string($value)) { + throw new \InvalidArgumentException('The $value argument must be a string.'); + } + + parent::offsetSet($offset, $value); + } +} diff --git a/lib/Raven/Event.php b/lib/Raven/Event.php index bed80a4b0..deb98fa8c 100644 --- a/lib/Raven/Event.php +++ b/lib/Raven/Event.php @@ -772,11 +772,11 @@ public function toArray() } if (!empty($this->serverOsContext)) { - $data['server_os'] = $this->serverOsContext; + $data['contexts']['os'] = $this->serverOsContext; } if (!empty($this->runtimeContext)) { - $data['runtime'] = $this->runtimeContext; + $data['contexts']['runtime'] = $this->runtimeContext; } if (!empty($this->breadcrumbs)) { diff --git a/lib/Raven/Middleware/ContextInterfaceMiddleware.php b/lib/Raven/Middleware/ContextInterfaceMiddleware.php index d132de7da..9b6990731 100644 --- a/lib/Raven/Middleware/ContextInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ContextInterfaceMiddleware.php @@ -12,7 +12,7 @@ namespace Raven\Middleware; use Psr\Http\Message\ServerRequestInterface; -use Raven\Context; +use Raven\Context\Context; use Raven\Event; /** @@ -24,18 +24,25 @@ final class ContextInterfaceMiddleware { /** - * @var Context The context storage + * @var Context The context */ private $context; + /** + * @var string The alias name of the context + */ + private $contextName; + /** * Constructor. * - * @param Context $context The context storage + * @param Context $context The context + * @param string $contextName The alias name of the context */ - public function __construct(Context $context) + public function __construct(Context $context, $contextName) { $this->context = $context; + $this->contextName = $contextName; } /** @@ -51,17 +58,28 @@ public function __construct(Context $context) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) { - $tagsContext = isset($payload['tags_context']) ? $payload['tags_context'] : []; - $extraContext = isset($payload['extra_context']) ? $payload['extra_context'] : []; - $serverOsContext = isset($payload['server_os_context']) ? $payload['server_os_context'] : []; - $runtimeContext = isset($payload['runtime_context']) ? $payload['runtime_context'] : []; - $userContext = isset($payload['user_context']) ? $payload['user_context'] : []; + $context = isset($payload[$this->contextName . '_context']) ? $payload[$this->contextName . '_context'] : []; + $context = array_merge($this->context->toArray(), $context); - $event = $event->withTagsContext(array_merge($this->context->getTags(), $tagsContext), false) - ->withExtraContext(array_merge($this->context->getExtraData(), $extraContext), false) - ->withUserContext(array_merge($this->context->getUserData(), $userContext), false) - ->withServerOsContext($serverOsContext, false) - ->withRuntimeContext($runtimeContext, false); + switch ($this->contextName) { + case Context::CONTEXT_USER: + $event = $event->withUserContext($context, false); + break; + case Context::CONTEXT_RUNTIME: + $event = $event->withRuntimeContext($context, false); + break; + case Context::CONTEXT_TAGS: + $event = $event->withTagsContext($context, false); + break; + case Context::CONTEXT_EXTRA: + $event = $event->withExtraContext($context, false); + break; + case Context::CONTEXT_SERVER_OS: + $event = $event->withServerOsContext($context, false); + break; + default: + throw new \RuntimeException(sprintf('The "%s" context is not supported.', $this->contextName)); + } return $next($event, $request, $exception, $payload); } diff --git a/lib/Raven/Middleware/MessageInterfaceMiddleware.php b/lib/Raven/Middleware/MessageInterfaceMiddleware.php index f251d1df7..e338e4aa2 100644 --- a/lib/Raven/Middleware/MessageInterfaceMiddleware.php +++ b/lib/Raven/Middleware/MessageInterfaceMiddleware.php @@ -20,7 +20,7 @@ * * @author Stefano Arlandini */ -class MessageInterfaceMiddleware +final class MessageInterfaceMiddleware { /** * Collects the needed data and sets it in the given event object. diff --git a/lib/Raven/Middleware/RequestInterfaceMiddleware.php b/lib/Raven/Middleware/RequestInterfaceMiddleware.php index 6afd6b774..4115c3245 100644 --- a/lib/Raven/Middleware/RequestInterfaceMiddleware.php +++ b/lib/Raven/Middleware/RequestInterfaceMiddleware.php @@ -20,7 +20,7 @@ * * @author Stefano Arlandini */ -class RequestInterfaceMiddleware +final class RequestInterfaceMiddleware { /** * Collects the needed data and sets it in the given event object. diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 4b5094cd4..269ae46c7 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -18,6 +18,8 @@ use Raven\Client; use Raven\ClientBuilder; use Raven\Configuration; +use Raven\Context\Context; +use Raven\Context\TagsContext; use Raven\Event; use Raven\Serializer; use Raven\Transport\TransportInterface; @@ -246,6 +248,8 @@ public function testGetLastEvent() /** * @group legacy + * + * @expectedDeprecation The Raven\Client::getLastEventId() method is deprecated since version 2.0. Use getLastEvent() instead. */ public function testGetLastEventId() { @@ -266,6 +270,41 @@ public function testGetLastEventId() $this->assertEquals('ddbd643a51904ccea6ce3098506f9d33', $client->getLastEventID()); } + public function testGetUserContext() + { + $client = ClientBuilder::create()->getClient(); + + $this->assertInstanceOf(Context::class, $client->getUserContext()); + } + + public function testGetTagsContext() + { + $client = ClientBuilder::create()->getClient(); + + $this->assertInstanceOf(TagsContext::class, $client->getTagsContext()); + } + + public function testGetExtraContext() + { + $client = ClientBuilder::create()->getClient(); + + $this->assertInstanceOf(Context::class, $client->getExtraContext()); + } + + public function testGetRuntimeContext() + { + $client = ClientBuilder::create()->getClient(); + + $this->assertInstanceOf(Context::class, $client->getRuntimeContext()); + } + + public function testGetServerOsContext() + { + $client = ClientBuilder::create()->getClient(); + + $this->assertInstanceOf(Context::class, $client->getServerOsContext()); + } + public function testAppPathLinux() { $client = ClientBuilder::create(['project_root' => '/foo/bar'])->getClient(); diff --git a/tests/Context/ContextTest.php b/tests/Context/ContextTest.php new file mode 100644 index 000000000..2aeffdbf2 --- /dev/null +++ b/tests/Context/ContextTest.php @@ -0,0 +1,135 @@ + 'bar']); + + $this->assertAttributeEquals(['foo' => 'bar'], 'data', $context); + } + + public function testMerge() + { + $context = new Context([ + 'foo' => 'bar', + 'bar' => [ + 'foobar' => 'barfoo', + ], + ]); + + $context->merge(['bar' => ['barfoo' => 'foobar']], true); + + $this->assertAttributeEquals(['foo' => 'bar', 'bar' => ['foobar' => 'barfoo', 'barfoo' => 'foobar']], 'data', $context); + + $context->merge(['bar' => 'foo']); + + $this->assertAttributeEquals(['foo' => 'bar', 'bar' => 'foo'], 'data', $context); + } + + public function testSetData() + { + $context = new Context(['foo' => 'bar']); + $context->setData(['bar' => 'foo']); + + $this->assertAttributeEquals(['foo' => 'bar', 'bar' => 'foo'], 'data', $context); + + $context->setData(['foo' => ['bar' => 'baz']]); + + $this->assertAttributeEquals(['foo' => ['bar' => 'baz'], 'bar' => 'foo'], 'data', $context); + } + + public function testReplaceData() + { + $context = new Context(['foo' => 'bar']); + $context->replaceData(['bar' => 'foo']); + + $this->assertAttributeEquals(['bar' => 'foo'], 'data', $context); + } + + public function testClear() + { + $context = new Context(['foo' => 'bar']); + + $this->assertAttributeEquals(['foo' => 'bar'], 'data', $context); + + $context->clear(); + + $this->assertAttributeEmpty('data', $context); + } + + public function testIsEmpty() + { + $context = new Context(); + + $this->assertTrue($context->isEmpty()); + + $context->setData(['foo' => 'bar']); + + $this->assertFalse($context->isEmpty()); + } + + public function testToArray() + { + $context = new Context(['foo' => 'bar']); + + $this->assertEquals(['foo' => 'bar'], $context->toArray()); + } + + public function testJsonSerialize() + { + $context = new Context(['foo' => 'bar']); + + $this->assertEquals('{"foo":"bar"}', json_encode($context)); + } + + public function testArrayLikeBehaviour() + { + $context = new Context(); + + $this->assertAttributeEquals([], 'data', $context); + $this->assertFalse(isset($context['foo'])); + + // Accessing a key that does not exists in the data object should behave + // like accessing a non-existent key of an array + $context['foo']; + + $error = error_get_last(); + + $this->assertInternalType('array', $error); + $this->assertEquals('Undefined index: foo', $error['message']); + + error_clear_last(); + + $context['foo'] = 'bar'; + + $this->assertAttributeEquals(['foo' => 'bar'], 'data', $context); + $this->assertTrue(isset($context['foo'])); + $this->assertEquals('bar', $context['foo']); + + unset($context['foo']); + + $this->assertArrayNotHasKey('foo', $context); + } + + public function testGetIterator() + { + $context = new Context(['foo' => 'bar', 'bar' => 'foo']); + + $this->assertEquals($context->toArray(), iterator_to_array($context)); + } +} diff --git a/tests/Context/TagsContextTest.php b/tests/Context/TagsContextTest.php new file mode 100644 index 000000000..f93cf7576 --- /dev/null +++ b/tests/Context/TagsContextTest.php @@ -0,0 +1,150 @@ +expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new TagsContext(['foo' => 'bar', 'bar' => 'foo']); + $context->merge($data, $recursive); + + $this->assertEquals($expectedData, $context->toArray()); + } + + public function mergeDataProvider() + { + return [ + [ + ['foo' => 'baz', 'baz' => 'foo'], + false, + ['foo' => 'baz', 'bar' => 'foo', 'baz' => 'foo'], + null, + ], + [ + ['foo' => 'bar'], + true, + null, + 'The tags context does not allow recursive merging of its data.', + ], + [ + ['foo' => new \stdClass()], + false, + null, + 'The $data argument must contains a simple array of string values.', + ], + ]; + } + + /** + * @dataProvider setDataDataProvider + */ + public function testSetData($data, $expectException) + { + if ($expectException) { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $data argument must contains a simple array of string values.'); + } + + $context = new TagsContext(); + $context->setData($data); + + $this->assertEquals(['foo' => 'bar'], $context->toArray()); + } + + public function setDataDataProvider() + { + return [ + [ + ['foo' => 'bar'], + false, + ], + [ + [new \stdClass()], + true, + ], + ]; + } + + /** + * @dataProvider replaceDataDataProvider + */ + public function testReplaceData($data, $expectException) + { + if ($expectException) { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $data argument must contains a simple array of string values.'); + } + + $context = new TagsContext(['foo', 'bar']); + $context->replaceData($data); + + $this->assertEquals(['bar', 'foo'], $context->toArray()); + } + + public function replaceDataDataProvider() + { + return [ + [ + ['bar', 'foo'], + false, + ], + [ + [new \stdClass()], + true, + ], + ]; + } + + /** + * @dataProvider offsetSetDataProvider + */ + public function testOffsetSet($offset, $value, $expectedExceptionMessage) + { + if (null !== $expectedExceptionMessage) { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new TagsContext(); + $context[$offset] = $value; + + $this->assertEquals(['foo' => 'bar'], $context->toArray()); + } + + public function offsetSetDataProvider() + { + return [ + [ + 'foo', + 'bar', + null, + ], + [ + 'foo', + new \stdClass(), + 'The $value argument must be a string.', + ], + ]; + } +} diff --git a/tests/ContextTest.php b/tests/ContextTest.php deleted file mode 100644 index 94b11ad7d..000000000 --- a/tests/ContextTest.php +++ /dev/null @@ -1,69 +0,0 @@ -setTag('foo', 'bar'); - $context->setTag('foo', 'baz'); - - $this->assertEquals(['foo' => 'baz'], $context->getTags()); - } - - public function testMergeUserData() - { - $context = new Context(); - - $context->mergeUserData(['foo' => 'bar']); - $context->mergeUserData(['baz' => 'bar']); - - $this->assertEquals(['foo' => 'bar', 'baz' => 'bar'], $context->getUserData()); - } - - public function testMergeUserDataWithSameKey() - { - $context = new Context(); - - $context->mergeUserData(['foo' => 'bar']); - $context->mergeUserData(['foo' => 'baz']); - - $this->assertEquals(['foo' => 'baz'], $context->getUserData()); - } - - public function testSetUserData() - { - $context = new Context(); - - $context->setUserData(['foo' => 'bar']); - $context->setUserData(['bar' => 'baz']); - - $this->assertEquals(['bar' => 'baz'], $context->getUserData()); - } - - public function testMergeExtraData() - { - $context = new Context(); - - $context->mergeExtraData(['foo' => 'bar']); - $context->mergeExtraData(['baz' => 'bar']); - - $this->assertEquals(['foo' => 'bar', 'baz' => 'bar'], $context->getExtraData()); - } - - public function testMergeExtraDataWithSameKey() - { - $context = new Context(); - - $context->mergeExtraData(['foo' => 'bar']); - $context->mergeExtraData(['foo' => 'baz']); - - $this->assertEquals(['foo' => 'baz'], $context->getExtraData()); - } -} diff --git a/tests/EventTest.php b/tests/EventTest.php index 2497ba6cc..731c9b91c 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -150,7 +150,7 @@ public function testToArrayWithBreadcrumbs() /** * @dataProvider gettersAndSettersDataProvider */ - public function testGettersAndSetters($propertyName, $propertyValue, $serializedPropertyName) + public function testGettersAndSetters($propertyName, $propertyValue, $expectedValue) { $getterMethod = 'get' . ucfirst($propertyName); $setterMethod = 'with' . ucfirst($propertyName); @@ -172,26 +172,25 @@ public function testGettersAndSetters($propertyName, $propertyValue, $serialized $data = $newEvent->toArray(); - $this->assertArrayHasKey($serializedPropertyName, $data); - $this->assertSame($propertyValue, $data[$serializedPropertyName]); + $this->assertArraySubset($expectedValue, $data); } public function gettersAndSettersDataProvider() { return [ - ['level', 'info', 'level'], - ['logger', 'ruby', 'logger'], - ['culprit', '', 'culprit'], - ['serverName', 'local.host', 'server_name'], - ['release', '0.0.1', 'release'], - ['modules', ['foo' => '0.0.1', 'bar' => '0.0.2'], 'modules'], - ['extraContext', ['foo' => 'bar'], 'extra'], - ['tagsContext', ['foo' => 'bar'], 'tags'], - ['userContext', ['foo' => 'bar'], 'user'], - ['serverOsContext', ['foo' => 'bar'], 'server_os'], - ['runtimeContext', ['foo' => 'bar'], 'runtime'], - ['fingerprint', ['foo', 'bar'], 'fingerprint'], - ['environment', 'foo', 'environment'], + ['level', 'info', ['level' => 'info']], + ['logger', 'ruby', ['logger' => 'ruby']], + ['culprit', 'foo', ['culprit' => 'foo']], + ['serverName', 'local.host', ['server_name' => 'local.host']], + ['release', '0.0.1', ['release' => '0.0.1']], + ['modules', ['foo' => '0.0.1', 'bar' => '0.0.2'], ['modules' => ['foo' => '0.0.1', 'bar' => '0.0.2']]], + ['extraContext', ['foo' => 'bar'], ['extra' => ['foo' => 'bar']]], + ['tagsContext', ['bar' => 'foo'], ['tags' => ['bar' => 'foo']]], + ['userContext', ['bar' => 'baz'], ['user' => ['bar' => 'baz']]], + ['serverOsContext', ['foobar' => 'barfoo'], ['contexts' => ['os' => ['foobar' => 'barfoo']]]], + ['runtimeContext', ['barfoo' => 'foobar'], ['contexts' => ['runtime' => ['barfoo' => 'foobar']]]], + ['fingerprint', ['foo', 'bar'], ['fingerprint' => ['foo', 'bar']]], + ['environment', 'foo', ['environment' => 'foo']], ]; } diff --git a/tests/Middleware/ContextInterfaceMiddlewareTest.php b/tests/Middleware/ContextInterfaceMiddlewareTest.php index 8b0b99893..125c0419a 100644 --- a/tests/Middleware/ContextInterfaceMiddlewareTest.php +++ b/tests/Middleware/ContextInterfaceMiddlewareTest.php @@ -13,43 +13,93 @@ use PHPUnit\Framework\TestCase; use Raven\Configuration; -use Raven\Context; +use Raven\Context\Context; use Raven\Event; use Raven\Middleware\ContextInterfaceMiddleware; class ContextInterfaceMiddlewareTest extends TestCase { - public function testInvoke() + /** + * @dataProvider invokeDataProvider + */ + public function testInvoke($contextName, $initialData, $payloadData, $expectedData, $expectedExceptionMessage) { + if (null !== $expectedExceptionMessage) { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } + $context = new Context(); - $context->setTag('bar', 'foo'); - $context->mergeUserData(['foo' => 'bar']); - $context->mergeExtraData(['bar' => 'baz']); + $context->setData($initialData); $configuration = new Configuration(); $event = new Event($configuration); $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, &$invokationCount) { + $callback = function (Event $eventArg) use ($event, $contextName, $expectedData, &$invokationCount) { + $method = preg_replace_callback('/_[a-zA-Z]/', function ($matches) { + return strtoupper($matches[0][1]); + }, 'get_' . $contextName . '_context'); + $this->assertNotSame($event, $eventArg); - $this->assertEquals(['bar' => 'foo', 'foobar' => 'barfoo'], $eventArg->getTagsContext()); - $this->assertEquals(['foo' => 'bar', 'baz' => 'foo'], $eventArg->getUserContext()); - $this->assertEquals(['bar' => 'baz', 'barbaz' => 'bazbar'], $eventArg->getExtraContext()); - $this->assertEquals(['foo' => 'bar'], $eventArg->getServerOsContext()); - $this->assertEquals(['bar' => 'foo'], $eventArg->getRuntimeContext()); + $this->assertEquals($expectedData, $eventArg->$method()); ++$invokationCount; }; - $middleware = new ContextInterfaceMiddleware($context); + $middleware = new ContextInterfaceMiddleware($context, $contextName); $middleware($event, $callback, null, null, [ - 'tags_context' => ['foobar' => 'barfoo'], - 'extra_context' => ['barbaz' => 'bazbar'], - 'server_os_context' => ['foo' => 'bar'], - 'runtime_context' => ['bar' => 'foo'], - 'user_context' => ['baz' => 'foo'], + $contextName . '_context' => $payloadData, ]); $this->assertEquals(1, $invokationCount); } + + public function invokeDataProvider() + { + return [ + [ + Context::CONTEXT_USER, + ['foo' => 'bar', 'foobaz' => 'bazfoo'], + ['foobaz' => 'bazfoo'], + ['foo' => 'bar', 'foobaz' => 'bazfoo'], + null, + ], + [ + Context::CONTEXT_RUNTIME, + ['baz' => 'foo'], + ['barfoo' => 'foobar'], + ['baz' => 'foo', 'barfoo' => 'foobar'], + null, + ], + [ + Context::CONTEXT_TAGS, + ['foo', 'bar'], + ['foobar'], + ['foo', 'bar', 'foobar'], + null, + ], + [ + Context::CONTEXT_EXTRA, + ['bar' => 'foo'], + ['barbaz' => 'bazbar'], + ['bar' => 'foo', 'barbaz' => 'bazbar'], + null, + ], + [ + Context::CONTEXT_SERVER_OS, + ['foo' => 'baz'], + ['bazfoo' => 'foobaz'], + ['foo' => 'baz', 'bazfoo' => 'foobaz'], + null, + ], + [ + 'foo', + [], + [], + [], + 'The "foo" context is not supported.', + ], + ]; + } } From be860c9612ee41fc34fe8c5462f96880dd919925 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 16 Mar 2018 11:03:13 +0100 Subject: [PATCH 0329/1161] Add RuntimeContext class to store runtime information (#567) --- lib/Raven/Client.php | 7 +- lib/Raven/Configuration.php | 2 +- lib/Raven/Context/RuntimeContext.php | 163 ++++++++++++++++++++ tests/Context/RuntimeContextTest.php | 218 +++++++++++++++++++++++++++ 4 files changed, 386 insertions(+), 4 deletions(-) create mode 100644 lib/Raven/Context/RuntimeContext.php create mode 100644 tests/Context/RuntimeContextTest.php diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index bdef9ced1..b2517a098 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -15,6 +15,7 @@ use Raven\Breadcrumbs\Breadcrumb; use Raven\Breadcrumbs\Recorder; use Raven\Context\Context; +use Raven\Context\RuntimeContext; use Raven\Context\TagsContext; use Raven\Middleware\BreadcrumbInterfaceMiddleware; use Raven\Middleware\ContextInterfaceMiddleware; @@ -119,7 +120,7 @@ class Client private $extraContext; /** - * @var Context The runtime context + * @var RuntimeContext The runtime context */ private $runtimeContext; @@ -157,7 +158,7 @@ public function __construct(Configuration $config, TransportInterface $transport $this->tagsContext = new TagsContext(); $this->userContext = new Context(); $this->extraContext = new Context(); - $this->runtimeContext = new Context(); + $this->runtimeContext = new RuntimeContext(); $this->serverOsContext = new Context(); $this->recorder = new Recorder(); $this->transaction = new TransactionStack(); @@ -589,7 +590,7 @@ public function getExtraContext() /** * Gets the runtime context. * - * @return Context + * @return RuntimeContext */ public function getRuntimeContext() { diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 378e361ed..68cf86496 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -631,7 +631,7 @@ public function setErrorTypes($errorTypes) } /** - * Configures the options for this processor. + * Configures the options of the client. * * @param OptionsResolver $resolver The resolver for the options * diff --git a/lib/Raven/Context/RuntimeContext.php b/lib/Raven/Context/RuntimeContext.php new file mode 100644 index 000000000..4abb2da1e --- /dev/null +++ b/lib/Raven/Context/RuntimeContext.php @@ -0,0 +1,163 @@ + + */ +class RuntimeContext extends Context +{ + /** + * @var OptionsResolver The options resolver + */ + private $resolver; + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function __construct(array $data = []) + { + $this->resolver = new OptionsResolver(); + + $this->configureOptions($this->resolver); + + parent::__construct($this->resolver->resolve($data)); + } + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function merge(array $data, $recursive = false) + { + $data = $this->resolver->resolve($data); + + parent::merge($data, $recursive); + } + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function setData(array $data) + { + $data = $this->resolver->resolve($data); + + parent::setData($data); + } + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function replaceData(array $data) + { + $data = $this->resolver->resolve($data); + + parent::replaceData($data); + } + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function offsetSet($offset, $value) + { + $data = $this->resolver->resolve([$offset => $value]); + + parent::offsetSet($offset, $data[$offset]); + } + + /** + * Gets the name of the runtime. + * + * @return string + */ + public function getName() + { + return $this->data['name']; + } + + /** + * Sets the name of the runtime. + * + * @param string $name The name + */ + public function setName($name) + { + $this->offsetSet('name', $name); + } + + /** + * Gets the version of the runtime. + * + * @return string + */ + public function getVersion() + { + return $this->data['version']; + } + + /** + * Sets the version of the runtime. + * + * @param string $version The version + */ + public function setVersion($version) + { + $this->offsetSet('version', $version); + } + + /** + * Configures the options of the context. + * + * @param OptionsResolver $resolver The resolver for the options + */ + private function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'name' => 'php', + 'version' => PHP_VERSION, + ]); + + $resolver->setAllowedTypes('name', 'string'); + $resolver->setAllowedTypes('version', 'string'); + } +} diff --git a/tests/Context/RuntimeContextTest.php b/tests/Context/RuntimeContextTest.php new file mode 100644 index 000000000..35b738ebc --- /dev/null +++ b/tests/Context/RuntimeContextTest.php @@ -0,0 +1,218 @@ +expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new RuntimeContext($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testMerge($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new RuntimeContext(); + $context->merge($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testSetData($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new RuntimeContext(); + $context->setData($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testReplaceData($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new RuntimeContext(); + $context->replaceData($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + public function valuesDataProvider() + { + return [ + [ + [], + [ + 'name' => 'php', + 'version' => PHP_VERSION, + ], + null, + null, + ], + [ + [ + 'name' => 'foo', + ], + [ + 'name' => 'foo', + 'version' => PHP_VERSION, + ], + null, + null, + ], + [ + [ + 'name' => 'foo', + 'version' => 'bar', + ], + [ + 'name' => 'foo', + 'version' => 'bar', + ], + null, + null, + ], + [ + [ + 'foo' => 'bar', + ], + [], + UndefinedOptionsException::class, + 'The option "foo" does not exist. Defined options are: "name", "version".', + ], + [ + [ + 'name' => 1, + ], + [], + InvalidOptionsException::class, + 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + [ + 'version' => 1, + ], + [], + InvalidOptionsException::class, + 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + ], + ]; + } + + /** + * @dataProvider offsetSetDataProvider + */ + public function testOffsetSet($key, $value, $expectedExceptionClass, $expectedExceptionMessage) + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new RuntimeContext(); + $context[$key] = $value; + + $this->assertArraySubset([$key => $value], $context->toArray()); + } + + public function offsetSetDataProvider() + { + return [ + [ + 'name', + 'foo', + null, + null, + ], + [ + 'name', + 1, + InvalidOptionsException::class, + 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + 'version', + 1, + InvalidOptionsException::class, + 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + 'foo', + 'bar', + UndefinedOptionsException::class, + 'The option "foo" does not exist. Defined options are: "name", "version".', + ], + ]; + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters($getterMethod, $setterMethod, $value) + { + $context = new RuntimeContext(); + $context->$setterMethod($value); + + $this->assertEquals($value, $context->$getterMethod()); + } + + public function gettersAndSettersDataProvider() + { + return [ + [ + 'getName', + 'setName', + 'foo', + ], + [ + 'getVersion', + 'setVersion', + 'bar', + ], + ]; + } +} From cbcae88765712d165f698d6161bee6bdfd4781b2 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 20 Mar 2018 01:17:12 +0100 Subject: [PATCH 0330/1161] Add ServerOsContext class to store server OS information (#569) --- lib/Raven/Client.php | 19 +- lib/Raven/Context/ServerOsContext.php | 207 +++++++++++++++++ tests/ClientTest.php | 6 +- tests/Context/ServerOsContextTest.php | 305 ++++++++++++++++++++++++++ 4 files changed, 520 insertions(+), 17 deletions(-) create mode 100644 lib/Raven/Context/ServerOsContext.php create mode 100644 tests/Context/ServerOsContextTest.php diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index b2517a098..a4833120d 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -16,6 +16,7 @@ use Raven\Breadcrumbs\Recorder; use Raven\Context\Context; use Raven\Context\RuntimeContext; +use Raven\Context\ServerOsContext; use Raven\Context\TagsContext; use Raven\Middleware\BreadcrumbInterfaceMiddleware; use Raven\Middleware\ContextInterfaceMiddleware; @@ -125,7 +126,7 @@ class Client private $runtimeContext; /** - * @var Context The server OS context + * @var ServerOsContext The server OS context */ private $serverOsContext; @@ -159,7 +160,7 @@ public function __construct(Configuration $config, TransportInterface $transport $this->userContext = new Context(); $this->extraContext = new Context(); $this->runtimeContext = new RuntimeContext(); - $this->serverOsContext = new Context(); + $this->serverOsContext = new ServerOsContext(); $this->recorder = new Recorder(); $this->transaction = new TransactionStack(); $this->serializer = new Serializer($this->config->getMbDetectOrder()); @@ -436,18 +437,6 @@ public function capture(array $payload) $event = $event->withLogger($payload['logger']); } - if (isset($payload['tags_context'])) { - $event = $event->withTagsContext($payload['tags_context']); - } - - if (isset($payload['extra_context'])) { - $event = $event->withExtraContext($payload['extra_context']); - } - - if (isset($payload['user_context'])) { - $event = $event->withUserContext($payload['user_context']); - } - if (isset($payload['message'])) { $payload['message'] = substr($payload['message'], 0, static::MESSAGE_LIMIT); } @@ -600,7 +589,7 @@ public function getRuntimeContext() /** * Gets the server OS context. * - * @return Context + * @return ServerOsContext */ public function getServerOsContext() { diff --git a/lib/Raven/Context/ServerOsContext.php b/lib/Raven/Context/ServerOsContext.php new file mode 100644 index 000000000..3608ec3c1 --- /dev/null +++ b/lib/Raven/Context/ServerOsContext.php @@ -0,0 +1,207 @@ + + */ +class ServerOsContext extends Context +{ + /** + * @var OptionsResolver The options resolver + */ + private $resolver; + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function __construct(array $data = []) + { + $this->resolver = new OptionsResolver(); + + $this->configureOptions($this->resolver); + + parent::__construct($this->resolver->resolve($data)); + } + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function merge(array $data, $recursive = false) + { + $data = $this->resolver->resolve($data); + + parent::merge($data, $recursive); + } + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function setData(array $data) + { + $data = $this->resolver->resolve($data); + + parent::setData($data); + } + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function replaceData(array $data) + { + $data = $this->resolver->resolve($data); + + parent::replaceData($data); + } + + /** + * {@inheritdoc} + * + * @throws UndefinedOptionsException If any of the options are not supported + * by the context + * @throws InvalidOptionsException If any of the options don't fulfill the + * specified validation rules + */ + public function offsetSet($offset, $value) + { + $data = $this->resolver->resolve([$offset => $value]); + + parent::offsetSet($offset, $data[$offset]); + } + + /** + * Gets the name of the operating system. + * + * @return string + */ + public function getName() + { + return $this->data['name']; + } + + /** + * Sets the name of the operating system. + * + * @param string $name The name + */ + public function setName($name) + { + $this->offsetSet('name', $name); + } + + /** + * Gets the version of the operating system. + * + * @return string + */ + public function getVersion() + { + return $this->data['version']; + } + + /** + * Sets the version of the operating system. + * + * @param string $version The version + */ + public function setVersion($version) + { + $this->offsetSet('version', $version); + } + + /** + * Gets the build of the operating system. + * + * @return string + */ + public function getBuild() + { + return $this->data['build']; + } + + /** + * Sets the build of the operating system. + * + * @param string $build The build + */ + public function setBuild($build) + { + $this->offsetSet('build', $build); + } + + /** + * Gets the version of the kernel of the operating system. + * + * @return string + */ + public function getKernelVersion() + { + return $this->data['kernel_version']; + } + + /** + * Sets the version of the kernel of the operating system. + * + * @param string $kernelVersion The kernel version + */ + public function setKernelVersion($kernelVersion) + { + $this->offsetSet('kernel_version', $kernelVersion); + } + + /** + * Configures the options of the context. + * + * @param OptionsResolver $resolver The resolver for the options + */ + private function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'name' => php_uname('s'), + 'version' => php_uname('r'), + 'build' => php_uname('v'), + 'kernel_version' => php_uname('a'), + ]); + + $resolver->setAllowedTypes('name', 'string'); + $resolver->setAllowedTypes('version', 'string'); + $resolver->setAllowedTypes('build', 'string'); + $resolver->setAllowedTypes('kernel_version', 'string'); + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 269ae46c7..5cc6da609 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -19,6 +19,8 @@ use Raven\ClientBuilder; use Raven\Configuration; use Raven\Context\Context; +use Raven\Context\RuntimeContext; +use Raven\Context\ServerOsContext; use Raven\Context\TagsContext; use Raven\Event; use Raven\Serializer; @@ -295,14 +297,14 @@ public function testGetRuntimeContext() { $client = ClientBuilder::create()->getClient(); - $this->assertInstanceOf(Context::class, $client->getRuntimeContext()); + $this->assertInstanceOf(RuntimeContext::class, $client->getRuntimeContext()); } public function testGetServerOsContext() { $client = ClientBuilder::create()->getClient(); - $this->assertInstanceOf(Context::class, $client->getServerOsContext()); + $this->assertInstanceOf(ServerOsContext::class, $client->getServerOsContext()); } public function testAppPathLinux() diff --git a/tests/Context/ServerOsContextTest.php b/tests/Context/ServerOsContextTest.php new file mode 100644 index 000000000..130c11a0d --- /dev/null +++ b/tests/Context/ServerOsContextTest.php @@ -0,0 +1,305 @@ +expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new ServerOsContext($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testMerge($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new ServerOsContext(); + $context->merge($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testSetData($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new ServerOsContext(); + $context->setData($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testReplaceData($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new ServerOsContext(); + $context->replaceData($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + public function valuesDataProvider() + { + return [ + [ + [], + [ + 'name' => php_uname('s'), + 'version' => php_uname('r'), + 'build' => php_uname('v'), + 'kernel_version' => php_uname('a'), + ], + null, + null, + ], + [ + [ + 'name' => 'foo', + ], + [ + 'name' => 'foo', + 'version' => php_uname('r'), + 'build' => php_uname('v'), + 'kernel_version' => php_uname('a'), + ], + null, + null, + ], + [ + [ + 'version' => 'bar', + ], + [ + 'name' => php_uname('s'), + 'version' => 'bar', + 'build' => php_uname('v'), + 'kernel_version' => php_uname('a'), + ], + null, + null, + ], + [ + [ + 'build' => 'baz', + ], + [ + 'name' => php_uname('s'), + 'version' => php_uname('r'), + 'build' => 'baz', + 'kernel_version' => php_uname('a'), + ], + null, + null, + ], + [ + [ + 'kernel_version' => 'foobarbaz', + ], + [ + 'name' => php_uname('s'), + 'version' => php_uname('r'), + 'build' => php_uname('v'), + 'kernel_version' => 'foobarbaz', + ], + null, + null, + ], + [ + [ + 'foo' => 'bar', + ], + [], + UndefinedOptionsException::class, + 'The option "foo" does not exist. Defined options are: "build", "kernel_version", "name", "version".', + ], + [ + [ + 'name' => 1, + ], + [], + InvalidOptionsException::class, + 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + [ + 'version' => 1, + ], + [], + InvalidOptionsException::class, + 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + [ + 'build' => 1, + ], + [], + InvalidOptionsException::class, + 'The option "build" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + [ + 'kernel_version' => 1, + ], + [], + InvalidOptionsException::class, + 'The option "kernel_version" with value 1 is expected to be of type "string", but is of type "integer".', + ], + ]; + } + + /** + * @dataProvider offsetSetDataProvider + */ + public function testOffsetSet($key, $value, $expectedExceptionClass, $expectedExceptionMessage) + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = new ServerOsContext(); + $context[$key] = $value; + + $this->assertArraySubset([$key => $value], $context->toArray()); + } + + public function offsetSetDataProvider() + { + return [ + [ + 'name', + 'foo', + null, + null, + ], + [ + 'name', + 1, + InvalidOptionsException::class, + 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + 'version', + 'foo', + null, + null, + ], + [ + 'version', + 1, + InvalidOptionsException::class, + 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + 'build', + 'foo', + null, + null, + ], + [ + 'build', + 1, + InvalidOptionsException::class, + 'The option "build" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + 'kernel_version', + 'foobarbaz', + null, + null, + ], + [ + 'kernel_version', + 1, + InvalidOptionsException::class, + 'The option "kernel_version" with value 1 is expected to be of type "string", but is of type "integer".', + ], + [ + 'foo', + 'bar', + UndefinedOptionsException::class, + 'The option "foo" does not exist. Defined options are: "build", "kernel_version", "name", "version".', + ], + ]; + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters($getterMethod, $setterMethod, $value) + { + $context = new ServerOsContext(); + $context->$setterMethod($value); + + $this->assertEquals($value, $context->$getterMethod()); + } + + public function gettersAndSettersDataProvider() + { + return [ + [ + 'getName', + 'setName', + 'foo', + ], + [ + 'getVersion', + 'setVersion', + 'bar', + ], + [ + 'getBuild', + 'setBuild', + 'baz', + ], + [ + 'getKernelVersion', + 'setKernelVersion', + 'foobarbaz', + ], + ]; + } +} From 8dc3f417cc2ff8c43d0ccfb82432db68752fb518 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 30 Mar 2018 01:09:26 +0200 Subject: [PATCH 0331/1161] Support logging of Composer packages (#577) --- composer.json | 2 +- lib/Raven/Configuration.php | 4 +- lib/Raven/Middleware/ModulesMiddleware.php | 71 ++++++++++++++++++++++ tests/Fixtures/composer.json | 8 +++ tests/Fixtures/composer.lock | 18 ++++++ tests/Middleware/ModulesMiddlewareTest.php | 57 +++++++++++++++++ 6 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 lib/Raven/Middleware/ModulesMiddleware.php create mode 100644 tests/Fixtures/composer.json create mode 100644 tests/Fixtures/composer.lock create mode 100644 tests/Middleware/ModulesMiddlewareTest.php diff --git a/composer.json b/composer.json index 1b558f5c5..b5d3a6719 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,6 @@ ], "require": { "php": "^5.6|^7.0", - "ext-json": "*", "ext-mbstring": "*", "php-http/async-client-implementation": "~1.0", "php-http/client-common": "~1.5", @@ -25,6 +24,7 @@ "zendframework/zend-diactoros": "~1.4" }, "require-dev": { + "composer/composer": "^1.6", "friendsofphp/php-cs-fixer": "~2.1", "monolog/monolog": "~1.0", "php-http/curl-client": "~1.7", diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 68cf86496..6fdd465cc 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -435,7 +435,7 @@ public function getProjectId() /** * Gets the project which the authenticated user is bound to. * - * @return string + * @return string|null */ public function getProjectRoot() { @@ -445,7 +445,7 @@ public function getProjectRoot() /** * Sets the project which the authenticated user is bound to. * - * @param string $path The path to the project root + * @param string|null $path The path to the project root */ public function setProjectRoot($path) { diff --git a/lib/Raven/Middleware/ModulesMiddleware.php b/lib/Raven/Middleware/ModulesMiddleware.php new file mode 100644 index 000000000..ce1354b1c --- /dev/null +++ b/lib/Raven/Middleware/ModulesMiddleware.php @@ -0,0 +1,71 @@ + + */ +final class ModulesMiddleware +{ + /** + * @var Configuration The Raven client configuration + */ + private $config; + + /** + * Constructor. + * + * @param Configuration $config The Raven client configuration + */ + public function __construct(Configuration $config) + { + if (!class_exists(Composer::class)) { + throw new \LogicException('You need the "composer/composer" package in order to use this middleware.'); + } + + $this->config = $config; + } + + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) + { + $composerFilePath = $this->config->getProjectRoot() . DIRECTORY_SEPARATOR . 'composer.json'; + + if (file_exists($composerFilePath)) { + $composer = Factory::create(new NullIO(), $composerFilePath, true); + $locker = $composer->getLocker(); + + if ($locker->isLocked()) { + $modules = []; + + foreach ($locker->getLockedRepository()->getPackages() as $package) { + $modules[$package->getName()] = $package->getVersion(); + } + + $event = $event->withModules($modules); + } + } + + return $next($event, $request, $exception, $payload); + } +} diff --git a/tests/Fixtures/composer.json b/tests/Fixtures/composer.json new file mode 100644 index 000000000..1736850eb --- /dev/null +++ b/tests/Fixtures/composer.json @@ -0,0 +1,8 @@ +{ + "name": "vendor-name/project-name", + "type": "library", + "require": { + "foo/bar": "1.2.3", + "foo/baz": "4.5.6" + } +} diff --git a/tests/Fixtures/composer.lock b/tests/Fixtures/composer.lock new file mode 100644 index 000000000..f38dcd18e --- /dev/null +++ b/tests/Fixtures/composer.lock @@ -0,0 +1,18 @@ +{ + "packages": [ + { + "name": "foo/bar", + "version": "1.2.3" + }, + { + "name": "foo/baz", + "version": "4.5.6" + } + ], + "packages-dev": null, + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false +} diff --git a/tests/Middleware/ModulesMiddlewareTest.php b/tests/Middleware/ModulesMiddlewareTest.php new file mode 100644 index 000000000..9a4eb02cd --- /dev/null +++ b/tests/Middleware/ModulesMiddlewareTest.php @@ -0,0 +1,57 @@ + __DIR__ . '/../Fixtures']); + $event = new Event($configuration); + + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($event, &$callbackInvoked) { + $this->assertNotSame($event, $eventArg); + $this->assertEquals(['foo/bar' => '1.2.3.0', 'foo/baz' => '4.5.6.0'], $eventArg->getModules()); + + $callbackInvoked = true; + }; + + $middleware = new ModulesMiddleware($configuration); + $middleware($event, $callback); + + $this->assertTrue($callbackInvoked); + } + + public function testInvokeDoesNothingWhenNoComposerLockFileExists() + { + $configuration = new Configuration(['project_root' => __DIR__]); + $event = new Event($configuration); + + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($event, &$callbackInvoked) { + $this->assertSame($event, $eventArg); + + $callbackInvoked = true; + }; + + $middleware = new ModulesMiddleware($configuration); + $middleware($event, $callback); + + $this->assertTrue($callbackInvoked); + } +} From 1b1d39ded4b10f60302adcf26a734bcc4131baeb Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 30 Mar 2018 21:22:35 +0200 Subject: [PATCH 0332/1161] Add support for ordering the middlewares (#565) --- lib/Raven/Client.php | 106 ++++----- lib/Raven/ClientBuilder.php | 39 ++++ lib/Raven/ClientBuilderInterface.php | 31 ++- .../BreadcrumbInterfaceMiddleware.php | 4 +- .../Middleware/ContextInterfaceMiddleware.php | 4 +- .../ExceptionInterfaceMiddleware.php | 4 +- .../Middleware/MessageInterfaceMiddleware.php | 4 +- lib/Raven/Middleware/MiddlewareStack.php | 171 ++++++++++++++ lib/Raven/Middleware/ProcessorMiddleware.php | 4 +- .../Middleware/RequestInterfaceMiddleware.php | 4 +- .../Middleware/UserInterfaceMiddleware.php | 4 +- lib/Raven/Processor/ProcessorRegistry.php | 2 +- tests/ClientBuilderTest.php | 30 ++- tests/ClientTest.php | 93 +++++--- tests/Middleware/MiddlewareStackTest.php | 214 ++++++++++++++++++ tests/Middleware/ProcessorMiddlewareTest.php | 4 +- 16 files changed, 601 insertions(+), 117 deletions(-) create mode 100644 lib/Raven/Middleware/MiddlewareStack.php create mode 100644 tests/Middleware/MiddlewareStackTest.php diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index a4833120d..56d80beb8 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -11,7 +11,6 @@ namespace Raven; -use Psr\Http\Message\ServerRequestInterface; use Raven\Breadcrumbs\Breadcrumb; use Raven\Breadcrumbs\Recorder; use Raven\Context\Context; @@ -22,6 +21,7 @@ use Raven\Middleware\ContextInterfaceMiddleware; use Raven\Middleware\ExceptionInterfaceMiddleware; use Raven\Middleware\MessageInterfaceMiddleware; +use Raven\Middleware\MiddlewareStack; use Raven\Middleware\ProcessorMiddleware; use Raven\Middleware\RequestInterfaceMiddleware; use Raven\Middleware\UserInterfaceMiddleware; @@ -131,14 +131,9 @@ class Client private $serverOsContext; /** - * @var callable The tip of the middleware call stack + * @var MiddlewareStack The stack of middlewares to compose an event to send */ - private $middlewareStackTip; - - /** - * @var bool Whether the stack of middleware callables is locked - */ - private $stackLocked = false; + private $middlewareStack; /** * @var Event The last event that was captured @@ -165,21 +160,21 @@ public function __construct(Configuration $config, TransportInterface $transport $this->transaction = new TransactionStack(); $this->serializer = new Serializer($this->config->getMbDetectOrder()); $this->reprSerializer = new ReprSerializer($this->config->getMbDetectOrder()); - $this->middlewareStackTip = function (Event $event) { + $this->middlewareStack = new MiddlewareStack(function (Event $event) { return $event; - }; - - $this->addMiddleware(new ProcessorMiddleware($this->processorRegistry)); - $this->addMiddleware(new MessageInterfaceMiddleware()); - $this->addMiddleware(new RequestInterfaceMiddleware()); - $this->addMiddleware(new UserInterfaceMiddleware()); - $this->addMiddleware(new ContextInterfaceMiddleware($this->tagsContext, Context::CONTEXT_TAGS)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->userContext, Context::CONTEXT_USER)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->extraContext, Context::CONTEXT_EXTRA)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->runtimeContext, Context::CONTEXT_RUNTIME)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->serverOsContext, Context::CONTEXT_SERVER_OS)); - $this->addMiddleware(new BreadcrumbInterfaceMiddleware($this->recorder)); - $this->addMiddleware(new ExceptionInterfaceMiddleware($this)); + }); + + $this->middlewareStack->addMiddleware(new ProcessorMiddleware($this->processorRegistry), -255); + $this->middlewareStack->addMiddleware(new MessageInterfaceMiddleware()); + $this->middlewareStack->addMiddleware(new RequestInterfaceMiddleware()); + $this->middlewareStack->addMiddleware(new UserInterfaceMiddleware()); + $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->tagsContext, Context::CONTEXT_TAGS)); + $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->userContext, Context::CONTEXT_USER)); + $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->extraContext, Context::CONTEXT_EXTRA)); + $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->runtimeContext, Context::CONTEXT_RUNTIME)); + $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->serverOsContext, Context::CONTEXT_SERVER_OS)); + $this->middlewareStack->addMiddleware(new BreadcrumbInterfaceMiddleware($this->recorder)); + $this->middlewareStack->addMiddleware(new ExceptionInterfaceMiddleware($this)); if (static::isHttpRequest() && isset($_SERVER['PATH_INFO'])) { $this->transaction->push($_SERVER['PATH_INFO']); @@ -223,29 +218,26 @@ public function getConfig() } /** - * Adds a new middleware to the end of the stack. - * - * @param callable $callable The middleware + * Adds a new middleware with the given priority to the stack. * - * @throws \RuntimeException If this method is called while the stack is dequeuing + * @param callable $middleware The middleware instance + * @param int $priority The priority. The higher this value, the + * earlier a processor will be executed in + * the chain (defaults to 0) */ - public function addMiddleware(callable $callable) + public function addMiddleware(callable $middleware, $priority = 0) { - if ($this->stackLocked) { - throw new \RuntimeException('Middleware can\'t be added once the stack is dequeuing'); - } - - $next = $this->middlewareStackTip; - - $this->middlewareStackTip = function (Event $event, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) use ($callable, $next) { - $result = $callable($event, $next, $request, $exception, $payload); - - if (!$result instanceof Event) { - throw new \UnexpectedValueException(sprintf('Middleware must return an instance of the "%s" class.', Event::class)); - } + $this->middlewareStack->addMiddleware($middleware, $priority); + } - return $result; - }; + /** + * Removes the given middleware from the stack. + * + * @param callable $middleware The middleware instance + */ + public function removeMiddleware(callable $middleware) + { + $this->middlewareStack->removeMiddleware($middleware); } /** @@ -441,7 +433,13 @@ public function capture(array $payload) $payload['message'] = substr($payload['message'], 0, static::MESSAGE_LIMIT); } - $event = $this->callMiddlewareStack($event, static::isHttpRequest() ? ServerRequestFactory::fromGlobals() : null, isset($payload['exception']) ? $payload['exception'] : null, $payload); + $event = $this->middlewareStack->executeStack( + $event, + static::isHttpRequest() ? ServerRequestFactory::fromGlobals() : null, + isset($payload['exception']) ? $payload['exception'] : null, + $payload + ); + $event = $this->sanitize($event); $this->send($event); @@ -601,28 +599,4 @@ public function setAllObjectSerialize($value) $this->serializer->setAllObjectSerialize($value); $this->reprSerializer->setAllObjectSerialize($value); } - - /** - * Calls the middleware stack. - * - * @param Event $event The event object - * @param ServerRequestInterface|null $request The request object, if available - * @param \Exception|null $exception The thrown exception, if any - * @param array $payload Additional payload data - * - * @return Event - */ - private function callMiddlewareStack(Event $event, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) - { - $start = $this->middlewareStackTip; - - $this->stackLocked = true; - - /** @var Event $event */ - $event = $start($event, $request, $exception, $payload); - - $this->stackLocked = false; - - return $event; - } } diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index 7009b35ac..2c8a2953e 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -111,6 +111,11 @@ final class ClientBuilder implements ClientBuilderInterface */ private $httpClientPlugins = []; + /** + * @var array List of middlewares and their priorities + */ + private $middlewares = []; + /** * @var array List of processors and their priorities */ @@ -201,6 +206,40 @@ public function removeHttpClientPlugin($className) return $this; } + /** + * {@inheritdoc} + */ + public function addMiddleware(callable $middleware, $priority = 0) + { + $this->middlewares[] = [$middleware, $priority]; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function removeMiddleware(callable $middleware) + { + foreach ($this->middlewares as $key => $value) { + if ($value[0] !== $middleware) { + continue; + } + + unset($this->middlewares[$key]); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMiddlewares() + { + return $this->middlewares; + } + /** * {@inheritdoc} */ diff --git a/lib/Raven/ClientBuilderInterface.php b/lib/Raven/ClientBuilderInterface.php index a4d476f41..4d7779af8 100644 --- a/lib/Raven/ClientBuilderInterface.php +++ b/lib/Raven/ClientBuilderInterface.php @@ -88,6 +88,35 @@ public function addHttpClientPlugin(Plugin $plugin); */ public function removeHttpClientPlugin($className); + /** + * Adds a new middleware with the given priority to the stack. + * + * @param callable $middleware The middleware instance + * @param int $priority The priority. The higher this value, the + * earlier a processor will be executed in + * the chain (defaults to 0) + * + * @return $this + */ + public function addMiddleware(callable $middleware, $priority = 0); + + /** + * Removes the given middleware from the stack. + * + * @param callable $middleware The middleware instance + * + * @return $this + */ + public function removeMiddleware(callable $middleware); + + /** + * Gets the list of middlewares that will be added to the client at the + * given priority. + * + * @return array + */ + public function getMiddlewares(); + /** * Adds a new processor to the processors chain with the specified priority. * @@ -110,7 +139,7 @@ public function addProcessor(ProcessorInterface $processor, $priority = 0); public function removeProcessor(ProcessorInterface $processor); /** - * Gets a list of processors that will be added to the client at the + * Gets the list of processors that will be added to the client at the * given priority. * * @return array diff --git a/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php b/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php index e18af7af9..c97ed8ef5 100644 --- a/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php +++ b/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php @@ -44,12 +44,12 @@ public function __construct(Recorder $recorder) * @param Event $event The event being processed * @param callable $next The next middleware to call * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|null $exception The thrown exception, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available * @param array $payload Additional data * * @return Event */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { foreach ($this->recorder as $breadcrumb) { $event = $event->withBreadcrumb($breadcrumb); diff --git a/lib/Raven/Middleware/ContextInterfaceMiddleware.php b/lib/Raven/Middleware/ContextInterfaceMiddleware.php index 9b6990731..85e12c2aa 100644 --- a/lib/Raven/Middleware/ContextInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ContextInterfaceMiddleware.php @@ -51,12 +51,12 @@ public function __construct(Context $context, $contextName) * @param Event $event The event being processed * @param callable $next The next middleware to call * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|null $exception The thrown exception, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available * @param array $payload Additional data * * @return Event */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { $context = isset($payload[$this->contextName . '_context']) ? $payload[$this->contextName . '_context'] : []; $context = array_merge($this->context->toArray(), $context); diff --git a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php index 1fef44e4c..1769d47d8 100644 --- a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php @@ -44,12 +44,12 @@ public function __construct(Client $client) * @param Event $event The event being processed * @param callable $next The next middleware to call * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|null $exception The thrown exception, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available * @param array $payload Additional data * * @return Event */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { // Do not override the level if it was set explicitly by the user if (!isset($payload['level']) && $exception instanceof \ErrorException) { diff --git a/lib/Raven/Middleware/MessageInterfaceMiddleware.php b/lib/Raven/Middleware/MessageInterfaceMiddleware.php index e338e4aa2..6a7f787b0 100644 --- a/lib/Raven/Middleware/MessageInterfaceMiddleware.php +++ b/lib/Raven/Middleware/MessageInterfaceMiddleware.php @@ -28,12 +28,12 @@ final class MessageInterfaceMiddleware * @param Event $event The event being processed * @param callable $next The next middleware to call * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|null $exception The thrown exception, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available * @param array $payload Additional data * * @return Event */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { $message = isset($payload['message']) ? $payload['message'] : null; $messageParams = isset($payload['message_params']) ? $payload['message_params'] : []; diff --git a/lib/Raven/Middleware/MiddlewareStack.php b/lib/Raven/Middleware/MiddlewareStack.php new file mode 100644 index 000000000..17c5d2fe9 --- /dev/null +++ b/lib/Raven/Middleware/MiddlewareStack.php @@ -0,0 +1,171 @@ + + */ +class MiddlewareStack +{ + /** + * @var callable The handler that will end the middleware call stack + */ + private $handler; + + /** + * @var array The list of middlewares + */ + private $stack = []; + + /** + * @var callable The tip of the middleware call stack + */ + private $middlewareStackTip; + + /** + * @var bool Whether the stack of middleware callables is locked + */ + private $stackLocked = false; + + /** + * Constructor. + * + * @param callable $handler The handler that will end the middleware call stack + */ + public function __construct(callable $handler) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler. + * + * @param Event $event The event being processed + * @param ServerRequestInterface|null $request The request, if available + * @param \Throwable|\Exception|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function executeStack(Event $event, ServerRequestInterface $request = null, $exception = null, array $payload = []) + { + $handler = $this->resolve(); + + $this->stackLocked = true; + + $event = $handler($event, $request, $exception, $payload); + + $this->stackLocked = false; + + if (!$event instanceof Event) { + throw new \UnexpectedValueException(sprintf('Middleware must return an instance of the "%s" class.', Event::class)); + } + + return $event; + } + + /** + * Adds a new middleware with the given priority to the stack. + * + * @param callable $middleware The middleware instance + * @param int $priority The priority. The higher this value, the + * earlier a processor will be executed in + * the chain (defaults to 0) + * + * @throws \RuntimeException If the method is called while the stack is dequeuing + */ + public function addMiddleware(callable $middleware, $priority = 0) + { + if ($this->stackLocked) { + throw new \RuntimeException('Middleware can\'t be added once the stack is dequeuing.'); + } + + $this->middlewareStackTip = null; + + $this->stack[$priority][] = $middleware; + } + + /** + * Removes the given middleware from the stack. + * + * @param callable $middleware The middleware instance + * + * @return bool True if the middleware was removed, false otherwise + * + * @throws \RuntimeException If the method is called while the stack is dequeuing + */ + public function removeMiddleware(callable $middleware) + { + if ($this->stackLocked) { + throw new \RuntimeException('Middleware can\'t be removed once the stack is dequeuing.'); + } + + $this->middlewareStackTip = null; + + $result = false; + + foreach ($this->stack as $priority => &$middlewares) { + foreach ($middlewares as $index => $value) { + if ($middleware !== $value) { + continue; + } + + array_splice($middlewares, $index, 1); + + $result = true; + } + } + + // Free the memory by breaking the reference + unset($middlewares); + + return $result; + } + + /** + * Resolves the stack of middleware callables into a chain where each middleware + * will call the next one in order of priority. + * + * @return callable + */ + private function resolve() + { + if (null === $this->middlewareStackTip) { + $prev = $this->handler; + + ksort($this->stack); + + if (!empty($this->stack)) { + foreach (array_merge(...$this->stack) as $middleware) { + $prev = function (Event $event, ServerRequestInterface $request = null, $exception = null, array $payload = []) use ($middleware, $prev) { + $event = $middleware($event, $prev, $request, $exception, $payload); + + if (!$event instanceof Event) { + throw new \UnexpectedValueException(sprintf('Middleware must return an instance of the "%s" class.', Event::class)); + } + + return $event; + }; + } + } + + $this->middlewareStackTip = $prev; + } + + return $this->middlewareStackTip; + } +} diff --git a/lib/Raven/Middleware/ProcessorMiddleware.php b/lib/Raven/Middleware/ProcessorMiddleware.php index d17f88800..100351143 100644 --- a/lib/Raven/Middleware/ProcessorMiddleware.php +++ b/lib/Raven/Middleware/ProcessorMiddleware.php @@ -44,12 +44,12 @@ public function __construct(ProcessorRegistry $processorRegistry) * @param Event $event The event being processed * @param callable $next The next middleware to call * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|null $exception The thrown exception, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available * @param array $payload Additional data * * @return Event */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { foreach ($this->processorRegistry->getProcessors() as $processor) { $event = $processor->process($event); diff --git a/lib/Raven/Middleware/RequestInterfaceMiddleware.php b/lib/Raven/Middleware/RequestInterfaceMiddleware.php index 4115c3245..a82091bcc 100644 --- a/lib/Raven/Middleware/RequestInterfaceMiddleware.php +++ b/lib/Raven/Middleware/RequestInterfaceMiddleware.php @@ -28,12 +28,12 @@ final class RequestInterfaceMiddleware * @param Event $event The event being processed * @param callable $next The next middleware to call * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|null $exception The thrown exception, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available * @param array $payload Additional data * * @return Event */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { if (null === $request) { return $next($event, $request, $exception, $payload); diff --git a/lib/Raven/Middleware/UserInterfaceMiddleware.php b/lib/Raven/Middleware/UserInterfaceMiddleware.php index 1f425ce72..3b5ad391d 100644 --- a/lib/Raven/Middleware/UserInterfaceMiddleware.php +++ b/lib/Raven/Middleware/UserInterfaceMiddleware.php @@ -27,12 +27,12 @@ final class UserInterfaceMiddleware * @param Event $event The event being processed * @param callable $next The next middleware to call * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|null $exception The thrown exception, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available * @param array $payload Additional data * * @return Event */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { $userContext = $event->getUserContext(); diff --git a/lib/Raven/Processor/ProcessorRegistry.php b/lib/Raven/Processor/ProcessorRegistry.php index 2f54240d3..acef94295 100644 --- a/lib/Raven/Processor/ProcessorRegistry.php +++ b/lib/Raven/Processor/ProcessorRegistry.php @@ -17,7 +17,7 @@ * * @author Stefano Arlandini */ -final class ProcessorRegistry +class ProcessorRegistry { /** * @var array List of processors sorted by priority diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index cf3f12574..08282a25f 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -140,6 +140,32 @@ public function testRemoveHttpClientPlugin() $this->assertSame($plugin2, reset($plugins)); } + public function testAddMiddlewares() + { + $middleware = function () {}; + + $clientBuilder = new ClientBuilder(); + $clientBuilder->addMiddleware($middleware, -10); + + $this->assertEquals([[$middleware, -10]], $clientBuilder->getMiddlewares()); + } + + public function testRemoveMiddleware() + { + $middleware1 = function () {}; + $middleware2 = function () {}; + + $clientBuilder = new ClientBuilder(); + $clientBuilder->addMiddleware($middleware1, -10); + $clientBuilder->addMiddleware($middleware2, 10); + + $this->assertEquals([[$middleware1, -10], [$middleware2, 10]], $clientBuilder->getMiddlewares()); + + $clientBuilder->removeMiddleware($middleware2); + + $this->assertEquals([[$middleware1, -10]], $clientBuilder->getMiddlewares()); + } + public function testAddProcessor() { /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ @@ -148,7 +174,7 @@ public function testAddProcessor() $clientBuilder = new ClientBuilder(); $clientBuilder->addProcessor($processor, -10); - $this->assertAttributeContains([$processor, -10], 'processors', $clientBuilder); + $this->assertContains([$processor, -10], $clientBuilder->getProcessors()); } public function testRemoveProcessor() @@ -160,7 +186,7 @@ public function testRemoveProcessor() $clientBuilder->addProcessor($processor, -10); $clientBuilder->removeProcessor($processor); - $this->assertAttributeNotContains([$processor, -10], 'processors', $clientBuilder); + $this->assertNotContains([$processor, -10], $clientBuilder->getProcessors()); } public function testGetClient() diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 5cc6da609..484b015c3 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -23,6 +23,9 @@ use Raven\Context\ServerOsContext; use Raven\Context\TagsContext; use Raven\Event; +use Raven\Middleware\MiddlewareStack; +use Raven\Processor\ProcessorInterface; +use Raven\Processor\ProcessorRegistry; use Raven\Serializer; use Raven\Transport\TransportInterface; @@ -66,56 +69,84 @@ public function registerShutdownFunction() class ClientTest extends TestCase { - public function testMiddlewareStackIsSeeded() + public function testAddMiddleware() { + $middleware = function () {}; + + /** @var MiddlewareStack|\PHPUnit_Framework_MockObject_MockObject $middlewareStack */ + $middlewareStack = $this->createMock(MiddlewareStack::class); + $middlewareStack->expects($this->once()) + ->method('addMiddleware') + ->with($middleware, -10); + $client = ClientBuilder::create()->getClient(); - $firstMiddleware = $this->getObjectAttribute($client, 'middlewareStackTip'); - $lastMiddleware = null; + $reflectionProperty = new \ReflectionProperty($client, 'middlewareStack'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($client, $middlewareStack); + $reflectionProperty->setAccessible(false); - $client->addMiddleware(function (Event $event, callable $next) use (&$lastMiddleware) { - $lastMiddleware = $next; + $client->addMiddleware($middleware, -10); + } - return $event; - }); + public function testRemoveMiddleware() + { + $middleware = function () {}; - $client->capture([]); + /** @var MiddlewareStack|\PHPUnit_Framework_MockObject_MockObject $middlewareStack */ + $middlewareStack = $this->createMock(MiddlewareStack::class); + $middlewareStack->expects($this->once()) + ->method('removeMiddleware') + ->with($middleware); + + $client = ClientBuilder::create()->getClient(); - $this->assertSame($firstMiddleware, $lastMiddleware); + $reflectionProperty = new \ReflectionProperty($client, 'middlewareStack'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($client, $middlewareStack); + $reflectionProperty->setAccessible(false); + + $client->removeMiddleware($middleware, -10); } - /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Middleware can't be added once the stack is dequeuing - */ - public function testAddMiddlewareThrowsWhileStackIsRunning() + public function testAddProcessor() { - $client = ClientBuilder::create()->getClient(); + /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ + $processor = $this->createMock(ProcessorInterface::class); - $client->addMiddleware(function (Event $event) use ($client) { - $client->addMiddleware(function () { - // Do nothing, it's just a middleware added to trigger the exception - }); + $processorRegistry = $this->createMock(ProcessorRegistry::class); + $processorRegistry->expects($this->once()) + ->method('addProcessor') + ->with($processor, -10); - return $event; - }); + $client = ClientBuilder::create()->getClient(); - $client->capture([]); + $reflectionProperty = new \ReflectionProperty($client, 'processorRegistry'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($client, $processorRegistry); + $reflectionProperty->setAccessible(false); + + $client->addProcessor($processor, -10); } - /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Middleware must return an instance of the "Raven\Event" class. - */ - public function testMiddlewareThrowsWhenBadValueIsReturned() + public function testRemoveProcessor() { + /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ + $processor = $this->createMock(ProcessorInterface::class); + + $processorRegistry = $this->createMock(ProcessorRegistry::class); + $processorRegistry->expects($this->once()) + ->method('removeProcessor') + ->with($processor); + $client = ClientBuilder::create()->getClient(); - $client->addMiddleware(function () { - // Do nothing, it's just a middleware added to trigger the exception - }); + $reflectionProperty = new \ReflectionProperty($client, 'processorRegistry'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($client, $processorRegistry); + $reflectionProperty->setAccessible(false); - $client->capture([]); + $client->removeProcessor($processor); } public function testCaptureMessage() diff --git a/tests/Middleware/MiddlewareStackTest.php b/tests/Middleware/MiddlewareStackTest.php new file mode 100644 index 000000000..dddd4e5d1 --- /dev/null +++ b/tests/Middleware/MiddlewareStackTest.php @@ -0,0 +1,214 @@ +createMock(ServerRequestInterface::class); + + $capturedException = new \Exception(); + $capturedPayload = ['foo' => 'bar']; + + $handlerCalled = false; + $handler = function (Event $event, ServerRequestInterface $request = null, $exception = null, array $payload = []) use (&$handlerCalled, $capturedRequest, $capturedException, $capturedPayload) { + // These asserts verify that the arguments passed through all + // middlewares without getting lost + $this->assertSame($capturedRequest, $request); + $this->assertSame($capturedException, $exception); + $this->assertSame($capturedPayload, $payload); + + $handlerCalled = true; + + return $event; + }; + + $middlewareCalls = [false, false]; + + $middlewareStack = new MiddlewareStack($handler); + $middlewareStack->addMiddleware($this->createMiddlewareAssertingInvokation($middlewareCalls[0])); + $middlewareStack->addMiddleware($this->createMiddlewareAssertingInvokation($middlewareCalls[1])); + + $middlewareStack->executeStack($event, $capturedRequest, $capturedException, $capturedPayload); + + $this->assertTrue($handlerCalled); + $this->assertNotContains(false, $middlewareCalls); + } + + public function testAddMiddleware() + { + $middlewareCalls = []; + + $handler = function (Event $event) use (&$middlewareCalls) { + $middlewareCalls[] = 4; + + return $event; + }; + + $middleware1 = function (Event $event, callable $next) use (&$middlewareCalls) { + $middlewareCalls[] = 1; + + return $next($event); + }; + + $middleware2 = function (Event $event, callable $next) use (&$middlewareCalls) { + $middlewareCalls[] = 2; + + return $next($event); + }; + + $middleware3 = function (Event $event, callable $next) use (&$middlewareCalls) { + $middlewareCalls[] = 3; + + return $next($event); + }; + + $middlewareStack = new MiddlewareStack($handler); + $middlewareStack->addMiddleware($middleware1, -10); + $middlewareStack->addMiddleware($middleware2); + $middlewareStack->addMiddleware($middleware3, -10); + + $middlewareStack->executeStack(new Event(new Configuration())); + + $this->assertEquals([2, 3, 1, 4], $middlewareCalls); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Middleware can't be added once the stack is dequeuing. + */ + public function testAddMiddlewareThrowsWhileStackIsRunning() + { + /** @var MiddlewareStack $middlewareStack */ + $middlewareStack = null; + + $middlewareStack = new MiddlewareStack(function () use (&$middlewareStack) { + $middlewareStack->addMiddleware(function () { + // Returning something is not important as the expected exception + // should be thrown before this point is ever reached + }); + }); + + $middlewareStack->executeStack(new Event(new Configuration())); + } + + public function testRemoveMiddleware() + { + $middlewareCalls = []; + + $middleware1 = function (Event $event, callable $next) use (&$middlewareCalls) { + $middlewareCalls[] = 1; + + return $next($event); + }; + + $middleware2 = function (Event $event, callable $next) use (&$middlewareCalls) { + $middlewareCalls[] = 2; + + return $next($event); + }; + + $middleware3 = function (Event $event, callable $next) use (&$middlewareCalls) { + $middlewareCalls[] = 3; + + return $next($event); + }; + + $middlewareStack = new MiddlewareStack(function (Event $event) use (&$middlewareCalls) { + $middlewareCalls[] = 4; + + return $event; + }); + + $this->assertFalse($middlewareStack->removeMiddleware($middleware1)); + + $middlewareStack->addMiddleware($middleware1, -10); + $middlewareStack->addMiddleware($middleware2); + $middlewareStack->addMiddleware($middleware3, -10); + + $this->assertTrue($middlewareStack->removeMiddleware($middleware3)); + + $middlewareStack->executeStack(new Event(new Configuration())); + + $this->assertEquals([2, 1, 4], $middlewareCalls); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Middleware can't be removed once the stack is dequeuing. + */ + public function testRemoveMiddlewareThrowsWhileStackIsRunning() + { + /** @var MiddlewareStack $middlewareStack */ + $middlewareStack = null; + + $middlewareStack = new MiddlewareStack(function () use (&$middlewareStack) { + $middlewareStack->removeMiddleware(function () { + // Returning something is not important as the expected exception + // should be thrown before this point is ever reached + }); + }); + + $middlewareStack->executeStack(new Event(new Configuration())); + } + + /** + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage Middleware must return an instance of the "Raven\Event" class. + */ + public function testMiddlewareThrowsWhenBadValueIsReturned() + { + $event = new Event(new Configuration()); + $middlewareStack = new MiddlewareStack(function (Event $event) { + return $event; + }); + + $middlewareStack->addMiddleware(function () { + // Return nothing so that the expected exception is triggered + }); + + $middlewareStack->executeStack($event); + } + + /** + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage Middleware must return an instance of the "Raven\Event" class. + */ + public function testMiddlewareThrowsWhenBadValueIsReturnedFromHandler() + { + $event = new Event(new Configuration()); + $middlewareStack = new MiddlewareStack(function () { + // Return nothing so that the expected exception is triggered + }); + + $middlewareStack->executeStack($event); + } + + protected function createMiddlewareAssertingInvokation(&$wasCalled) + { + return function (Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) use (&$wasCalled) { + $wasCalled = true; + + return $next($event, $request, $exception, $payload); + }; + } +} diff --git a/tests/Middleware/ProcessorMiddlewareTest.php b/tests/Middleware/ProcessorMiddlewareTest.php index 32e91659c..4d642f4c5 100644 --- a/tests/Middleware/ProcessorMiddlewareTest.php +++ b/tests/Middleware/ProcessorMiddlewareTest.php @@ -21,7 +21,7 @@ class ProcessorMiddlewareTest extends TestCase { public function testInvoke() { - $client = ClientBuilder::create([])->getClient(); + $client = ClientBuilder::create()->getClient(); $event = new Event($client->getConfig()); $processorRegistry = $this->getObjectAttribute($client, 'processorRegistry'); @@ -45,7 +45,7 @@ public function testInvoke() */ public function testInvokeProcessorThatReturnsNothingThrows() { - $client = ClientBuilder::create([])->getClient(); + $client = ClientBuilder::create()->getClient(); $event = new Event($client->getConfig()); $processorRegistry = $this->getObjectAttribute($client, 'processorRegistry'); From 494b7eeb4601c36c994dbce874b0ea6cded3eec8 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 3 Apr 2018 09:56:44 +0200 Subject: [PATCH 0333/1161] Refactor the TransactionStack class (#566) --- UPGRADE-2.0.md | 67 ++++++++++- lib/Raven/Client.php | 71 ++++++----- lib/Raven/TransactionStack.php | 98 ++++++++++----- tests/Breadcrumbs/ErrorHandlerTest.php | 2 +- tests/Breadcrumbs/MonologHandlerTest.php | 4 +- tests/ClientTest.php | 28 ++++- tests/TransactionStackTest.php | 144 ++++++++++++++++++++--- 7 files changed, 336 insertions(+), 78 deletions(-) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index b8ba2ebf6..060043a37 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -377,6 +377,21 @@ $client->getExtraContext()->setData(array('foo' => 'bar')); ``` +- The `transaction` property has been made private. You should use `Client::getTransactionStack` + instead to access the instance of the object. + + Before: + + ```php + $client->transaction->push('foo'); + ``` + + After: + + ```php + $client->getTransactionStack()->push('foo'); + ``` + ### Client builder - To simplify the creation of a `Client` object instance, a new builder class @@ -406,7 +421,7 @@ better reflect its purpose. The constructor accepts an array of options to make the behaviour of which cookies to sanitize configurable. -# Context +### Context - The `Raven_Context` class has been renamed to `Context` and added to the `Raven` namespace to follow the PSR-4 convention. @@ -414,3 +429,53 @@ - The `tags`, `extra` and `user` properties of the `Raven_Context` class have been removed. Each instance of the new class represents now a single context type only and provides some useful methods to interact with the data. + +### Transactions + +- The method `TransactionStack::push` has changed its signature. It used to accept + a single value only, but now more values can be passed in a single call. + + Before: + + ```php + $client->transaction->push('foo'); + $client->transaction->push('bar'); + ``` + + After: + + ```php + $client->getTransactionStack()->push('foo'); + $client->getTransactionStack()->push('bar'); + + // or + + $client->getTransactionStack()->push('foo', 'bar'); + ``` + +- The method `TransactionStack::pop` has changed its signature by removing the + `$context` argument. Consequently the behaviour of the method in regards to + the returned value changed as well: it's not possible anymore to pop all values + up to one that equals the value of `$context`. + + Before: + + ```php + $client->transaction->push('foo', 'bar', 'baz'); + + $value = $client->transaction->pop(); // $value is 'baz' + $value = $client->transaction->pop('foo'); // $value is 'foo' + $value = $client->transaction->pop(); // $value is null + ``` + + After: + + ```php + $client->getTransactionStack()->push('foo', 'bar', 'baz'); + + while (!$client->getTransactionStack()->isEmpty()) { + $value = $client->getTransactionStack()->pop(); // $value is 'baz', then 'bar', then 'foo' + } + + $value = $client->getTransactionStack()->pop(); // $value is null + ``` diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 56d80beb8..ec45568c8 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -55,21 +55,11 @@ class Client */ const MESSAGE_LIMIT = 1024; - /** - * @var Recorder The breadcrumbs recorder - */ - protected $recorder; - /** * This constant defines the client's user-agent string. */ const USER_AGENT = 'sentry-php/' . self::VERSION; - /** - * @var TransactionStack The transaction stack - */ - public $transaction; - /** * @var string[]|null */ @@ -95,6 +85,16 @@ class Client */ protected $config; + /** + * @var Recorder The breadcrumbs recorder + */ + private $breadcrumbRecorder; + + /** + * @var TransactionStack The transaction stack + */ + private $transactionStack; + /** * @var TransportInterface The transport */ @@ -156,28 +156,31 @@ public function __construct(Configuration $config, TransportInterface $transport $this->extraContext = new Context(); $this->runtimeContext = new RuntimeContext(); $this->serverOsContext = new ServerOsContext(); - $this->recorder = new Recorder(); - $this->transaction = new TransactionStack(); + $this->breadcrumbRecorder = new Recorder(); + $this->transactionStack = new TransactionStack(); $this->serializer = new Serializer($this->config->getMbDetectOrder()); $this->reprSerializer = new ReprSerializer($this->config->getMbDetectOrder()); $this->middlewareStack = new MiddlewareStack(function (Event $event) { return $event; }); - $this->middlewareStack->addMiddleware(new ProcessorMiddleware($this->processorRegistry), -255); - $this->middlewareStack->addMiddleware(new MessageInterfaceMiddleware()); - $this->middlewareStack->addMiddleware(new RequestInterfaceMiddleware()); - $this->middlewareStack->addMiddleware(new UserInterfaceMiddleware()); - $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->tagsContext, Context::CONTEXT_TAGS)); - $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->userContext, Context::CONTEXT_USER)); - $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->extraContext, Context::CONTEXT_EXTRA)); - $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->runtimeContext, Context::CONTEXT_RUNTIME)); - $this->middlewareStack->addMiddleware(new ContextInterfaceMiddleware($this->serverOsContext, Context::CONTEXT_SERVER_OS)); - $this->middlewareStack->addMiddleware(new BreadcrumbInterfaceMiddleware($this->recorder)); - $this->middlewareStack->addMiddleware(new ExceptionInterfaceMiddleware($this)); - - if (static::isHttpRequest() && isset($_SERVER['PATH_INFO'])) { - $this->transaction->push($_SERVER['PATH_INFO']); + $this->addMiddleware(new ProcessorMiddleware($this->processorRegistry), -255); + $this->addMiddleware(new MessageInterfaceMiddleware()); + $this->addMiddleware(new RequestInterfaceMiddleware()); + $this->addMiddleware(new UserInterfaceMiddleware()); + $this->addMiddleware(new ContextInterfaceMiddleware($this->tagsContext, Context::CONTEXT_TAGS)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->userContext, Context::CONTEXT_USER)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->extraContext, Context::CONTEXT_EXTRA)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->runtimeContext, Context::CONTEXT_RUNTIME)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->serverOsContext, Context::CONTEXT_SERVER_OS)); + $this->addMiddleware(new BreadcrumbInterfaceMiddleware($this->breadcrumbRecorder)); + $this->addMiddleware(new ExceptionInterfaceMiddleware($this)); + + $request = ServerRequestFactory::fromGlobals(); + $serverParams = $request->getServerParams(); + + if (isset($serverParams['PATH_INFO'])) { + $this->transactionStack->push($serverParams['PATH_INFO']); } if ($this->config->getSerializeAllObjects()) { @@ -196,7 +199,7 @@ public function __construct(Configuration $config, TransportInterface $transport */ public function leaveBreadcrumb(Breadcrumb $breadcrumb) { - $this->recorder->record($breadcrumb); + $this->breadcrumbRecorder->record($breadcrumb); } /** @@ -204,7 +207,7 @@ public function leaveBreadcrumb(Breadcrumb $breadcrumb) */ public function clearBreadcrumbs() { - $this->recorder->clear(); + $this->breadcrumbRecorder->clear(); } /** @@ -217,6 +220,16 @@ public function getConfig() return $this->config; } + /** + * Gets the transaction stack. + * + * @return TransactionStack + */ + public function getTransactionStack() + { + return $this->transactionStack; + } + /** * Adds a new middleware with the given priority to the stack. * @@ -418,7 +431,7 @@ public function capture(array $payload) if (isset($payload['culprit'])) { $event = $event->withCulprit($payload['culprit']); } else { - $event = $event->withCulprit($this->transaction->peek()); + $event = $event->withCulprit($this->transactionStack->peek()); } if (isset($payload['level'])) { diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index 8cbf82eae..e09489618 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -1,4 +1,5 @@ + */ +final class TransactionStack implements \Countable { /** - * @var array + * @var string[] The transaction stack */ - public $stack; + private $transactions = []; - public function __construct() + /** + * Class constructor. + * + * @param array $values An array of initial values + */ + public function __construct(array $values = []) { - $this->stack = []; + $this->push(...$values); } + /** + * Clears the stack by removing all values. + */ public function clear() { - $this->stack = []; + $this->transactions = []; } - public function peek() + /** + * Checks whether the stack is empty. + * + * @return bool + */ + public function isEmpty() { - $len = count($this->stack); - if (0 === $len) { - return null; - } - - return $this->stack[$len - 1]; + return empty($this->transactions); } - public function push($context) + /** + * Pushes the given values onto the stack. + * + * @param string[] $values The values to push + * + * @throws \InvalidArgumentException If any of the values is not a string + */ + public function push(...$values) { - $this->stack[] = $context; + foreach ($values as $value) { + if (!is_string($value)) { + throw new \InvalidArgumentException(sprintf('The $values argument must contain string values only.')); + } + + $this->transactions[] = $value; + } } - /** @noinspection PhpInconsistentReturnPointsInspection - * @param string|null $context + /** + * Gets the value at the top of the stack without removing it. * - * @return mixed + * @return string|null */ - public function pop($context = null) + public function peek() { - if (!$context) { - return array_pop($this->stack); + if (empty($this->transactions)) { + return null; } - while (!empty($this->stack)) { - if (array_pop($this->stack) === $context) { - return $context; - } + + return $this->transactions[count($this->transactions) - 1]; + } + + /** + * Removes and returns the value at the top of the stack. + * + * @return string|null + */ + public function pop() + { + if (empty($this->transactions)) { + return null; } - // @codeCoverageIgnoreStart + + return array_pop($this->transactions); } - // @codeCoverageIgnoreEnd + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->transactions); + } } diff --git a/tests/Breadcrumbs/ErrorHandlerTest.php b/tests/Breadcrumbs/ErrorHandlerTest.php index 20acadb65..1cbbe5478 100644 --- a/tests/Breadcrumbs/ErrorHandlerTest.php +++ b/tests/Breadcrumbs/ErrorHandlerTest.php @@ -27,7 +27,7 @@ public function testSimple() $handler = new ErrorHandler($client); $handler->handleError(E_WARNING, 'message'); - $breadcrumbsRecorder = $this->getObjectAttribute($client, 'recorder'); + $breadcrumbsRecorder = $this->getObjectAttribute($client, 'breadcrumbRecorder'); /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ $breadcrumbs = iterator_to_array($breadcrumbsRecorder); diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php index 39c2e8d79..b2e42c888 100644 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -54,7 +54,7 @@ public function testSimple() $logger->pushHandler($handler); $logger->addWarning('foo'); - $breadcrumbsRecorder = $this->getObjectAttribute($client, 'recorder'); + $breadcrumbsRecorder = $this->getObjectAttribute($client, 'breadcrumbRecorder'); /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ $breadcrumbs = iterator_to_array($breadcrumbsRecorder); @@ -78,7 +78,7 @@ public function testErrorInMessage() $logger->pushHandler($handler); $logger->addError($this->getSampleErrorMessage()); - $breadcrumbsRecorder = $this->getObjectAttribute($client, 'recorder'); + $breadcrumbsRecorder = $this->getObjectAttribute($client, 'breadcrumbRecorder'); /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ $breadcrumbs = iterator_to_array($breadcrumbsRecorder); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 484b015c3..72b6e0e05 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -69,6 +69,32 @@ public function registerShutdownFunction() class ClientTest extends TestCase { + public function testConstructorInitializesTransactionStack() + { + $_SERVER['PATH_INFO'] = '/foo'; + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $client = ClientBuilder::create()->getClient(); + $transactionStack = $client->getTransactionStack(); + + $this->assertNotEmpty($transactionStack); + $this->assertEquals('/foo', $transactionStack->peek()); + } + + public function testConstructorInitializesTransactionStackInCli() + { + $client = ClientBuilder::create()->getClient(); + + $this->assertEmpty($client->getTransactionStack()); + } + + public function testGetTransactionStack() + { + $client = ClientBuilder::create()->getClient(); + + $this->assertAttributeSame($client->getTransactionStack(), 'transactionStack', $client); + } + public function testAddMiddleware() { $middleware = function () {}; @@ -742,7 +768,7 @@ public function testClearBreadcrumb() ] ) ); - $reflection = new \ReflectionProperty($client, 'recorder'); + $reflection = new \ReflectionProperty($client, 'breadcrumbRecorder'); $reflection->setAccessible(true); $this->assertNotEmpty(iterator_to_array($reflection->getValue($client))); diff --git a/tests/TransactionStackTest.php b/tests/TransactionStackTest.php index 78fdde8f2..acde7aec9 100644 --- a/tests/TransactionStackTest.php +++ b/tests/TransactionStackTest.php @@ -12,27 +12,137 @@ namespace Raven\Tests; use PHPUnit\Framework\TestCase; +use Raven\TransactionStack; class TransactionStackTest extends TestCase { - public function testSimple() - { - $stack = new \Raven\TransactionStack(); - $stack->push('hello'); - /** @noinspection PhpVoidFunctionResultUsedInspection */ - /** @noinspection PhpUnusedLocalVariableInspection */ - $foo = $stack->push('foo'); - $stack->push('bar'); - $stack->push('world'); - $this->assertEquals($stack->peek(), 'world'); - $this->assertEquals($stack->pop(), 'world'); - $this->assertEquals($stack->pop('foo'), 'foo'); - $this->assertEquals($stack->peek(), 'hello'); - $this->assertEquals($stack->pop(), 'hello'); - $this->assertEquals($stack->peek(), null); + public function testConstructor() + { + $stack = new TransactionStack(['a', 'b']); + + $this->assertAttributeEquals(['a', 'b'], 'transactions', $stack); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The $values argument must contain string values only. + */ + public function testConstructorThrowsIfValuesAreNotAllStrings() + { + new TransactionStack(['a', 1]); + } + + public function testClear() + { + $stack = new TransactionStack(['a', 'b']); + + $this->assertAttributeEquals(['a', 'b'], 'transactions', $stack); $stack->clear(); - $this->assertInternalType('array', $stack->stack); - $this->assertEquals(0, count($stack->stack)); + + $this->assertEmpty($stack); + } + + public function testIsEmpty() + { + $stack = new TransactionStack(); + + $this->assertEmpty($stack); + $this->assertTrue($stack->isEmpty()); + + $stack->push('a'); + + $this->assertNotEmpty($stack); + $this->assertFalse($stack->isEmpty()); + } + + public function testPush() + { + $stack = new TransactionStack(); + $stack->push('a'); + + $this->assertAttributeEquals(['a'], 'transactions', $stack); + + $stack->push('b', 'c'); + + $this->assertAttributeEquals(['a', 'b', 'c'], 'transactions', $stack); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The $values argument must contain string values only. + */ + public function testPushThrowsIfValuesAreNotAllStrings() + { + $stack = new TransactionStack(); + + $this->assertEmpty($stack); + + $stack->push('a', 1); + } + + /** + * @dataProvider peekDataProvider + */ + public function testPeek($initialData, $expectedData, $expectedRemainingData) + { + $stack = new TransactionStack($initialData); + + $this->assertEquals($expectedData, $stack->peek()); + $this->assertAttributeEquals($expectedRemainingData, 'transactions', $stack); + } + + public function peekDataProvider() + { + return [ + [ + ['a', 'b'], + 'b', + ['a', 'b'], + ], + [ + [], + null, + [], + ], + ]; + } + + /** + * @dataProvider popDataProvider + */ + public function testPop($initialData, $expectedData, $expectedRemainingData) + { + $stack = new TransactionStack($initialData); + + $this->assertEquals($expectedData, $stack->pop()); + $this->assertAttributeEquals($expectedRemainingData, 'transactions', $stack); + } + + public function popDataProvider() + { + return [ + [ + ['a', 'b'], + 'b', + ['a'], + ], + [ + [], + null, + [], + ], + ]; + } + + public function testCount() + { + $stack = new TransactionStack(); + + $this->assertCount(0, $stack); + + $stack->push('a'); + + $this->assertCount(1, $stack); } } From 8a37e398963b2c81bd0f0fa8d3b68dc530417c59 Mon Sep 17 00:00:00 2001 From: Fred Cox Date: Tue, 24 Apr 2018 13:53:13 +0300 Subject: [PATCH 0334/1161] Improve unit tests and UPGRADE instructions of the excluded_exceptions option (#581) --- UPGRADE-2.0.md | 3 ++- tests/ConfigurationTest.php | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 060043a37..e0961a085 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -5,7 +5,8 @@ - The `environment` option has been renamed to `current_environment`. - The `http_proxy` option has been renamed to `proxy`. - The `processorOptions` option has been renamed to `processors_options`. -- The `exclude` option has been renamed to `excluded_exceptions`. +- The `exclude` option has been renamed to `excluded_exceptions`. Unlike `exclude`, + `excluded_exceptions` will also exclude all subclasses. - The `send_callback` option has been renamed to `should_capture`. - The `name` option has been renamed to `server_name`. - The `project` option has been removed. diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 64c264bdf..5a81d4382 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -240,6 +240,11 @@ public function excludedExceptionsDataProvider() new \Exception(), false, ], + [ + [\Exception::class], + new \BadFunctionCallException(), + true, + ], ]; } From 8f04ade4f06bffcb23362253d7954467dbf168e2 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 9 May 2018 16:53:48 +0200 Subject: [PATCH 0335/1161] [2.0] Merge up to 1.9 (#592) * Fix gzipCompress function name PHP doesn't have `gzipCompress` function, use `gzcompress` instead http://php.net/manual/en/function.gzcompress.php (cherry picked from commit d391712) * Fix notice during tests * Fix Client PHPDoc tag (port of #548 to 2.x) * Added syntax highlighting to README.md (#551) (cherry picked from commit 4dd0a3e) * Add use statement for HttpException class (cherry picked from commit 389c3ac, port of #556 to 2.x) * Change badge with badge poser. (cherry picked from commit 47ce095, port of #557 to 2.x) * Update docs config (cherry picked from commit f9c90cf) * Add test to handle non default port in host (port of #572 to 2.x) * Update changelog with 1.8.4 info (port of #573 to 2.x) * Allow serialization 5 levels deep (port of #554 to 2.x) * Make Serializer string limit customizable (port of #559 to 2.x) * Avoid looping when exception throws exception (port of #587 to 2.x) * Fix monolog handler not accepting Throwable (port of #586 to 2.x) * Fix serializer to account for non-UTF-8 chars (port of #553 to 2.x) * Add 1.9.0 changelog entries (cherry picked from commit 01171cf) * Add timeout and excluded_options config docs (cherry picked from commit 6860a3e) * Add back unrelease heading to changelog (cherry picked from commit 3f52e6f) * Fix CS --- CHANGELOG.md | 20 ++++ README.md | 14 +-- docs/config.rst | 22 +++- docs/integrations/laravel.rst | 2 + docs/sentry-doc-config.json | 6 +- lib/Raven/Breadcrumbs/MonologHandler.php | 10 +- lib/Raven/Client.php | 4 +- lib/Raven/Serializer.php | 43 +++++-- tests/Breadcrumbs/MonologHandlerTest.php | 107 ++++++++++++++---- tests/ClientTest.php | 89 +++++++++++++-- tests/Context/ContextTest.php | 2 +- tests/Fixtures/classes/CarelessException.php | 13 +++ .../RequestInterfaceMiddlewareTest.php | 16 +++ tests/SerializerAbstractTest.php | 49 ++++++-- 14 files changed, 328 insertions(+), 69 deletions(-) create mode 100644 tests/Fixtures/classes/CarelessException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 11c6ca486..30dcb6105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ - ... +## 1.9.0 (2018-05-03) + +- Fixed undefined variable (#588) +- Fix for exceptions throwing exceptions when setting event id (#587) +- Fix monolog handler not accepting Throwable (#586) +- Add `excluded_exceptions` option to exclude exceptions and their extending exceptions (#583) +- Fix `HTTP_X_FORWARDED_PROTO` header detection (#578) +- Fix sending events async in PHP 5 (#576) +- Avoid double reporting due to `ErrorException`s (#574) +- Make it possible to overwrite serializer message limit of 1024 (#559) +- Allow request data to be nested up to 5 levels deep (#554) +- Update serializer to handle UTF-8 characters correctly (#553) + +## 1.8.4 (2018-03-20) + +- Revert ignoring fatal errors on PHP 7+ (#571) +- Add PHP runtime information (#564) +- Cleanup the `site` value if it's empty (#555) +- Add `application/json` input handling (#546) + ## 1.8.3 (2018-02-07) - Serialize breadcrumbs to prevent issues with binary data (#538) diff --git a/README.md b/README.md index cec8d54cd..ebaba4866 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ # Sentry for PHP [![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master)](http://travis-ci.org/getsentry/sentry-php) -[![Total Downloads](https://img.shields.io/packagist/dt/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) -[![Downloads per month](https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) -[![Latest stable version](https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) -[![License](http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) +[![Monthly Downloads](https://poser.pugx.org/sentry/sentry/d/monthly)](https://packagist.org/packages/sentry/sentry) +[![Latest Stable Version](https://poser.pugx.org/sentry/sentry/v/stable)](https://packagist.org/packages/sentry/sentry) +[![License](https://poser.pugx.org/sentry/sentry/license)](https://packagist.org/packages/sentry/sentry) [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) @@ -131,7 +131,7 @@ $ git checkout -b releases/1.9.x 3. Update the hardcoded version tag in ``Client.php``: -``` +```php class Raven_Client { const VERSION = '1.9.0'; @@ -170,7 +170,7 @@ git checkout master 9. Update the version in ``Client.php``: -``` +```php class Raven_Client { const VERSION = '1.10.x-dev'; @@ -179,7 +179,7 @@ class Raven_Client 10. Lastly, update the composer version in ``composer.json``: -``` +```json "extra": { "branch-alias": { "dev-master": "1.10.x-dev" diff --git a/docs/config.rst b/docs/config.rst index b97e8de53..6e193db0d 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -171,7 +171,7 @@ The following settings are available for the client: 'User-Agent' => $client->getUserAgent(), 'X-Sentry-Auth' => $client->getAuthHeader(), ), - 'body' => gzipCompress(jsonEncode($data)), + 'body' => gzcompress(jsonEncode($data)), )) }, @@ -241,6 +241,26 @@ The following settings are available for the client: ) ) +.. describe:: timeout + + The timeout for sending requests to the Sentry server in seconds, default is 2 seconds. + + .. code-block:: php + + 'timeout' => 2, + +.. describe:: excluded_exceptions + + Exception that should not be reported, exceptions extending exceptions in this list will also + be excluded, default is an empty array. + + In the example below, when you exclude ``LogicException`` you will also exclude ``BadFunctionCallException`` + since it extends ``LogicException``. + + .. code-block:: php + + 'excluded_exceptions' => array('LogicException'), + .. _sentry-php-request-context: Providing Request Context diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index acd72a337..60b3ba167 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -64,6 +64,8 @@ step is not required anymore an you can skip ahead to the next one: = 70000 && $record['context']['exception'] instanceof \Throwable) + ) + ) { /** - * @var \Exception + * @var \Exception|\Throwable */ $exc = $record['context']['exception']; diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ec45568c8..2f5e89d32 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -33,7 +33,7 @@ /** * Raven PHP Client. * - * @doc https://docs.sentry.io/clients/php/config/ + * @see https://docs.sentry.io/clients/php/config/ */ class Client { @@ -471,7 +471,7 @@ public function sanitize(Event $event) $tagsContext = $event->getTagsContext(); if (!empty($request)) { - $event = $event->withRequest($this->serializer->serialize($request)); + $event = $event->withRequest($this->serializer->serialize($request, 5)); } if (!empty($userContext)) { $event = $event->withUserContext($this->serializer->serialize($userContext, 3)); diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 7d8cdadca..8cc4d443f 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -50,14 +50,24 @@ class Serializer */ protected $_all_object_serialize = false; + /** + * The default maximum message lengths. Longer strings will be truncated. + * + * @var int + */ + protected $messageLimit; + /** * @param null|string $mb_detect_order + * @param null|int $messageLimit */ - public function __construct($mb_detect_order = null) + public function __construct($mb_detect_order = null, $messageLimit = Client::MESSAGE_LIMIT) { if (null != $mb_detect_order) { $this->mb_detect_order = $mb_detect_order; } + + $this->messageLimit = (int) $messageLimit; } /** @@ -122,19 +132,22 @@ public function serializeObject($object, $max_depth = 3, $_depth = 0, $hashes = protected function serializeString($value) { $value = (string) $value; - if (function_exists('mb_detect_encoding') - && function_exists('mb_convert_encoding') - ) { + + if (extension_loaded('mbstring')) { // we always guarantee this is coerced, even if we can't detect encoding if ($currentEncoding = mb_detect_encoding($value, $this->mb_detect_order)) { $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); } else { $value = mb_convert_encoding($value, 'UTF-8'); } - } - if (strlen($value) > 1024) { - $value = substr($value, 0, 1014) . ' {clipped}'; + if (mb_strlen($value) > $this->messageLimit) { + $value = mb_substr($value, 0, $this->messageLimit - 10, 'UTF-8') . ' {clipped}'; + } + } else { + if (strlen($value) > $this->messageLimit) { + $value = substr($value, 0, $this->messageLimit - 10) . ' {clipped}'; + } } return $value; @@ -197,4 +210,20 @@ public function getAllObjectSerialize() { return $this->_all_object_serialize; } + + /** + * @return int + */ + public function getMessageLimit() + { + return $this->messageLimit; + } + + /** + * @param int $messageLimit + */ + public function setMessageLimit($messageLimit) + { + $this->messageLimit = $messageLimit; + } } diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php index b2e42c888..129b11496 100644 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -12,6 +12,7 @@ namespace Raven\Tests\Breadcrumbs; use Monolog\Logger; +use ParseError; use PHPUnit\Framework\TestCase; use Raven\Breadcrumbs\Breadcrumb; use Raven\Breadcrumbs\MonologHandler; @@ -44,52 +45,108 @@ protected function getSampleErrorMessage() public function testSimple() { - $client = $client = ClientBuilder::create([ - 'install_default_breadcrumb_handlers' => false, - ])->getClient(); - - $handler = new MonologHandler($client); + $client = $this->createClient(); + $logger = $this->createLoggerWithHandler($client); - $logger = new Logger('sentry'); - $logger->pushHandler($handler); $logger->addWarning('foo'); - $breadcrumbsRecorder = $this->getObjectAttribute($client, 'breadcrumbRecorder'); + $breadcrumbs = $this->getBreadcrumbs($client); + $this->assertCount(1, $breadcrumbs); + $this->assertEquals('foo', $breadcrumbs[0]->getMessage()); + $this->assertEquals(Client::LEVEL_WARNING, $breadcrumbs[0]->getLevel()); + $this->assertEquals('sentry', $breadcrumbs[0]->getCategory()); + } - /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ - $breadcrumbs = iterator_to_array($breadcrumbsRecorder); + public function testErrorInMessage() + { + $client = $this->createClient(); + $logger = $this->createLoggerWithHandler($client); + + $logger->addError($this->getSampleErrorMessage()); + $breadcrumbs = $this->getBreadcrumbs($client); $this->assertCount(1, $breadcrumbs); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumbs[0]->getType()); + $this->assertEquals(Client::LEVEL_ERROR, $breadcrumbs[0]->getLevel()); + $this->assertEquals('sentry', $breadcrumbs[0]->getCategory()); + $this->assertEquals('An unhandled exception', $breadcrumbs[0]->getMetadata()['value']); + } + + public function testExceptionBeingParsed() + { + $client = $this->createClient(); + $logger = $this->createLoggerWithHandler($client); - $this->assertEquals($breadcrumbs[0]->getMessage(), 'foo'); - $this->assertEquals($breadcrumbs[0]->getLevel(), Client::LEVEL_WARNING); - $this->assertEquals($breadcrumbs[0]->getCategory(), 'sentry'); + $logger->addError('A message', ['exception' => new \Exception('Foo bar')]); + + $breadcrumbs = $this->getBreadcrumbs($client); + $this->assertCount(1, $breadcrumbs); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumbs[0]->getType()); + $this->assertEquals('Foo bar', $breadcrumbs[0]->getMetadata()['value']); + $this->assertEquals('sentry', $breadcrumbs[0]->getCategory()); + $this->assertEquals(Client::LEVEL_ERROR, $breadcrumbs[0]->getLevel()); + $this->assertNull($breadcrumbs[0]->getMessage()); } - public function testErrorInMessage() + public function testThrowableBeingParsedAsException() + { + if (PHP_VERSION_ID <= 70000) { + $this->markTestSkipped('PHP 7.0 introduced Throwable'); + } + + $client = $this->createClient(); + $logger = $this->createLoggerWithHandler($client); + $throwable = new ParseError('Foo bar'); + + $logger->addError('This is a throwable', ['exception' => $throwable]); + + $breadcrumbs = $this->getBreadcrumbs($client); + $this->assertCount(1, $breadcrumbs); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumbs[0]->getType()); + $this->assertEquals('Foo bar', $breadcrumbs[0]->getMetadata()['value']); + $this->assertEquals('sentry', $breadcrumbs[0]->getCategory()); + $this->assertEquals(Client::LEVEL_ERROR, $breadcrumbs[0]->getLevel()); + $this->assertNull($breadcrumbs[0]->getMessage()); + } + + /** + * @return Client + */ + private function createClient() { $client = $client = ClientBuilder::create([ 'install_default_breadcrumb_handlers' => false, ])->getClient(); - $handler = new MonologHandler($client); + return $client; + } + /** + * @param Client $client + * + * @return Logger + */ + private function createLoggerWithHandler(Client $client) + { + $handler = new MonologHandler($client); $logger = new Logger('sentry'); $logger->pushHandler($handler); - $logger->addError($this->getSampleErrorMessage()); + return $logger; + } + + /** + * @param Client $client + * + * @return Breadcrumb[] + */ + private function getBreadcrumbs(Client $client) + { $breadcrumbsRecorder = $this->getObjectAttribute($client, 'breadcrumbRecorder'); - /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ $breadcrumbs = iterator_to_array($breadcrumbsRecorder); + $this->assertContainsOnlyInstancesOf(Breadcrumb::class, $breadcrumbs); - $this->assertCount(1, $breadcrumbs); - - $metaData = $breadcrumbs[0]->getMetadata(); - - $this->assertEquals($breadcrumbs[0]->getType(), Breadcrumb::TYPE_ERROR); - $this->assertEquals($breadcrumbs[0]->getLevel(), Client::LEVEL_ERROR); - $this->assertEquals($breadcrumbs[0]->getCategory(), 'sentry'); - $this->assertEquals($metaData['value'], 'An unhandled exception'); + return $breadcrumbs; } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 72b6e0e05..d13e15e46 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -27,6 +27,7 @@ use Raven\Processor\ProcessorInterface; use Raven\Processor\ProcessorRegistry; use Raven\Serializer; +use Raven\Tests\Fixtures\classes\CarelessException; use Raven\Transport\TransportInterface; // XXX: Is there a better way to stub the client? @@ -503,32 +504,79 @@ public function testSanitizeUser() $this->assertEquals(['email' => 'foo@example.com'], $event->getUserContext()); } - public function testSanitizeRequest() + /** + * @dataProvider deepRequestProvider + */ + public function testSanitizeRequest(array $postData, array $expectedData) { $client = ClientBuilder::create()->getClient(); $event = new Event($client->getConfig()); $event = $event->withRequest([ - 'context' => [ - 'line' => 1216, - 'stack' => [ - 1, [2], 3, - ], + 'method' => 'POST', + 'url' => 'https://example.com/something', + 'query_string' => '', + 'data' => [ + '_method' => 'POST', + 'data' => $postData, ], ]); $event = $client->sanitize($event); $this->assertArraySubset([ - 'context' => [ - 'line' => 1216, - 'stack' => [ - 1, 'Array of length 1', 3, - ], + 'method' => 'POST', + 'url' => 'https://example.com/something', + 'query_string' => '', + 'data' => [ + '_method' => 'POST', + 'data' => $expectedData, ], ], $event->getRequest()); } + public function deepRequestProvider() + { + return [ + [ + [ + 'MyModel' => [ + 'flatField' => 'my value', + 'nestedField' => [ + 'key' => 'my other value', + ], + ], + ], + [ + 'MyModel' => [ + 'flatField' => 'my value', + 'nestedField' => [ + 'key' => 'my other value', + ], + ], + ], + ], + [ + [ + 'Level 1' => [ + 'Level 2' => [ + 'Level 3' => [ + 'Level 4' => 'something', + ], + ], + ], + ], + [ + 'Level 1' => [ + 'Level 2' => [ + 'Level 3' => 'Array of length 1', + ], + ], + ], + ], + ]; + } + private function assertMixedValueAndArray($expected_value, $actual_value) { if (null === $expected_value) { @@ -795,4 +843,23 @@ public function testSetReprSerializer() $this->assertSame($serializer, $client->getReprSerializer()); } + + public function testHandlingExceptionThrowingAnException() + { + $client = ClientBuilder::create()->getClient(); + $client->captureException($this->createCarelessExceptionWithStacktrace()); + $event = $client->getLastEvent(); + // Make sure the exception is of the careless exception and not the exception thrown inside + // the __set method of that exception caused by setting the event_id on the exception instance + $this->assertSame(CarelessException::class, $event->getException()['values'][0]['type']); + } + + private function createCarelessExceptionWithStacktrace() + { + try { + throw new CarelessException('Foo bar'); + } catch (\Exception $ex) { + return $ex; + } + } } diff --git a/tests/Context/ContextTest.php b/tests/Context/ContextTest.php index 2aeffdbf2..ceba26e67 100644 --- a/tests/Context/ContextTest.php +++ b/tests/Context/ContextTest.php @@ -106,7 +106,7 @@ public function testArrayLikeBehaviour() // Accessing a key that does not exists in the data object should behave // like accessing a non-existent key of an array - $context['foo']; + @$context['foo']; $error = error_get_last(); diff --git a/tests/Fixtures/classes/CarelessException.php b/tests/Fixtures/classes/CarelessException.php new file mode 100644 index 000000000..aa8e94223 --- /dev/null +++ b/tests/Fixtures/classes/CarelessException.php @@ -0,0 +1,13 @@ + 'http://www.example.com:123/foo', + 'method' => 'GET', + 'cookies' => [], + 'headers' => [], + ], + [ + 'url' => 'http://www.example.com:123/foo', + 'method' => 'GET', + 'cookies' => [], + 'headers' => [ + 'Host' => ['www.example.com:123'], + ], + ], + ], [ [ 'uri' => 'http://www.example.com/foo?foo=bar&bar=baz', diff --git a/tests/SerializerAbstractTest.php b/tests/SerializerAbstractTest.php index 4b2d904e5..8d87f81a7 100644 --- a/tests/SerializerAbstractTest.php +++ b/tests/SerializerAbstractTest.php @@ -366,16 +366,27 @@ public function testLongString($serialize_all_objects) if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } - for ($i = 0; $i < 100; ++$i) { - foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { - $input = ''; - for ($i = 0; $i < $length; ++$i) { - $input .= chr(mt_rand(0, 255)); - } - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(1024, strlen($result)); - } + + foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { + $input = str_repeat('x', $length); + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(1024, strlen($result)); + } + } + + public function testLongStringWithOverwrittenMessageLength() + { + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer */ + $serializer = new $class_name(); + $serializer->setMessageLimit(500); + + foreach ([100, 490, 499, 500, 501, 1000, 10000] as $length) { + $input = str_repeat('x', $length); + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(500, strlen($result)); } } @@ -409,6 +420,24 @@ public function testSetAllObjectSerialize() $serializer->setAllObjectSerialize(false); $this->assertFalse($serializer->getAllObjectSerialize()); } + + public function testClippingUTF8Characters() + { + if (!extension_loaded('mbstring')) { + $this->markTestSkipped('mbstring extension is not enabled.'); + } + + $testString = 'Прекратите надеÑтьÑÑ, что ваши пользователи будут Ñообщать об ошибках'; + $class_name = static::get_test_class(); + /** @var \Raven\Serializer $serializer */ + $serializer = new $class_name(null, 19); + + $clipped = $serializer->serialize($testString); + + $this->assertEquals('Прекратит {clipped}', $clipped); + $this->assertNotNull(json_encode($clipped)); + $this->assertSame(JSON_ERROR_NONE, json_last_error()); + } } /** From 3ccf56c8cd4c9805ab5229ed8408bd3979c7e464 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 10 May 2018 11:37:31 +0200 Subject: [PATCH 0336/1161] Drop the trust_x_forwarded_proto configuration option (#599) --- lib/Raven/ClientBuilder.php | 2 -- lib/Raven/Configuration.php | 24 ------------------------ tests/ClientBuilderTest.php | 1 - tests/ConfigurationTest.php | 1 - 4 files changed, 28 deletions(-) diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index 2c8a2953e..c28dcf542 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -41,8 +41,6 @@ * * @method int getSendAttempts() * @method setSendAttempts(int $attemptsCount) - * @method bool isTrustXForwardedProto() - * @method setIsTrustXForwardedProto(bool $value) * @method string[] getPrefixes() * @method setPrefixes(array $prefixes) * @method bool getSerializeAllObjects() diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 6fdd465cc..038dd15d1 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -87,28 +87,6 @@ public function setSendAttempts($attemptsCount) $this->options = $this->resolver->resolve($options); } - /** - * Checks whether the X-FORWARDED-PROTO header should be trusted. - * - * @return bool - */ - public function isTrustXForwardedProto() - { - return $this->options['trust_x_forwarded_proto']; - } - - /** - * Sets whether the X-FORWARDED-PROTO header should be trusted. - * - * @param bool $value The value of the option - */ - public function setIsTrustXForwardedProto($value) - { - $options = array_merge($this->options, ['trust_x_forwarded_proto' => $value]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the prefixes which should be stripped from filenames to create * relative paths. @@ -642,7 +620,6 @@ private function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'send_attempts' => 6, - 'trust_x_forwarded_proto' => false, 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'serialize_all_object' => false, 'sample_rate' => 1, @@ -667,7 +644,6 @@ private function configureOptions(OptionsResolver $resolver) ]); $resolver->setAllowedTypes('send_attempts', 'int'); - $resolver->setAllowedTypes('trust_x_forwarded_proto', 'bool'); $resolver->setAllowedTypes('prefixes', 'array'); $resolver->setAllowedTypes('serialize_all_object', 'bool'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 08282a25f..16f0d3cd3 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -242,7 +242,6 @@ public function testCallExistingMethodForwardsCallToConfiguration($setterMethod, public function optionsDataProvider() { return [ - ['setIsTrustXForwardedProto', true], ['setPrefixes', ['foo', 'bar']], ['setSerializeAllObjects', false], ['setSampleRate', 0.5], diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 5a81d4382..fb09001d5 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -45,7 +45,6 @@ public function optionsDataProvider() { return [ ['send_attempts', 1, 'getSendAttempts', 'setSendAttempts'], - ['trust_x_forwarded_proto', false, 'isTrustXForwardedProto', 'setIsTrustXForwardedProto'], ['prefixes', ['foo', 'bar'], 'getPrefixes', 'setPrefixes'], ['serialize_all_object', false, 'getSerializeAllObjects', 'setSerializeAllObjects'], ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], From ec2ec1e3c28266382e84aac69d0ae530449bdfe3 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 13 May 2018 18:00:47 +0200 Subject: [PATCH 0337/1161] Refactor the error and exception handlers (#584) --- .php_cs | 1 + UPGRADE-2.0.md | 215 ++++++++-- lib/Raven/AbstractErrorHandler.php | 329 +++++++++++++++ lib/Raven/BreadcrumbErrorHandler.php | 58 +++ lib/Raven/Breadcrumbs/Breadcrumb.php | 12 +- lib/Raven/Breadcrumbs/ErrorHandler.php | 45 --- lib/Raven/Client.php | 39 +- lib/Raven/ClientBuilder.php | 2 - lib/Raven/Configuration.php | 24 -- lib/Raven/ErrorHandler.php | 204 +--------- lib/Raven/Stacktrace.php | 4 +- lib/Raven/Transport/HttpTransport.php | 5 +- phpunit.xml.dist | 4 +- tests/AbstractErrorHandlerTest.php | 93 +++++ .../BreadcrumbErrorHandlerTest.php | 325 +++++++++++++++ tests/Breadcrumbs/ErrorHandlerTest.php | 41 -- tests/Breadcrumbs/MonologHandlerTest.php | 4 +- tests/ClientBuilderTest.php | 1 - tests/ClientTest.php | 133 +------ tests/ConfigurationTest.php | 1 - tests/ErrorHandlerTest.php | 375 +++++++++--------- tests/phpt/exception_rethrown.phpt | 35 ++ tests/phpt/exception_thrown.phpt | 35 ++ tests/phpt/fatal_error.phpt | 43 ++ .../phpt/fatal_error_not_captured_twice.phpt | 41 ++ tests/phpt/out_of_memory.phpt | 43 ++ 26 files changed, 1412 insertions(+), 700 deletions(-) create mode 100644 lib/Raven/AbstractErrorHandler.php create mode 100644 lib/Raven/BreadcrumbErrorHandler.php delete mode 100644 lib/Raven/Breadcrumbs/ErrorHandler.php create mode 100644 tests/AbstractErrorHandlerTest.php create mode 100644 tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php delete mode 100644 tests/Breadcrumbs/ErrorHandlerTest.php create mode 100644 tests/phpt/exception_rethrown.phpt create mode 100644 tests/phpt/exception_thrown.phpt create mode 100644 tests/phpt/fatal_error.phpt create mode 100644 tests/phpt/fatal_error_not_captured_twice.phpt create mode 100644 tests/phpt/out_of_memory.phpt diff --git a/.php_cs b/.php_cs index da318ed84..724043b6a 100644 --- a/.php_cs +++ b/.php_cs @@ -20,6 +20,7 @@ return PhpCsFixer\Config::create() ->in(__DIR__) ->exclude([ 'tests/resources', + 'tests/phpt' ]) ) ; diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index e0961a085..8224d4645 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -29,6 +29,7 @@ user the burden of adding an already configured processor instance to the client. - The `transport` option has been removed in favour of setting it using the client builder. +- The `install_default_breadcrumb_handlers` option has been removed. - The `http_client_options` has been added to set the options that applies to the HTTP client chosen by the user as underlying transport method. - The `open_timeout` option has been added to set the maximum number of seconds @@ -44,7 +45,10 @@ ### Client -- The constructor of the `Client` class has changed its signature. The only way +- The `Raven_Client` class has been renamed to `Client` to follow the PSR-4 + convention. + +- The constructor of the `Raven_Client` class has changed its signature. The only way to set the DSN is now to set it in the options passed to the `Configuration` class. @@ -66,8 +70,8 @@ } ``` -- The methods `Client::getRelease` and `Client::setRelease` have been removed. - You should use `Configuration::getRelease()` and `Configuration::setRelease` +- The methods `Raven_Client::getRelease` and `Raven_Client::setRelease` have + been removed. You should use `Configuration::getRelease()` and `Configuration::setRelease` instead. Before: @@ -84,8 +88,8 @@ $client->getConfig()->setRelease(...); ``` -- The methods `Client::getEnvironment` and `Client::setEnvironment` have been - removed. You should use `Configuration::getCurrentEnvironment` and +- The methods `Raven_Client::getEnvironment` and `Raven_Client::setEnvironment` + have been removed. You should use `Configuration::getCurrentEnvironment` and `Configuration::setCurrentEnvironment` instead. Before: @@ -102,9 +106,9 @@ $client->getConfig()->setCurrentEnvironment(...); ``` -- The methods `Client::getDefaultPrefixes` and `Client::setPrefixes` have been - removed. You should use `Configuration::getPrefixes` and `Configuration::setPrefixes` - instead. +- The methods `Raven_Client::getDefaultPrefixes` and `Raven_Client::setPrefixes` + have been removed. You should use `Configuration::getPrefixes` and + `Configuration::setPrefixes` instead. Before: @@ -120,8 +124,8 @@ $client->getConfig()->setPrefixes(...); ``` -- The methods `Client::getAppPath` and `Client::setAppPath` have been removed. - You should use `Configuration::getProjectRoot` and `Configuration::setProjectRoot` +- The methods `Raven_Client::getAppPath` and `Raven_Client::setAppPath` have been + removed. You should use `Configuration::getProjectRoot` and `Configuration::setProjectRoot` instead. Before: @@ -137,7 +141,7 @@ $client->getConfig()->getProjectRoot(); $client->getConfig()->setProjectRoot(...); -- The methods `Client::getExcludedAppPaths` and `Client::setExcludedAppPaths` +- The methods `Raven_Client::getExcludedAppPaths` and `Raven_Client::setExcludedAppPaths` have been removed. You should use `Configuration::getExcludedProjectPaths` and `Configuration::setExcludedProjectPaths` instead. @@ -154,7 +158,7 @@ $client->getConfig()->getExcludedProjectPaths(); $client->getConfig()->setExcludedProjectPaths(...); -- The methods `Client::getSendCallback` and `Client::setSendCallback` have been +- The methods `Raven_Client::getSendCallback` and `Raven_Client::setSendCallback` have been removed. You should use `Configuration::shouldCapture` and `Configuration::setShouldCapture` instead. @@ -171,7 +175,7 @@ $client->getConfig()->shouldCapture(); $client->getConfig()->setShouldCapture(...); -- The method `Client::getServerEndpoint` has been removed. You should use +- The method `Raven_Client::getServerEndpoint` has been removed. You should use `Configuration::getServer` instead. Before: @@ -186,7 +190,7 @@ $client->getConfig()->getServer(); ``` -- The method `Client::getTransport` has been removed. You should use +- The method `Raven_Client::getTransport` has been removed. You should use `Configuration::getTransport` instead. Before: @@ -201,7 +205,7 @@ $client->getConfig()->getTransport(); ``` -- The method `Client::getErrorTypes` has been removed. You should use +- The method `Raven_Client::getErrorTypes` has been removed. You should use `Configuration::getErrorTypes` instead. Before: @@ -216,13 +220,13 @@ $client->getConfig()->getErrorTypes(); ``` -- The `Client::getDefaultProcessors` method has been removed. +- The `Raven_Client::getDefaultProcessors` method has been removed. -- The `Client::message` method has been removed. +- The `Raven_Client::message` method has been removed. -- The `Client::captureQuery` method has been removed. +- The `Raven_Client::captureQuery` method has been removed. -- The `Client::captureMessage` method has changed its signature by removing the +- The `Raven_Client::captureMessage` method has changed its signature by removing the `$stack` and `$vars` arguments. Before: @@ -243,8 +247,8 @@ } ``` -- The `Client::captureException` method has changed its signature by removing the - `$logger` and `$vars` arguments. +- The `Raven_Client::captureException` method has changed its signature by removing + the `$logger` and `$vars` arguments. Before: @@ -264,9 +268,10 @@ } ``` -- The `$vars` argument of the `Client::captureException`, `Client::captureMessage` and - `Client::captureQuery` methods accepted some values that were setting additional data - in the event like the tags or the user data. Some of them have changed name. +- The `$vars` argument of the `Raven_Client::captureException`, `Raven_Client::captureMessage` + and `Raven_Client::captureQuery` methods accepted some values that were + setting additional data in the event like the tags or the user data. + Some of them have changed name. Before: @@ -296,11 +301,12 @@ level of the event. This has been changed so that only the `ErrorException` or its derivated classes are considered for this behavior. -- The method `Client::createProcessors` has been removed as there is no need to create - instances of the processors from outside the `Client` class. +- The method `Raven_Client::createProcessors` has been removed as there is no + need to create instances of the processors from outside the `Client` class. -- The method `Client::setProcessors` has been removed. You should use `Client::addProcessor` - and `Client::removeProcessor` instead to manage the processors that will be executed. +- The method `Raven_Client::setProcessors` has been removed. You should use + `Client::addProcessor` and `Client::removeProcessor` instead to manage the + processors that will be executed. Before: @@ -326,15 +332,15 @@ $client->addProcessor($processor2, 255); // Note the priority: higher the priority earlier a processor will be executed. This is equivalent to adding first $processor2 and then $processor1 ``` -- The method `Client::process` has been removed as there is no need to process event data +- The method `Raven_Client::process` has been removed as there is no need to process event data from outside the `Client` class. - The `Raven_Processor` class has been removed. There is not anymore a base abstract class for the processors, but a `ProcessorInterface` interface has been introduced. -- The `Client::user_context` method has been removed. You should use `Client::getUserContext` - instead. +- The `Raven_Client::user_context` method has been removed. You should use + `Client::getUserContext` instead. Before: @@ -348,8 +354,8 @@ $client->getUserContext()->setData(array('foo' => 'bar')); ``` -- The `Client::tags_context` method has been removed. You should use `Client::getTagsContext` - instead. +- The `Raven_Client::tags_context` method has been removed. You should use + `Client::getTagsContext` instead. Before: @@ -363,8 +369,8 @@ $client->getTagsContext()->setData(array('foo', 'bar')); ``` -- The `Client::extra_context` method has been removed. You should use `Client::getExtraContext` - instead. +- The `Raven_Client::extra_context` method has been removed. You should use + `Client::getExtraContext` instead. Before: @@ -393,6 +399,43 @@ $client->getTransactionStack()->push('foo'); ``` +- The method `Raven_Client::install` has been removed. You should use `ErrorHandler::register` + instead to register an error handler and associate it with the client. + + Before: + + ```php + $client->install(); + ``` + + After: + + ```php + use Raven\ErrorHandler; + + ErrorHandler::register($client); + ``` + +- The method `Raven_Client::registerDefaultBreadcrumbHandlers` has been removed. + You should use `BreadcrumbErrorHandler::register` instead to register an error + handler and associate it with the client. + + Before: + + ```php + $client = new Raven_Client(array('install_default_breadcrumb_handlers' => true)); + ``` + + After: + + ```php + use Raven\BreadcrumbErrorHandler; + + $client = new Client([...]); + + BreadcrumbErrorHandler::register($client); + ``` + ### Client builder - To simplify the creation of a `Client` object instance, a new builder class @@ -401,7 +444,7 @@ Before: ```php - $client = new Client([...]); + $client = new Raven_Client([...]); ``` After: @@ -463,7 +506,7 @@ ```php $client->transaction->push('foo', 'bar', 'baz'); - + $value = $client->transaction->pop(); // $value is 'baz' $value = $client->transaction->pop('foo'); // $value is 'foo' $value = $client->transaction->pop(); // $value is null @@ -480,3 +523,101 @@ $value = $client->getTransactionStack()->pop(); // $value is null ``` + +## Error handlers + +- The `Raven_Breadcrumbs_ErrorHandler` class has been renamed to `BreadcrumbErrorHandler` + to better reflect its purpose and to follow the PSR-4 convention. + +- The constructor of the `Raven_Breadcrumbs_ErrorHandler` class has changed its + visibility to `protected`. To create a new instance of the class use the new + method `BreadcrumbErrorHandler::register`. + +- The method `Raven_Breadcrumbs_ErrorHandler::install` has been renamed to + `register` and changed its signature to accept the instance of the Raven + client to associate with the handler itself. + + Before: + + ```php + $errorHandler = new Raven_Breadcrumbs_ErrorHandler($client); + $errorHandler->install(); + ``` + + After: + + ```php + use Raven\BreadcrumbErrorHandler; + + $errorHandler = BreadcrumbErrorHandler::register($client); + ``` + +- The `Raven_ErrorHandler` class has been renamed to `ErrorHandler` to follow + the PSR-4 convention. + +- The constructor of the `Raven_ErrorHandler` class has changed its visibility + to `protected`. To create a new instance of the class use the new method + `ErrorHandler::register`. + + Before: + + ```php + $errorHandler = new Raven_ErrorHandler($client); + ``` + + After: + + ```php + use Raven\ErrorHandler; + + $errorHandler = ErrorHandler::register($client); + ``` + +- The method `Raven_ErrorHandler::handleError` has changed its signature by removing + the `$context` argument and it has been marked as internal to make it clear that + it should not be called publicly and its method visibility is subject to changes + without any notice. + +- The methods `Raven_ErrorHandler::registerErrorHandler`, `Raven_ErrorHandler::registerExceptionHandler` + and `Raven_ErrorHandler::registerShutdownFunction` have been removed. You should + use the `ErrorHandler::register` method instead, but note that it registers all + error handlers (error, exception and fatal error) at once and there is no way + anymore to only use one of them. + + Before: + + ```php + $errorHandler = new Raven_ErrorHandler($client); + $errorHandler->registerErrorHandler(); + $errorHandler->registerExceptionHandler(); + $errorHandler->registerShutdownFunction(); + ``` + + After: + + ```php + use Raven\ErrorHandler; + + $errorHandler = ErrorHandler::register($client); + ``` + +- The method `Raven_ErrorHandler::handleError` has changed its signature by + removing the `$context` argument and it has been marked as internal to + make it clear that it should not be called publicly and its method visibility + is subject to changes without any notice. + +- The method `Raven_ErrorHandler::handleFatalError` has changed its signature + by adding an optional argument named `$error` and it has been marked as internal + to make it clear that it should not be called publicly and its method visibility + is subject to changes without any notice. + +- The method `Raven_ErrorHandler::handleException` has changed its signature by + removing the `$isError` and `$vars` arguments and it has been marked as internal + to make it clear that it should not be called publicly and its method visibility + is subject to changes without any notice. + +- The method `Raven_ErrorHandler::bitwiseOr` has been removed and there is no + replacement for it. + +- The method `Raven_ErrorHandler::shouldCaptureFatalError` has been removed and + there is no replacement for it. diff --git a/lib/Raven/AbstractErrorHandler.php b/lib/Raven/AbstractErrorHandler.php new file mode 100644 index 000000000..df03c627b --- /dev/null +++ b/lib/Raven/AbstractErrorHandler.php @@ -0,0 +1,329 @@ + + */ +abstract class AbstractErrorHandler +{ + /** + * @var Client The Raven client + */ + protected $client; + + /** + * @var \ReflectionProperty A reflection cached instance that points to the + * trace property of the exception objects + */ + protected $exceptionReflection; + + /** + * @var callable|null The previous error handler, if any + */ + protected $previousErrorHandler; + + /** + * @var callable|null The previous exception handler, if any + */ + protected $previousExceptionHandler; + + /** + * @var int The errors that will be catched by the error handler + */ + protected $capturedErrors = E_ALL; + + /** + * @var bool Flag indicating whether this error handler is the first in the + * chain of registered error handlers + */ + protected $isRoot = false; + + /** + * @var string A portion of pre-allocated memory data that will be reclaimed + * in case a fatal error occurs to handle it + */ + protected static $reservedMemory; + + /** + * @var array List of error levels and their description + */ + const ERROR_LEVELS_DESCRIPTION = [ + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + E_NOTICE => 'Notice', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_WARNING => 'Warning', + E_USER_WARNING => 'User Warning', + E_COMPILE_WARNING => 'Compile Warning', + E_CORE_WARNING => 'Core Warning', + E_USER_ERROR => 'User Error', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + E_COMPILE_ERROR => 'Compile Error', + E_PARSE => 'Parse Error', + E_ERROR => 'Error', + E_CORE_ERROR => 'Core Error', + ]; + + /** + * Constructor. + * + * @param Client $client The Raven client + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + */ + protected function __construct(Client $client, $reservedMemorySize = 10240) + { + if (!is_int($reservedMemorySize) || $reservedMemorySize <= 0) { + throw new \UnexpectedValueException('The value of the $reservedMemorySize argument must be an integer greater than 0.'); + } + + $this->client = $client; + $this->exceptionReflection = new \ReflectionProperty(\Exception::class, 'trace'); + $this->exceptionReflection->setAccessible(true); + + if (null === self::$reservedMemory) { + self::$reservedMemory = str_repeat('x', $reservedMemorySize); + + register_shutdown_function([$this, 'handleFatalError']); + } + + $this->previousErrorHandler = set_error_handler([$this, 'handleError']); + + if (null === $this->previousErrorHandler) { + restore_error_handler(); + + // Specifying the error types catched by the error handler with the + // first call to the set_error_handler method would cause the PHP + // bug https://bugs.php.net/63206 if the handler is not the first + // one + set_error_handler([$this, 'handleError'], $this->capturedErrors); + + $this->isRoot = true; + } + + $this->previousExceptionHandler = set_exception_handler([$this, 'handleException']); + } + + /** + * Sets the PHP error levels that will be captured by the Raven client when + * a PHP error occurs. + * + * @param int $levels A bit field of E_* constants for captured errors + * @param bool $replace Whether to replace or amend the previous value + * + * @return int The previous value + */ + public function captureAt($levels, $replace = false) + { + $prev = $this->capturedErrors; + + $this->capturedErrors = $levels; + + if (!$replace) { + $this->capturedErrors |= $prev; + } + + $this->reRegister($prev); + + return $prev; + } + + /** + * Handles errors by capturing them through the Raven client according to + * the configured bit field. + * + * @param int $level The level of the error raised, represented by one + * of the E_* constants + * @param string $message The error message + * @param string $file The filename the error was raised in + * @param int $line The line number the error was raised at + * + * @return bool Whether the standard PHP error handler should be called + * + * @internal + */ + public function handleError($level, $message, $file, $line) + { + $shouldReportError = (bool) (error_reporting() & $level); + $shouldCaptureError = (bool) ($this->capturedErrors & $level); + + if (!$shouldCaptureError || (!$shouldCaptureError && !$shouldReportError)) { + return false; + } + + $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); + $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $file, $line); + + $this->exceptionReflection->setValue($errorAsException, $backtrace); + + try { + $this->handleException($errorAsException); + } catch (\Exception $exception) { + // Do nothing as this error handler should be as trasparent as possible + } + + if (null !== $this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $level, $message, $file, $line); + } + + return $shouldReportError; + } + + /** + * Handles fatal errors by capturing them through the Raven client. This + * method is used as callback of a shutdown function. + * + * @param array|null $error The error details as returned by error_get_last() + * + * @internal + */ + public function handleFatalError(array $error = null) + { + // If there is not enough memory that can be used to handle the error + // do nothing + if (null === self::$reservedMemory) { + return; + } + + self::$reservedMemory = null; + $errorAsException = null; + + if (null === $error) { + $error = error_get_last(); + } + + if (!empty($error) && $error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)) { + $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); + } + + try { + if (null !== $errorAsException) { + $this->handleException($errorAsException); + } + } catch (\ErrorException $errorAsException) { + // Ignore this re-throw + } + } + + /** + * Handles the given exception by capturing it through the Raven client and + * then forwarding it to another handler. + * + * @param \Exception|\Throwable $exception The exception to handle + * + * @throws \Exception|\Throwable + * + * @internal + */ + public function handleException($exception) + { + $this->doHandleException($exception); + + $previousExceptionHandlerException = $exception; + + // Unset the previous exception handler to prevent infinite loop in case + // we need to handle an exception thrown from it + $previousExceptionHandler = $this->previousExceptionHandler; + $this->previousExceptionHandler = null; + + try { + if (null !== $previousExceptionHandler) { + $previousExceptionHandler($exception); + } + } catch (\Exception $previousExceptionHandlerException) { + // Do nothing, we just need to set the $previousExceptionHandlerException + // variable to the exception we just catched to compare it later + // with the original object instance + } + + // If the exception object instance is the same as the one catched from + // the previous exception handler, if any, give it back to the native + // PHP handler to prevent infinite circular loop + if ($exception === $previousExceptionHandlerException) { + // Disable the fatal error handler or the error will be reported twice + self::$reservedMemory = null; + + throw $exception; + } + + $this->handleException($previousExceptionHandlerException); + } + + /** + * Re-registers the error handler if the mask that configures the intercepted + * error types changed. + * + * @param int $previousThrownErrors The previous error mask + */ + protected function reRegister($previousThrownErrors) + { + if ($this->capturedErrors === $previousThrownErrors) { + return; + } + + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + + restore_error_handler(); + + if ($handler === $this) { + restore_error_handler(); + + if ($this->isRoot) { + set_error_handler([$this, 'handleError'], $this->capturedErrors); + } else { + set_error_handler([$this, 'handleError']); + } + } + } + + /** + * Cleans and returns the backtrace without the first frames that belong to + * this error handler. + * + * @param array $backtrace The backtrace to clear + * @param string $file The filename the backtrace was raised in + * @param int $line The line number the backtrace was raised at + * + * @return array + */ + protected function cleanBacktraceFromErrorHandlerFrames($backtrace, $file, $line) + { + $cleanedBacktrace = $backtrace; + $index = 0; + + while ($index < count($backtrace)) { + if (isset($backtrace[$index]['file'], $backtrace[$index]['line']) && $backtrace[$index]['line'] === $line && $backtrace[$index]['file'] === $file) { + $cleanedBacktrace = array_slice($cleanedBacktrace, 1 + $index); + + break; + } + + ++$index; + } + + return $cleanedBacktrace; + } + + /** + * Handles the given exception. This method can be overridden to customize + * the logging of an exception. + * + * @param \Exception|\Throwable $exception The exception to handle + * + * @throws \Exception|\Throwable + */ + abstract protected function doHandleException($exception); +} diff --git a/lib/Raven/BreadcrumbErrorHandler.php b/lib/Raven/BreadcrumbErrorHandler.php new file mode 100644 index 000000000..319806939 --- /dev/null +++ b/lib/Raven/BreadcrumbErrorHandler.php @@ -0,0 +1,58 @@ + + */ +class BreadcrumbErrorHandler extends AbstractErrorHandler +{ + /** + * Registers this error handler by associating its instance with the given + * Raven client. + * + * @param Client $client The Raven client + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * + * @return BreadcrumbErrorHandler + */ + public static function register(Client $client, $reservedMemorySize = 10240) + { + return new self($client, $reservedMemorySize); + } + + /** + * {@inheritdoc} + */ + public function doHandleException($exception) + { + if (!$exception instanceof \ErrorException) { + return; + } + + $this->client->leaveBreadcrumb(new Breadcrumb( + $this->client->translateSeverity($exception->getSeverity()), + Breadcrumb::TYPE_ERROR, + 'error_reporting', + $exception->getMessage(), + [ + 'code' => $exception->getCode(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + ] + )); + } +} diff --git a/lib/Raven/Breadcrumbs/Breadcrumb.php b/lib/Raven/Breadcrumbs/Breadcrumb.php index e64738e42..67eff3b40 100644 --- a/lib/Raven/Breadcrumbs/Breadcrumb.php +++ b/lib/Raven/Breadcrumbs/Breadcrumb.php @@ -78,9 +78,9 @@ final class Breadcrumb implements \JsonSerializable * @param string $type The type of the breadcrumb * @param string $category The category of the breadcrumb * @param string|null $message Optional text message - * @param array $metaData Additional information about the breadcrumb + * @param array $metadata Additional information about the breadcrumb */ - public function __construct($level, $type, $category, $message = null, array $metaData = []) + public function __construct($level, $type, $category, $message = null, array $metadata = []) { if (!in_array($level, self::getLevels(), true)) { throw new InvalidArgumentException('The value of the $level argument must be one of the Raven\Client::LEVEL_* constants.'); @@ -90,7 +90,7 @@ public function __construct($level, $type, $category, $message = null, array $me $this->level = $level; $this->category = $category; $this->message = $message; - $this->metadata = $metaData; + $this->metadata = $metadata; $this->timestamp = microtime(true); } @@ -101,13 +101,13 @@ public function __construct($level, $type, $category, $message = null, array $me * @param string $type The type of the breadcrumb * @param string $category The category of the breadcrumb * @param string|null $message Optional text message - * @param array $metaData Additional information about the breadcrumb + * @param array $metadata Additional information about the breadcrumb * * @return static */ - public static function create($level, $type, $category, $message = null, array $metaData = []) + public static function create($level, $type, $category, $message = null, array $metadata = []) { - return new static($level, $type, $category, $message, $metaData); + return new static($level, $type, $category, $message, $metadata); } /** diff --git a/lib/Raven/Breadcrumbs/ErrorHandler.php b/lib/Raven/Breadcrumbs/ErrorHandler.php deleted file mode 100644 index 0cd02be5e..000000000 --- a/lib/Raven/Breadcrumbs/ErrorHandler.php +++ /dev/null @@ -1,45 +0,0 @@ -ravenClient = $ravenClient; - } - - public function handleError($code, $message, $file = '', $line = 0, $context = []) - { - $this->ravenClient->leaveBreadcrumb( - new Breadcrumb($this->ravenClient->translateSeverity($code), Breadcrumb::TYPE_ERROR, 'error_reporting', $message, [ - 'code' => $code, - 'line' => $line, - 'file' => $file, - ]) - ); - - if (null !== $this->existingHandler) { - return call_user_func($this->existingHandler, $code, $message, $file, $line, $context); - } else { - return false; - } - } - - public function install() - { - $this->existingHandler = set_error_handler([$this, 'handleError'], E_ALL); - - return $this; - } -} diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 2f5e89d32..535c9081f 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -66,11 +66,6 @@ class Client public $severityMap; public $storeErrorsForBulkSend = false; - /** - * @var ErrorHandler - */ - protected $errorHandler; - /** * @var \Raven\Serializer */ @@ -186,10 +181,6 @@ public function __construct(Configuration $config, TransportInterface $transport if ($this->config->getSerializeAllObjects()) { $this->setAllObjectSerialize(true); } - - if ($this->config->shouldInstallDefaultBreadcrumbHandlers()) { - $this->registerDefaultBreadcrumbHandlers(); - } } /** @@ -306,25 +297,6 @@ public function setSerializer(Serializer $serializer) $this->serializer = $serializer; } - /** - * Installs any available automated hooks (such as error_reporting). - * - * @throws \Raven\Exception - */ - public function install() - { - if ($this->errorHandler) { - throw new \Raven\Exception(__CLASS__ . '->install() must only be called once'); - } - - $this->errorHandler = new ErrorHandler($this, false, $this->getConfig()->getErrorTypes()); - $this->errorHandler->registerExceptionHandler(); - $this->errorHandler->registerErrorHandler(); - $this->errorHandler->registerShutdownFunction(); - - return $this; - } - /** * Logs a message. * @@ -402,12 +374,6 @@ public function getLastEventId() return str_replace('-', '', $this->lastEvent->getId()->toString()); } - protected function registerDefaultBreadcrumbHandlers() - { - $handler = new Breadcrumbs\ErrorHandler($this); - $handler->install(); - } - /** * @return bool * @codeCoverageIgnore @@ -526,15 +492,16 @@ public function translateSeverity($severity) case E_DEPRECATED: case E_USER_DEPRECATED: case E_WARNING: - case E_CORE_WARNING: - case E_COMPILE_WARNING: case E_USER_WARNING: case E_RECOVERABLE_ERROR: return self::LEVEL_WARNING; case E_ERROR: case E_PARSE: case E_CORE_ERROR: + case E_CORE_WARNING: case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + return self::LEVEL_FATAL; case E_USER_ERROR: return self::LEVEL_ERROR; case E_NOTICE: diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index c28dcf542..7cd18d5f3 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -47,8 +47,6 @@ * @method setSerializeAllObjects(bool $serializeAllObjects) * @method float getSampleRate() * @method setSampleRate(float $sampleRate) - * @method bool shouldInstallDefaultBreadcrumbHandlers() - * @method setInstallDefaultBreadcrumbHandlers($installDefaultBreadcrumbHandlers) * @method string getMbDetectOrder() * @method setMbDetectOrder(string $detectOrder) * @method bool getAutoLogStacks() diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 038dd15d1..7d1b7ffcf 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -157,28 +157,6 @@ public function setSampleRate($sampleRate) $this->options = $this->resolver->resolve($options); } - /** - * Gets whether the default breadcrumb handlers should be installed. - * - * @return bool - */ - public function shouldInstallDefaultBreadcrumbHandlers() - { - return $this->options['install_default_breadcrumb_handlers']; - } - - /** - * Sets whether the default breadcrumb handlers should be installed. - * - * @param bool $installDefaultBreadcrumbHandlers Flag indicating if the default handlers should be installed - */ - public function setInstallDefaultBreadcrumbHandlers($installDefaultBreadcrumbHandlers) - { - $options = array_merge($this->options, ['install_default_breadcrumb_handlers' => $installDefaultBreadcrumbHandlers]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the character encoding detection order. * @@ -623,7 +601,6 @@ private function configureOptions(OptionsResolver $resolver) 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'serialize_all_object' => false, 'sample_rate' => 1, - 'install_default_breadcrumb_handlers' => true, 'mb_detect_order' => null, 'auto_log_stacks' => true, 'context_lines' => 3, @@ -647,7 +624,6 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('prefixes', 'array'); $resolver->setAllowedTypes('serialize_all_object', 'bool'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); - $resolver->setAllowedTypes('install_default_breadcrumb_handlers', 'bool'); $resolver->setAllowedTypes('mb_detect_order', ['null', 'array']); $resolver->setAllowedTypes('auto_log_stacks', 'bool'); $resolver->setAllowedTypes('context_lines', 'int'); diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index ec0cb0c79..e248e933a 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -1,7 +1,5 @@ registerExceptionHandler(); - * $error_handler->registerErrorHandler(); - * $error_handler->registerShutdownFunction(); + * @author Stefano Arlandini */ - -// TODO(dcramer): deprecate default error types in favor of runtime configuration -// unless a reason can be determined that making them dynamic is better. They -// currently are not used outside of the fatal handler. -class ErrorHandler +class ErrorHandler extends AbstractErrorHandler { - protected $old_exception_handler; - protected $call_existing_exception_handler = false; - protected $old_error_handler; - protected $call_existing_error_handler = false; - protected $reservedMemory; - /** @var \Raven\Client */ - protected $client; - protected $send_errors_last = false; - protected $fatal_error_types = [ - E_ERROR, - E_PARSE, - E_CORE_ERROR, - E_CORE_WARNING, - E_COMPILE_ERROR, - E_COMPILE_WARNING, - E_STRICT, - ]; - /** - * @var array - * Error types which should be processed by the handler. - * A 'null' value implies "whatever error_reporting is at time of error". - */ - protected $error_types = null; - - public function __construct( - $client, - $send_errors_last = false, - $error_types = null, - $__error_types = null - ) { - // support legacy fourth argument for error types - if (null === $error_types) { - $error_types = $__error_types; - } - - $this->client = $client; - $this->error_types = $error_types; - $this->fatal_error_types = array_reduce($this->fatal_error_types, [$this, 'bitwiseOr']); - if ($send_errors_last) { - $this->send_errors_last = true; - $this->client->storeErrorsForBulkSend = true; - } - } - - public function bitwiseOr($a, $b) - { - return $a | $b; - } - - public function handleException($e, $isError = false, $vars = null) - { - $e->event_id = $this->client->captureException($e, $vars); - - if (!$isError && $this->call_existing_exception_handler) { - if (null !== $this->old_exception_handler) { - call_user_func($this->old_exception_handler, $e); - } else { - throw $e; - } - } - } - - public function handleError($type, $message, $file = '', $line = 0, $context = []) - { - // http://php.net/set_error_handler - // The following error types cannot be handled with a user defined function: E_ERROR, - // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and - // most of E_STRICT raised in the file where set_error_handler() is called. - - if (0 !== error_reporting()) { - $error_types = $this->error_types; - if (null === $error_types) { - $error_types = error_reporting(); - } - if ($error_types & $type) { - $e = new \ErrorException($message, 0, $type, $file, $line); - $this->handleException($e, true, $context); - } - } - - if ($this->call_existing_error_handler) { - if (null !== $this->old_error_handler) { - return call_user_func( - $this->old_error_handler, - $type, - $message, - $file, - $line, - $context - ); - } else { - return false; - } - } - - return true; - } - - public function handleFatalError() - { - unset($this->reservedMemory); - - if (null === $error = error_get_last()) { - return; - } - - if ($this->shouldCaptureFatalError($error['type'])) { - $e = new \ErrorException( - @$error['message'], - 0, - @$error['type'], - @$error['file'], - @$error['line'] - ); - $this->handleException($e, true); - } - } - - public function shouldCaptureFatalError($type) - { - // Do not capture E_ERROR since those can be caught by userland since PHP 7.0 - // E_ERROR should already be handled by the exception handler - // This prevents duplicated exceptions in PHP 7.0+ - if (PHP_VERSION_ID >= 70000 && E_ERROR === $type) { - return false; - } - - return $type & $this->fatal_error_types; - } - - /** - * Register a handler which will intercept unhandled exceptions and report them to the - * associated Sentry client. + * Registers this error handler by associating its instance with the given + * Raven client. * - * @param bool $call_existing Call any existing exception handlers after processing this instance + * @param Client $client The Raven client + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler * - * @return \Raven\ErrorHandler + * @return ErrorHandler */ - public function registerExceptionHandler($call_existing = true) + public static function register(Client $client, $reservedMemorySize = 10240) { - $this->old_exception_handler = set_exception_handler([$this, 'handleException']); - $this->call_existing_exception_handler = $call_existing; - - return $this; + return new self($client, $reservedMemorySize); } /** - * Register a handler which will intercept standard PHP errors and report them to the - * associated Sentry client. - * - * @param bool $call_existing Call any existing errors handlers after processing this instance - * @param array $error_types All error types that should be sent - * - * @return \Raven\ErrorHandler + * {@inheritdoc} */ - public function registerErrorHandler($call_existing = true, $error_types = null) + protected function doHandleException($exception) { - if (null !== $error_types) { - $this->error_types = $error_types; - } - $this->old_error_handler = set_error_handler([$this, 'handleError'], E_ALL); - $this->call_existing_error_handler = $call_existing; - - return $this; - } - - /** - * Register a fatal error handler, which will attempt to capture errors which - * shutdown the PHP process. These are commonly things like OOM or timeouts. - * - * @param int $reservedMemorySize Number of kilobytes memory space to reserve, - * which is utilized when handling fatal errors - * - * @return \Raven\ErrorHandler - */ - public function registerShutdownFunction($reservedMemorySize = 10) - { - register_shutdown_function([$this, 'handleFatalError']); - - $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); - - return $this; + $this->client->captureException($exception); } } diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 68a4f02c5..2f66574ca 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -44,7 +44,7 @@ class Stacktrace implements \JsonSerializable protected $frames = []; /** - * @var array The list of functions to import a file + * @var string[] The list of functions to import a file */ protected static $importStatements = [ 'include', @@ -85,7 +85,7 @@ public static function create(Client $client) * @param Client $client The Raven client * @param array $backtrace The backtrace * @param string $file The file that originated the backtrace - * @param string $line The line at which the backtrace originated + * @param int $line The line at which the backtrace originated * * @return static */ diff --git a/lib/Raven/Transport/HttpTransport.php b/lib/Raven/Transport/HttpTransport.php index f1b46b00b..73b3a0f6f 100644 --- a/lib/Raven/Transport/HttpTransport.php +++ b/lib/Raven/Transport/HttpTransport.php @@ -60,7 +60,10 @@ public function __construct(Configuration $config, HttpAsyncClient $httpClient, $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; - register_shutdown_function(function () { + // By calling the cleanupPendingRequests function from a shutdown function + // registered inside another shutdown function we can be confident that it + // will be executed last + register_shutdown_function('register_shutdown_function', function () { // When the library will support PHP 7.1+ only this closure can be // replaced with a simple call to \Closure::fromCallable $this->cleanupPendingRequests(); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 87acf9089..89cadfc81 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ @@ -20,6 +19,7 @@ tests + tests/phpt diff --git a/tests/AbstractErrorHandlerTest.php b/tests/AbstractErrorHandlerTest.php new file mode 100644 index 000000000..e89f75476 --- /dev/null +++ b/tests/AbstractErrorHandlerTest.php @@ -0,0 +1,93 @@ +client = $this->getMockBuilder(Client::class) + ->disableOriginalConstructor() + ->setMethodsExcept(['translateSeverity']) + ->getMock(); + } + + public function testConstructor() + { + try { + $errorHandler = $this->createErrorHandler($this->client); + $previousErrorHandler = set_error_handler('var_dump'); + + restore_error_handler(); + + $this->assertSame([$errorHandler, 'handleError'], $previousErrorHandler); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + /** + * @dataProvider constructorThrowsWhenReservedMemorySizeIsWrongDataProvider + * + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage The value of the $reservedMemorySize argument must be an integer greater than 0. + */ + public function testConstructorThrowsWhenReservedMemorySizeIsWrong($reservedMemorySize) + { + $this->createErrorHandler($this->client, $reservedMemorySize); + } + + public function constructorThrowsWhenReservedMemorySizeIsWrongDataProvider() + { + return [ + [-1], + [0], + ['foo'], + ]; + } + + /** + * @dataProvider captureAtDataProvider + */ + public function testCaptureAt($levels, $replace, $expectedCapturedErrors) + { + try { + $errorHandler = $this->createErrorHandler($this->client); + $previousCapturedErrors = $this->getObjectAttribute($errorHandler, 'capturedErrors'); + + $this->assertEquals($previousCapturedErrors, $errorHandler->captureAt($levels, $replace)); + $this->assertAttributeEquals($expectedCapturedErrors, 'capturedErrors', $errorHandler); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function captureAtDataProvider() + { + return [ + [E_USER_NOTICE, false, E_ALL], + [E_USER_NOTICE, true, E_USER_NOTICE], + ]; + } + + abstract protected function createErrorHandler(...$arguments); +} diff --git a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php new file mode 100644 index 000000000..069e3e872 --- /dev/null +++ b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php @@ -0,0 +1,325 @@ +client->expects($this->once()) + ->method('leaveBreadcrumb') + ->with($this->callback(function ($breadcrumb) { + /* @var Breadcrumb $breadcrumb */ + $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); + $this->assertEquals('User Notice: foo bar', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); + $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals('error_reporting', $breadcrumb->getCategory()); + $this->assertArraySubset([ + 'code' => 0, + 'file' => __FILE__, + 'line' => __LINE__ + 20, + ], $breadcrumb->getMetadata()); + + return true; + })); + + try { + $errorHandler = $this->createErrorHandler($this->client); + $errorHandler->captureAt(0, true); + + $reflectionProperty = new \ReflectionProperty(BreadcrumbErrorHandler::class, 'previousErrorHandler'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($errorHandler, null); + $reflectionProperty->setAccessible(false); + + $this->assertFalse($errorHandler->handleError(0, 'foo bar', __FILE__, __LINE__)); + + $errorHandler->captureAt(E_USER_NOTICE, true); + + $this->assertFalse($errorHandler->handleError(E_USER_WARNING, 'foo bar', __FILE__, __LINE__)); + $this->assertTrue($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__)); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleErrorWithPreviousErrorHandler() + { + $this->client->expects($this->once()) + ->method('leaveBreadcrumb') + ->with($this->callback(function ($breadcrumb) { + /* @var Breadcrumb $breadcrumb */ + $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); + $this->assertEquals('User Notice: foo bar', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); + $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals('error_reporting', $breadcrumb->getCategory()); + $this->assertArraySubset([ + 'code' => 0, + 'file' => __FILE__, + 'line' => __LINE__ + 20, + ], $breadcrumb->getMetadata()); + + return true; + })); + + $previousErrorHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); + $previousErrorHandler->expects($this->once()) + ->method('__invoke') + ->with(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__ + 11) + ->willReturn(false); + + try { + $errorHandler = $this->createErrorHandler($this->client); + + $reflectionProperty = new \ReflectionProperty(BreadcrumbErrorHandler::class, 'previousErrorHandler'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($errorHandler, $previousErrorHandler); + $reflectionProperty->setAccessible(false); + + $errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleFatalError() + { + $this->client->expects($this->once()) + ->method('leaveBreadcrumb') + ->with($this->callback(function ($breadcrumb) { + /* @var Breadcrumb $breadcrumb */ + $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); + $this->assertEquals('Parse Error: foo bar', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); + $this->assertEquals(Client::LEVEL_FATAL, $breadcrumb->getLevel()); + $this->assertEquals('error_reporting', $breadcrumb->getCategory()); + $this->assertArraySubset([ + 'code' => 0, + 'file' => __FILE__, + 'line' => __LINE__ + 12, + ], $breadcrumb->getMetadata()); + + return true; + })); + + try { + $errorHandler = $this->createErrorHandler($this->client); + $errorHandler->handleFatalError([ + 'type' => E_PARSE, + 'message' => 'foo bar', + 'file' => __FILE__, + 'line' => __LINE__, + ]); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleFatalErrorWithNonFatalErrorDoesNothing() + { + $this->client->expects($this->never()) + ->method('leaveBreadcrumb'); + + try { + $errorHandler = $this->createErrorHandler($this->client); + $errorHandler->handleFatalError([ + 'type' => E_USER_NOTICE, + 'message' => 'foo bar', + 'file' => __FILE__, + 'line' => __LINE__, + ]); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleExceptionSkipsNotErrorExceptionException() + { + $exception = new \Exception('foo bar'); + + $this->client->expects($this->never()) + ->method('leaveBreadcrumb'); + + try { + $errorHandler = $this->createErrorHandler($this->client); + + try { + $errorHandler->handleException($exception); + + $this->fail('Exception expected'); + } catch (\Exception $catchedException) { + $this->assertSame($exception, $catchedException); + } + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleExceptionWithPreviousExceptionHandler() + { + $exception = new \ErrorException('foo bar', 0, E_USER_NOTICE); + + $this->client->expects($this->once()) + ->method('leaveBreadcrumb') + ->with($this->callback(function ($breadcrumb) { + /* @var Breadcrumb $breadcrumb */ + $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); + $this->assertEquals('foo bar', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); + $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals('error_reporting', $breadcrumb->getCategory()); + $this->assertArraySubset([ + 'code' => 0, + 'file' => __FILE__, + 'line' => __LINE__ - 14, + ], $breadcrumb->getMetadata()); + + return true; + })); + + $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); + $previousExceptionHandler->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + try { + $errorHandler = $this->createErrorHandler($this->client); + + $reflectionProperty = new \ReflectionProperty(BreadcrumbErrorHandler::class, 'previousExceptionHandler'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); + $reflectionProperty->setAccessible(false); + + try { + $errorHandler->handleException($exception); + + $this->fail('Exception expected'); + } catch (\Exception $catchedException) { + $this->assertSame($exception, $catchedException); + } + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleExceptionWithThrowingPreviousExceptionHandler() + { + $exception1 = new \ErrorException('foo bar', 0, E_USER_NOTICE); + $exception2 = new \ErrorException('bar foo', 0, E_USER_NOTICE); + + $this->client->expects($this->exactly(2)) + ->method('leaveBreadcrumb') + ->withConsecutive($this->callback(function ($breadcrumb) { + /* @var Breadcrumb $breadcrumb */ + $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); + $this->assertEquals('foo bar', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); + $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals('error_reporting', $breadcrumb->getCategory()); + $this->assertArraySubset([ + 'code' => 0, + 'file' => __FILE__, + 'line' => __LINE__ - 15, + ], $breadcrumb->getMetadata()); + + return true; + }), $this->callback(function ($breadcrumb) { + /* @var Breadcrumb $breadcrumb */ + $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); + $this->assertEquals('bar foo', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); + $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals('error_reporting', $breadcrumb->getCategory()); + $this->assertArraySubset([ + 'code' => 0, + 'file' => __FILE__, + 'line' => __LINE__ - 29, + ], $breadcrumb->getMetadata()); + + return true; + })); + + $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); + $previousExceptionHandler->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception1)) + ->will($this->throwException($exception2)); + + try { + $errorHandler = $this->createErrorHandler($this->client); + + $reflectionProperty = new \ReflectionProperty(BreadcrumbErrorHandler::class, 'previousExceptionHandler'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); + $reflectionProperty->setAccessible(false); + + try { + $errorHandler->handleException($exception1); + + $this->fail('Exception expected'); + } catch (\Exception $catchedException) { + $this->assertSame($exception2, $catchedException); + } + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testThrownErrorLeavesBreadcrumb() + { + $this->client->expects($this->once()) + ->method('leaveBreadcrumb') + ->with($this->callback(function ($breadcrumb) { + /* @var Breadcrumb $breadcrumb */ + $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); + $this->assertEquals('User Warning: foo bar', $breadcrumb->getMessage()); + $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); + $this->assertEquals(Client::LEVEL_WARNING, $breadcrumb->getLevel()); + $this->assertEquals('error_reporting', $breadcrumb->getCategory()); + $this->assertArraySubset([ + 'code' => 0, + 'file' => __FILE__, + 'line' => __LINE__ + 9, + ], $breadcrumb->getMetadata()); + + return true; + })); + + try { + $this->createErrorHandler($this->client); + + @trigger_error('foo bar', E_USER_WARNING); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + protected function createErrorHandler(...$arguments) + { + return BreadcrumbErrorHandler::register(...$arguments); + } +} diff --git a/tests/Breadcrumbs/ErrorHandlerTest.php b/tests/Breadcrumbs/ErrorHandlerTest.php deleted file mode 100644 index 1cbbe5478..000000000 --- a/tests/Breadcrumbs/ErrorHandlerTest.php +++ /dev/null @@ -1,41 +0,0 @@ - false, - ])->getClient(); - - $handler = new ErrorHandler($client); - $handler->handleError(E_WARNING, 'message'); - - $breadcrumbsRecorder = $this->getObjectAttribute($client, 'breadcrumbRecorder'); - - /** @var \Raven\Breadcrumbs\Breadcrumb[] $breadcrumbs */ - $breadcrumbs = iterator_to_array($breadcrumbsRecorder); - - $this->assertCount(1, $breadcrumbs); - - $this->assertEquals($breadcrumbs[0]->getMessage(), 'message'); - $this->assertEquals($breadcrumbs[0]->getLevel(), Client::LEVEL_WARNING); - $this->assertEquals($breadcrumbs[0]->getCategory(), 'error_reporting'); - } -} diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php index 129b11496..aa7f3e934 100644 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -114,9 +114,7 @@ public function testThrowableBeingParsedAsException() */ private function createClient() { - $client = $client = ClientBuilder::create([ - 'install_default_breadcrumb_handlers' => false, - ])->getClient(); + $client = $client = ClientBuilder::create()->getClient(); return $client; } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 16f0d3cd3..eb6f0b778 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -245,7 +245,6 @@ public function optionsDataProvider() ['setPrefixes', ['foo', 'bar']], ['setSerializeAllObjects', false], ['setSampleRate', 0.5], - ['setInstallDefaultBreadcrumbHandlers', false], ['setMbDetectOrder', ['foo', 'bar']], ['setAutoLogStacks', false], ['setContextLines', 0], diff --git a/tests/ClientTest.php b/tests/ClientTest.php index d13e15e46..b45c2589d 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -14,10 +14,8 @@ use PHPUnit\Framework\TestCase; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidFactory; -use Raven\Breadcrumbs\ErrorHandler; use Raven\Client; use Raven\ClientBuilder; -use Raven\Configuration; use Raven\Context\Context; use Raven\Context\RuntimeContext; use Raven\Context\ServerOsContext; @@ -30,44 +28,6 @@ use Raven\Tests\Fixtures\classes\CarelessException; use Raven\Transport\TransportInterface; -// XXX: Is there a better way to stub the client? -class Dummy_Raven_Client extends \Raven\Client -{ - private $__sent_events = []; - public $dummy_breadcrumbs_handlers_has_set = false; - public $dummy_shutdown_handlers_has_set = false; - - public function getSentEvents() - { - return $this->__sent_events; - } - - public function send(Event $event) - { - if (!$this->config->shouldCapture($event)) { - return; - } - - $this->__sent_events[] = $event; - } - - public static function isHttpRequest() - { - return true; - } - - // short circuit breadcrumbs - public function registerDefaultBreadcrumbHandlers() - { - $this->dummy_breadcrumbs_handlers_has_set = true; - } - - public function registerShutdownFunction() - { - $this->dummy_shutdown_handlers_has_set = true; - } -} - class ClientTest extends TestCase { public function testConstructorInitializesTransactionStack() @@ -383,18 +343,6 @@ public function testAppPathWindows() $this->assertEquals('C:\\foo\\bar\\', $client->getConfig()->getProjectRoot()); } - /** - * @expectedException \Raven\Exception - * @expectedExceptionMessage Raven\Client->install() must only be called once - */ - public function testCannotInstallTwice() - { - $client = ClientBuilder::create()->getClient(); - - $client->install(); - $client->install(); - } - public function testSanitizeExtra() { $client = ClientBuilder::create()->getClient(); @@ -649,74 +597,12 @@ public function testTranslateSeverity() $this->assertEquals('error', $client->translateSeverity(123457)); } - public function testRegisterDefaultBreadcrumbHandlers() - { - if (isset($_ENV['HHVM']) and (1 == $_ENV['HHVM'])) { - $this->markTestSkipped('HHVM stacktrace behaviour'); - - return; - } - - $previous = set_error_handler([$this, 'stabClosureErrorHandler'], E_USER_NOTICE); - - ClientBuilder::create()->getClient(); - - $this->_closure_called = false; - - trigger_error('foobar', E_USER_NOTICE); - set_error_handler($previous, E_ALL); - - $this->assertTrue($this->_closure_called); - - if (isset($this->_debug_backtrace[1]['function']) && ($this->_debug_backtrace[1]['function'] == 'call_user_func') && version_compare(PHP_VERSION, '7.0', '>=')) { - $offset = 2; - } elseif (version_compare(PHP_VERSION, '7.0', '>=')) { - $offset = 1; - } else { - $offset = 2; - } - - $this->assertEquals(ErrorHandler::class, $this->_debug_backtrace[$offset]['class']); - } - - private $_closure_called = false; - - public function stabClosureVoid() - { - $this->_closure_called = true; - } - - public function stabClosureNull() - { - $this->_closure_called = true; - - return null; - } - - public function stabClosureFalse() - { - $this->_closure_called = true; - - return false; - } - - private $_debug_backtrace = []; - - public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $context = []) - { - $this->_closure_called = true; - $this->_debug_backtrace = debug_backtrace(); - - return true; - } - public function testSendChecksShouldCaptureOption() { $shouldCaptureCalled = false; $client = ClientBuilder::create([ 'server' => 'http://public:secret@example.com/1', - 'install_default_breadcrumb_handlers' => false, 'should_capture' => function () use (&$shouldCaptureCalled) { $shouldCaptureCalled = true; @@ -729,23 +615,6 @@ public function testSendChecksShouldCaptureOption() $this->assertTrue($shouldCaptureCalled); } - public function test__construct_handlers() - { - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - - foreach ([true, false] as $u1) { - $client = new Dummy_Raven_Client( - new Configuration([ - 'install_default_breadcrumb_handlers' => $u1, - ]), - $transport - ); - - $this->assertEquals($u1, $client->dummy_breadcrumbs_handlers_has_set); - } - } - /** * @dataProvider sampleRateAbsoluteDataProvider */ @@ -818,9 +687,11 @@ public function testClearBreadcrumb() ); $reflection = new \ReflectionProperty($client, 'breadcrumbRecorder'); $reflection->setAccessible(true); + $this->assertNotEmpty(iterator_to_array($reflection->getValue($client))); $client->clearBreadcrumbs(); + $this->assertEmpty(iterator_to_array($reflection->getValue($client))); } diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index fb09001d5..e5b93fbae 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -48,7 +48,6 @@ public function optionsDataProvider() ['prefixes', ['foo', 'bar'], 'getPrefixes', 'setPrefixes'], ['serialize_all_object', false, 'getSerializeAllObjects', 'setSerializeAllObjects'], ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], - ['install_default_breadcrumb_handlers', false, 'shouldInstallDefaultBreadcrumbHandlers', 'setInstallDefaultBreadcrumbHandlers'], ['mb_detect_order', null, 'getMbDetectOrder', 'setMbDetectOrder'], ['auto_log_stacks', false, 'getAutoLogStacks', 'setAutoLogStacks'], ['context_lines', 3, 'getContextLines', 'setContextLines'], diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 6034319c6..7d22baa8f 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -11,240 +11,251 @@ namespace Raven\Tests; -use PHPUnit\Framework\TestCase; +use Raven\AbstractErrorHandler; +use Raven\ErrorHandler; -class ErrorHandlerTest extends TestCase +class ErrorHandlerTest extends AbstractErrorHandlerTest { - private $errorLevel; - private $errorHandlerCalled; - private $existingErrorHandler; - - public function setUp() + public function testHandleError() { - $this->errorLevel = error_reporting(); - $this->errorHandlerCalled = false; - $this->existingErrorHandler = set_error_handler([$this, 'errorHandler'], -1); - // improves the reliability of tests - if (function_exists('error_clear_last')) { - error_clear_last(); - } - } + $this->client->expects($this->exactly(1)) + ->method('captureException') + ->with($this->callback(function ($exception) { + /* @var \ErrorException $exception */ + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertEquals(123, $exception->getLine()); + $this->assertEquals(E_USER_NOTICE, $exception->getSeverity()); + $this->assertEquals('User Notice: foo bar', $exception->getMessage()); - public function errorHandler() - { - $this->errorHandlerCalled = true; - } + $backtrace = $exception->getTrace(); - public function tearDown() - { - restore_exception_handler(); - set_error_handler($this->existingErrorHandler); - // // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level - // set_error_handler([$this, 'errorHandler'], error_reporting()); - error_reporting($this->errorLevel); - } + $this->assertGreaterThanOrEqual(2, $backtrace); - public function testErrorsAreLoggedAsExceptions() - { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException', 'sendUnsentErrors']) - ->getMock(); - $client->expects($this->once()) - ->method('captureException') - ->with($this->isInstanceOf('ErrorException')); - - $handler = new \Raven\ErrorHandler($client, E_ALL); - $handler->handleError(E_WARNING, 'message'); - } + $this->assertEquals('handleError', $backtrace[0]['function']); + $this->assertEquals(AbstractErrorHandler::class, $backtrace[0]['class']); + $this->assertEquals('->', $backtrace[0]['type']); - public function testExceptionsAreLogged() - { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $client->expects($this->once()) - ->method('captureException') - ->with($this->isInstanceOf('ErrorException')); + $this->assertEquals('testHandleError', $backtrace[1]['function']); + $this->assertEquals(__CLASS__, $backtrace[1]['class']); + $this->assertEquals('->', $backtrace[1]['type']); - $e = new \ErrorException('message', 0, E_WARNING, '', 0); + return true; + })); - $handler = new \Raven\ErrorHandler($client); - $handler->handleException($e); - } + try { + $errorHandler = $this->createErrorHandler($this->client); + $errorHandler->captureAt(0, true); - public function testErrorHandlerPassErrorReportingPass() - { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $client->expects($this->once()) - ->method('captureException'); + $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousErrorHandler'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($errorHandler, null); + $reflectionProperty->setAccessible(false); - $handler = new \Raven\ErrorHandler($client); - $handler->registerErrorHandler(false, -1); + $this->assertFalse($errorHandler->handleError(0, 'foo bar', __FILE__, __LINE__)); - error_reporting(E_USER_WARNING); - trigger_error('Warning', E_USER_WARNING); + $errorHandler->captureAt(E_USER_NOTICE, true); + + $this->assertFalse($errorHandler->handleError(E_USER_WARNING, 'foo bar', __FILE__, __LINE__)); + $this->assertTrue($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, 123)); + } finally { + restore_error_handler(); + restore_exception_handler(); + } } - public function testErrorHandlerPropagates() + public function testHandleErrorWithPreviousErrorHandler() { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $client->expects($this->never()) - ->method('captureException'); + $this->client->expects($this->once()) + ->method('captureException') + ->with($this->callback(function ($exception) { + /* @var \ErrorException $exception */ + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertEquals(123, $exception->getLine()); + $this->assertEquals(E_USER_NOTICE, $exception->getSeverity()); + $this->assertEquals('User Notice: foo bar', $exception->getMessage()); - $handler = new \Raven\ErrorHandler($client); - $handler->registerErrorHandler(true, E_DEPRECATED); + $backtrace = $exception->getTrace(); - error_reporting(E_USER_WARNING); - trigger_error('Warning', E_USER_WARNING); + $this->assertGreaterThanOrEqual(2, $backtrace); - $this->assertEquals($this->errorHandlerCalled, 1); - } - - public function testExceptionHandlerPropagatesToNative() - { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $client->expects($this->exactly(2)) - ->method('captureException') - ->with($this->isInstanceOf('Exception')); + $this->assertEquals('handleError', $backtrace[0]['function']); + $this->assertEquals(AbstractErrorHandler::class, $backtrace[0]['class']); + $this->assertEquals('->', $backtrace[0]['type']); - $handler = new \Raven\ErrorHandler($client); + $this->assertEquals('testHandleErrorWithPreviousErrorHandler', $backtrace[1]['function']); + $this->assertEquals(__CLASS__, $backtrace[1]['class']); + $this->assertEquals('->', $backtrace[1]['type']); - set_exception_handler(null); - $handler->registerExceptionHandler(false); + return true; + })); - $testException = new \Exception('Test exception'); + $previousErrorHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); + $previousErrorHandler->expects($this->once()) + ->method('__invoke') + ->with(E_USER_NOTICE, 'foo bar', __FILE__, 123) + ->willReturn(false); - $didRethrow = false; try { - $handler->handleException($testException); - } catch (\Exception $e) { - $didRethrow = true; - } - - $this->assertFalse($didRethrow); + $errorHandler = $this->createErrorHandler($this->client); - set_exception_handler(null); - $handler->registerExceptionHandler(true); + $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousErrorHandler'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($errorHandler, $previousErrorHandler); + $reflectionProperty->setAccessible(false); - $didRethrow = false; - $rethrownException = null; - try { - $handler->handleException($testException); - } catch (\Exception $e) { - $didRethrow = true; - $rethrownException = $e; + $errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, 123); + } finally { + restore_error_handler(); + restore_exception_handler(); } - - $this->assertTrue($didRethrow); - $this->assertSame($testException, $rethrownException); } - public function testErrorHandlerRespectsErrorReportingDefault() + public function testHandleFatalError() { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $client->expects($this->once()) - ->method('captureException'); - - error_reporting(E_DEPRECATED); - - $handler = new \Raven\ErrorHandler($client); - $handler->registerErrorHandler(true); - - error_reporting(E_ALL); - trigger_error('Warning', E_USER_WARNING); + $this->client->expects($this->exactly(1)) + ->method('captureException') + ->with($this->callback(function ($exception) { + /* @var \ErrorException $exception */ + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertEquals(123, $exception->getLine()); + $this->assertEquals(E_PARSE, $exception->getSeverity()); + $this->assertEquals('Parse Error: foo bar', $exception->getMessage()); + + return true; + })); - $this->assertEquals($this->errorHandlerCalled, 1); + try { + $errorHandler = $this->createErrorHandler($this->client); + $errorHandler->handleFatalError([ + 'type' => E_PARSE, + 'message' => 'foo bar', + 'file' => __FILE__, + 'line' => 123, + ]); + } finally { + restore_error_handler(); + restore_exception_handler(); + } } - // Because we cannot **know** that a user silenced an error, we always - // defer to respecting the error reporting settings. - public function testSilentErrorsAreNotReportedWithGlobal() + public function testHandleFatalErrorWithNonFatalErrorDoesNothing() { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $client->expects($this->never()) - ->method('captureException'); - - error_reporting(E_ALL); - - $handler = new \Raven\ErrorHandler($client); - $handler->registerErrorHandler(true); - - @$undefined; + $this->client->expects($this->never()) + ->method('captureException'); - // also ensure it doesnt get reported by the fatal handler - $handler->handleFatalError(); + try { + $errorHandler = $this->createErrorHandler($this->client); + $errorHandler->handleFatalError([ + 'type' => E_USER_NOTICE, + 'message' => 'foo bar', + 'file' => __FILE__, + 'line' => __LINE__, + ]); + } finally { + restore_error_handler(); + restore_exception_handler(); + } } - // Because we cannot **know** that a user silenced an error, we always - // defer to respecting the error reporting settings. - public function testSilentErrorsAreNotReportedWithLocal() + public function testHandleException() { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $client->expects($this->never()) - ->method('captureException'); - - $handler = new \Raven\ErrorHandler($client); - $handler->registerErrorHandler(true, E_ALL); + $exception = new \Exception('foo bar'); - @$my_array[2]; + $this->client->expects($this->once()) + ->method('captureException') + ->with($this->identicalTo($exception)); - // also ensure it doesnt get reported by the fatal handler - $handler->handleFatalError(); + try { + $errorHandler = $this->createErrorHandler($this->client); + + try { + $errorHandler->handleException($exception); + + $this->fail('Exception expected'); + } catch (\Exception $catchedException) { + $this->assertSame($exception, $catchedException); + } + } finally { + restore_error_handler(); + restore_exception_handler(); + } } - public function testShouldCaptureFatalErrorBehavior() + public function testHandleExceptionWithPreviousExceptionHandler() { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $handler = new \Raven\ErrorHandler($client); + $exception = new \Exception('foo bar'); - $this->assertEquals($handler->shouldCaptureFatalError(E_ERROR), PHP_VERSION_ID < 70000); + $this->client->expects($this->once()) + ->method('captureException') + ->with($this->identicalTo($exception)); - $this->assertEquals($handler->shouldCaptureFatalError(E_WARNING), false); + $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); + $previousExceptionHandler->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + try { + $errorHandler = $this->createErrorHandler($this->client); + + $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousExceptionHandler'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); + $reflectionProperty->setAccessible(false); + + try { + $errorHandler->handleException($exception); + + $this->fail('Exception expected'); + } catch (\Exception $catchedException) { + $this->assertSame($exception, $catchedException); + } + } finally { + restore_error_handler(); + restore_exception_handler(); + } } - public function testErrorHandlerDefaultsErrorReporting() + public function testHandleExceptionWithThrowingPreviousExceptionHandler() { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $client->expects($this->never()) - ->method('captureException'); + $exception1 = new \Exception('foo bar'); + $exception2 = new \Exception('bar foo'); - error_reporting(E_USER_ERROR); + $this->client->expects($this->exactly(2)) + ->method('captureException') + ->withConsecutive($this->identicalTo($exception1), $this->identicalTo($exception2)); - $handler = new \Raven\ErrorHandler($client); - $handler->registerErrorHandler(false); + $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); + $previousExceptionHandler->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception1)) + ->will($this->throwException($exception2)); - trigger_error('Warning', E_USER_WARNING); + try { + $errorHandler = $this->createErrorHandler($this->client); + + $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousExceptionHandler'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); + $reflectionProperty->setAccessible(false); + + try { + $errorHandler->handleException($exception1); + + $this->fail('Exception expected'); + } catch (\Exception $catchedException) { + $this->assertSame($exception2, $catchedException); + } + } finally { + restore_error_handler(); + restore_exception_handler(); + } } - public function testFluidInterface() + protected function createErrorHandler(...$arguments) { - $client = $this->getMockBuilder('Client') - ->setMethods(['captureException']) - ->getMock(); - $handler = new \Raven\ErrorHandler($client); - $result = $handler->registerErrorHandler(); - $this->assertEquals($result, $handler); - $result = $handler->registerExceptionHandler(); - $this->assertEquals($result, $handler); - // TODO(dcramer): cant find a great way to test resetting the shutdown - // handler - // $result = $handler->registerShutdownHandler(); - // $this->assertEquals($result, $handler); + return ErrorHandler::register(...$arguments); } } diff --git a/tests/phpt/exception_rethrown.phpt b/tests/phpt/exception_rethrown.phpt new file mode 100644 index 000000000..2f5f72287 --- /dev/null +++ b/tests/phpt/exception_rethrown.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test rethrowing in custom exception handler +--FILE-- +getClient(); + +ErrorHandler::register($client); + +throw new \Exception('foo bar'); +?> +--EXPECTREGEX-- +custom exception handler called +Fatal error: (Uncaught Exception: foo bar|Uncaught exception 'Exception' with message 'foo bar') in [^\r\n]+:\d+ +Stack trace: +.+ \ No newline at end of file diff --git a/tests/phpt/exception_thrown.phpt b/tests/phpt/exception_thrown.phpt new file mode 100644 index 000000000..c5f9986dc --- /dev/null +++ b/tests/phpt/exception_thrown.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test throwing exception in custom exception handler +--FILE-- +getClient(); + +ErrorHandler::register($client); + +throw new \Exception('foo bar'); +?> +--EXPECTREGEX-- +custom exception handler called +Fatal error: (Uncaught Exception: bar foo|Uncaught exception 'Exception' with message 'bar foo') in [^\r\n]+:\d+ +Stack trace: +.+ \ No newline at end of file diff --git a/tests/phpt/fatal_error.phpt b/tests/phpt/fatal_error.phpt new file mode 100644 index 000000000..c087dd3c6 --- /dev/null +++ b/tests/phpt/fatal_error.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test catching fatal errors +--FILE-- + 'http://public:secret@local.host/1', + 'send_attempts' => 1, +])->getClient(); + +ErrorHandler::register($client); + +register_shutdown_function('register_shutdown_function', function () use ($client) { + /** @var \Raven\Transport\HttpTransport $transport */ + $transport = Assert::getObjectAttribute($client, 'transport'); + + Assert::assertNotNull($client->getLastEvent()); + Assert::assertAttributeEmpty('pendingRequests', $transport); + + echo 'Shutdown function called'; +}); + +class TestClass implements \Serializable +{ +} +?> +--EXPECTF-- +Fatal error: Class Raven\Tests\TestClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/phpt/fatal_error_not_captured_twice.phpt b/tests/phpt/fatal_error_not_captured_twice.phpt new file mode 100644 index 000000000..fdec9a6f4 --- /dev/null +++ b/tests/phpt/fatal_error_not_captured_twice.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test catching fatal errors does not capture twice +--FILE-- +setTransport($transport) + ->getClient(); + +ErrorHandler::register($client); + +register_shutdown_function('register_shutdown_function', function () use ($spool) { + Assert::assertAttributeCount(1, 'events', $spool); + + echo 'Shutdown function called'; +}); + +\Foo\Bar::baz(); +?> +--EXPECTREGEX-- +Fatal error: (?:Class 'Foo\\Bar' not found in [^\r\n]+ on line \d+|Uncaught Error: Class 'Foo\\Bar' not found in [^\r\n]+:\d+) +(?:Stack trace:[\s\S]+)?Shutdown function called \ No newline at end of file diff --git a/tests/phpt/out_of_memory.phpt b/tests/phpt/out_of_memory.phpt new file mode 100644 index 000000000..ef0e40edd --- /dev/null +++ b/tests/phpt/out_of_memory.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test catching out of memory fatal error +--FILE-- + 'http://public:secret@local.host/1', + 'send_attempts' => 1, +])->getClient(); + +ErrorHandler::register($client); + +register_shutdown_function('register_shutdown_function', function () use ($client) { + /** @var \Raven\Transport\HttpTransport $transport */ + $transport = Assert::getObjectAttribute($client, 'transport'); + + Assert::assertNotNull($client->getLastEvent()); + Assert::assertAttributeEmpty('pendingRequests', $transport); + + echo 'Shutdown function called'; +}); + +$foo = str_repeat('x', 1024 * 1024 * 30); +?> +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d +Shutdown function called \ No newline at end of file From 4d6ff9297f94f89d4dd01024c4eb65f5fab7607d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 19 May 2018 00:58:38 +0200 Subject: [PATCH 0338/1161] Add a SanitizerMiddleware middleware (#602) --- UPGRADE-2.0.md | 27 ++- lib/Raven/Client.php | 85 +++------ lib/Raven/Middleware/SanitizerMiddleware.php | 80 ++++++++ tests/ClientTest.php | 182 ------------------- tests/Middleware/SanitizerMiddlewareTest.php | 82 +++++++++ 5 files changed, 211 insertions(+), 245 deletions(-) create mode 100644 lib/Raven/Middleware/SanitizerMiddleware.php create mode 100644 tests/Middleware/SanitizerMiddlewareTest.php diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 8224d4645..14d7814d6 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -335,9 +335,8 @@ - The method `Raven_Client::process` has been removed as there is no need to process event data from outside the `Client` class. -- The `Raven_Processor` class has been removed. There is not anymore a base - abstract class for the processors, but a `ProcessorInterface` interface has - been introduced. +- The method `Raven_Client::sanitize` has been removed and the sanitization + happens now inside the `SanitizerMiddleware` middleware. - The `Raven_Client::user_context` method has been removed. You should use `Client::getUserContext` instead. @@ -461,9 +460,25 @@ ### Processors -- The `RemoveCookiesProessor` class has been renamed to `SanitizeCookiesProcessor` to - better reflect its purpose. The constructor accepts an array of options to make the - behaviour of which cookies to sanitize configurable. +- The `Raven_Processor_RemoveCookiesProcessor` class has been renamed to `SanitizeCookiesProcessor` + to better reflect its purpose. The constructor accepts an array of options to + make the behaviour of which cookies to sanitize configurable. + +- The `Raven_Processor_SanitizeStacktraceProcessor` class has been renamed to + `SanitizeStacktraceProcessor` to follow the PSR-4 convention. + +- The `Raven_Processor_SanitizeHttpHeadersProcessor` class has been renamed to + `SanitizeHttpHeadersProcessor` to follow the PSR-4 convention. + +- The `Raven_Processor_RemoveHttpBodyProcessor` class has been renamed to + `RemoveHttpBodyProcessor` to follow the PSR-4 convention. + +- The `Raven_Processor_SanitizeDataProcessor` class has been renamed to + `SanitizeDataProcessor` to follow the PSR-4 convention. + +- The `Raven_Processor` class has been removed. There is not anymore a base + abstract class for the processors, but a `ProcessorInterface` interface has + been introduced. ### Context diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 535c9081f..efb1a1592 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -24,6 +24,7 @@ use Raven\Middleware\MiddlewareStack; use Raven\Middleware\ProcessorMiddleware; use Raven\Middleware\RequestInterfaceMiddleware; +use Raven\Middleware\SanitizerMiddleware; use Raven\Middleware\UserInterfaceMiddleware; use Raven\Processor\ProcessorInterface; use Raven\Processor\ProcessorRegistry; @@ -64,21 +65,21 @@ class Client * @var string[]|null */ public $severityMap; - public $storeErrorsForBulkSend = false; /** - * @var \Raven\Serializer + * @var Serializer The serializer */ - protected $serializer; + private $serializer; + /** - * @var \Raven\Serializer + * @var Serializer The representation serializer */ - protected $reprSerializer; + private $reprSerializer; /** * @var Configuration The client configuration */ - protected $config; + private $config; /** * @var Recorder The breadcrumbs recorder @@ -159,17 +160,7 @@ public function __construct(Configuration $config, TransportInterface $transport return $event; }); - $this->addMiddleware(new ProcessorMiddleware($this->processorRegistry), -255); - $this->addMiddleware(new MessageInterfaceMiddleware()); - $this->addMiddleware(new RequestInterfaceMiddleware()); - $this->addMiddleware(new UserInterfaceMiddleware()); - $this->addMiddleware(new ContextInterfaceMiddleware($this->tagsContext, Context::CONTEXT_TAGS)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->userContext, Context::CONTEXT_USER)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->extraContext, Context::CONTEXT_EXTRA)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->runtimeContext, Context::CONTEXT_RUNTIME)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->serverOsContext, Context::CONTEXT_SERVER_OS)); - $this->addMiddleware(new BreadcrumbInterfaceMiddleware($this->breadcrumbRecorder)); - $this->addMiddleware(new ExceptionInterfaceMiddleware($this)); + $this->addDefaultMiddlewares(); $request = ServerRequestFactory::fromGlobals(); $serverParams = $request->getServerParams(); @@ -374,15 +365,6 @@ public function getLastEventId() return str_replace('-', '', $this->lastEvent->getId()->toString()); } - /** - * @return bool - * @codeCoverageIgnore - */ - protected static function isHttpRequest() - { - return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; - } - /** * Captures a new event using the provided data. * @@ -414,13 +396,11 @@ public function capture(array $payload) $event = $this->middlewareStack->executeStack( $event, - static::isHttpRequest() ? ServerRequestFactory::fromGlobals() : null, + isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null, isset($payload['exception']) ? $payload['exception'] : null, $payload ); - $event = $this->sanitize($event); - $this->send($event); $this->lastEvent = $event; @@ -428,34 +408,6 @@ public function capture(array $payload) return str_replace('-', '', $event->getId()->toString()); } - public function sanitize(Event $event) - { - // attempt to sanitize any user provided data - $request = $event->getRequest(); - $userContext = $event->getUserContext(); - $extraContext = $event->getExtraContext(); - $tagsContext = $event->getTagsContext(); - - if (!empty($request)) { - $event = $event->withRequest($this->serializer->serialize($request, 5)); - } - if (!empty($userContext)) { - $event = $event->withUserContext($this->serializer->serialize($userContext, 3)); - } - if (!empty($extraContext)) { - $event = $event->withExtraContext($this->serializer->serialize($extraContext)); - } - if (!empty($tagsContext)) { - foreach ($tagsContext as $key => $value) { - $tagsContext[$key] = @(string) $value; - } - - $event = $event->withTagsContext($tagsContext); - } - - return $event; - } - /** * Sends the given event to the Sentry server. * @@ -579,4 +531,23 @@ public function setAllObjectSerialize($value) $this->serializer->setAllObjectSerialize($value); $this->reprSerializer->setAllObjectSerialize($value); } + + /** + * Adds the default middlewares to this client instance. + */ + private function addDefaultMiddlewares() + { + $this->addMiddleware(new SanitizerMiddleware($this->serializer), -255); + $this->addMiddleware(new ProcessorMiddleware($this->processorRegistry), -250); + $this->addMiddleware(new MessageInterfaceMiddleware()); + $this->addMiddleware(new RequestInterfaceMiddleware()); + $this->addMiddleware(new UserInterfaceMiddleware()); + $this->addMiddleware(new ContextInterfaceMiddleware($this->tagsContext, Context::CONTEXT_TAGS)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->userContext, Context::CONTEXT_USER)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->extraContext, Context::CONTEXT_EXTRA)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->runtimeContext, Context::CONTEXT_RUNTIME)); + $this->addMiddleware(new ContextInterfaceMiddleware($this->serverOsContext, Context::CONTEXT_SERVER_OS)); + $this->addMiddleware(new BreadcrumbInterfaceMiddleware($this->breadcrumbRecorder)); + $this->addMiddleware(new ExceptionInterfaceMiddleware($this)); + } } diff --git a/lib/Raven/Middleware/SanitizerMiddleware.php b/lib/Raven/Middleware/SanitizerMiddleware.php new file mode 100644 index 000000000..948902ce1 --- /dev/null +++ b/lib/Raven/Middleware/SanitizerMiddleware.php @@ -0,0 +1,80 @@ + + */ +final class SanitizerMiddleware +{ + /** + * @var Serializer The serializer instance + */ + private $serializer; + + /** + * Constructor. + * + * @param Serializer $serializer The serializer instance + */ + public function __construct(Serializer $serializer) + { + $this->serializer = $serializer; + } + + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) + { + if (!empty($request = $event->getRequest())) { + $event = $event->withRequest($this->serializer->serialize($request, 5)); + } + + if (!empty($userContext = $event->getUserContext())) { + $event = $event->withUserContext($this->serializer->serialize($userContext)); + } + + if (!empty($runtimeContext = $event->getRuntimeContext())) { + $event = $event->withRuntimeContext($this->serializer->serialize($runtimeContext)); + } + + if (!empty($serverOsContext = $event->getServerOsContext())) { + $event = $event->withServerOsContext($this->serializer->serialize($serverOsContext)); + } + + if (!empty($extraContext = $event->getExtraContext())) { + $event = $event->withExtraContext($this->serializer->serialize($extraContext)); + } + + if (!empty($tagsContext = $event->getTagsContext())) { + $event = $event->withTagsContext($this->serializer->serialize($tagsContext)); + } + + return $next($event, $request, $exception, $payload); + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index b45c2589d..a16ad67a6 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -343,188 +343,6 @@ public function testAppPathWindows() $this->assertEquals('C:\\foo\\bar\\', $client->getConfig()->getProjectRoot()); } - public function testSanitizeExtra() - { - $client = ClientBuilder::create()->getClient(); - - $event = new Event($client->getConfig()); - $event = $event->withExtraContext([ - 'foo' => [ - 'line' => 1216, - 'stack' => [ - 1, [2], 3, - ], - ], - ]); - - $event = $client->sanitize($event); - - $this->assertEquals([ - 'foo' => [ - 'line' => 1216, - 'stack' => [ - 1, 'Array of length 1', 3, - ], - ], - ], $event->getExtraContext()); - } - - public function testSanitizeObjects() - { - $client = ClientBuilder::create(['serialize_all_object' => true])->getClient(); - $clone = ClientBuilder::create()->getClient(); - - $event = new Event($client->getConfig()); - $event = $event->withExtraContext([ - 'object' => $clone, - ]); - - $reflection = new \ReflectionClass($clone); - $expected = []; - foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { - $value = $property->getValue($clone); - if (is_array($value)) { - $property->setValue($clone, []); - $expected[$property->getName()] = []; - continue; - } - if (!is_object($value)) { - $expected[$property->getName()] = $value; - continue; - } - - $new_value = []; - $reflection2 = new \ReflectionClass($value); - foreach ($reflection2->getProperties(\ReflectionProperty::IS_PUBLIC) as $property2) { - $sub_value = $property2->getValue($value); - if (is_array($sub_value)) { - $new_value[$property2->getName()] = 'Array of length ' . count($sub_value); - continue; - } - if (is_object($sub_value)) { - $sub_value = null; - $property2->setValue($value, null); - } - $new_value[$property2->getName()] = $sub_value; - } - - ksort($new_value); - $expected[$property->getName()] = $new_value; - unset($reflection2, $property2, $sub_value, $new_value); - } - unset($reflection, $property, $value, $reflection, $clone); - ksort($expected); - - $event = $client->sanitize($event); - - $this->assertEquals(['object' => $expected], $event->getExtraContext()); - } - - public function testSanitizeTags() - { - $client = ClientBuilder::create()->getClient(); - - $event = new Event($client->getConfig()); - $event = $event->withTagsContext([ - 'foo' => 'bar', - 'baz' => ['biz'], - ]); - - $event = $client->sanitize($event); - - $this->assertEquals([ - 'foo' => 'bar', - 'baz' => 'Array', - ], $event->getTagsContext()); - } - - public function testSanitizeUser() - { - $client = ClientBuilder::create()->getClient(); - - $event = new Event($client->getConfig()); - $event = $event->withUserContext([ - 'email' => 'foo@example.com', - ]); - - $client->sanitize($event); - - $this->assertEquals(['email' => 'foo@example.com'], $event->getUserContext()); - } - - /** - * @dataProvider deepRequestProvider - */ - public function testSanitizeRequest(array $postData, array $expectedData) - { - $client = ClientBuilder::create()->getClient(); - - $event = new Event($client->getConfig()); - $event = $event->withRequest([ - 'method' => 'POST', - 'url' => 'https://example.com/something', - 'query_string' => '', - 'data' => [ - '_method' => 'POST', - 'data' => $postData, - ], - ]); - - $event = $client->sanitize($event); - - $this->assertArraySubset([ - 'method' => 'POST', - 'url' => 'https://example.com/something', - 'query_string' => '', - 'data' => [ - '_method' => 'POST', - 'data' => $expectedData, - ], - ], $event->getRequest()); - } - - public function deepRequestProvider() - { - return [ - [ - [ - 'MyModel' => [ - 'flatField' => 'my value', - 'nestedField' => [ - 'key' => 'my other value', - ], - ], - ], - [ - 'MyModel' => [ - 'flatField' => 'my value', - 'nestedField' => [ - 'key' => 'my other value', - ], - ], - ], - ], - [ - [ - 'Level 1' => [ - 'Level 2' => [ - 'Level 3' => [ - 'Level 4' => 'something', - ], - ], - ], - ], - [ - 'Level 1' => [ - 'Level 2' => [ - 'Level 3' => 'Array of length 1', - ], - ], - ], - ], - ]; - } - private function assertMixedValueAndArray($expected_value, $actual_value) { if (null === $expected_value) { diff --git a/tests/Middleware/SanitizerMiddlewareTest.php b/tests/Middleware/SanitizerMiddlewareTest.php new file mode 100644 index 000000000..6e967d4a5 --- /dev/null +++ b/tests/Middleware/SanitizerMiddlewareTest.php @@ -0,0 +1,82 @@ +withRequest(['bar' => 'baz']); + $event = $event->withUserContext(['foo' => 'bar']); + $event = $event->withTagsContext(['foo', 'bar']); + $event = $event->withServerOsContext(['bar' => 'foo']); + $event = $event->withRuntimeContext(['foo' => 'baz']); + $event = $event->withExtraContext(['baz' => 'foo']); + + /** @var Serializer|\PHPUnit_Framework_MockObject_MockObject $sanitizer */ + $sanitizer = $this->createMock(Serializer::class); + $sanitizer->expects($this->exactly(6)) + ->method('serialize') + ->withConsecutive( + [ + $event->getRequest(), + 5, + ], + [ + $event->getUserContext(), + ], + [ + $event->getRuntimeContext(), + ], + [ + $event->getServerOsContext(), + ], + [ + $event->getExtraContext(), + ], + [ + $event->getTagsContext(), + ] + ) + ->willReturnCallback(function ($eventData) { + // This is here just because otherwise the event object will + // not be updated if the new value being set is the same as + // the previous one + return array_flip($eventData); + }); + + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($event, &$callbackInvoked) { + $this->assertNotSame($event, $eventArg); + $this->assertEquals(['baz' => 'bar'], $eventArg->getRequest()); + $this->assertEquals(['bar' => 'foo'], $eventArg->getUserContext()); + $this->assertEquals(['baz' => 'foo'], $eventArg->getRuntimeContext()); + $this->assertEquals(['foo' => 'bar'], $eventArg->getServerOsContext()); + $this->assertEquals(['foo' => 'baz'], $eventArg->getExtraContext()); + $this->assertEquals(['foo' => 0, 'bar' => 1], $eventArg->getTagsContext()); + + $callbackInvoked = true; + }; + + $middleware = new SanitizerMiddleware($sanitizer); + $middleware($event, $callback); + + $this->assertTrue($callbackInvoked); + } +} From cd31f5ed655c7f47c6de402e95ac07ea467ad6b5 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Sun, 17 Jun 2018 18:01:25 +0200 Subject: [PATCH 0339/1161] Fix build due to BC in CS fixer: namespaced constants (#614) --- lib/Raven/Breadcrumbs/MonologHandler.php | 2 +- lib/Raven/Client.php | 2 +- lib/Raven/Configuration.php | 6 +++--- lib/Raven/Middleware/ModulesMiddleware.php | 2 +- tests/Breadcrumbs/MonologHandlerTest.php | 2 +- tests/StacktraceTest.php | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 4583e3785..2ce3d9e60 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -69,7 +69,7 @@ protected function write(array $record) isset($record['context']['exception']) && ( $record['context']['exception'] instanceof \Exception - || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable) + || (\PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable) ) ) { /** diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index efb1a1592..76f647d53 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -396,7 +396,7 @@ public function capture(array $payload) $event = $this->middlewareStack->executeStack( $event, - isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null, + isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null, isset($payload['exception']) ? $payload['exception'] : null, $payload ); diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 7d1b7ffcf..5b7073790 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -742,11 +742,11 @@ private function normalizeAbsolutePath($value) } if ( - DIRECTORY_SEPARATOR === substr($path, 0, 1) - && DIRECTORY_SEPARATOR !== substr($path, -1) + \DIRECTORY_SEPARATOR === substr($path, 0, 1) + && \DIRECTORY_SEPARATOR !== substr($path, -1) && '.php' !== substr($path, -4) ) { - $path .= DIRECTORY_SEPARATOR; + $path .= \DIRECTORY_SEPARATOR; } return $path; diff --git a/lib/Raven/Middleware/ModulesMiddleware.php b/lib/Raven/Middleware/ModulesMiddleware.php index ce1354b1c..40c75ab4c 100644 --- a/lib/Raven/Middleware/ModulesMiddleware.php +++ b/lib/Raven/Middleware/ModulesMiddleware.php @@ -49,7 +49,7 @@ public function __construct(Configuration $config) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - $composerFilePath = $this->config->getProjectRoot() . DIRECTORY_SEPARATOR . 'composer.json'; + $composerFilePath = $this->config->getProjectRoot() . \DIRECTORY_SEPARATOR . 'composer.json'; if (file_exists($composerFilePath)) { $composer = Factory::create(new NullIO(), $composerFilePath, true); diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php index aa7f3e934..47d1861d2 100644 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -90,7 +90,7 @@ public function testExceptionBeingParsed() public function testThrowableBeingParsedAsException() { - if (PHP_VERSION_ID <= 70000) { + if (\PHP_VERSION_ID <= 70000) { $this->markTestSkipped('PHP 7.0 introduced Throwable'); } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 4b511ae45..5aaf7ddc8 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -289,7 +289,7 @@ public function testGetFrameArgumentsDoesNotModifyCapturedArgs() protected function getFixturePath($file) { - return realpath(__DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . $file); + return realpath(__DIR__ . \DIRECTORY_SEPARATOR . 'Fixtures' . \DIRECTORY_SEPARATOR . $file); } protected function getFixture($file) From 666211aa7efe46718fc03d77d16f8c0efdd8165d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 17 Jun 2018 18:12:25 +0200 Subject: [PATCH 0340/1161] [2.0] Error handling documentation for v2 (#608) * Add the documentation about how to handle errors * Apply code review changes --- docs/error_handling.rst | 91 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 docs/error_handling.rst diff --git a/docs/error_handling.rst b/docs/error_handling.rst new file mode 100644 index 000000000..fec640ac8 --- /dev/null +++ b/docs/error_handling.rst @@ -0,0 +1,91 @@ +Error handling +############## + +To capture unhandled errors and exceptions you have to manually register the +error handler and associate it to an instance of the client. The easiest way +to do it is to simply call the ``ErrorHandler::register`` method and pass the +client instance as first argument. + +.. code-block:: php + + use Raven\ErrorHandler; + + // Initialize the client + + ErrorHandler::register($client); + +By default the error handler reserves 10 megabytes of memory to handle fatal +errors. You can customize the amount by specifying it in bytes as the second +argument. For example, the code below will reserve 20 megabytes of memory. + +.. code-block:: php + + use Raven\ErrorHandler; + + // Initialize the client + + ErrorHandler::register($client, 20480); + +For some frameworks or projects there are specific integrations provided both +officially and by third party users that automatically register the error +handlers. In that case please refer to their documentation. + +Capture errors +============================= + +The error handler can be customized to set which error types should be captured +and sent to the Sentry server: you may want to report all the errors but capture +only some of them. For example, the code below will capture all errors except +``E_DEPRECATED`` and ``E_USER_DEPRECATED``. Note that the ``error_reporting`` +PHP ini option will be respected and any other handler that was present before +the Sentry error handler was registered will still be called regardeless. + +.. code-block:: php + + use Raven\ErrorHandler; + + // Initialize the client + + $errorHandler = ErrorHandler::register($client); + $errorHandler->captureAt(E_ALL &~ E_DEPRECATED &~ E_USER_DEPRECATED, true); + +While calling the ``ErrorHandler::captureAt`` method you can decide whether the +new mask will replace entirely the previous one or not by changing the value of +the second argument. For example, suppose that you first disable the capturing +of the ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` error types and sometime later +you want to re-enable only the first type of errors. In this case you have two +ways to do the same thing: know in advance the old mask and replace it like you +did in the example above or set to ``false`` the ``$replace`` argument (this is +the default value) and the new value will be appended to the existing mask: + +.. code-block:: php + + use Raven\ErrorHandler; + + // Initialize the client + + $errorHandler = ErrorHandler::register($client); + $errorHandler->captureAt(E_ALL &~ E_DEPRECATED &~ E_USER_DEPRECATED, true); + + // some time later you decide to re-enable capturing of E_DEPRECATED errors + + $errorHandler->captureAt(E_DEPRECATED, false); + +Capture breadcrumbs +=================== + +Sentry supports logging breadcrumbs, which are a set of steps that led to an +event. By default the client logs the URL of the page being visited as the +first breadcrumb (if the context in which the app is running is a web request). +To automate the capturing of errors as breadcrumbs (e.g. you may want to automate +logging of warnings leading to an event) you can register an additional error +handler the same way as before. + +.. code-block:: php + + use Raven\BreadcrumbErrorHandler; + + // Initialize the client + + $errorHandler = BreadcrumbErrorHandler::register($client); + $errorHandler->captureAt(E_WARNING | E_USER_WARNING, true); From 367a47115994493dbf8792055199fa179fb902df Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 17 Jun 2018 18:12:34 +0200 Subject: [PATCH 0341/1161] Add the documentation for the transports (#609) --- docs/transport.rst | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 docs/transport.rst diff --git a/docs/transport.rst b/docs/transport.rst new file mode 100644 index 000000000..120bd7341 --- /dev/null +++ b/docs/transport.rst @@ -0,0 +1,86 @@ +Sending events +############## + +Sending an event is very straightforward: you create an instance of the client +by configuring it and passing to it a transport and then you can use it to send +the event. + +Transport types +=============== + +Transports are the classes in Sentry PHP that are responsible for communicating +with a service in order to deliver an event. There are several types of transports +available out-of-the-box, all of which implement the ``TransportInterface`` +interface; + +- The ``NullTransport`` which is used in case no server where errors should be + sent is set in the client configuration. +- The ``HttpTransport`` which is the default and will be used when the server + is set in the client configuration. +- The ``SpoolTransport`` which can be used to defer the sending of events (e.g. + by putting them into a queue). + +The null transport +================== + +Although not so common there could be cases in which you don't want to send +events at all. The ``NullTransport`` transport does this: it simply ignores +the events, but report them as sent. + +.. code-block:: php + + use Raven\Client; + use Raven\Configuration; + use Raven\Transport\NullTransport; + + // Even though the server is configured for the client, using the NullTransport + // transport won't send any event + + $configuration = new Configuration(['server' => 'http://public:secret@example.com/1']); + $client = new Client($configuration, new NullTransport()); + +The HTTP transport +================== + +The ``HttpTransport`` sends events over the HTTP protocol using Httplug_: the +best adapter available is automatically selected when creating a client instance +through the client builder, but you can override it using the appropriate methods. + +.. code-block:: php + + use Raven\Client; + use Raven\Configuration; + use Raven\Transport\HttpTransport; + + $configuration = new Configuration(['server' => 'http://public:secret@example.com/1']); + $transport = new HttpTransport($configuration, HttpAsyncClientDiscovery::find(), MessageFactoryDiscovery::find()); + $client = new Client($configuration, $transport); + +The spool transport +=================== + +The default behavior is to send events immediatly. You may, however, want to +avoid waiting for the communication to the Sentry server that could be slow +or unreliable. This can be avoided by choosing the ``SpoolTransport`` which +stores the events in a queue so that another process can read it and and take +care of sending them. Currently only spooling to memory is supported. + +.. code-block:: php + + use Raven\Client; + use Raven\Configuration; + use Raven\Transport\SpoolTransport; + + // The transport used by the client to send the events uses the memory spool + // which stores the events in a queue in-memory + + $spool = new MemorySpool(); + $transport = new NullTransport(); + $client = new Client(new Configuration(), new SpoolTransport($spool)); + + // When the spool queue is flushed the events are sent using the transport + // passed as parameter of the flushQueue method. + + $spool->flushQueue($transport); + +.. _Httplug: http://httplug.io/ From 916a7cd8007b2c5ac9cd8f256fa9249bfe5aae15 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 17 Jun 2018 18:12:47 +0200 Subject: [PATCH 0342/1161] [2.0] Add the documentation about the middlewares (#610) * Add the documentation about the middlewares * Apply code review changes --- docs/middleware.rst | 112 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 docs/middleware.rst diff --git a/docs/middleware.rst b/docs/middleware.rst new file mode 100644 index 000000000..c532503c1 --- /dev/null +++ b/docs/middleware.rst @@ -0,0 +1,112 @@ +Middlewares +########### + +Middlewares are an essential part of the event sending lifecycle. Each captured +event is passed through the middleware chain before being sent to the server and +each middleware can edit the data to add, change or remove information. There are +several built-in middlewares whose list is: + +- ``BreadcrumbInterfaceMiddleware``: adds all the recorded breadcrumbs up to the + point the event was generated. +- ``ContextInterfaceMiddleware``: adds the data stored in the contexts to the + event. +- ``ExceptionInterfaceMiddleware``: fetches the stacktrace for the captured event + if it's an exception (and has an stacktrace) and integrates additional data like + a small piece of source code for each stackframe. +- ``MessageInterfaceMiddleware``: adds a message (if present) to the event + and optionally format it using ``vsprintf``. +- ``ModulesMiddleware``: fetches informations about the packages installed through + Composer. The ``composer.lock`` file must be present for this middleware to work. +- ``ProcessorMiddleware``: executes the registered processors by passing to them + the event instance. +- ``RequestInterfaceMiddleware``: adds the HTTP request information (e.g. the + headers or the query string) to the event. +- ``SanitizerMiddleware``: sanitizes the data of the event to ensure that it + can be encoded correctly as JSON and the data is serialized in the appropriate + format for their representation. +- ``UserInterfaceMiddleware``: adds some user-related information like the client + IP address to the event. + +Writing a middleware +==================== + +The only requirement for a middleware is that it must be a callable. What this +means is that you can register an anonymous function as middleware as well as +create a class with the magic method ``__invoke`` and they will both work fine. +The signature of the function that will be called must be the following: + +.. code-block:: php + + function (\Raven\Event $event, callable $next, \Psr\Http\Message\ServerRequestInterface $request = null, $exception = null, array $payload = []) + +The middleware can call the next one in the chain or can directly return the +event instance and break the chain. Additional data supplied by the user while +calling the ``capture*`` methods of the ``Client`` class will be passed to each +middleware in the ``$payload`` argument. The example below shows how a simple +middleware that customizes the message captured with an event can be written: + +.. code-block:: php + + use Psr\Http\Message\ServerRequestInterface; + use Raven\ClientBuilder; + use Raven\Event; + + class CustomMiddleware + { + public function (Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) + { + $event = $event->withMessage('hello world'); + + return $next($event, $request, $exception, $payload); + } + } + +As you can see the ``$event`` variable must always be reassigned to the result of +the called ``with*`` method: this is because the ``Event`` class is immutable by +design and any method that edit its data will act on a new instance of the event +instead of the original one. + +Using a middleware +================== + +The middleware needs to be added to the stack before it can be used. Each one +can have a priority which defines in which order they will run. If you don't +specify a priority the default one of 0 will be assigned. The built-in middlewares +have the following priorities: + +- ``BreadcrumbInterfaceMiddleware``: 0 +- ``ContextInterfaceMiddleware``: 0 +- ``ExceptionInterfaceMiddleware``: 0 +- ``MessageInterfaceMiddleware``: 0 +- ``ModulesMiddleware``: 0 +- ``ProcessorMiddleware``: -250 (this middleware should always be at the end of + the chain) +- ``RequestInterfaceMiddleware``: 0 +- ``SanitizerMiddleware``: -255 (this middleware should always be the last one) +- ``UserInterfaceMiddleware``: 0 + +The higher the priority value is, the earlier a middleware will be executed in +the chain. To add the middleware to the stack you can use the ``addMiddleware`` +method which can be found in both the ``Client`` and ``ClientBuilder`` classes. +To remove a middleware you can use the ``removeMiddleware`` method instead. You +can manage the middlewares at runtime and the chain will be recomputed accordingly. + +.. code-block:: php + + use Psr\Http\Message\ServerRequestInterface; + use Raven\ClientBuilder; + use Raven\Event; + + $middleware = function (Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { + // Do something here + + return $next($event, $request, $exception, $payload); + }; + + $clientBuiler = new ClientBuilder(); + $clientBuilder->addMiddleware($middleware, 10); + $clientBuilder->removeMiddleware($middleware); + + $client = $clientBuilder->getClient(); + $client->addMiddleware($middleware, -10); + $client->removeMiddleware($middleware); From 04d1435e845bebf804680fa276393aeb5da12373 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 17 Jun 2018 18:12:56 +0200 Subject: [PATCH 0343/1161] Add the documentation about the processors (#611) --- docs/processor.rst | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 docs/processor.rst diff --git a/docs/processor.rst b/docs/processor.rst new file mode 100644 index 000000000..34941223d --- /dev/null +++ b/docs/processor.rst @@ -0,0 +1,77 @@ +Processors +########## + +The processors are classes that are executed as the last step of the event +sending lifecycle before the event data is serialized and sent using the +configured transport. There are several built-in processors (not all are +enabled by default) whose list is: + +- ``RemoveHttpBodyProcessor``: sanitizes the data sent as body of a POST + request. +- ``SanitizeCookiesProcessor``: sanitizes the cookies sent with the request + by hiding sensitive information. +- ``SanitizeDataProcessor``: sanitizes the data of the event by removing + sensitive information. +- ``SanitizeHttpHeadersProcessor``: sanitizes the headers of the request by + hiding sensitive information. +- ``SanitizeStacktraceProcessor``: sanitizes the captured stacktrace by + removing the excerpts of source code attached to each frame. + +Writing a processor +=================== + +You can write your own processor by creating a class that implements the +``ProcessorInterface`` interface. + +.. code-block:: php + + use Raven\Event; + use Raven\Processor\ProcessorInterface; + + class MyCustomProcessor implements ProcessorInterface + { + public function process(Event $event) + { + // Do something on the event object instance + + return $event; + } + } + +Using a processor +================= + +The processors needs to be registered with the client instance before they are +used. Each one can have a priority which defines in which order they will run. +By default they have a priority of 0. The higher the priority value is, the +earlier a processor will be executed: this is similar to how the middlewares +work. You can add or remove the processors at runtime, and they will be executed +sequentially one after the other. The built-in processors have the following +priorities: + +- ``SanitizeCookiesProcessor``: 0 +- ``RemoveHttpBodyProcessor``: 0 +- ``SanitizeHttpHeadersProcessor``: 0 +- ``SanitizeDataProcessor``: -255 + +It's important to leave the ``SanitizeDataProcessor`` as the last processor so +that any sensitive information present in the event data will be sanitized +before being sent out from your network according to the configuration you +specified. To add a middleware to the client you can use the ``addProcessor`` +method which can be found in both the ``Client`` and ``ClientBuilder`` classes +while the ``removeProcessor`` can be used to remove the processors instead. + +.. code-block:: php + + use Raven\ClientBuilder; + use Raven\Processor\RemoveHttpBodyProcessor; + + $processor = new RemoveHttpBodyProcessor(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->addProcessor($processor, 10); + $clientBuilder->removeProcessor($processor); + + $client = $clientBuilder->getClient(); + $client->addProcessor($processor, -10); + $client->removeProcessor($processor); From 3c54b0b60c1c44009d6249e29ddf68dfb902dea2 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 19 Jun 2018 15:47:56 +0200 Subject: [PATCH 0344/1161] Implement support for the public DSN in the client configuration (#616) Port of #615 to 2.x branch --- lib/Raven/Configuration.php | 6 ++-- .../HttpClient/Authentication/SentryAuth.php | 28 +++++++++++++------ tests/ConfigurationTest.php | 11 +++++++- .../Authentication/SentryAuthTest.php | 28 +++++++++++++++++++ 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 5b7073790..4bc628031 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -661,11 +661,11 @@ private function configureOptions(OptionsResolver $resolver) return false; } - if (!isset($parsed['scheme'], $parsed['user'], $parsed['pass'], $parsed['host'], $parsed['path'])) { + if (!isset($parsed['scheme'], $parsed['user'], $parsed['host'], $parsed['path'])) { return false; } - if (empty($parsed['user']) || empty($parsed['pass'])) { + if (empty($parsed['user']) || (isset($parsed['pass']) && empty($parsed['pass']))) { return false; } @@ -700,7 +700,7 @@ private function configureOptions(OptionsResolver $resolver) $this->server .= substr($parsed['path'], 0, strripos($parsed['path'], '/')); $this->publicKey = $parsed['user']; - $this->secretKey = $parsed['pass']; + $this->secretKey = isset($parsed['pass']) ? $parsed['pass'] : null; $parts = explode('/', $parsed['path']); diff --git a/lib/Raven/HttpClient/Authentication/SentryAuth.php b/lib/Raven/HttpClient/Authentication/SentryAuth.php index 327dade0c..593fcf0ff 100644 --- a/lib/Raven/HttpClient/Authentication/SentryAuth.php +++ b/lib/Raven/HttpClient/Authentication/SentryAuth.php @@ -44,14 +44,26 @@ public function __construct(Configuration $configuration) */ public function authenticate(RequestInterface $request) { - $header = sprintf( - 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=%s, sentry_secret=%s', - Client::PROTOCOL, - Client::USER_AGENT, - microtime(true), - $this->configuration->getPublicKey(), - $this->configuration->getSecretKey() - ); + $headerKeys = array_filter([ + 'sentry_version' => Client::PROTOCOL, + 'sentry_client' => Client::USER_AGENT, + 'sentry_timestamp' => sprintf('%F', microtime(true)), + 'sentry_key' => $this->configuration->getPublicKey(), + 'sentry_secret' => $this->configuration->getSecretKey(), + ]); + + $isFirstItem = true; + $header = 'Sentry '; + + foreach ($headerKeys as $headerKey => $headerValue) { + if (!$isFirstItem) { + $header .= ', '; + } + + $header .= $headerKey . '=' . $headerValue; + + $isFirstItem = false; + } return $request->withHeader('X-Sentry-Auth', $header); } diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index e5b93fbae..1291c0729 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -82,6 +82,15 @@ public function testServerOption($dsn, $options) public function serverOptionDataProvider() { return [ + [ + 'http://public@example.com/1', + [ + 'project_id' => 1, + 'public_key' => 'public', + 'secret_key' => null, + 'server' => 'http://example.com', + ], + ], [ 'http://public:secret@example.com/1', [ @@ -156,7 +165,7 @@ public function invalidServerOptionDataProvider() ['http://public:secret@/1'], ['http://public:secret@example.com'], ['http://:secret@example.com/1'], - ['http://public@example.com/1'], + ['http://public:@example.com'], ['tcp://public:secret@example.com/1'], ]; } diff --git a/tests/HttpClient/Authentication/SentryAuthTest.php b/tests/HttpClient/Authentication/SentryAuthTest.php index 41cd8faf5..5da20d547 100644 --- a/tests/HttpClient/Authentication/SentryAuthTest.php +++ b/tests/HttpClient/Authentication/SentryAuthTest.php @@ -49,4 +49,32 @@ public function testAuthenticate() $this->assertSame($newRequest, $authentication->authenticate($request)); } + + public function testAuthenticateWithNoSecretKey() + { + $configuration = new Configuration(['server' => 'http://public@example.com/']); + $authentication = new SentryAuth($configuration); + + /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $request */ + $request = $this->getMockBuilder(RequestInterface::class) + ->getMock(); + + /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $newRequest */ + $newRequest = $this->getMockBuilder(RequestInterface::class) + ->getMock(); + + $headerValue = sprintf( + 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=public', + Client::PROTOCOL, + Client::USER_AGENT, + microtime(true) + ); + + $request->expects($this->once()) + ->method('withHeader') + ->with('X-Sentry-Auth', $headerValue) + ->willReturn($newRequest); + + $this->assertSame($newRequest, $authentication->authenticate($request)); + } } From ea8d65cea38acc1bae3ff5d30f34adcd55faf6a9 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 20 Jun 2018 09:47:12 +0200 Subject: [PATCH 0345/1161] [2.0] Use jobs in Travis build (#618) * Use jobs in Travis build; do not report coverage multiple times * Avoid duplicate job for CS * Fix prefer-lowest job * Bump minimum requirements in dev for PHPUnit 5 and symfony/phpunit-bridge * Bump minimum Monolog version * Remove error_clear_last() usages in tests * Require a minimum version for php-http/message * Try to remove one last error_clear_last() * Try to bump dev requirement to avoid failures again * Fix CS * Fix coverage job in Travis * Revert 7eacc94; import polyfill for error_clear_last() instead * Fix CS * Make Scrutinizer wait for only 1 coverage report --- .scrutinizer.yml | 2 +- .travis.yml | 36 +++++++++++++++++++++++++---------- composer.json | 12 +++++------- tests/ClientTest.php | 20 +++++++++++++++---- tests/Context/ContextTest.php | 2 -- 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index e9150c0d9..f8d25d1a9 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,7 +5,7 @@ tools: php_code_coverage: true external_code_coverage: timeout: 2400 # There can be another pull request in progress - runs: 4 # PHP 5.6 + PHP 7.0 + PHP 7.1 + PHP 7.2 + runs: 1 build: environment: diff --git a/.travis.yml b/.travis.yml index 5734a80cf..65d020d48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,17 +22,33 @@ cache: before_install: - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi - - composer self-update install: travis_retry composer install --no-interaction script: - - composer phpcs - - composer tests-travis - -after_script: - - wget https://scrutinizer-ci.com/ocular.phar - - if [ $(phpenv version-name) = "5.6" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi - - if [ $(phpenv version-name) = "7.0" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi - - if [ $(phpenv version-name) = "7.1" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi - - if [ $(phpenv version-name) = "7.2" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT; fi + - composer tests + +jobs: + include: + - stage: Test + php: 5.6 + env: + COMPOSER_OPTIONS: "--prefer-lowest" + REMOVE_XDEBUG: "1" + install: travis_retry composer update --no-interaction --prefer-lowest + - stage: Code style + php: 7.1 + env: + CS-FIXER: true + REMOVE_XDEBUG: "1" + script: + - composer phpcs + - stage: Coverage + php: 7.1 + env: + REMOVE_XDEBUG: "0" + script: + - vendor/bin/phpunit --verbose --configuration phpunit.xml.dist --coverage-clover tests/clover.xml + after_success: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT diff --git a/composer.json b/composer.json index b5d3a6719..2b2ce716d 100644 --- a/composer.json +++ b/composer.json @@ -26,11 +26,12 @@ "require-dev": { "composer/composer": "^1.6", "friendsofphp/php-cs-fixer": "~2.1", - "monolog/monolog": "~1.0", - "php-http/curl-client": "~1.7", + "monolog/monolog": "^1.3", + "php-http/curl-client": "^1.7.1", + "php-http/message": "^1.5", "php-http/mock-client": "~1.0", - "phpunit/phpunit": "^5.7|^6.0", - "symfony/phpunit-bridge": "~2.7|~3.0" + "phpunit/phpunit": "^5.7.27|^6.0", + "symfony/phpunit-bridge": "^4.0" }, "suggest": { "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" @@ -58,9 +59,6 @@ "tests": [ "vendor/bin/phpunit --verbose" ], - "tests-travis": [ - "vendor/bin/phpunit --verbose --configuration phpunit.xml.dist --coverage-clover tests/clover.xml" - ], "tests-report": [ "vendor/bin/phpunit --verbose --configuration phpunit.xml.dist --coverage-html tests/html-report" ], diff --git a/tests/ClientTest.php b/tests/ClientTest.php index a16ad67a6..82e7c63fa 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -198,7 +198,7 @@ public function testCaptureLastError() $client->captureLastError(); - error_clear_last(); + $this->clearLastError(); } public function testCaptureLastErrorDoesNothingWhenThereIsNoError() @@ -206,13 +206,13 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError() /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ $client = $this->getMockBuilder(Client::class) ->disableOriginalConstructor() - ->setMethodsExcept(['captureException']) + ->setMethodsExcept(['captureLastError']) ->getMock(); $client->expects($this->never()) - ->method('capture'); + ->method('captureException'); - error_clear_last(); + $this->clearLastError(); $client->captureLastError(); } @@ -551,4 +551,16 @@ private function createCarelessExceptionWithStacktrace() return $ex; } } + + /** + * @see https://github.com/symfony/polyfill/blob/52332f49d18c413699d2dccf465234356f8e0b2c/src/Php70/Php70.php#L52-L61 + */ + private function clearLastError() + { + $handler = function () { return false; }; + + set_error_handler($handler); + @trigger_error(''); + restore_error_handler(); + } } diff --git a/tests/Context/ContextTest.php b/tests/Context/ContextTest.php index ceba26e67..4ea653053 100644 --- a/tests/Context/ContextTest.php +++ b/tests/Context/ContextTest.php @@ -113,8 +113,6 @@ public function testArrayLikeBehaviour() $this->assertInternalType('array', $error); $this->assertEquals('Undefined index: foo', $error['message']); - error_clear_last(); - $context['foo'] = 'bar'; $this->assertAttributeEquals(['foo' => 'bar'], 'data', $context); From 3aa160f5ac3bffe6894e4bb01d4a22f733ebc2af Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 21 Jun 2018 09:26:47 +0200 Subject: [PATCH 0346/1161] [2.0] Add PHPStan to CI (#619) * Add PHPStan * Fix some namespaces * Fix src path for PHPStan task * Add PHPStan config and fix all errors for level 0 * Raise PHPStan to level 2 * Require PHPStan on the fly to avoid issues in CI * Add phpstan/phpstan-phpunit * Fix all errors up to level 3 * Do not require PHPStan directly to avoid issues under PHP 5.6 * Fix CS --- .travis.yml | 10 ++++++++-- composer.json | 4 ++++ lib/Raven/AbstractErrorHandler.php | 4 ++-- lib/Raven/Breadcrumbs/Recorder.php | 2 +- lib/Raven/Client.php | 4 ++-- lib/Raven/Configuration.php | 2 +- lib/Raven/Frame.php | 6 +++--- lib/Raven/Middleware/MiddlewareStack.php | 4 ++-- lib/Raven/Serializer.php | 2 +- lib/Raven/Stacktrace.php | 2 +- lib/Raven/TransactionStack.php | 2 +- phpstan.neon | 9 +++++++++ tests/ClientTest.php | 4 ++-- tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php | 2 +- tests/Middleware/ContextInterfaceMiddlewareTest.php | 2 +- tests/Middleware/ExceptionInterfaceMiddlewareTest.php | 2 +- tests/Middleware/MessageInterfaceMiddlewareTest.php | 2 +- tests/Middleware/ModulesMiddlewareTest.php | 2 +- tests/Middleware/RequestInterfaceMiddlewareTest.php | 4 ++-- tests/Middleware/SanitizerMiddlewareTest.php | 2 +- tests/Middleware/UserInterfaceMiddlewareTest.php | 2 +- 21 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 phpstan.neon diff --git a/.travis.yml b/.travis.yml index 65d020d48..c6d7372b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,13 +36,19 @@ jobs: COMPOSER_OPTIONS: "--prefer-lowest" REMOVE_XDEBUG: "1" install: travis_retry composer update --no-interaction --prefer-lowest - - stage: Code style - php: 7.1 + - stage: Code style & static analysis + - php: 7.1 env: CS-FIXER: true REMOVE_XDEBUG: "1" script: - composer phpcs + - php: 7.1 + env: + PHPSTAN: true + REMOVE_XDEBUG: "1" + script: + - composer phpstan - stage: Coverage php: 7.1 env: diff --git a/composer.json b/composer.json index 2b2ce716d..b8419eadb 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,10 @@ ], "phpcs": [ "vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run" + ], + "phpstan": [ + "composer require --dev phpstan/phpstan-shim ^0.9.2 phpstan/phpstan-phpunit ^0.9.2", + "vendor/bin/phpstan.phar analyse lib tests -c phpstan.neon -l 3" ] }, "config": { diff --git a/lib/Raven/AbstractErrorHandler.php b/lib/Raven/AbstractErrorHandler.php index df03c627b..8199374c2 100644 --- a/lib/Raven/AbstractErrorHandler.php +++ b/lib/Raven/AbstractErrorHandler.php @@ -51,8 +51,8 @@ abstract class AbstractErrorHandler protected $isRoot = false; /** - * @var string A portion of pre-allocated memory data that will be reclaimed - * in case a fatal error occurs to handle it + * @var string|null A portion of pre-allocated memory data that will be reclaimed + * in case a fatal error occurs to handle it */ protected static $reservedMemory; diff --git a/lib/Raven/Breadcrumbs/Recorder.php b/lib/Raven/Breadcrumbs/Recorder.php index 98ee528c4..81caa5e3a 100644 --- a/lib/Raven/Breadcrumbs/Recorder.php +++ b/lib/Raven/Breadcrumbs/Recorder.php @@ -42,7 +42,7 @@ final class Recorder implements \Countable, \Iterator private $size = 0; /** - * @var Breadcrumb[] The list of recorded breadcrumbs + * @var \SplFixedArray|Breadcrumb[] The list of recorded breadcrumbs */ private $breadcrumbs; diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 76f647d53..22446fe22 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -259,9 +259,9 @@ public function removeProcessor(ProcessorInterface $processor) } /** - * Gets the representation serialier. + * Gets the representation serializer. * - * @return ReprSerializer + * @return Serializer */ public function getReprSerializer() { diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 4bc628031..4d4aaa772 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -27,7 +27,7 @@ class Configuration private $options = []; /** - * @var string A simple server string, set to the DSN found on your Sentry settings + * @var string|null A simple server string, set to the DSN found on your Sentry settings */ private $server; diff --git a/lib/Raven/Frame.php b/lib/Raven/Frame.php index 42154636b..144999b85 100644 --- a/lib/Raven/Frame.php +++ b/lib/Raven/Frame.php @@ -40,8 +40,8 @@ final class Frame implements \JsonSerializable private $preContext; /** - * @var string[] The source code written at the line number of the file that - * originated this frame + * @var string|null The source code written at the line number of the file that + * originated this frame */ private $contextLine; @@ -131,7 +131,7 @@ public function setPreContext(array $preContext = null) * Gets the source code written at the line number of the file that originated * this frame. * - * @return string[]|null + * @return string|null */ public function getContextLine() { diff --git a/lib/Raven/Middleware/MiddlewareStack.php b/lib/Raven/Middleware/MiddlewareStack.php index 17c5d2fe9..7c2dc5b5a 100644 --- a/lib/Raven/Middleware/MiddlewareStack.php +++ b/lib/Raven/Middleware/MiddlewareStack.php @@ -27,12 +27,12 @@ class MiddlewareStack private $handler; /** - * @var array The list of middlewares + * @var array The list of middlewares */ private $stack = []; /** - * @var callable The tip of the middleware call stack + * @var callable|null The tip of the middleware call stack */ private $middlewareStackTip; diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 8cc4d443f..c6acf984c 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -108,7 +108,7 @@ public function serialize($value, $max_depth = 3, $_depth = 0) * @param int $_depth * @param string[] $hashes * - * @return array|string + * @return array|string|bool|float|int|null */ public function serializeObject($object, $max_depth = 3, $_depth = 0, $hashes = []) { diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 2f66574ca..82eb49dad 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -34,7 +34,7 @@ class Stacktrace implements \JsonSerializable protected $serializer; /** - * @var ReprSerializer The representation serializer + * @var Serializer The representation serializer */ protected $reprSerializer; diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index e09489618..f02a3a501 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -55,7 +55,7 @@ public function isEmpty() /** * Pushes the given values onto the stack. * - * @param string[] $values The values to push + * @param array $values The values to push * * @throws \InvalidArgumentException If any of the values is not a string */ diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 000000000..3c99d7013 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + ignoreErrors: + - '/Constructor of class Raven\\HttpClient\\Encoding\\Base64EncodingStream has an unused parameter \$(readFilterOptions|writeFilterOptions)/' + - '/Call to an undefined method Raven\\ClientBuilder::methodThatDoesNotExists\(\)/' + - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' + excludes_analyse: + - tests/resources +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 82e7c63fa..00bbb0f86 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -93,7 +93,7 @@ public function testRemoveMiddleware() $reflectionProperty->setValue($client, $middlewareStack); $reflectionProperty->setAccessible(false); - $client->removeMiddleware($middleware, -10); + $client->removeMiddleware($middleware); } public function testAddProcessor() @@ -287,7 +287,7 @@ public function testGetLastEventId() Uuid::setFactory(new UuidFactory()); - $this->assertEquals('ddbd643a51904ccea6ce3098506f9d33', $client->getLastEventID()); + $this->assertEquals('ddbd643a51904ccea6ce3098506f9d33', $client->getLastEventId()); } public function testGetUserContext() diff --git a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php index b338fed21..f9f5e58a5 100644 --- a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php +++ b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\Breadcrumbs\Breadcrumb; diff --git a/tests/Middleware/ContextInterfaceMiddlewareTest.php b/tests/Middleware/ContextInterfaceMiddlewareTest.php index 125c0419a..338d9da6f 100644 --- a/tests/Middleware/ContextInterfaceMiddlewareTest.php +++ b/tests/Middleware/ContextInterfaceMiddlewareTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\Configuration; diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php index 11632f854..e35cba23c 100644 --- a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php +++ b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\Client; diff --git a/tests/Middleware/MessageInterfaceMiddlewareTest.php b/tests/Middleware/MessageInterfaceMiddlewareTest.php index 10b43f8e7..93f68e1f0 100644 --- a/tests/Middleware/MessageInterfaceMiddlewareTest.php +++ b/tests/Middleware/MessageInterfaceMiddlewareTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\Configuration; diff --git a/tests/Middleware/ModulesMiddlewareTest.php b/tests/Middleware/ModulesMiddlewareTest.php index 9a4eb02cd..5998931c8 100644 --- a/tests/Middleware/ModulesMiddlewareTest.php +++ b/tests/Middleware/ModulesMiddlewareTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\Configuration; diff --git a/tests/Middleware/RequestInterfaceMiddlewareTest.php b/tests/Middleware/RequestInterfaceMiddlewareTest.php index 258688e28..7763adaed 100644 --- a/tests/Middleware/RequestInterfaceMiddlewareTest.php +++ b/tests/Middleware/RequestInterfaceMiddlewareTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -57,7 +57,7 @@ public function testInvoke($requestData, $expectedValue) } $invokationCount = 0; - $callback = function (Event $eventArg, ServerRequestInterface $requestArg) use ($event, $request, $expectedValue, &$invokationCount) { + $callback = function (Event $eventArg, ServerRequestInterface $requestArg) use ($request, $expectedValue, &$invokationCount) { $this->assertSame($request, $requestArg); $this->assertEquals($expectedValue, $eventArg->getRequest()); diff --git a/tests/Middleware/SanitizerMiddlewareTest.php b/tests/Middleware/SanitizerMiddlewareTest.php index 6e967d4a5..e5d57ed7d 100644 --- a/tests/Middleware/SanitizerMiddlewareTest.php +++ b/tests/Middleware/SanitizerMiddlewareTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\Configuration; diff --git a/tests/Middleware/UserInterfaceMiddlewareTest.php b/tests/Middleware/UserInterfaceMiddlewareTest.php index 0d47a2895..ecafdb5e4 100644 --- a/tests/Middleware/UserInterfaceMiddlewareTest.php +++ b/tests/Middleware/UserInterfaceMiddlewareTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\Configuration; From 8ef3b0dd672a0a08f341fec0b5f4512fa3b8cc5b Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 21 Jun 2018 16:16:45 +0200 Subject: [PATCH 0347/1161] [2.0] Callable serialization without invoking (#620) * Add serialization for callables * Fix tests after rebase * Refactor serializer tests * Refactor serializer; rename abstract test * Fix test errors under PHP 5; remove duplicated job under CS stage in Travis --- .travis.yml | 2 +- lib/Raven/Serializer.php | 72 +++- tests/AbstractSerializerTest.php | 547 ++++++++++++++++++++++++++ tests/ReprSerializerTest.php | 35 +- tests/SerializerAbstractTest.php | 453 --------------------- tests/SerializerTest.php | 41 +- tests/resources/php70_serializing.inc | 28 ++ tests/resources/php71_serializing.inc | 36 ++ 8 files changed, 703 insertions(+), 511 deletions(-) create mode 100644 tests/AbstractSerializerTest.php delete mode 100644 tests/SerializerAbstractTest.php create mode 100644 tests/resources/php70_serializing.inc create mode 100644 tests/resources/php71_serializing.inc diff --git a/.travis.yml b/.travis.yml index c6d7372b5..caa517b55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ jobs: REMOVE_XDEBUG: "1" install: travis_retry composer update --no-interaction --prefer-lowest - stage: Code style & static analysis - - php: 7.1 + php: 7.1 env: CS-FIXER: true REMOVE_XDEBUG: "1" diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index c6acf984c..9a241bb01 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -93,7 +93,11 @@ public function serialize($value, $max_depth = 3, $_depth = 0) } if (is_object($value)) { - if (('stdClass' == get_class($value)) or $this->_all_object_serialize) { + if (is_callable($value)) { + return $this->serializeCallable($value); + } + + if ($this->_all_object_serialize || ('stdClass' === get_class($value))) { return $this->serializeObject($value, $max_depth, $_depth, []); } } @@ -173,6 +177,72 @@ protected function serializeValue($value) } } + /** + * @param callable $callable + * + * @return string + */ + public function serializeCallable($callable) + { + if (is_array($callable)) { + $reflection = new \ReflectionMethod($callable[0], $callable[1]); + $class = $reflection->getDeclaringClass(); + } else { + $reflection = new \ReflectionFunction($callable); + $class = null; + } + + $value = $reflection->isClosure() ? 'Lambda ' : 'Callable '; + + if (version_compare(PHP_VERSION, '7.0.0') >= 0 && $reflection->getReturnType()) { + $value .= $reflection->getReturnType() . ' '; + } + + if ($class) { + $value .= $class->getName() . '::'; + } + + return $value . $reflection->getName() . ' ' . $this->serializeCallableParameters($reflection); + } + + /** + * @param \ReflectionFunctionAbstract $reflection + * + * @return string + */ + private function serializeCallableParameters(\ReflectionFunctionAbstract $reflection) + { + $params = []; + foreach ($reflection->getParameters() as &$param) { + $paramType = null; + if (version_compare(PHP_VERSION, '7.0.0') >= 0) { + $paramType = $param->hasType() ? $param->getType() : 'mixed'; + } else { + if ($param->isArray()) { + $paramType = 'array'; + } elseif ($param->isCallable()) { + $paramType = 'callable'; + } + } + if ($paramType && $param->allowsNull()) { + $paramType .= '|null'; + } + + $paramName = ($param->isPassedByReference() ? '&' : '') . $param->getName(); + if ($param->isOptional()) { + $paramName = '[' . $paramName . ']'; + } + + if ($paramType) { + $params[] = $paramType . ' ' . $paramName; + } else { + $params[] = $paramName; + } + } + + return '[' . implode('; ', $params) . ']'; + } + /** * @return string * @codeCoverageIgnore diff --git a/tests/AbstractSerializerTest.php b/tests/AbstractSerializerTest.php new file mode 100644 index 000000000..859bea2f7 --- /dev/null +++ b/tests/AbstractSerializerTest.php @@ -0,0 +1,547 @@ + false], + ['serializeAllObjects' => true], + ]; + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testArraysAreArrays($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + $input = [1, 2, 3]; + $result = $serializer->serialize($input); + $this->assertEquals(['1', '2', '3'], $result); + + $result = $serializer->serialize([Client::class, 'getConfig']); + $this->assertEquals([Client::class, 'getConfig'], $result); + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testStdClassAreArrays($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + $input = new \stdClass(); + $input->foo = 'BAR'; + $result = $serializer->serialize($input); + $this->assertEquals(['foo' => 'BAR'], $result); + } + + public function testObjectsAreStrings() + { + $serializer = $this->getSerializerUnderTest(); + $input = new SerializerTestObject(); + $result = $serializer->serialize($input); + $this->assertEquals('Object Raven\Tests\SerializerTestObject', $result); + } + + public function testObjectsAreNotStrings() + { + $serializer = $this->getSerializerUnderTest(); + $serializer->setAllObjectSerialize(true); + $input = new SerializerTestObject(); + $result = $serializer->serialize($input); + $this->assertEquals(['key' => 'value'], $result); + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testIntsAreInts($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + $input = 1; + $result = $serializer->serialize($input); + $this->assertInternalType('integer', $result); + $this->assertEquals(1, $result); + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testFloats($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + $input = 1.5; + $result = $serializer->serialize($input); + $this->assertInternalType('double', $result); + $this->assertEquals(1.5, $result); + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testBooleans($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + $input = true; + $result = $serializer->serialize($input); + $this->assertTrue($result); + + $input = false; + $result = $serializer->serialize($input); + $this->assertFalse($result); + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testNull($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + $input = null; + $result = $serializer->serialize($input); + $this->assertNull($result); + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testRecursionMaxDepth($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + $input = []; + $input[] = &$input; + $result = $serializer->serialize($input, 3); + $this->assertEquals([[['Array of length 1']]], $result); + + $result = $serializer->serialize([], 3); + $this->assertEquals([], $result); + + $result = $serializer->serialize([[]], 3); + $this->assertEquals([[]], $result); + + $result = $serializer->serialize([[[]]], 3); + $this->assertEquals([[[]]], $result); + + $result = $serializer->serialize([[[[]]]], 3); + $this->assertEquals([[['Array of length 0']]], $result); + } + + public function dataRecursionInObjectsDataProvider() + { + $object = new SerializerTestObject(); + $object->key = $object; + yield [ + 'object' => $object, + 'expectedResult' => ['key' => 'Object Raven\Tests\SerializerTestObject'], + ]; + + $object = new SerializerTestObject(); + $object2 = new SerializerTestObject(); + $object2->key = $object; + $object->key = $object2; + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => 'Object Raven\Tests\SerializerTestObject']], + ]; + + $object = new SerializerTestObject(); + $object2 = new SerializerTestObject(); + $object2->key = 'foobar'; + $object->key = $object2; + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => 'foobar']], + ]; + + $object3 = new SerializerTestObject(); + $object3->key = 'foobar'; + $object2 = new SerializerTestObject(); + $object2->key = $object3; + $object = new SerializerTestObject(); + $object->key = $object2; + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => ['key' => 'foobar']]], + ]; + + $object4 = new SerializerTestObject(); + $object4->key = 'foobar'; + $object3 = new SerializerTestObject(); + $object3->key = $object4; + $object2 = new SerializerTestObject(); + $object2->key = $object3; + $object = new SerializerTestObject(); + $object->key = $object2; + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject']]], + ]; + + $object3 = new SerializerTestObject(); + $object2 = new SerializerTestObject(); + $object2->key = $object3; + $object2->keys = 'keys'; + $object = new SerializerTestObject(); + $object->key = $object2; + $object3->key = $object2; + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject'], 'keys' => 'keys']], + ]; + } + + /** + * @param object $object + * @param array $expectedResult + * + * @dataProvider dataRecursionInObjectsDataProvider + */ + public function testRecursionInObjects($object, $expectedResult) + { + $serializer = $this->getSerializerUnderTest(); + $serializer->setAllObjectSerialize(true); + + $result1 = $serializer->serialize($object, 3); + $result2 = $serializer->serializeObject($object, 3); + $this->assertEquals($expectedResult, $result1); + $this->assertContains(gettype($result1), ['array', 'string', 'null', 'float', 'integer', 'object']); + $this->assertEquals($expectedResult, $result2); + $this->assertContains(gettype($result2), ['array', 'string']); + } + + public function testRecursionMaxDepthForObject() + { + $serializer = $this->getSerializerUnderTest(); + $serializer->setAllObjectSerialize(true); + + $result = $serializer->serialize((object) ['key' => (object) ['key' => 12345]], 3); + $this->assertEquals(['key' => ['key' => 12345]], $result); + + $result = $serializer->serialize((object) ['key' => (object) ['key' => (object) ['key' => 12345]]], 3); + $this->assertEquals(['key' => ['key' => ['key' => 12345]]], $result); + + $result = $serializer->serialize( + (object) ['key' => (object) ['key' => (object) ['key' => (object) ['key' => 12345]]]], + 3 + ); + $this->assertEquals(['key' => ['key' => ['key' => 'Object stdClass']]], $result); + } + + public function testObjectInArray() + { + $serializer = $this->getSerializerUnderTest(); + $input = ['foo' => new SerializerTestObject()]; + $result = $serializer->serialize($input); + $this->assertEquals(['foo' => 'Object Raven\\Tests\\SerializerTestObject'], $result); + } + + public function testObjectInArraySerializeAll() + { + $serializer = $this->getSerializerUnderTest(); + $serializer->setAllObjectSerialize(true); + $input = ['foo' => new SerializerTestObject()]; + $result = $serializer->serialize($input); + $this->assertEquals(['foo' => ['key' => 'value']], $result); + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testBrokenEncoding($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + foreach (['7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90'] as $key) { + $input = pack('H*', $key); + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + if (function_exists('mb_detect_encoding')) { + $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); + } + } + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testLongString($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + + foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { + $input = str_repeat('x', $length); + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(1024, strlen($result)); + } + } + + public function testLongStringWithOverwrittenMessageLength() + { + $serializer = $this->getSerializerUnderTest(); + $serializer->setMessageLimit(500); + + foreach ([100, 490, 499, 500, 501, 1000, 10000] as $length) { + $input = str_repeat('x', $length); + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(500, strlen($result)); + } + } + + /** + * @param bool $serializeAllObjects + * @dataProvider serializeAllObjectsProvider + */ + public function testSerializeValueResource($serializeAllObjects) + { + $serializer = $this->getSerializerUnderTest(); + if ($serializeAllObjects) { + $serializer->setAllObjectSerialize(true); + } + $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); + $fo = fopen($filename, 'wb'); + + $result = $serializer->serialize($fo); + $this->assertInternalType('string', $result); + $this->assertEquals('Resource stream', $result); + } + + public function testSetAllObjectSerialize() + { + $serializer = $this->getSerializerUnderTest(); + $serializer->setAllObjectSerialize(true); + $this->assertTrue($serializer->getAllObjectSerialize()); + $serializer->setAllObjectSerialize(false); + $this->assertFalse($serializer->getAllObjectSerialize()); + } + + public function testClippingUTF8Characters() + { + if (!extension_loaded('mbstring')) { + $this->markTestSkipped('mbstring extension is not enabled.'); + } + + $testString = 'Прекратите надеÑтьÑÑ, что ваши пользователи будут Ñообщать об ошибках'; + $class_name = static::getSerializerUnderTest(); + /** @var \Raven\Serializer $serializer */ + $serializer = new $class_name(null, 19); + + $clipped = $serializer->serialize($testString); + + $this->assertEquals('Прекратит {clipped}', $clipped); + $this->assertNotNull(json_encode($clipped)); + $this->assertSame(JSON_ERROR_NONE, json_last_error()); + } + + public function serializableCallableProvider() + { + $closure1 = function (array $param1) { + return $param1 * 2; + }; + $closure2 = function ($param1a) { + throw new \Exception('Don\'t even think about invoke me'); + }; + $closure4 = function (callable $param1c) { + throw new \Exception('Don\'t even think about invoke me'); + }; + $closure5 = function (\stdClass $param1d) { + throw new \Exception('Don\'t even think about invoke me'); + }; + $closure6 = function (\stdClass $param1e = null) { + throw new \Exception('Don\'t even think about invoke me'); + }; + $closure7 = function (array &$param1f) { + throw new \Exception('Don\'t even think about invoke me'); + }; + $closure8 = function (array &$param1g = null) { + throw new \Exception('Don\'t even think about invoke me'); + }; + + if (version_compare(PHP_VERSION, '7.0.0') >= 0) { + $data = [ + [ + 'callable' => $closure1, + 'expected' => 'Lambda Raven\\Tests\\{closure} [array param1]', + ], [ + 'callable' => $closure2, + 'expected' => 'Lambda Raven\\Tests\\{closure} [mixed|null param1a]', + ], [ + 'callable' => $closure4, + 'expected' => 'Lambda Raven\\Tests\\{closure} [callable param1c]', + ], [ + 'callable' => $closure5, + 'expected' => 'Lambda Raven\\Tests\\{closure} [stdClass param1d]', + ], [ + 'callable' => $closure6, + 'expected' => 'Lambda Raven\\Tests\\{closure} [stdClass|null [param1e]]', + ], [ + 'callable' => $closure7, + 'expected' => 'Lambda Raven\\Tests\\{closure} [array ¶m1f]', + ], [ + 'callable' => $closure8, + 'expected' => 'Lambda Raven\\Tests\\{closure} [array|null [¶m1g]]', + ], [ + 'callable' => [$this, 'serializableCallableProvider'], + 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::serializableCallableProvider []', + ], [ + 'callable' => [Client::class, 'getConfig'], + 'expected' => 'Callable Raven\Client::getConfig []', + ], [ + 'callable' => [TestCase::class, 'setUpBeforeClass'], + 'expected' => 'Callable PHPUnit\\Framework\\TestCase::setUpBeforeClass []', + ], [ + 'callable' => [$this, 'setUpBeforeClass'], + 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::setUpBeforeClass []', + ], [ + 'callable' => [self::class, 'setUpBeforeClass'], + 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::setUpBeforeClass []', + ], + ]; + require_once 'resources/php70_serializing.inc'; + + if (version_compare(PHP_VERSION, '7.1.0') >= 0) { + require_once 'resources/php71_serializing.inc'; + } + + return $data; + } + + return [ + [ + 'callable' => $closure1, + 'expected' => 'Lambda Raven\\Tests\\{closure} [array param1]', + ], [ + 'callable' => $closure2, + 'expected' => 'Lambda Raven\\Tests\\{closure} [param1a]', + ], [ + 'callable' => $closure4, + 'expected' => 'Lambda Raven\\Tests\\{closure} [callable param1c]', + ], [ + 'callable' => $closure5, + 'expected' => 'Lambda Raven\\Tests\\{closure} [param1d]', + ], [ + 'callable' => $closure6, + 'expected' => 'Lambda Raven\\Tests\\{closure} [[param1e]]', + ], [ + 'callable' => $closure7, + 'expected' => 'Lambda Raven\\Tests\\{closure} [array ¶m1f]', + ], [ + 'callable' => $closure8, + 'expected' => 'Lambda Raven\\Tests\\{closure} [array|null [¶m1g]]', + ], [ + 'callable' => [$this, 'serializableCallableProvider'], + 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::serializableCallableProvider []', + ], [ + 'callable' => [Client::class, 'getConfig'], + 'expected' => 'Callable Raven\Client::getConfig []', + ], [ + 'callable' => [TestCase::class, 'setUpBeforeClass'], + 'expected' => 'Callable PHPUnit_Framework_TestCase::setUpBeforeClass []', + ], [ + 'callable' => [$this, 'setUpBeforeClass'], + 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::setUpBeforeClass []', + ], [ + 'callable' => [self::class, 'setUpBeforeClass'], + 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::setUpBeforeClass []', + ], + ]; + } + + /** + * @param callable $callable + * @param string $expected + * + * @dataProvider serializableCallableProvider + */ + public function testSerializeCallable($callable, $expected) + { + $serializer = $this->getSerializerUnderTest(); + $actual = $serializer->serializeCallable($callable); + $this->assertEquals($expected, $actual); + + $actual2 = $serializer->serialize($callable); + $actual3 = $serializer->serialize([$callable]); + if (is_array($callable)) { + $this->assertInternalType('array', $actual2); + $this->assertInternalType('array', $actual3); + } else { + $this->assertEquals($expected, $actual2); + $this->assertEquals([$expected], $actual3); + } + } +} + +/** + * Class SerializerTestObject. + * + * @property mixed $keys + */ +class SerializerTestObject +{ + private $foo = 'bar'; + + public $key = 'value'; +} diff --git a/tests/ReprSerializerTest.php b/tests/ReprSerializerTest.php index 7af36ce37..71d5633e5 100644 --- a/tests/ReprSerializerTest.php +++ b/tests/ReprSerializerTest.php @@ -11,25 +11,22 @@ namespace Raven\Tests; -require_once 'SerializerAbstractTest.php'; +use Raven\ReprSerializer; -class ReprSerializerTest extends \Raven\Tests\SerializerAbstractTest +class ReprSerializerTest extends AbstractSerializerTest { - /** - * @return string - */ - protected static function get_test_class() + protected function getSerializerUnderTest() { - return '\\Raven\\ReprSerializer'; + return new ReprSerializer(); } /** * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam + * @dataProvider serializeAllObjectsProvider */ public function testIntsAreInts($serialize_all_objects) { - $serializer = new \Raven\ReprSerializer(); + $serializer = $this->getSerializerUnderTest(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } @@ -41,11 +38,11 @@ public function testIntsAreInts($serialize_all_objects) /** * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam + * @dataProvider serializeAllObjectsProvider */ public function testFloats($serialize_all_objects) { - $serializer = new \Raven\ReprSerializer(); + $serializer = $this->getSerializerUnderTest(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } @@ -57,11 +54,11 @@ public function testFloats($serialize_all_objects) /** * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam + * @dataProvider serializeAllObjectsProvider */ public function testBooleans($serialize_all_objects) { - $serializer = new \Raven\ReprSerializer(); + $serializer = $this->getSerializerUnderTest(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } @@ -77,11 +74,11 @@ public function testBooleans($serialize_all_objects) /** * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam + * @dataProvider serializeAllObjectsProvider */ public function testNull($serialize_all_objects) { - $serializer = new \Raven\ReprSerializer(); + $serializer = $this->getSerializerUnderTest(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } @@ -93,12 +90,12 @@ public function testNull($serialize_all_objects) /** * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam + * @dataProvider serializeAllObjectsProvider * @covers \Raven\ReprSerializer::serializeValue */ public function testSerializeRoundedFloat($serialize_all_objects) { - $serializer = new \Raven\ReprSerializer(); + $serializer = $this->getSerializerUnderTest(); if ($serialize_all_objects) { $serializer->setAllObjectSerialize(true); } @@ -107,11 +104,11 @@ public function testSerializeRoundedFloat($serialize_all_objects) $this->assertInternalType('string', $result); $this->assertEquals('1.0', $result); - $result = $serializer->serialize((float) floor(5 / 2)); + $result = $serializer->serialize(floor(5 / 2)); $this->assertInternalType('string', $result); $this->assertEquals('2.0', $result); - $result = $serializer->serialize((float) floor(12345.678901234)); + $result = $serializer->serialize(floor(12345.678901234)); $this->assertInternalType('string', $result); $this->assertEquals('12345.0', $result); } diff --git a/tests/SerializerAbstractTest.php b/tests/SerializerAbstractTest.php deleted file mode 100644 index 8d87f81a7..000000000 --- a/tests/SerializerAbstractTest.php +++ /dev/null @@ -1,453 +0,0 @@ - false], - ['serialize_all_objects' => true], - ]; - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testArraysAreArrays($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = [1, 2, 3]; - $result = $serializer->serialize($input); - $this->assertEquals(['1', '2', '3'], $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testStdClassAreArrays($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = new \stdClass(); - $input->foo = 'BAR'; - $result = $serializer->serialize($input); - $this->assertEquals(['foo' => 'BAR'], $result); - } - - public function testObjectsAreStrings() - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - $input = new \Raven\Tests\SerializerTestObject(); - $result = $serializer->serialize($input); - $this->assertEquals('Object Raven\Tests\SerializerTestObject', $result); - } - - public function testObjectsAreNotStrings() - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - $serializer->setAllObjectSerialize(true); - $input = new \Raven\Tests\SerializerTestObject(); - $result = $serializer->serialize($input); - $this->assertEquals(['key' => 'value'], $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testIntsAreInts($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = 1; - $result = $serializer->serialize($input); - $this->assertInternalType('integer', $result); - $this->assertEquals(1, $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testFloats($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = 1.5; - $result = $serializer->serialize($input); - $this->assertInternalType('double', $result); - $this->assertEquals(1.5, $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testBooleans($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = true; - $result = $serializer->serialize($input); - $this->assertTrue($result); - - $input = false; - $result = $serializer->serialize($input); - $this->assertFalse($result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testNull($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = null; - $result = $serializer->serialize($input); - $this->assertNull($result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testRecursionMaxDepth($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = []; - $input[] = &$input; - $result = $serializer->serialize($input, 3); - $this->assertEquals([[['Array of length 1']]], $result); - - $result = $serializer->serialize([], 3); - $this->assertEquals([], $result); - - $result = $serializer->serialize([[]], 3); - $this->assertEquals([[]], $result); - - $result = $serializer->serialize([[[]]], 3); - $this->assertEquals([[[]]], $result); - - $result = $serializer->serialize([[[[]]]], 3); - $this->assertEquals([[['Array of length 0']]], $result); - } - - public function dataRecursionInObjects() - { - $data = []; - // case 1 - $object = new SerializerTestObject(); - $object->key = $object; - $data[] = [ - 'object' => $object, - 'result_serialize' => ['key' => 'Object Raven\Tests\SerializerTestObject'], - ]; - - // case 2 - $object = new SerializerTestObject(); - $object2 = new SerializerTestObject(); - $object2->key = $object; - $object->key = $object2; - $data[] = [ - 'object' => $object, - 'result_serialize' => ['key' => ['key' => 'Object Raven\Tests\SerializerTestObject']], - ]; - - // case 3 - $object = new SerializerTestObject(); - $object2 = new SerializerTestObject(); - $object2->key = 'foobar'; - $object->key = $object2; - $data[] = [ - 'object' => $object, - 'result_serialize' => ['key' => ['key' => 'foobar']], - ]; - - // case 4 - $object3 = new SerializerTestObject(); - $object3->key = 'foobar'; - $object2 = new SerializerTestObject(); - $object2->key = $object3; - $object = new SerializerTestObject(); - $object->key = $object2; - $data[] = [ - 'object' => $object, - 'result_serialize' => ['key' => ['key' => ['key' => 'foobar']]], - ]; - - // case 5 - $object4 = new SerializerTestObject(); - $object4->key = 'foobar'; - $object3 = new SerializerTestObject(); - $object3->key = $object4; - $object2 = new SerializerTestObject(); - $object2->key = $object3; - $object = new SerializerTestObject(); - $object->key = $object2; - $data[] = [ - 'object' => $object, - 'result_serialize' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject']]], - ]; - - // case 6 - $object3 = new SerializerTestObject(); - $object2 = new SerializerTestObject(); - $object2->key = $object3; - $object2->keys = 'keys'; - $object = new SerializerTestObject(); - $object->key = $object2; - $object3->key = $object2; - $data[] = [ - 'object' => $object, - 'result_serialize' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject'], - 'keys' => 'keys', ]], - ]; - - foreach ($data as &$datum) { - if (!isset($datum['result_serialize_object'])) { - $datum['result_serialize_object'] = $datum['result_serialize']; - } - } - - return $data; - } - - /** - * @param object $object - * @param array $result_serialize - * @param array $result_serialize_object - * - * @dataProvider dataRecursionInObjects - */ - public function testRecursionInObjects($object, $result_serialize, $result_serialize_object) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - $serializer->setAllObjectSerialize(true); - - $result1 = $serializer->serialize($object, 3); - $result2 = $serializer->serializeObject($object, 3); - $this->assertEquals($result_serialize, $result1); - $this->assertEquals($result_serialize_object, $result2); - } - - public function testRecursionMaxDepthForObject() - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - $serializer->setAllObjectSerialize(true); - - $result = $serializer->serialize((object) ['key' => (object) ['key' => 12345]], 3); - $this->assertEquals(['key' => ['key' => 12345]], $result); - - $result = $serializer->serialize((object) ['key' => (object) ['key' => (object) ['key' => 12345]]], 3); - $this->assertEquals(['key' => ['key' => ['key' => 12345]]], $result); - - $result = $serializer->serialize( - (object) ['key' => (object) ['key' => (object) ['key' => (object) ['key' => 12345]]]], - 3 - ); - $this->assertEquals(['key' => ['key' => ['key' => 'Object stdClass']]], $result); - } - - public function testObjectInArray() - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - $input = ['foo' => new \Raven\Tests\SerializerTestObject()]; - $result = $serializer->serialize($input); - $this->assertEquals(['foo' => 'Object Raven\\Tests\\SerializerTestObject'], $result); - } - - public function testObjectInArraySerializeAll() - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - $serializer->setAllObjectSerialize(true); - $input = ['foo' => new \Raven\Tests\SerializerTestObject()]; - $result = $serializer->serialize($input); - $this->assertEquals(['foo' => ['key' => 'value']], $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testBrokenEncoding($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - foreach (['7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90'] as $key) { - $input = pack('H*', $key); - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - if (function_exists('mb_detect_encoding')) { - $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); - } - } - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testLongString($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - - foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { - $input = str_repeat('x', $length); - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(1024, strlen($result)); - } - } - - public function testLongStringWithOverwrittenMessageLength() - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - $serializer->setMessageLimit(500); - - foreach ([100, 490, 499, 500, 501, 1000, 10000] as $length) { - $input = str_repeat('x', $length); - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(500, strlen($result)); - } - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - */ - public function testSerializeValueResource($serialize_all_objects) - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); - $fo = fopen($filename, 'wb'); - - $result = $serializer->serialize($fo); - $this->assertInternalType('string', $result); - $this->assertEquals('Resource stream', $result); - } - - public function testSetAllObjectSerialize() - { - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(); - $serializer->setAllObjectSerialize(true); - $this->assertTrue($serializer->getAllObjectSerialize()); - $serializer->setAllObjectSerialize(false); - $this->assertFalse($serializer->getAllObjectSerialize()); - } - - public function testClippingUTF8Characters() - { - if (!extension_loaded('mbstring')) { - $this->markTestSkipped('mbstring extension is not enabled.'); - } - - $testString = 'Прекратите надеÑтьÑÑ, что ваши пользователи будут Ñообщать об ошибках'; - $class_name = static::get_test_class(); - /** @var \Raven\Serializer $serializer */ - $serializer = new $class_name(null, 19); - - $clipped = $serializer->serialize($testString); - - $this->assertEquals('Прекратит {clipped}', $clipped); - $this->assertNotNull(json_encode($clipped)); - $this->assertSame(JSON_ERROR_NONE, json_last_error()); - } -} - -/** - * Class SerializerTestObject. - * - * @property mixed $keys - */ -class SerializerTestObject -{ - private $foo = 'bar'; - - public $key = 'value'; -} diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index fc9fbc9e0..4a50619a6 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -2,45 +2,12 @@ namespace Raven\Tests; -require_once 'SerializerAbstractTest.php'; +use Raven\Serializer; -class SerializerTest extends \Raven\Tests\SerializerAbstractTest +class SerializerTest extends AbstractSerializerTest { - /** - * @return string - */ - protected static function get_test_class() + protected function getSerializerUnderTest() { - return '\\Raven\\Serializer'; - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - * @covers \Raven\Serializer::serializeString - */ - public function testBrokenEncoding($serialize_all_objects) - { - parent::testBrokenEncoding($serialize_all_objects); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - * @covers \Raven\Serializer::serializeString - */ - public function testLongString($serialize_all_objects) - { - parent::testLongString($serialize_all_objects); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider dataGetBaseParam - * @covers \Raven\Serializer::serializeValue - */ - public function testSerializeValueResource($serialize_all_objects) - { - parent::testSerializeValueResource($serialize_all_objects); + return new Serializer(); } } diff --git a/tests/resources/php70_serializing.inc b/tests/resources/php70_serializing.inc new file mode 100644 index 000000000..f301f1fb1 --- /dev/null +++ b/tests/resources/php70_serializing.inc @@ -0,0 +1,28 @@ + $closure_701, + 'expected' => 'Lambda {closure} [int param1_70a]', + ], [ + 'callable' => $closure_702, + 'expected' => 'Lambda int {closure} [mixed|null ¶m]', + ], + ] +); diff --git a/tests/resources/php71_serializing.inc b/tests/resources/php71_serializing.inc new file mode 100644 index 000000000..5acddf87a --- /dev/null +++ b/tests/resources/php71_serializing.inc @@ -0,0 +1,36 @@ + $closure_711, + 'expected' => 'Lambda int {closure} [int param]', + ], [ + 'callable' => $closure_712, + 'expected' => 'Lambda {closure} [int|null param1_70b]', + ], [ + 'callable' => $closure_713, + 'expected' => 'Lambda void {closure} [int|null param1_70c]', + ] + ] +); From 823a5ff0f351ae3250ad2423f768796b9bdcf6d6 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 21 Jun 2018 22:27:04 +0200 Subject: [PATCH 0348/1161] Add a ClientInterface interface (#603) --- lib/Raven/AbstractErrorHandler.php | 8 +- lib/Raven/BreadcrumbErrorHandler.php | 6 +- lib/Raven/Breadcrumbs/MonologHandler.php | 11 +- lib/Raven/Client.php | 191 +++++--------- lib/Raven/ClientBuilderInterface.php | 2 +- lib/Raven/ClientInterface.php | 234 ++++++++++++++++++ lib/Raven/ErrorHandler.php | 6 +- .../HttpClient/Authentication/SentryAuth.php | 2 +- .../ExceptionInterfaceMiddleware.php | 13 +- .../Middleware/MessageInterfaceMiddleware.php | 3 +- lib/Raven/Serializer.php | 2 +- lib/Raven/Stacktrace.php | 28 +-- tests/Breadcrumbs/MonologHandlerTest.php | 6 +- tests/ClientTest.php | 11 +- tests/EventTest.php | 3 +- .../Authentication/SentryAuthTest.php | 4 +- .../Processor/RemoveHttpBodyProcessorTest.php | 4 +- .../SanitizeCookiesProcessorTest.php | 4 +- tests/Processor/SanitizeDataProcessorTest.php | 4 +- .../SanitizeHttpHeadersProcessorTest.php | 4 +- .../SanitizeStacktraceProcessorTest.php | 4 +- tests/StacktraceTest.php | 4 +- 22 files changed, 368 insertions(+), 186 deletions(-) create mode 100644 lib/Raven/ClientInterface.php diff --git a/lib/Raven/AbstractErrorHandler.php b/lib/Raven/AbstractErrorHandler.php index 8199374c2..f18ce732e 100644 --- a/lib/Raven/AbstractErrorHandler.php +++ b/lib/Raven/AbstractErrorHandler.php @@ -19,7 +19,7 @@ abstract class AbstractErrorHandler { /** - * @var Client The Raven client + * @var ClientInterface The Raven client */ protected $client; @@ -80,10 +80,10 @@ abstract class AbstractErrorHandler /** * Constructor. * - * @param Client $client The Raven client - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * @param ClientInterface $client The Raven client + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler */ - protected function __construct(Client $client, $reservedMemorySize = 10240) + protected function __construct(ClientInterface $client, $reservedMemorySize = 10240) { if (!is_int($reservedMemorySize) || $reservedMemorySize <= 0) { throw new \UnexpectedValueException('The value of the $reservedMemorySize argument must be an integer greater than 0.'); diff --git a/lib/Raven/BreadcrumbErrorHandler.php b/lib/Raven/BreadcrumbErrorHandler.php index 319806939..6a3a83973 100644 --- a/lib/Raven/BreadcrumbErrorHandler.php +++ b/lib/Raven/BreadcrumbErrorHandler.php @@ -24,12 +24,12 @@ class BreadcrumbErrorHandler extends AbstractErrorHandler * Registers this error handler by associating its instance with the given * Raven client. * - * @param Client $client The Raven client - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * @param ClientInterface $client The Raven client + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler * * @return BreadcrumbErrorHandler */ - public static function register(Client $client, $reservedMemorySize = 10240) + public static function register(ClientInterface $client, $reservedMemorySize = 10240) { return new self($client, $reservedMemorySize); } diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index 2ce3d9e60..c73f89655 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -5,6 +5,7 @@ use Monolog\Handler\AbstractProcessingHandler; use Monolog\Logger; use Raven\Client; +use Raven\ClientInterface; class MonologHandler extends AbstractProcessingHandler { @@ -25,16 +26,16 @@ class MonologHandler extends AbstractProcessingHandler protected $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; /** - * @var \Raven\Client the client object that sends the message to the server + * @var ClientInterface the client object that sends the message to the server */ protected $ravenClient; /** - * @param Client $ravenClient The Raven client - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param ClientInterface $ravenClient The Raven client + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + public function __construct(ClientInterface $ravenClient, $level = Logger::DEBUG, $bubble = true) { parent::__construct($level, $bubble); diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 22446fe22..88c2b69e8 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -32,34 +32,41 @@ use Zend\Diactoros\ServerRequestFactory; /** - * Raven PHP Client. + * Default implementation of the {@see ClientInterface} interface. * - * @see https://docs.sentry.io/clients/php/config/ + * @author Stefano Arlandini */ -class Client +class Client implements ClientInterface { + /** + * The version of the library. + */ const VERSION = '2.0.x-dev'; - const PROTOCOL = '6'; + /** + * The version of the protocol to communicate with the Sentry server. + */ + const PROTOCOL_VERSION = '6'; /** - * Debug log levels. + * This constant defines the client's user-agent string. */ - const LEVEL_DEBUG = 'debug'; - const LEVEL_INFO = 'info'; - const LEVEL_WARNING = 'warning'; - const LEVEL_ERROR = 'error'; - const LEVEL_FATAL = 'fatal'; + const USER_AGENT = 'sentry-php/' . self::VERSION; /** - * Default message limit. + * This constant defines the maximum length of the message captured by the + * message SDK interface. */ - const MESSAGE_LIMIT = 1024; + const MESSAGE_MAX_LENGTH_LIMIT = 1024; /** - * This constant defines the client's user-agent string. + * Debug log levels. */ - const USER_AGENT = 'sentry-php/' . self::VERSION; + const LEVEL_DEBUG = 'debug'; + const LEVEL_INFO = 'info'; + const LEVEL_WARNING = 'warning'; + const LEVEL_ERROR = 'error'; + const LEVEL_FATAL = 'fatal'; /** * @var string[]|null @@ -72,9 +79,9 @@ class Client private $serializer; /** - * @var Serializer The representation serializer + * @var ReprSerializer The representation serializer */ - private $reprSerializer; + private $representationSerializer; /** * @var Configuration The client configuration @@ -155,7 +162,7 @@ public function __construct(Configuration $config, TransportInterface $transport $this->breadcrumbRecorder = new Recorder(); $this->transactionStack = new TransactionStack(); $this->serializer = new Serializer($this->config->getMbDetectOrder()); - $this->reprSerializer = new ReprSerializer($this->config->getMbDetectOrder()); + $this->representationSerializer = new ReprSerializer($this->config->getMbDetectOrder()); $this->middlewareStack = new MiddlewareStack(function (Event $event) { return $event; }); @@ -175,9 +182,7 @@ public function __construct(Configuration $config, TransportInterface $transport } /** - * Records the given breadcrumb. - * - * @param Breadcrumb $breadcrumb The breadcrumb instance + * {@inheritdoc} */ public function leaveBreadcrumb(Breadcrumb $breadcrumb) { @@ -185,7 +190,7 @@ public function leaveBreadcrumb(Breadcrumb $breadcrumb) } /** - * Clears all recorded breadcrumbs. + * {@inheritdoc} */ public function clearBreadcrumbs() { @@ -193,9 +198,7 @@ public function clearBreadcrumbs() } /** - * Gets the configuration of the client. - * - * @return Configuration + * {@inheritdoc} */ public function getConfig() { @@ -203,9 +206,7 @@ public function getConfig() } /** - * Gets the transaction stack. - * - * @return TransactionStack + * {@inheritdoc} */ public function getTransactionStack() { @@ -213,12 +214,7 @@ public function getTransactionStack() } /** - * Adds a new middleware with the given priority to the stack. - * - * @param callable $middleware The middleware instance - * @param int $priority The priority. The higher this value, the - * earlier a processor will be executed in - * the chain (defaults to 0) + * {@inheritdoc} */ public function addMiddleware(callable $middleware, $priority = 0) { @@ -226,9 +222,7 @@ public function addMiddleware(callable $middleware, $priority = 0) } /** - * Removes the given middleware from the stack. - * - * @param callable $middleware The middleware instance + * {@inheritdoc} */ public function removeMiddleware(callable $middleware) { @@ -236,12 +230,7 @@ public function removeMiddleware(callable $middleware) } /** - * Adds a new processor to the processors chain with the specified priority. - * - * @param ProcessorInterface $processor The processor instance - * @param int $priority The priority. The higher this value, - * the earlier a processor will be - * executed in the chain (defaults to 0) + * {@inheritdoc} */ public function addProcessor(ProcessorInterface $processor, $priority = 0) { @@ -249,9 +238,7 @@ public function addProcessor(ProcessorInterface $processor, $priority = 0) } /** - * Removes the given processor from the list. - * - * @param ProcessorInterface $processor The processor instance + * {@inheritdoc} */ public function removeProcessor(ProcessorInterface $processor) { @@ -259,43 +246,48 @@ public function removeProcessor(ProcessorInterface $processor) } /** - * Gets the representation serializer. - * - * @return Serializer + * {@inheritdoc} */ - public function getReprSerializer() + public function setAllObjectSerialize($value) { - return $this->reprSerializer; + $this->serializer->setAllObjectSerialize($value); + $this->representationSerializer->setAllObjectSerialize($value); } - public function setReprSerializer(Serializer $reprSerializer) + /** + * {@inheritdoc} + */ + public function getRepresentationSerializer() { - $this->reprSerializer = $reprSerializer; + return $this->representationSerializer; } /** - * Gets the serializer. - * - * @return Serializer + * {@inheritdoc} + */ + public function setRepresentationSerializer(ReprSerializer $representationSerializer) + { + $this->representationSerializer = $representationSerializer; + } + + /** + * {@inheritdoc} */ public function getSerializer() { return $this->serializer; } + /** + * {@inheritdoc} + */ public function setSerializer(Serializer $serializer) { $this->serializer = $serializer; } /** - * Logs a message. - * - * @param string $message The message (primary description) for the event - * @param array $params Params to use when formatting the message - * @param array $payload Additional attributes to pass with this event - * - * @return string + * {@inheritdoc} */ public function captureMessage($message, array $params = [], array $payload = []) { @@ -306,12 +298,7 @@ public function captureMessage($message, array $params = [], array $payload = [] } /** - * Logs an exception. - * - * @param \Throwable|\Exception $exception The exception object - * @param array $payload Additional attributes to pass with this event - * - * @return string + * {@inheritdoc} */ public function captureException($exception, array $payload = []) { @@ -321,9 +308,7 @@ public function captureException($exception, array $payload = []) } /** - * Logs the most recent error (obtained with {@link error_get_last}). - * - * @return string|null + * {@inheritdoc} */ public function captureLastError() { @@ -339,10 +324,7 @@ public function captureLastError() } /** - * Gets the last event that was captured by the client. However, it could - * have been sent or still sit in the queue of pending events. - * - * @return Event + * {@inheritdoc} */ public function getLastEvent() { @@ -350,9 +332,7 @@ public function getLastEvent() } /** - * Return the last captured event's ID or null if none available. - * - * @deprecated since version 2.0, to be removed in 3.0. Use getLastEvent() instead. + * {@inheritdoc} */ public function getLastEventId() { @@ -366,11 +346,7 @@ public function getLastEventId() } /** - * Captures a new event using the provided data. - * - * @param array $payload The data of the event being captured - * - * @return string + * {@inheritdoc} */ public function capture(array $payload) { @@ -382,18 +358,10 @@ public function capture(array $payload) $event = $event->withCulprit($this->transactionStack->peek()); } - if (isset($payload['level'])) { - $event = $event->withLevel($payload['level']); - } - if (isset($payload['logger'])) { $event = $event->withLogger($payload['logger']); } - if (isset($payload['message'])) { - $payload['message'] = substr($payload['message'], 0, static::MESSAGE_LIMIT); - } - $event = $this->middlewareStack->executeStack( $event, isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null, @@ -409,9 +377,7 @@ public function capture(array $payload) } /** - * Sends the given event to the Sentry server. - * - * @param Event $event The event to send + * {@inheritdoc} */ public function send(Event $event) { @@ -428,11 +394,7 @@ public function send(Event $event) } /** - * Translate a PHP Error constant into a Sentry log level group. - * - * @param string $severity PHP E_$x error constant - * - * @return string Sentry log level group + * {@inheritdoc} */ public function translateSeverity($severity) { @@ -466,10 +428,7 @@ public function translateSeverity($severity) } /** - * Provide a map of PHP Error constants to Sentry logging groups to use instead - * of the defaults in translateSeverity(). - * - * @param string[] $map + * {@inheritdoc} */ public function registerSeverityMap($map) { @@ -477,9 +436,7 @@ public function registerSeverityMap($map) } /** - * Gets the user context. - * - * @return Context + * {@inheritdoc} */ public function getUserContext() { @@ -487,9 +444,7 @@ public function getUserContext() } /** - * Gets the tags context. - * - * @return TagsContext + * {@inheritdoc} */ public function getTagsContext() { @@ -497,9 +452,7 @@ public function getTagsContext() } /** - * Gets the extra context. - * - * @return Context + * {@inheritdoc} */ public function getExtraContext() { @@ -507,9 +460,7 @@ public function getExtraContext() } /** - * Gets the runtime context. - * - * @return RuntimeContext + * {@inheritdoc} */ public function getRuntimeContext() { @@ -517,21 +468,13 @@ public function getRuntimeContext() } /** - * Gets the server OS context. - * - * @return ServerOsContext + * {@inheritdoc} */ public function getServerOsContext() { return $this->serverOsContext; } - public function setAllObjectSerialize($value) - { - $this->serializer->setAllObjectSerialize($value); - $this->reprSerializer->setAllObjectSerialize($value); - } - /** * Adds the default middlewares to this client instance. */ diff --git a/lib/Raven/ClientBuilderInterface.php b/lib/Raven/ClientBuilderInterface.php index 4d7779af8..a972ad454 100644 --- a/lib/Raven/ClientBuilderInterface.php +++ b/lib/Raven/ClientBuilderInterface.php @@ -149,7 +149,7 @@ public function getProcessors(); /** * Gets the instance of the client built using the configured options. * - * @return Client + * @return ClientInterface */ public function getClient(); } diff --git a/lib/Raven/ClientInterface.php b/lib/Raven/ClientInterface.php new file mode 100644 index 000000000..b011c48f1 --- /dev/null +++ b/lib/Raven/ClientInterface.php @@ -0,0 +1,234 @@ + + */ +interface ClientInterface +{ + /** + * Gets the configuration of the client. + * + * @return Configuration + */ + public function getConfig(); + + /** + * Gets the transaction stack. + * + * @return TransactionStack + */ + public function getTransactionStack(); + + /** + * Adds a new middleware with the given priority to the stack. + * + * @param callable $middleware The middleware instance + * @param int $priority The priority. The higher this value, the + * earlier a processor will be executed in + * the chain (defaults to 0) + */ + public function addMiddleware(callable $middleware, $priority = 0); + + /** + * Removes the given middleware from the stack. + * + * @param callable $middleware The middleware instance + */ + public function removeMiddleware(callable $middleware); + + /** + * Adds a new processor to the processors chain with the specified priority. + * + * @param ProcessorInterface $processor The processor instance + * @param int $priority The priority. The higher this value, + * the earlier a processor will be + * executed in the chain (defaults to 0) + */ + public function addProcessor(ProcessorInterface $processor, $priority = 0); + + /** + * Removes the given processor from the list. + * + * @param ProcessorInterface $processor The processor instance + */ + public function removeProcessor(ProcessorInterface $processor); + + /** + * Records the given breadcrumb. + * + * @param Breadcrumb $breadcrumb The breadcrumb instance + */ + public function leaveBreadcrumb(Breadcrumb $breadcrumb); + + /** + * Clears all recorded breadcrumbs. + */ + public function clearBreadcrumbs(); + + /** + * Logs a message. + * + * @param string $message The message (primary description) for the event + * @param array $params Params to use when formatting the message + * @param array $payload Additional attributes to pass with this event + * + * @return string + */ + public function captureMessage($message, array $params = [], array $payload = []); + + /** + * Logs an exception. + * + * @param \Throwable|\Exception $exception The exception object + * @param array $payload Additional attributes to pass with this event + * + * @return string + */ + public function captureException($exception, array $payload = []); + + /** + * Logs the most recent error (obtained with {@link error_get_last}). + * + * @return string|null + */ + public function captureLastError(); + + /** + * Gets the last event that was captured by the client. However, it could + * have been sent or still sit in the queue of pending events. + * + * @return Event + */ + public function getLastEvent(); + + /** + * Return the last captured event's ID or null if none available. + * + * @deprecated since version 2.0, to be removed in 3.0. Use getLastEvent() instead. + */ + public function getLastEventId(); + + /** + * Captures a new event using the provided data. + * + * @param array $payload The data of the event being captured + * + * @return string + */ + public function capture(array $payload); + + /** + * Sends the given event to the Sentry server. + * + * @param Event $event The event to send + */ + public function send(Event $event); + + /** + * Translate a PHP Error constant into a Sentry log level group. + * + * @param string $severity PHP E_$x error constant + * + * @return string Sentry log level group + */ + public function translateSeverity($severity); + + /** + * Provide a map of PHP Error constants to Sentry logging groups to use instead + * of the defaults in translateSeverity(). + * + * @param string[] $map + */ + public function registerSeverityMap($map); + + /** + * Gets the user context. + * + * @return Context + */ + public function getUserContext(); + + /** + * Gets the tags context. + * + * @return TagsContext + */ + public function getTagsContext(); + + /** + * Gets the extra context. + * + * @return Context + */ + public function getExtraContext(); + + /** + * Gets the runtime context. + * + * @return RuntimeContext + */ + public function getRuntimeContext(); + + /** + * Gets the server OS context. + * + * @return ServerOsContext + */ + public function getServerOsContext(); + + /** + * Sets whether all the objects should be serialized by the representation + * serializer. + * + * @param bool $value Whether the serialization of all objects is enabled or not + */ + public function setAllObjectSerialize($value); + + /** + * Gets the representation serialier. + * + * @return ReprSerializer + */ + public function getRepresentationSerializer(); + + /** + * Sets the representation serializer. + * + * @param ReprSerializer $representationSerializer The serializer instance + */ + public function setRepresentationSerializer(ReprSerializer $representationSerializer); + + /** + * Gets the serializer. + * + * @return Serializer + */ + public function getSerializer(); + + /** + * Sets the serializer. + * + * @param Serializer $serializer The serializer instance + */ + public function setSerializer(Serializer $serializer); +} diff --git a/lib/Raven/ErrorHandler.php b/lib/Raven/ErrorHandler.php index e248e933a..cf7006341 100644 --- a/lib/Raven/ErrorHandler.php +++ b/lib/Raven/ErrorHandler.php @@ -25,12 +25,12 @@ class ErrorHandler extends AbstractErrorHandler * Registers this error handler by associating its instance with the given * Raven client. * - * @param Client $client The Raven client - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * @param ClientInterface $client The Raven client + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler * * @return ErrorHandler */ - public static function register(Client $client, $reservedMemorySize = 10240) + public static function register(ClientInterface $client, $reservedMemorySize = 10240) { return new self($client, $reservedMemorySize); } diff --git a/lib/Raven/HttpClient/Authentication/SentryAuth.php b/lib/Raven/HttpClient/Authentication/SentryAuth.php index 593fcf0ff..76a75ad1c 100644 --- a/lib/Raven/HttpClient/Authentication/SentryAuth.php +++ b/lib/Raven/HttpClient/Authentication/SentryAuth.php @@ -45,7 +45,7 @@ public function __construct(Configuration $configuration) public function authenticate(RequestInterface $request) { $headerKeys = array_filter([ - 'sentry_version' => Client::PROTOCOL, + 'sentry_version' => Client::PROTOCOL_VERSION, 'sentry_client' => Client::USER_AGENT, 'sentry_timestamp' => sprintf('%F', microtime(true)), 'sentry_key' => $this->configuration->getPublicKey(), diff --git a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php index 1769d47d8..79bb747c0 100644 --- a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php @@ -12,7 +12,7 @@ namespace Raven\Middleware; use Psr\Http\Message\ServerRequestInterface; -use Raven\Client; +use Raven\ClientInterface; use Raven\Event; use Raven\Stacktrace; @@ -24,16 +24,16 @@ final class ExceptionInterfaceMiddleware { /** - * @var Client The Raven client + * @var ClientInterface The Raven client */ private $client; /** * Constructor. * - * @param Client $client The Raven client + * @param ClientInterface $client The Raven client */ - public function __construct(Client $client) + public function __construct(ClientInterface $client) { $this->client = $client; } @@ -51,8 +51,9 @@ public function __construct(Client $client) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - // Do not override the level if it was set explicitly by the user - if (!isset($payload['level']) && $exception instanceof \ErrorException) { + if (isset($payload['level'])) { + $event = $event->withLevel($payload['level']); + } elseif ($exception instanceof \ErrorException) { $event = $event->withLevel($this->client->translateSeverity($exception->getSeverity())); } diff --git a/lib/Raven/Middleware/MessageInterfaceMiddleware.php b/lib/Raven/Middleware/MessageInterfaceMiddleware.php index 6a7f787b0..f3a6a090d 100644 --- a/lib/Raven/Middleware/MessageInterfaceMiddleware.php +++ b/lib/Raven/Middleware/MessageInterfaceMiddleware.php @@ -12,6 +12,7 @@ namespace Raven\Middleware; use Psr\Http\Message\ServerRequestInterface; +use Raven\Client; use Raven\Event; /** @@ -39,7 +40,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $messageParams = isset($payload['message_params']) ? $payload['message_params'] : []; if (null !== $message) { - $event = $event->withMessage($message, $messageParams); + $event = $event->withMessage(substr($message, 0, Client::MESSAGE_MAX_LENGTH_LIMIT), $messageParams); } return $next($event, $request, $exception, $payload); diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 9a241bb01..91c25fa34 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -61,7 +61,7 @@ class Serializer * @param null|string $mb_detect_order * @param null|int $messageLimit */ - public function __construct($mb_detect_order = null, $messageLimit = Client::MESSAGE_LIMIT) + public function __construct($mb_detect_order = null, $messageLimit = Client::MESSAGE_MAX_LENGTH_LIMIT) { if (null != $mb_detect_order) { $this->mb_detect_order = $mb_detect_order; diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index 82eb49dad..c3f0382db 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -24,7 +24,7 @@ class Stacktrace implements \JsonSerializable const CONTEXT_NUM_LINES = 5; /** - * @var Client The Raven client + * @var ClientInterface The Raven client */ protected $client; @@ -56,23 +56,23 @@ class Stacktrace implements \JsonSerializable /** * Constructor. * - * @param Client $client The Raven client + * @param ClientInterface $client The Raven client */ - public function __construct(Client $client) + public function __construct(ClientInterface $client) { $this->client = $client; $this->serializer = $client->getSerializer(); - $this->reprSerializer = $client->getReprSerializer(); + $this->reprSerializer = $client->getRepresentationSerializer(); } /** * Creates a new instance of this class using the current backtrace data. * - * @param Client $client The Raven client + * @param ClientInterface $client The Raven client * * @return static */ - public static function create(Client $client) + public static function create(ClientInterface $client) { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); @@ -82,14 +82,14 @@ public static function create(Client $client) /** * Creates a new instance of this class from the given backtrace. * - * @param Client $client The Raven client - * @param array $backtrace The backtrace - * @param string $file The file that originated the backtrace - * @param int $line The line at which the backtrace originated + * @param ClientInterface $client The Raven client + * @param array $backtrace The backtrace + * @param string $file The file that originated the backtrace + * @param int $line The line at which the backtrace originated * * @return static */ - public static function createFromBacktrace(Client $client, array $backtrace, $file, $line) + public static function createFromBacktrace(ClientInterface $client, array $backtrace, $file, $line) { $stacktrace = new static($client); @@ -180,7 +180,7 @@ public function addFrame($file, $line, array $backtraceFrame) $argumentValue = $this->reprSerializer->serialize($argumentValue); if (is_string($argumentValue) || is_numeric($argumentValue)) { - $frameArguments[(string) $argumentName] = substr($argumentValue, 0, Client::MESSAGE_LIMIT); + $frameArguments[(string) $argumentName] = substr($argumentValue, 0, Client::MESSAGE_MAX_LENGTH_LIMIT); } else { $frameArguments[(string) $argumentName] = $argumentValue; } @@ -312,7 +312,7 @@ protected function stripPrefixFromFilePath($filePath) * * @return array */ - protected static function getFrameArgumentsValues($frame, $maxValueLength = Client::MESSAGE_LIMIT) + protected static function getFrameArgumentsValues($frame, $maxValueLength = Client::MESSAGE_MAX_LENGTH_LIMIT) { if (!isset($frame['args'])) { return []; @@ -335,7 +335,7 @@ protected static function getFrameArgumentsValues($frame, $maxValueLength = Clie * * @return array */ - public static function getFrameArguments($frame, $maxValueLength = Client::MESSAGE_LIMIT) + public static function getFrameArguments($frame, $maxValueLength = Client::MESSAGE_MAX_LENGTH_LIMIT) { if (!isset($frame['args'])) { return []; diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php index 47d1861d2..79749c243 100644 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -12,12 +12,12 @@ namespace Raven\Tests\Breadcrumbs; use Monolog\Logger; -use ParseError; use PHPUnit\Framework\TestCase; use Raven\Breadcrumbs\Breadcrumb; use Raven\Breadcrumbs\MonologHandler; use Raven\Client; use Raven\ClientBuilder; +use Raven\ClientInterface; class MonologHandlerTest extends TestCase { @@ -96,7 +96,7 @@ public function testThrowableBeingParsedAsException() $client = $this->createClient(); $logger = $this->createLoggerWithHandler($client); - $throwable = new ParseError('Foo bar'); + $throwable = new \ParseError('Foo bar'); $logger->addError('This is a throwable', ['exception' => $throwable]); @@ -110,7 +110,7 @@ public function testThrowableBeingParsedAsException() } /** - * @return Client + * @return ClientInterface */ private function createClient() { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 00bbb0f86..7d5e9251b 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -24,6 +24,7 @@ use Raven\Middleware\MiddlewareStack; use Raven\Processor\ProcessorInterface; use Raven\Processor\ProcessorRegistry; +use Raven\ReprSerializer; use Raven\Serializer; use Raven\Tests\Fixtures\classes\CarelessException; use Raven\Transport\TransportInterface; @@ -483,12 +484,12 @@ public function testSetAllObjectSerialize() $client->setAllObjectSerialize(true); $this->assertTrue($client->getSerializer()->getAllObjectSerialize()); - $this->assertTrue($client->getReprSerializer()->getAllObjectSerialize()); + $this->assertTrue($client->getRepresentationSerializer()->getAllObjectSerialize()); $client->setAllObjectSerialize(false); $this->assertFalse($client->getSerializer()->getAllObjectSerialize()); - $this->assertFalse($client->getReprSerializer()->getAllObjectSerialize()); + $this->assertFalse($client->getRepresentationSerializer()->getAllObjectSerialize()); } public function testClearBreadcrumb() @@ -526,11 +527,11 @@ public function testSetSerializer() public function testSetReprSerializer() { $client = ClientBuilder::create()->getClient(); - $serializer = $this->prophesize(Serializer::class)->reveal(); + $serializer = $this->prophesize(ReprSerializer::class)->reveal(); - $client->setReprSerializer($serializer); + $client->setRepresentationSerializer($serializer); - $this->assertSame($serializer, $client->getReprSerializer()); + $this->assertSame($serializer, $client->getRepresentationSerializer()); } public function testHandlingExceptionThrowingAnException() diff --git a/tests/EventTest.php b/tests/EventTest.php index 731c9b91c..fbaf6751c 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -9,6 +9,7 @@ use Raven\Breadcrumbs\Breadcrumb; use Raven\Client; use Raven\ClientBuilder; +use Raven\ClientInterface; use Raven\Configuration; use Raven\Event; @@ -35,7 +36,7 @@ class EventTest extends TestCase protected $configuration; /** - * @var Client + * @var ClientInterface */ protected $client; diff --git a/tests/HttpClient/Authentication/SentryAuthTest.php b/tests/HttpClient/Authentication/SentryAuthTest.php index 5da20d547..439aebd9e 100644 --- a/tests/HttpClient/Authentication/SentryAuthTest.php +++ b/tests/HttpClient/Authentication/SentryAuthTest.php @@ -37,7 +37,7 @@ public function testAuthenticate() $headerValue = sprintf( 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=public, sentry_secret=secret', - Client::PROTOCOL, + Client::PROTOCOL_VERSION, Client::USER_AGENT, microtime(true) ); @@ -65,7 +65,7 @@ public function testAuthenticateWithNoSecretKey() $headerValue = sprintf( 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=public', - Client::PROTOCOL, + Client::PROTOCOL_VERSION, Client::USER_AGENT, microtime(true) ); diff --git a/tests/Processor/RemoveHttpBodyProcessorTest.php b/tests/Processor/RemoveHttpBodyProcessorTest.php index 0c98bc737..f1660dff8 100644 --- a/tests/Processor/RemoveHttpBodyProcessorTest.php +++ b/tests/Processor/RemoveHttpBodyProcessorTest.php @@ -12,15 +12,15 @@ namespace Raven\Tests\Processor; use PHPUnit\Framework\TestCase; -use Raven\Client; use Raven\ClientBuilder; +use Raven\ClientInterface; use Raven\Event; use Raven\Processor\RemoveHttpBodyProcessor; class RemoveHttpBodyProcessorTest extends TestCase { /** - * @var Client + * @var ClientInterface */ protected $client; diff --git a/tests/Processor/SanitizeCookiesProcessorTest.php b/tests/Processor/SanitizeCookiesProcessorTest.php index eb9fafba6..6d498ab0b 100644 --- a/tests/Processor/SanitizeCookiesProcessorTest.php +++ b/tests/Processor/SanitizeCookiesProcessorTest.php @@ -12,15 +12,15 @@ namespace Raven\Tests\Processor; use PHPUnit\Framework\TestCase; -use Raven\Client; use Raven\ClientBuilder; +use Raven\ClientInterface; use Raven\Event; use Raven\Processor\SanitizeCookiesProcessor; class SanitizeCookiesProcessorTest extends TestCase { /** - * @var Client + * @var ClientInterface */ protected $client; diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index 5b4534033..eb6dbfee9 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -12,8 +12,8 @@ namespace Raven\Tests\Processor; use PHPUnit\Framework\TestCase; -use Raven\Client; use Raven\ClientBuilder; +use Raven\ClientInterface; use Raven\Event; use Raven\Processor\SanitizeDataProcessor; use Raven\Stacktrace; @@ -21,7 +21,7 @@ class SanitizeDataProcessorTest extends TestCase { /** - * @var Client + * @var ClientInterface */ protected $client; diff --git a/tests/Processor/SanitizeHttpHeadersProcessorTest.php b/tests/Processor/SanitizeHttpHeadersProcessorTest.php index dbe0b2e53..2fb1192bc 100644 --- a/tests/Processor/SanitizeHttpHeadersProcessorTest.php +++ b/tests/Processor/SanitizeHttpHeadersProcessorTest.php @@ -12,15 +12,15 @@ namespace Raven\Tests\Processor; use PHPUnit\Framework\TestCase; -use Raven\Client; use Raven\ClientBuilder; +use Raven\ClientInterface; use Raven\Event; use Raven\Processor\SanitizeHttpHeadersProcessor; class SanitizeHttpHeadersProcessorTest extends TestCase { /** - * @var Client + * @var ClientInterface */ protected $client; diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php index 2abfc8853..21478b8f2 100644 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -12,8 +12,8 @@ namespace Raven\Tests\Processor; use PHPUnit\Framework\TestCase; -use Raven\Client; use Raven\ClientBuilder; +use Raven\ClientInterface; use Raven\Event; use Raven\Processor\SanitizeStacktraceProcessor; use Raven\Stacktrace; @@ -26,7 +26,7 @@ class SanitizeStacktraceProcessorTest extends TestCase protected $processor; /** - * @var Client + * @var ClientInterface */ protected $client; diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 5aaf7ddc8..03a2898e2 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -12,15 +12,15 @@ namespace Raven\Tests; use PHPUnit\Framework\TestCase; -use Raven\Client; use Raven\ClientBuilder; +use Raven\ClientInterface; use Raven\Frame; use Raven\Stacktrace; class StacktraceTest extends TestCase { /** - * @var Client + * @var ClientInterface */ protected $client; From 776d151f261fa1caec6fd5327e4ed49f2ada5646 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 6 Jul 2018 16:44:28 +0200 Subject: [PATCH 0349/1161] Rename the "server" configuration option to "dsn" (#626) --- UPGRADE-2.0.md | 3 ++- lib/Raven/ClientBuilder.php | 8 +++---- lib/Raven/Configuration.php | 22 +++++++++---------- tests/ClientBuilderTest.php | 12 +++++----- tests/ClientTest.php | 8 +++---- tests/ConfigurationTest.php | 12 +++++----- .../Authentication/SentryAuthTest.php | 4 ++-- tests/phpt/fatal_error.phpt | 2 +- tests/phpt/out_of_memory.phpt | 2 +- 9 files changed, 37 insertions(+), 36 deletions(-) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 14d7814d6..2d0c685e1 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -42,6 +42,7 @@ object instances should be serialized. - The `context_lines` option has been added to configure the number of lines of code context to capture. +- The `server` option has been renamed to `dsn`. ### Client @@ -176,7 +177,7 @@ $client->getConfig()->setShouldCapture(...); - The method `Raven_Client::getServerEndpoint` has been removed. You should use - `Configuration::getServer` instead. + `Configuration::getDsn` instead. Before: diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index 7cd18d5f3..fb8ae523b 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -69,7 +69,7 @@ * @method setLogger(string $logger) * @method string getRelease() * @method setRelease(string $release) - * @method string getServer() + * @method string getDsn() * @method string getServerName() * @method setServerName(string $serverName) * @method string[] getTags() @@ -317,8 +317,8 @@ public function __call($name, $arguments) */ private function createHttpClientInstance() { - if (null !== $this->configuration->getServer()) { - $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->configuration->getServer()))); + if (null !== $this->configuration->getDsn()) { + $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->configuration->getDsn()))); } $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => Client::USER_AGENT])); @@ -340,7 +340,7 @@ private function createTransportInstance() return $this->transport; } - if (null !== $this->configuration->getServer()) { + if (null !== $this->configuration->getDsn()) { return new HttpTransport($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); } diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 4d4aaa772..019d5aeee 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -29,7 +29,7 @@ class Configuration /** * @var string|null A simple server string, set to the DSN found on your Sentry settings */ - private $server; + private $dsn; /** * @var string The project ID number to send to the Sentry server @@ -479,9 +479,9 @@ public function setRelease($release) * * @return string */ - public function getServer() + public function getDsn() { - return $this->server; + return $this->dsn; } /** @@ -613,7 +613,7 @@ private function configureOptions(OptionsResolver $resolver) 'project_root' => null, 'logger' => 'php', 'release' => null, - 'server' => isset($_SERVER['SENTRY_DSN']) ? $_SERVER['SENTRY_DSN'] : null, + 'dsn' => isset($_SERVER['SENTRY_DSN']) ? $_SERVER['SENTRY_DSN'] : null, 'server_name' => gethostname(), 'should_capture' => null, 'tags' => [], @@ -636,14 +636,14 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('project_root', ['null', 'string']); $resolver->setAllowedTypes('logger', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); - $resolver->setAllowedTypes('server', ['null', 'boolean', 'string']); + $resolver->setAllowedTypes('dsn', ['null', 'boolean', 'string']); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('should_capture', ['null', 'callable']); $resolver->setAllowedTypes('tags', 'array'); $resolver->setAllowedTypes('error_types', ['null', 'int']); $resolver->setAllowedValues('encoding', ['gzip', 'json']); - $resolver->setAllowedValues('server', function ($value) { + $resolver->setAllowedValues('dsn', function ($value) { switch (strtolower($value)) { case '': case 'false': @@ -676,7 +676,7 @@ private function configureOptions(OptionsResolver $resolver) return true; }); - $resolver->setNormalizer('server', function (Options $options, $value) { + $resolver->setNormalizer('dsn', function (Options $options, $value) { switch (strtolower($value)) { case '': case 'false': @@ -685,20 +685,20 @@ private function configureOptions(OptionsResolver $resolver) case '(empty)': case 'null': case '(null)': - $this->server = null; + $this->dsn = null; return null; } $parsed = @parse_url($value); - $this->server = $parsed['scheme'] . '://' . $parsed['host']; + $this->dsn = $parsed['scheme'] . '://' . $parsed['host']; if (isset($parsed['port']) && ((80 !== $parsed['port'] && 'http' === $parsed['scheme']) || (443 !== $parsed['port'] && 'https' === $parsed['scheme']))) { - $this->server .= ':' . $parsed['port']; + $this->dsn .= ':' . $parsed['port']; } - $this->server .= substr($parsed['path'], 0, strripos($parsed['path'], '/')); + $this->dsn .= substr($parsed['path'], 0, strripos($parsed['path'], '/')); $this->publicKey = $parsed['user']; $this->secretKey = isset($parsed['pass']) ? $parsed['pass'] : null; diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index eb6f0b778..60e107fa0 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -37,7 +37,7 @@ public function testCreate() public function testHttpTransportIsUsedWhenServeIsConfigured() { - $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); @@ -58,7 +58,7 @@ public function testSetUriFactory() /** @var UriFactory|\PHPUnit_Framework_MockObject_MockObject $uriFactory */ $uriFactory = $this->createMock(UriFactory::class); - $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setUriFactory($uriFactory); $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); @@ -69,7 +69,7 @@ public function testSetMessageFactory() /** @var MessageFactory|\PHPUnit_Framework_MockObject_MockObject $messageFactory */ $messageFactory = $this->createMock(MessageFactory::class); - $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setMessageFactory($messageFactory); $this->assertAttributeSame($messageFactory, 'messageFactory', $clientBuilder); @@ -84,7 +84,7 @@ public function testSetTransport() /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ $transport = $this->createMock(TransportInterface::class); - $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setTransport($transport); $this->assertAttributeSame($transport, 'transport', $clientBuilder); @@ -96,7 +96,7 @@ public function testSetHttpClient() /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); - $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setHttpClient($httpClient); $this->assertAttributeSame($httpClient, 'httpClient', $clientBuilder); @@ -191,7 +191,7 @@ public function testRemoveProcessor() public function testGetClient() { - $clientBuilder = new ClientBuilder(['server' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); $client = $clientBuilder->getClient(); $this->assertInstanceOf(Client::class, $client); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 7d5e9251b..95479a2ce 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -225,7 +225,7 @@ public function testCapture() $transport->expects($this->once()) ->method('send'); - $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1']) + $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) ->setTransport($transport) ->getClient(); @@ -421,7 +421,7 @@ public function testSendChecksShouldCaptureOption() $shouldCaptureCalled = false; $client = ClientBuilder::create([ - 'server' => 'http://public:secret@example.com/1', + 'dsn' => 'http://public:secret@example.com/1', 'should_capture' => function () use (&$shouldCaptureCalled) { $shouldCaptureCalled = true; @@ -464,13 +464,13 @@ public function sampleRateAbsoluteDataProvider() return [ [ [ - 'server' => 'http://public:secret@example.com/1', + 'dsn' => 'http://public:secret@example.com/1', 'sample_rate' => 0, ], ], [ [ - 'server' => 'http://public:secret@example.com/1', + 'dsn' => 'http://public:secret@example.com/1', 'sample_rate' => 1, ], ], diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 1291c0729..42eed584d 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -71,12 +71,12 @@ public function optionsDataProvider() */ public function testServerOption($dsn, $options) { - $configuration = new Configuration(['server' => $dsn]); + $configuration = new Configuration(['dsn' => $dsn]); $this->assertEquals($options['project_id'], $configuration->getProjectId()); $this->assertEquals($options['public_key'], $configuration->getPublicKey()); $this->assertEquals($options['secret_key'], $configuration->getSecretKey()); - $this->assertEquals($options['server'], $configuration->getServer()); + $this->assertEquals($options['server'], $configuration->getDsn()); } public function serverOptionDataProvider() @@ -152,11 +152,11 @@ public function serverOptionDataProvider() * @dataProvider invalidServerOptionDataProvider * * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException - * @expectedExceptionMessageRegExp /^The option "server" with value "(.*)" is invalid.$/ + * @expectedExceptionMessageRegExp /^The option "dsn" with value "(.*)" is invalid.$/ */ public function testServerOptionsWithInvalidServer($dsn) { - new Configuration(['server' => $dsn]); + new Configuration(['dsn' => $dsn]); } public function invalidServerOptionDataProvider() @@ -175,12 +175,12 @@ public function invalidServerOptionDataProvider() */ public function testParseDSNWithDisabledValue($dsn) { - $configuration = new Configuration(['server' => $dsn]); + $configuration = new Configuration(['dsn' => $dsn]); $this->assertNull($configuration->getProjectId()); $this->assertNull($configuration->getPublicKey()); $this->assertNull($configuration->getSecretKey()); - $this->assertNull($configuration->getServer()); + $this->assertNull($configuration->getDsn()); } public function disabledDsnProvider() diff --git a/tests/HttpClient/Authentication/SentryAuthTest.php b/tests/HttpClient/Authentication/SentryAuthTest.php index 439aebd9e..99983a65e 100644 --- a/tests/HttpClient/Authentication/SentryAuthTest.php +++ b/tests/HttpClient/Authentication/SentryAuthTest.php @@ -24,7 +24,7 @@ class SentryAuthTest extends TestCase { public function testAuthenticate() { - $configuration = new Configuration(['server' => 'http://public:secret@example.com/']); + $configuration = new Configuration(['dsn' => 'http://public:secret@example.com/']); $authentication = new SentryAuth($configuration); /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $request */ @@ -52,7 +52,7 @@ public function testAuthenticate() public function testAuthenticateWithNoSecretKey() { - $configuration = new Configuration(['server' => 'http://public@example.com/']); + $configuration = new Configuration(['dsn' => 'http://public@example.com/']); $authentication = new SentryAuth($configuration); /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $request */ diff --git a/tests/phpt/fatal_error.phpt b/tests/phpt/fatal_error.phpt index c087dd3c6..740f1bd61 100644 --- a/tests/phpt/fatal_error.phpt +++ b/tests/phpt/fatal_error.phpt @@ -18,7 +18,7 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; $client = ClientBuilder::create([ - 'server' => 'http://public:secret@local.host/1', + 'dsn' => 'http://public:secret@local.host/1', 'send_attempts' => 1, ])->getClient(); diff --git a/tests/phpt/out_of_memory.phpt b/tests/phpt/out_of_memory.phpt index ef0e40edd..1c978eecf 100644 --- a/tests/phpt/out_of_memory.phpt +++ b/tests/phpt/out_of_memory.phpt @@ -20,7 +20,7 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; $client = ClientBuilder::create([ - 'server' => 'http://public:secret@local.host/1', + 'dsn' => 'http://public:secret@local.host/1', 'send_attempts' => 1, ])->getClient(); From 5c3b3dd5bcfe5d560dcef40fd266aeda96c24842 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 17 Jul 2018 17:52:31 +0200 Subject: [PATCH 0350/1161] Add the configuration options documentation (#624) --- docs/configuration.rst | 238 ++++++++++++++++++++++++++++++++++++++++ docs/error_handling.rst | 2 +- 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 docs/configuration.rst diff --git a/docs/configuration.rst b/docs/configuration.rst new file mode 100644 index 000000000..8f26bbcb8 --- /dev/null +++ b/docs/configuration.rst @@ -0,0 +1,238 @@ +Configuration options +##################### + +The Raven client can be configured through a bunch of configuration options. +Some of them are automatically populated by environment variables when a new +instance of the ``Configuration`` class is created. The following is an exhaustive +list of them with a description of what they do and some example values. + +Send attempts +============= + +The number of attempts that should should be made to send an event before erroring +and dropping it from the queue. + +.. code-block:: php + + $configuration = new Configuration(['send_attempts' => 1]); + $configuration->setSendAttempts(1); + +By default this option is set to 6. + +Server +====== + +The DSN of the Sentry server the authenticated user is bound to. + +.. code-block:: php + + // Before Sentry 9 the private DSN must be used + $configuration = new Configuration(['server' => 'http://public:private@example.com/1']); + + // After Sentry 9 the public DSN must be used + $configuration = new Configuration(['server' => 'http://public@example.com/1']); + +By default this option is borrowed from the ``SENTRY_DSN`` environment variable, +if it exists. A value of ``null`` disables completely the sending of any event. +Since Sentry 9 the public DSN must be used instead of the private one, which +is deprecated and whose support will be removed in future releases of the server. + +Server name +=========== + +The name of the server the SDK is running on (it's usually the hostname). + +.. code-block:: php + + $configuration = new Configuration(['server_name' => 'www.my-site.com']); + $configuration->setServerName('www.my-site.com'); + +By default this option is set to the hostname of the server the SDK is running +on retrieved from a call to the ``gethostname()`` method. + +Release +======= + +The release tag to be passed with every event sent to Sentry. This permits to +track in which versions of your application each error happens. + +.. code-block:: php + + $configuration = new Configuration(['release' => '1.2.3']); + $configuration->setRelease('1.2.3'); + +Logger +====== + +The name of the logger which creates the events. + +.. code-block:: php + + $configuration = new Configuration(['logger' => 'foo']); + $configuration->setLogger('foo'); + +By default this option is set to ``php``. + +Excluded loggers +================ + +The list of logger 'progname's to exclude from breadcrumbs. + +.. code-block:: php + + $configuration = new Configuration(['excluded_loggers' => ['foo']); + $configuration->setExcludedLoggers(['foo']); + +Project root +============ + +The root of the project source code. As Sentry is able to distinguish project +files from third-parties ones (e.g. vendors), this option can be configured to +mark the directory containing all the source code of the application. + +.. code-block:: php + + $configuration = new Configuration(['project_root' => 'project-folder/src/']); + $configuration->setProjectRoot('project-folder/src/'); + +For example, assuming that the directory structure shown below exists, marking +the project root as ``project-folder/src/`` means that every file inside that +directory that is part of a stacktrace frame will be marked as "application +code". + +.. code-block:: + + project-folder/ + ├── vendor/ + ├── foo/ + ├── src/ + ├── bar/ <-- these are going to be marked as application files + +Current environment +=================== + +The name of the current environment. There can be multiple environments per +application, and each event belongs to one of them. + +.. code-block:: php + + $configuration = new Configuration(['current_environment' => 'development']); + $configuration->setCurrentEnvironment('development'); + +Environments +============ + +The environments are a feature that allows events to be easily filtered in +Sentry. An application can have multiple environments, but just one is active +at the same time. This option let you configure the environments names: if +the current environment is not whitelisted here, any event tagged with it +won't be sent. If no environment is listed here, the behavior of checking +the whitelist won't be considered and any event will be sent regardeless. + +.. code-block:: php + + $configuration = new Configuration(['environments' => ['development', 'production']]); + $configuration->setEnvironments(['development', 'production']); + +By default this option is set to an empty array. + +Encoding +======== + +This option sets the encoding type of the requests sent to the Sentry server. +There are two supported values: ``json`` and ``gzip``. The first one sends data +using plain JSON, so the request size will be bigger. The second one compresses +the request using GZIP, which can use more CPU power but will reduce the size +of the payload. + +.. code-block:: php + + $configuration = new Configuration(['encoding' => 'json']); + $configuration->setEncoding('json'); + +By default this option is set to ``gzip``. + +Context lines +============= + +This option sets the number of lines of code context to capture. If ``null`` is +set as the value, no source code lines will be added to each stacktrace frame. + +.. code-block:: php + + $configuration = new Configuration(['context_lines' => 3]); + $configuration->setContextLines(3); + +By default this option is set to 3. + +Stacktrace logging +================== + +This option sets whether the stacktrace of the captured errors should be +automatically captured or not. + +.. code-block:: php + + $configuration = new Configuration(['auto_log_stacks' => true]); + $configuration->setAutoLogStacks(true); + +By default this option is set to ``true``. + +Excluded exceptions +=================== + +Sometimes you may want to skip capturing certain exceptions. This option sets +the FCQN of the classes of the exceptions that you don't want to capture. The +check is done using the ``instanceof`` operator against each item of the array +and if at least one of them passes the event will be discarded. + +.. code-block:: php + + $configuration = new Configuration(['excluded_exceptions' => ['RuntimeException']); + $configuration->setExcludedExceptions(['RuntimeException']); + +Sample rate +=========== + +The sampling factor to apply to events. A value of 0 will deny sending any +events, and a value of 1 will send 100% of events. + +.. code-block:: php + + $configuration = new Configuration(['sample_rate' => 1]); + $configuration->setSampleRate(1); + +By default this option is set to 1, so all events will be sent regardeless. + +Excluded application paths +========================== + +This option configures the list of paths to exclude from the `app_path` detection. + +.. code-block:: php + + $configuration = new Configuration(['excluded_app_paths' => ['foo']); + $configuration->setExcludedProjectPaths(['foo']); + + +Prefixes +======== + +This option sets the list of prefixes which should be stripped from the filenames +to create relative paths. + +.. code-block:: php + + $configuration = new Configuration(['prefixes' => ['/var/www']); + $configuration->setPrefixes(['/var/www']); + +Should capture callback +======================= + +This option sets a callable that will be called before sending an event and is +the last place where you can stop it from being sent. + +.. code-block:: php + + $configuration = new Configuration(['should_capture' => function () { return true }]); + $configuration->setShouldCapture(function () { return true }); diff --git a/docs/error_handling.rst b/docs/error_handling.rst index fec640ac8..50ca89d00 100644 --- a/docs/error_handling.rst +++ b/docs/error_handling.rst @@ -31,7 +31,7 @@ officially and by third party users that automatically register the error handlers. In that case please refer to their documentation. Capture errors -============================= +============== The error handler can be customized to set which error types should be captured and sent to the Sentry server: you may want to report all the errors but capture From bb8d0cfb6c1e656fd439ccb7d893d240eca359ec Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Sun, 22 Jul 2018 14:41:38 +0200 Subject: [PATCH 0351/1161] [2.0] Array recursive scrubbing (#622) * Implement recursive array scrubbing with matching key * Add tweaking and optimization * Fix CS * Enhance the test data depth --- lib/Raven/Event.php | 2 +- lib/Raven/Processor/SanitizeDataProcessor.php | 51 +++++++++++-------- tests/Processor/SanitizeDataProcessorTest.php | 41 +++++++++++++-- 3 files changed, 70 insertions(+), 24 deletions(-) diff --git a/lib/Raven/Event.php b/lib/Raven/Event.php index deb98fa8c..531dd4708 100644 --- a/lib/Raven/Event.php +++ b/lib/Raven/Event.php @@ -123,7 +123,7 @@ final class Event implements \JsonSerializable private $exception; /** - * @var Stacktrace The stacktrace that generated this event + * @var Stacktrace|null The stacktrace that generated this event */ private $stacktrace; diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php index 268f482ae..60f2705c1 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -14,6 +14,7 @@ namespace Raven\Processor; use Raven\Event; +use Raven\Stacktrace; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -65,25 +66,32 @@ private function configureOptions(OptionsResolver $resolver) /** * Replace any array values with our mask if the field name or the value matches a respective regex. * - * @param mixed $item Associative array value - * @param string $key Associative array key + * @param array $data Associative array to be sanitized */ - public function sanitize(&$item, $key) + public function sanitize(&$data) { - if (empty($item)) { - return; - } + foreach ($data as $key => &$item) { + if (preg_match($this->options['fields_re'], $key)) { + if (is_array($item)) { + array_walk_recursive($item, function (&$value) { + $value = self::STRING_MASK; + }); - if (preg_match($this->options['values_re'], $item)) { - $item = self::STRING_MASK; - } + break; + } - if (empty($key)) { - return; - } + $item = self::STRING_MASK; + } + + if (is_array($item)) { + $this->sanitize($item); + + break; + } - if (preg_match($this->options['fields_re'], $key)) { - $item = self::STRING_MASK; + if (preg_match($this->options['values_re'], $item)) { + $item = self::STRING_MASK; + } } } @@ -110,12 +118,17 @@ public function sanitizeHttp(&$data) } if (!empty($data['data']) && is_array($data['data'])) { - array_walk_recursive($data['data'], [$this, 'sanitize']); + $this->sanitize($data['data']); } return $data; } + /** + * @param Stacktrace $data + * + * @return Stacktrace + */ public function sanitizeStacktrace($data) { foreach ($data->getFrames() as &$frame) { @@ -125,8 +138,7 @@ public function sanitizeStacktrace($data) $vars = $frame->getVars(); - array_walk_recursive($vars, [$this, 'sanitize']); - + $this->sanitize($vars); $frame->setVars($vars); } @@ -147,7 +159,7 @@ public function process(Event $event) $event = $event->withException($this->sanitizeException($exception)); } - if (!empty($stacktrace)) { + if ($stacktrace) { $event = $event->withStacktrace($this->sanitizeStacktrace($stacktrace)); } @@ -156,8 +168,7 @@ public function process(Event $event) } if (!empty($extraContext)) { - array_walk_recursive($extraContext, [$this, 'sanitize']); - + $this->sanitize($extraContext); $event = $event->withExtraContext($extraContext); } diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index eb6dbfee9..d29c26820 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -72,10 +72,9 @@ public function testProcess($inputData, $expectedData) // We must convert the backtrace to a Stacktrace instance here because // PHPUnit executes the data provider before the setUp method and so // the client instance cannot be accessed from there - $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), $event->getException()); + $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), + $event->getException()); } - - $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); } public function processDataProvider() @@ -193,6 +192,42 @@ public function processDataProvider() ], ], ], + [ + [ + 'extra_context' => [ + 'foobar' => 'some-data', + 'authorization' => [ + 'foo' => 'secret1', + 'bar' => 'secret2', + 'baz' => [ + 'nested1' => 'nestedSecret1', + 'nested2' => 'nestedSecret2', + 'nested3' => [ + 'deep' => 'nestedSecret2', + ], + ], + ], + 'foobaz' => 'some-data', + ], + ], + [ + 'extra_context' => [ + 'foobar' => 'some-data', + 'authorization' => [ + 'foo' => SanitizeDataProcessor::STRING_MASK, + 'bar' => SanitizeDataProcessor::STRING_MASK, + 'baz' => [ + 'nested1' => SanitizeDataProcessor::STRING_MASK, + 'nested2' => SanitizeDataProcessor::STRING_MASK, + 'nested3' => [ + 'deep' => SanitizeDataProcessor::STRING_MASK, + ], + ], + ], + 'foobaz' => 'some-data', + ], + ], + ], ]; } From a39bb0278e5e8e7cae993dfb0e9f4998f066caa7 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 26 Jul 2018 22:04:36 +0200 Subject: [PATCH 0352/1161] Add the quickstart documentation (#633) --- docs/quickstart.rst | 123 ++++++++++++++ docs/usage.rst | 311 ---------------------------------- lib/Raven/Client.php | 4 +- lib/Raven/ClientInterface.php | 6 +- tests/ClientTest.php | 21 ++- 5 files changed, 142 insertions(+), 323 deletions(-) create mode 100644 docs/quickstart.rst delete mode 100644 docs/usage.rst diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 000000000..79aaa957d --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,123 @@ +Quickstart +########## + +Using Sentry with PHP is straightforward. After installation of the library you +can directly interface with the client and start submitting data. + +Creating a Client +================= + +The most important part is the creation of the client instance. To ease the +instantiation of the object a builder class is provided which permits an easy +configuration of the options and the features of the client. Using it is the +recommended approach as it hides the complexity of directly using the constructor +of the ``Client`` class which needs two arguments: + +``$config`` + (``Configuration``) Storage for the client configuration. While the DSN is + immutable after it has been set, almost all other options can be changed at + any time while the client is already in use. + +``$transport`` + (``TransportInterface``) Class that is responsible for sending the events + over the wire. + +When using the client builder both these arguments are filled when getting the +instance of the client. The configuration options can be set by calling on the +client builder instance the same methods available in the ``Configuration`` +class. The transport, the middlewares and the processors can either be managed +before the client instance is initialized. + +.. code-block:: php + + use Raven\ClientBuilder; + + $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1'])->getClient(); + +Sending errors +============== + +The client provides some methods to send both the last thrown error or a catched +exception: + +.. code-block:: php + + $client->captureLastError(); + $client->captureException(new \Exception('foo')); + +Sending messages +================ + +You may want to report messages instead of errors. To do it you can use the +``captureMessage`` method, which accept a string representing the message and +an optional list of parameters that will be substituted in it by the ``vsprintf`` +function before sending the event. + +.. code-block:: php + + // Both lines will report the same message + $client->captureMessage('foo bar'); + $client->captureMessage('foo %s', ['bar']); + +Sending other data +================== + +Sometimes you want to report an event in which you want to fill data yourself. +This is where the generic ``capture`` method comes in: it takes a payload of +data and creates an event from it. + +.. code-block:: php + + $client->capture(['level' => 'debug', 'message' => 'foo bar']); + +Sending additional data +======================= + +You may want to send additional data with an event that has been captured by +one of the ``capture*`` methods along the lines of what you can do in the +previous doc section. A payload of data can always be specified as last argument +of the methods discussed above and according to the key you pass you can set +additional data or override some preset information. + +.. code-block:: php + + // The error level of the captured error will be overwritten + $client->captureLastError(['level' => 'warning']); + + // An additional parametrized message will be sent with the captured exception + $client->captureException(new \RuntimeException('foo bar'), ['message' => 'bar %s', 'message_params' => ['baz']]); + + // The logger will be overwritten + $client->captureMessage('foo', [], ['logger' => 'custom']); + +Getting back an event ID +======================== + +An event ID is a UUID4 globally uniqued ID that is generated for the event and +that you can use to find the exact event from within Sentry. It is returned from +any ``capture*`` method. There is also a function called ``getLastEvent`` which +you can use to retrieve the lsat captured event and from there get its own ID. + +.. code-block:: php + + // Both the following lines will return the same ID, but it's recommended to always get it from the capture method + $eventId = $client->captureLastError(); + $eventId = $client->getLastEvent()->getId(); + +Capturing breadcrumbs manually +============================== + +Even though breadcrumbs can be captured automatically when an error or exception +occurs, you may want to report them manually too. The client gives access to some +methods to report and clear the breadcrumbs. + +.. code-block:: php + + use Raven\Breadcrumbs\Breadcrumb; + + $client->leaveBreadcrumb(Breadcrumb::create('debug', 'error', 'error_reporting', 'foo bar')); + $client->clearBreadcrumbs(); + +The default implementation of the breadcrumbs recorder is a circular buffer, so +when you reach out the maximum number of items that it can store at the same time +(100 by default) the oldest items will be replaced with the newest ones. diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index 943f970a8..000000000 --- a/docs/usage.rst +++ /dev/null @@ -1,311 +0,0 @@ -Usage -===== - -Using Sentry with PHP is straightforward. After installation of the library -you can directly interface with the client and start submitting data. - -Basics ------- - -The most important part is the creation of the raven client. Create it -once and reference it from anywhere you want to interface with Sentry: - -.. code-block:: php - - $sentryClient = new \Raven\Client('___DSN___'); - - -Capturing Errors ----------------- - -Sentry includes basic functionality for reporting any uncaught -exceptions or PHP errors. This is done via the error handler, -and appropriate hooks for each of PHP's built-in reporting: - -.. code-block:: php - - $error_handler = new \Raven\ErrorHandler($sentryClient); - $error_handler->registerExceptionHandler(); - $error_handler->registerErrorHandler(); - $error_handler->registerShutdownFunction(); - -.. note:: Calling ``install()`` on a \Raven\Client instance will automatically - register these handlers. - - -Reporting Exceptions --------------------- - -If you want to report exceptions manually you can use the -`captureException` function. - -.. code-block:: php - - // Basic Reporting - $sentryClient->captureException($ex); - - // Provide some additional data with an exception - $sentryClient->captureException($ex, array( - 'extra' => array( - 'php_version' => phpversion() - ), - )); - - -Reporting Other Errors ----------------------- - -Sometimes you don't have an actual exception object, but something bad happened and you -want to report it anyways. This is where `captureMessage` comes in. It -takes a message and reports it to sentry. - -.. code-block:: php - - // Capture a message - $sentryClient->captureMessage('my log message'); - -Note, ``captureMessage`` has a slightly different API than ``captureException`` to support -parameterized formatting: - -.. code-block:: php - - $sentryClient->captureMessage('my %s message', array('log'), array( - 'extra' => array( - 'foo' => 'bar', - ), - )); - - -Optional Attributes -------------------- - -With calls to ``captureException`` or ``captureMessage`` additional data -can be supplied: - -.. code-block:: php - - $sentryClient->captureException($ex, array( - 'attr' => 'value', - )); - - -.. describe:: extra - -Additional context for this event. Must be a mapping. Children can be any native JSON type. - -.. code-block:: php - - array( - 'extra' => array('key' => 'value') - ) - -.. describe:: fingerprint - -The fingerprint for grouping this event. - -.. code-block:: php - - array( - 'fingerprint' => ['{{ default }}', 'other value'] - ) - -.. describe:: level - -The level of the event. Defaults to ``error``. - -.. code-block:: php - - array( - 'level' => 'warning' - ) - -Sentry is aware of the following levels: - -* debug (the least serious) -* info -* warning -* error -* fatal (the most serious) - -.. describe:: logger - -The logger name for the event. - -.. code-block:: php - - array( - 'logger' => 'default' - ) - -.. describe:: tags - -Tags to index with this event. Must be a mapping of strings. - -.. code-block:: php - - array( - 'tags' => array('key' => 'value') - ) - -.. describe:: user - -The acting user. - -.. code-block:: php - - array( - 'user' => array( - 'id' => 42, - 'email' => 'clever-girl' - ) - ) - -Getting Back an Event ID ------------------------- - -An event id is a globally unique id for the event that was just sent. This -event id can be used to find the exact event from within Sentry. - -This is often used to display for the user and report an error to customer -service. - -.. code-block:: php - - $sentryClient->getLastEventID(); - -.. _php-user-feedback: - -User Feedback -------------- - -To enable user feedback for crash reports you will need to create an error handler -which is aware of the last event ID. - -.. sourcecode:: php - - captureException($exc); - - return $this->render('500.html', array( - 'sentry_event_id' => $event_id, - ), 500); - } - } - -Then in your template you can load up the feedback widget: - -.. sourcecode:: html+django - - - - - {% if sentry_event_id %} - - {% endif %} - -That's it! - -For more details on this feature, see the :doc:`User Feedback guide <../../../learn/user-feedback>`. - - -Handling Failures ------------------ - -The SDK attempts to minimize failures, and when they happen will always try to avoid bubbling them up -to your application. If you do want to know when an event fails to record, you can use the ``getLastError`` -helper: - -.. code-block:: php - - if ($sentryClient->getLastError() !== null) { - echo "Something went very, very wrong"; - // $sentryClient->getLastError() contains the error that occurred - } else { - // Give the user feedback - echo "Sorry, there was an error!"; - echo "Your reference ID is " . $event_id; - } - - -Breadcrumbs ------------ - -Sentry supports capturing breadcrumbs -- events that happened prior to an issue. - -.. code-block:: php - - $sentryClient->breadcrumbs->record(array( - 'message' => 'Authenticating user as ' . $username, - 'category' => 'auth', - 'level' => 'info', - )); - - -Filtering Out Errors --------------------- - -Its common that you might want to prevent automatic capture of certain areas. Ideally you simply would avoid calling out to Sentry in that case, but that's often easier said than done. Instead, you can provide a function which the SDK will call before it sends any data, allowing you both to mutate that data, as well as prevent it from being sent to the server. - -.. code-block:: php - - $sentryClient->setSendCallback(function($data) { - $ignore_types = array('Symfony\Component\HttpKernel\Exception\NotFoundHttpException'); - - if (isset($data['exception']) && in_array($data['exception']['values'][0]['type'], $ignore_types)) - { - return false; - } - }); - - -Error Control Operators ------------------------ - -In PHP its fairly common to use the `suppression operator `_ -to avoid bubbling up handled errors: - -.. code-block:: php - - $my_file = @file('non_existent_file'); - -In these situations, Sentry will never capture the error. If you wish to capture it at that stage -you'd need to manually call out to the PHP client: - -.. code-block:: php - - $my_file = @file('non_existent_file'); - if (!$my_file) { - // ... - $sentryClient->captureLastError(); - } - - -Testing Your Connection ------------------------ - -The PHP client includes a simple helper script to test your connection and -credentials with the Sentry master server:: - - $ bin/sentry test ___DSN___ - Client configuration: - -> server: [___API_URL___] - -> project: ___PROJECT_ID___ - -> public_key: ___PUBLIC_KEY___ - -> secret_key: ___SECRET_KEY___ - - Sending a test event: - -> event ID: f1765c9aed4f4ceebe5a93df9eb2d34f - - Done! diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 88c2b69e8..ada1ed272 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -310,7 +310,7 @@ public function captureException($exception, array $payload = []) /** * {@inheritdoc} */ - public function captureLastError() + public function captureLastError(array $payload = []) { $error = error_get_last(); @@ -320,7 +320,7 @@ public function captureLastError() $exception = new \ErrorException(@$error['message'], 0, @$error['type'], @$error['file'], @$error['line']); - return $this->captureException($exception); + return $this->captureException($exception, $payload); } /** diff --git a/lib/Raven/ClientInterface.php b/lib/Raven/ClientInterface.php index b011c48f1..61d5d7885 100644 --- a/lib/Raven/ClientInterface.php +++ b/lib/Raven/ClientInterface.php @@ -109,9 +109,11 @@ public function captureException($exception, array $payload = []); /** * Logs the most recent error (obtained with {@link error_get_last}). * + * @param array $payload Additional attributes to pass with this event + * * @return string|null */ - public function captureLastError(); + public function captureLastError(array $payload = []); /** * Gets the last event that was captured by the client. However, it could @@ -124,6 +126,8 @@ public function getLastEvent(); /** * Return the last captured event's ID or null if none available. * + * @return string|null + * * @deprecated since version 2.0, to be removed in 3.0. Use getLastEvent() instead. */ public function getLastEventId(); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 95479a2ce..b43309512 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -186,18 +186,21 @@ public function testCaptureLastError() $client->expects($this->once()) ->method('captureException') - ->with($this->logicalAnd( - $this->isInstanceOf(\ErrorException::class), - $this->attributeEqualTo('message', 'foo'), - $this->attributeEqualTo('code', 0), - $this->attributeEqualTo('severity', E_USER_NOTICE), - $this->attributeEqualTo('file', __FILE__), - $this->attributeEqualTo('line', __LINE__ + 3) - )); + ->with( + $this->logicalAnd( + $this->isInstanceOf(\ErrorException::class), + $this->attributeEqualTo('message', 'foo'), + $this->attributeEqualTo('code', 0), + $this->attributeEqualTo('severity', E_USER_NOTICE), + $this->attributeEqualTo('file', __FILE__), + $this->attributeEqualTo('line', __LINE__ + 5) + ), + ['foo' => 'bar'] + ); @trigger_error('foo', E_USER_NOTICE); - $client->captureLastError(); + $client->captureLastError(['foo' => 'bar']); $this->clearLastError(); } From 19a735a56b079712800acb62c07a192df1b63922 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 3 May 2018 14:09:05 +0200 Subject: [PATCH 0353/1161] Prepare master for next release cycle (cherry picked from commit 43e7cb9080b420db6e0220dc3d2c8fa9ae013658) --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ebaba4866..a3628abc1 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Tagging a Release 2. Create a new branch for the minor version (if not present): ``` -$ git checkout -b releases/1.9.x +$ git checkout -b releases/1.10.x ``` 3. Update the hardcoded version tag in ``Client.php``: @@ -134,20 +134,20 @@ $ git checkout -b releases/1.9.x ```php class Raven_Client { - const VERSION = '1.9.0'; + const VERSION = '1.10.0'; } ``` 4. Commit the change: ``` -$ git commit -a -m "1.9.0" +$ git commit -a -m "1.10.0" ``` 5. Tag the branch: ``` -git tag 1.9.0 +git tag 1.10.0 ``` 6. Push the tag: @@ -165,7 +165,7 @@ git checkout master 8. Add the next minor release to the ``CHANGES`` file: ``` -## 1.10.0 (unreleased) +## 1.11.0 (unreleased) ``` 9. Update the version in ``Client.php``: @@ -173,7 +173,7 @@ git checkout master ```php class Raven_Client { - const VERSION = '1.10.x-dev'; + const VERSION = '1.11.x-dev'; } ``` @@ -182,7 +182,7 @@ class Raven_Client ```json "extra": { "branch-alias": { - "dev-master": "1.10.x-dev" + "dev-master": "1.11.x-dev" } } ``` From f5a046163ebe217cb5c4c7a1a85646fe86c9068f Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 3 Aug 2018 15:36:14 +0200 Subject: [PATCH 0354/1161] Update release instructions to 2.x --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a3628abc1..93ef9b398 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Tagging a Release 2. Create a new branch for the minor version (if not present): ``` -$ git checkout -b releases/1.10.x +$ git checkout -b releases/2.1.x ``` 3. Update the hardcoded version tag in ``Client.php``: @@ -134,20 +134,20 @@ $ git checkout -b releases/1.10.x ```php class Raven_Client { - const VERSION = '1.10.0'; + const VERSION = '2.1.0'; } ``` 4. Commit the change: ``` -$ git commit -a -m "1.10.0" +$ git commit -a -m "2.1.0" ``` 5. Tag the branch: ``` -git tag 1.10.0 +git tag 2.1.0 ``` 6. Push the tag: From ac6ccb84a18d254c1fbd7996f786c01045a47eaf Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 10 May 2018 16:17:21 +0200 Subject: [PATCH 0355/1161] Update the DSN environment variable name (cherry picked from commit c2fbfe175f45d6199b165b61984dcc7bb6c58d2a) --- docs/integrations/laravel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 60b3ba167..4fb1d1b67 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -51,7 +51,7 @@ Add your DSN to ``.env``: .. code-block:: bash - SENTRY_DSN=___DSN___ + SENTRY_LARAVEL_DSN=___DSN___ Finally, if you wish to wire up User Feedback, you can do so by creating a custom error view in `resources/views/errors/500.blade.php`. From b4a1aede152101e0f4d4ec4e935ad536c55469a7 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 10 May 2018 16:20:42 +0200 Subject: [PATCH 0356/1161] Add a note on Laravel 4.x support (cherry picked from commit cc26653d16876d704a6b7aa633a76c06b432098e) --- docs/integrations/laravel.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 4fb1d1b67..2ef6495f4 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -124,9 +124,11 @@ Laravel 4.x Install the ``sentry/sentry-laravel`` package: +Laravel 4.x is supported until version 0.8.x. + .. code-block:: bash - $ composer require sentry/sentry-laravel + $ composer require "sentry/sentry-laravel:0.8.*" Add the Sentry service provider and facade in ``config/app.php``: From 643758b5c3916037bd21a5e98cc56353551b0e6e Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 21 Jun 2018 14:04:41 +0200 Subject: [PATCH 0357/1161] Switch from ___DSN___ to ___PUBLIC_DSN___ (cherry picked from commit 64f3bf0da7d1635ac6398d301e2156c00049c211) --- docs/index.rst | 2 +- docs/integrations/laravel.rst | 8 ++++---- docs/integrations/monolog.rst | 4 ++-- docs/integrations/symfony2.rst | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index cac1a0ff0..80642228a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,7 +29,7 @@ once and reference it from anywhere you want to interface with Sentry: .. code-block:: php - $client = new \Raven\Client('___DSN___'); + $client = new \Raven\Client('___PUBLIC_DSN___'); Once you have the client you can either use it manually or enable the automatic error and exception capturing which is recomended: diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst index 2ef6495f4..d750ab763 100644 --- a/docs/integrations/laravel.rst +++ b/docs/integrations/laravel.rst @@ -51,7 +51,7 @@ Add your DSN to ``.env``: .. code-block:: bash - SENTRY_LARAVEL_DSN=___DSN___ + SENTRY_LARAVEL_DSN=___PUBLIC_DSN___ Finally, if you wish to wire up User Feedback, you can do so by creating a custom error view in `resources/views/errors/500.blade.php`. @@ -157,7 +157,7 @@ Add your DSN to ``config/sentry.php``: '___DSN___', + 'dsn' => '___PUBLIC_DSN___', // ... ); @@ -208,7 +208,7 @@ Create the Sentry configuration file (``config/sentry.php``): '___DSN___', + 'dsn' => '___PUBLIC_DSN___', // capture release as git sha // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), @@ -285,7 +285,7 @@ The following settings are available for the client: .. code-block:: php - 'dsn' => '___DSN___', + 'dsn' => '___PUBLIC_DSN___', .. describe:: release diff --git a/docs/integrations/monolog.rst b/docs/integrations/monolog.rst index 0bb907a99..97ce9dfe8 100644 --- a/docs/integrations/monolog.rst +++ b/docs/integrations/monolog.rst @@ -8,7 +8,7 @@ Monolog supports Sentry out of the box, so you'll just need to configure a handl .. sourcecode:: php - $client = new \Raven\Client('___DSN___'); + $client = new \Raven\Client('___PUBLIC_DSN___'); $handler = new Monolog\Handler\RavenHandler($client); $handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n")); @@ -48,7 +48,7 @@ Sentry provides a breadcrumb handler to automatically send logs along as crumbs: .. sourcecode:: php - $client = new \Raven\Client('___DSN___'); + $client = new \Raven\Client('___PUBLIC_DSN___'); $handler = new \Raven\Breadcrumbs\MonologHandler($client); $monolog->pushHandler($handler); diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst index b11916445..1f18eb13f 100644 --- a/docs/integrations/symfony2.rst +++ b/docs/integrations/symfony2.rst @@ -39,4 +39,4 @@ Add your DSN to ``app/config/config.yml``: .. code-block:: yaml sentry: - dsn: "___DSN___" + dsn: "___PUBLIC_DSN___" From 3faa0085569a577540e40c8fabca5cd5f808835e Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 21 Jun 2018 14:06:24 +0200 Subject: [PATCH 0358/1161] Remove secret part from example DSN (cherry picked from commit de1b6ee9dd896d4504af5b7ea4576e6a6b4ac72b) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93ef9b398..2b38c0614 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ use Raven\ClientBuilder; require 'vendor/autoload.php'; // Instantiate the SDK with your DSN -$client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1'])->getClient(); +$client = ClientBuilder::create(['server' => 'http://public@example.com/1'])->getClient(); // Capture an exception $eventId = $client->captureException(new \RuntimeException('Hello World!')); From 272a6aad7446ef5f50478ec382db07c1c7557cf9 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 19 Jun 2018 09:46:04 +0200 Subject: [PATCH 0359/1161] Update changelog for version 1.9.1 (cherry picked from commit a124d5d807ddd28e2f3270bcd61841e25612a947) --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30dcb6105..8323963e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ - ... +## 1.9.1 (2018-06-19) + +- Allow the use of a public DSN (private part of the DSN was deprecated in Sentry 9) (#615) +- Send transaction as transaction not as culprit (#601) + ## 1.9.0 (2018-05-03) - Fixed undefined variable (#588) From 35c62825fc6ea12c152dc51cffa997a91b7ae62e Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 3 Aug 2018 19:57:46 +0200 Subject: [PATCH 0360/1161] Emit transactions, not culprit (#638) --- lib/Raven/Client.php | 6 +++--- lib/Raven/Event.php | 18 +++++++++--------- tests/ClientTest.php | 4 ++-- tests/EventTest.php | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index ada1ed272..eb1d0b7e2 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -352,10 +352,10 @@ public function capture(array $payload) { $event = new Event($this->config); - if (isset($payload['culprit'])) { - $event = $event->withCulprit($payload['culprit']); + if (isset($payload['transaction'])) { + $event = $event->withTransaction($payload['transaction']); } else { - $event = $event->withCulprit($this->transactionStack->peek()); + $event = $event->withTransaction($this->transactionStack->peek()); } if (isset($payload['logger'])) { diff --git a/lib/Raven/Event.php b/lib/Raven/Event.php index 531dd4708..fe28404b0 100644 --- a/lib/Raven/Event.php +++ b/lib/Raven/Event.php @@ -45,7 +45,7 @@ final class Event implements \JsonSerializable /** * @var string the name of the transaction (or culprit) which caused this exception */ - private $culprit; + private $transaction; /** * @var string The name of the server (e.g. the host name) @@ -238,27 +238,27 @@ public function withLogger($logger) * * @return string */ - public function getCulprit() + public function getTransaction() { - return $this->culprit; + return $this->transaction; } /** * Sets the name of the transaction (or culprit) which caused this * exception. * - * @param string $culprit The transaction name + * @param string $transaction The transaction name * * @return static */ - public function withCulprit($culprit) + public function withTransaction($transaction) { - if ($culprit === $this->culprit) { + if ($transaction === $this->transaction) { return $this; } $new = clone $this; - $new->culprit = $culprit; + $new->transaction = $transaction; return $new; } @@ -735,8 +735,8 @@ public function toArray() $data['logger'] = $this->logger; } - if (null !== $this->culprit) { - $data['culprit'] = $this->culprit; + if (null !== $this->transaction) { + $data['transaction'] = $this->transaction; } if (null !== $this->serverName) { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index b43309512..36d045023 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -233,7 +233,7 @@ public function testCapture() ->getClient(); $inputData = [ - 'culprit' => 'foo bar', + 'transaction' => 'foo bar', 'level' => Client::LEVEL_DEBUG, 'logger' => 'foo', 'tags_context' => ['foo', 'bar'], @@ -246,7 +246,7 @@ public function testCapture() $event = $client->getLastEvent(); $this->assertEquals(str_replace('-', '', $event->getId()->toString()), $eventId); - $this->assertEquals($inputData['culprit'], $event->getCulprit()); + $this->assertEquals($inputData['transaction'], $event->getTransaction()); $this->assertEquals($inputData['level'], $event->getLevel()); $this->assertEquals($inputData['logger'], $event->getLogger()); $this->assertEquals($inputData['tags_context'], $event->getTagsContext()); diff --git a/tests/EventTest.php b/tests/EventTest.php index fbaf6751c..1fb4f75b0 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -181,7 +181,7 @@ public function gettersAndSettersDataProvider() return [ ['level', 'info', ['level' => 'info']], ['logger', 'ruby', ['logger' => 'ruby']], - ['culprit', 'foo', ['culprit' => 'foo']], + ['transaction', 'foo', ['transaction' => 'foo']], ['serverName', 'local.host', ['server_name' => 'local.host']], ['release', '0.0.1', ['release' => '0.0.1']], ['modules', ['foo' => '0.0.1', 'bar' => '0.0.2'], ['modules' => ['foo' => '0.0.1', 'bar' => '0.0.2']]], From 272af676a3c073e7073562cb44466d372e34cbbb Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 7 Aug 2018 20:26:36 +0200 Subject: [PATCH 0361/1161] Support longer creditcard numbers in the SanitizeDataProcessor processor (#640) --- lib/Raven/Processor/SanitizeDataProcessor.php | 2 +- tests/Processor/SanitizeDataProcessorTest.php | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php index 60f2705c1..21d20e86f 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -54,7 +54,7 @@ private function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'fields_re' => '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', - 'values_re' => '/^(?:\d[ -]*?){13,16}$/', + 'values_re' => '/^(?:\d[ -]*?){13,19}$/', 'session_cookie_name' => ini_get('session.name'), ]); diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index d29c26820..bc945385c 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -125,7 +125,19 @@ public function processDataProvider() [ [ 'extra_context' => [ - 'ccnumba' => '4242424242424242', + 'ccnumba' => str_repeat('9', 13), + ], + ], + [ + 'extra_context' => [ + 'ccnumba' => SanitizeDataProcessor::STRING_MASK, + ], + ], + ], + [ + [ + 'extra_context' => [ + 'ccnumba' => str_repeat('9', 19), ], ], [ From 50baa8963944395065426fe914e9f2b75db98a5e Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 28 Aug 2018 10:45:34 +0200 Subject: [PATCH 0362/1161] Fix the build broken by updated CS rules (#649) --- lib/Raven/AbstractErrorHandler.php | 10 +++---- lib/Raven/Breadcrumbs/Breadcrumb.php | 4 +-- lib/Raven/Breadcrumbs/MonologHandler.php | 2 +- lib/Raven/Breadcrumbs/Recorder.php | 2 +- lib/Raven/Client.php | 2 +- lib/Raven/ClientBuilder.php | 2 +- lib/Raven/Configuration.php | 4 +-- lib/Raven/Context/TagsContext.php | 8 ++--- .../ExceptionInterfaceMiddleware.php | 2 +- .../Processor/RemoveHttpBodyProcessor.php | 2 +- .../Processor/SanitizeCookiesProcessor.php | 2 +- lib/Raven/Processor/SanitizeDataProcessor.php | 8 ++--- .../SanitizeHttpHeadersProcessor.php | 2 +- lib/Raven/ReprSerializer.php | 14 ++++----- lib/Raven/Serializer.php | 30 +++++++++---------- lib/Raven/Stacktrace.php | 16 +++++----- lib/Raven/TransactionStack.php | 6 ++-- tests/AbstractSerializerTest.php | 14 ++++----- tests/ClientTest.php | 8 ++--- tests/EventTest.php | 8 ++--- .../ExceptionInterfaceMiddlewareTest.php | 2 +- tests/Util/JSONTest.php | 2 +- 22 files changed, 75 insertions(+), 75 deletions(-) diff --git a/lib/Raven/AbstractErrorHandler.php b/lib/Raven/AbstractErrorHandler.php index f18ce732e..97eb57b46 100644 --- a/lib/Raven/AbstractErrorHandler.php +++ b/lib/Raven/AbstractErrorHandler.php @@ -85,7 +85,7 @@ abstract class AbstractErrorHandler */ protected function __construct(ClientInterface $client, $reservedMemorySize = 10240) { - if (!is_int($reservedMemorySize) || $reservedMemorySize <= 0) { + if (!\is_int($reservedMemorySize) || $reservedMemorySize <= 0) { throw new \UnexpectedValueException('The value of the $reservedMemorySize argument must be an integer greater than 0.'); } @@ -175,7 +175,7 @@ public function handleError($level, $message, $file, $line) } if (null !== $this->previousErrorHandler) { - return call_user_func($this->previousErrorHandler, $level, $message, $file, $line); + return \call_user_func($this->previousErrorHandler, $level, $message, $file, $line); } return $shouldReportError; @@ -274,7 +274,7 @@ protected function reRegister($previousThrownErrors) } $handler = set_error_handler('var_dump'); - $handler = is_array($handler) ? $handler[0] : null; + $handler = \is_array($handler) ? $handler[0] : null; restore_error_handler(); @@ -304,9 +304,9 @@ protected function cleanBacktraceFromErrorHandlerFrames($backtrace, $file, $line $cleanedBacktrace = $backtrace; $index = 0; - while ($index < count($backtrace)) { + while ($index < \count($backtrace)) { if (isset($backtrace[$index]['file'], $backtrace[$index]['line']) && $backtrace[$index]['line'] === $line && $backtrace[$index]['file'] === $file) { - $cleanedBacktrace = array_slice($cleanedBacktrace, 1 + $index); + $cleanedBacktrace = \array_slice($cleanedBacktrace, 1 + $index); break; } diff --git a/lib/Raven/Breadcrumbs/Breadcrumb.php b/lib/Raven/Breadcrumbs/Breadcrumb.php index 67eff3b40..5939c932a 100644 --- a/lib/Raven/Breadcrumbs/Breadcrumb.php +++ b/lib/Raven/Breadcrumbs/Breadcrumb.php @@ -82,7 +82,7 @@ final class Breadcrumb implements \JsonSerializable */ public function __construct($level, $type, $category, $message = null, array $metadata = []) { - if (!in_array($level, self::getLevels(), true)) { + if (!\in_array($level, self::getLevels(), true)) { throw new InvalidArgumentException('The value of the $level argument must be one of the Raven\Client::LEVEL_* constants.'); } @@ -158,7 +158,7 @@ public function getLevel() */ public function withLevel($level) { - if (!in_array($level, self::getLevels(), true)) { + if (!\in_array($level, self::getLevels(), true)) { throw new InvalidArgumentException('The value of the $level argument must be one of the Raven\Client::LEVEL_* constants.'); } diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/lib/Raven/Breadcrumbs/MonologHandler.php index c73f89655..2b74c8102 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/lib/Raven/Breadcrumbs/MonologHandler.php @@ -80,7 +80,7 @@ protected function write(array $record) /** @noinspection PhpUndefinedMethodInspection */ $breadcrumb = new Breadcrumb($this->logLevels[$record['level']], Breadcrumb::TYPE_ERROR, $record['channel'], null, [ - 'type' => get_class($exc), + 'type' => \get_class($exc), 'value' => $exc->getMessage(), ]); diff --git a/lib/Raven/Breadcrumbs/Recorder.php b/lib/Raven/Breadcrumbs/Recorder.php index 81caa5e3a..65b7fef97 100644 --- a/lib/Raven/Breadcrumbs/Recorder.php +++ b/lib/Raven/Breadcrumbs/Recorder.php @@ -58,7 +58,7 @@ final class Recorder implements \Countable, \Iterator */ public function __construct($maxSize = self::MAX_ITEMS) { - if (!is_int($maxSize) || $maxSize < 1) { + if (!\is_int($maxSize) || $maxSize < 1) { throw new InvalidArgumentException(sprintf('The $maxSize argument must be an integer greater than 0.')); } diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index eb1d0b7e2..177ed6e0d 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -398,7 +398,7 @@ public function send(Event $event) */ public function translateSeverity($severity) { - if (is_array($this->severityMap) && isset($this->severityMap[$severity])) { + if (\is_array($this->severityMap) && isset($this->severityMap[$severity])) { return $this->severityMap[$severity]; } diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index fb8ae523b..f8c4b6119 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -305,7 +305,7 @@ public function __call($name, $arguments) throw new \BadMethodCallException(sprintf('The method named "%s" does not exists.', $name)); } - call_user_func_array([$this->configuration, $name], $arguments); + \call_user_func_array([$this->configuration, $name], $arguments); return $this; } diff --git a/lib/Raven/Configuration.php b/lib/Raven/Configuration.php index 019d5aeee..c1f25b3c7 100644 --- a/lib/Raven/Configuration.php +++ b/lib/Raven/Configuration.php @@ -518,7 +518,7 @@ public function shouldCapture(Event $event = null) { $result = true; - if (!empty($this->options['environments']) && !in_array($this->options['current_environment'], $this->options['environments'])) { + if (!empty($this->options['environments']) && !\in_array($this->options['current_environment'], $this->options['environments'])) { $result = false; } @@ -669,7 +669,7 @@ private function configureOptions(OptionsResolver $resolver) return false; } - if (!in_array(strtolower($parsed['scheme']), ['http', 'https'])) { + if (!\in_array(strtolower($parsed['scheme']), ['http', 'https'])) { return false; } diff --git a/lib/Raven/Context/TagsContext.php b/lib/Raven/Context/TagsContext.php index a0ffcbf7c..547aff2c7 100644 --- a/lib/Raven/Context/TagsContext.php +++ b/lib/Raven/Context/TagsContext.php @@ -29,7 +29,7 @@ public function merge(array $data, $recursive = false) } foreach ($data as $value) { - if (!is_string($value)) { + if (!\is_string($value)) { throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); } } @@ -43,7 +43,7 @@ public function merge(array $data, $recursive = false) public function setData(array $data) { foreach ($data as $value) { - if (!is_string($value)) { + if (!\is_string($value)) { throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); } } @@ -57,7 +57,7 @@ public function setData(array $data) public function replaceData(array $data) { foreach ($data as $value) { - if (!is_string($value)) { + if (!\is_string($value)) { throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); } } @@ -70,7 +70,7 @@ public function replaceData(array $data) */ public function offsetSet($offset, $value) { - if (!is_string($value)) { + if (!\is_string($value)) { throw new \InvalidArgumentException('The $value argument must be a string.'); } diff --git a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php index 79bb747c0..4921f8f4d 100644 --- a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php @@ -67,7 +67,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r } $data = [ - 'type' => get_class($currentException), + 'type' => \get_class($currentException), 'value' => $this->client->getSerializer()->serialize($currentException->getMessage()), ]; diff --git a/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/lib/Raven/Processor/RemoveHttpBodyProcessor.php index 9ec86b316..7108f4e13 100644 --- a/lib/Raven/Processor/RemoveHttpBodyProcessor.php +++ b/lib/Raven/Processor/RemoveHttpBodyProcessor.php @@ -29,7 +29,7 @@ public function process(Event $event) { $request = $event->getRequest(); - if (isset($request['method']) && in_array(strtoupper($request['method']), ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { + if (isset($request['method']) && \in_array(strtoupper($request['method']), ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { $request['data'] = self::STRING_MASK; } diff --git a/lib/Raven/Processor/SanitizeCookiesProcessor.php b/lib/Raven/Processor/SanitizeCookiesProcessor.php index fd7666cd8..cfe179d3c 100644 --- a/lib/Raven/Processor/SanitizeCookiesProcessor.php +++ b/lib/Raven/Processor/SanitizeCookiesProcessor.php @@ -65,7 +65,7 @@ public function process(Event $event) } foreach ($request['cookies'] as $name => $value) { - if (!in_array($name, $cookiesToSanitize)) { + if (!\in_array($name, $cookiesToSanitize)) { continue; } diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php index 21d20e86f..d2b0e9596 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -72,7 +72,7 @@ public function sanitize(&$data) { foreach ($data as $key => &$item) { if (preg_match($this->options['fields_re'], $key)) { - if (is_array($item)) { + if (\is_array($item)) { array_walk_recursive($item, function (&$value) { $value = self::STRING_MASK; }); @@ -83,7 +83,7 @@ public function sanitize(&$data) $item = self::STRING_MASK; } - if (is_array($item)) { + if (\is_array($item)) { $this->sanitize($item); break; @@ -110,14 +110,14 @@ public function sanitizeException(&$data) public function sanitizeHttp(&$data) { - if (!empty($data['cookies']) && is_array($data['cookies'])) { + if (!empty($data['cookies']) && \is_array($data['cookies'])) { $cookies = &$data['cookies']; if (!empty($cookies[$this->options['session_cookie_name']])) { $cookies[$this->options['session_cookie_name']] = self::STRING_MASK; } } - if (!empty($data['data']) && is_array($data['data'])) { + if (!empty($data['data']) && \is_array($data['data'])) { $this->sanitize($data['data']); } diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php index dde9cd8b3..aec8f06c7 100644 --- a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php +++ b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -53,7 +53,7 @@ public function process(Event $event) } foreach ($request['headers'] as $header => &$value) { - if (in_array($header, $this->options['sanitize_http_headers'], true)) { + if (\in_array($header, $this->options['sanitize_http_headers'], true)) { $value = self::STRING_MASK; } } diff --git a/lib/Raven/ReprSerializer.php b/lib/Raven/ReprSerializer.php index aa68a0ca5..b8a5beb05 100644 --- a/lib/Raven/ReprSerializer.php +++ b/lib/Raven/ReprSerializer.php @@ -25,16 +25,16 @@ protected function serializeValue($value) return 'false'; } elseif (true === $value) { return 'true'; - } elseif (is_float($value) && (int) $value == $value) { + } elseif (\is_float($value) && (int) $value == $value) { return $value . '.0'; - } elseif (is_int($value) || is_float($value)) { + } elseif (\is_int($value) || \is_float($value)) { return (string) $value; - } elseif (is_object($value) || 'object' == gettype($value)) { - return 'Object ' . get_class($value); - } elseif (is_resource($value)) { + } elseif (\is_object($value) || 'object' == \gettype($value)) { + return 'Object ' . \get_class($value); + } elseif (\is_resource($value)) { return 'Resource ' . get_resource_type($value); - } elseif (is_array($value)) { - return 'Array of length ' . count($value); + } elseif (\is_array($value)) { + return 'Array of length ' . \count($value); } else { return $this->serializeString($value); } diff --git a/lib/Raven/Serializer.php b/lib/Raven/Serializer.php index 91c25fa34..2be098650 100644 --- a/lib/Raven/Serializer.php +++ b/lib/Raven/Serializer.php @@ -83,7 +83,7 @@ public function __construct($mb_detect_order = null, $messageLimit = Client::MES public function serialize($value, $max_depth = 3, $_depth = 0) { if ($_depth < $max_depth) { - if (is_array($value)) { + if (\is_array($value)) { $new = []; foreach ($value as $k => $v) { $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); @@ -92,12 +92,12 @@ public function serialize($value, $max_depth = 3, $_depth = 0) return $new; } - if (is_object($value)) { - if (is_callable($value)) { + if (\is_object($value)) { + if (\is_callable($value)) { return $this->serializeCallable($value); } - if ($this->_all_object_serialize || ('stdClass' === get_class($value))) { + if ($this->_all_object_serialize || ('stdClass' === \get_class($value))) { return $this->serializeObject($value, $max_depth, $_depth, []); } } @@ -116,13 +116,13 @@ public function serialize($value, $max_depth = 3, $_depth = 0) */ public function serializeObject($object, $max_depth = 3, $_depth = 0, $hashes = []) { - if (($_depth >= $max_depth) or in_array(spl_object_hash($object), $hashes)) { + if (($_depth >= $max_depth) or \in_array(spl_object_hash($object), $hashes)) { return $this->serializeValue($object); } $hashes[] = spl_object_hash($object); $return = []; foreach ($object as $key => &$value) { - if (is_object($value)) { + if (\is_object($value)) { $new_value = $this->serializeObject($value, $max_depth, $_depth + 1, $hashes); } else { $new_value = $this->serialize($value, $max_depth, $_depth + 1); @@ -137,7 +137,7 @@ protected function serializeString($value) { $value = (string) $value; - if (extension_loaded('mbstring')) { + if (\extension_loaded('mbstring')) { // we always guarantee this is coerced, even if we can't detect encoding if ($currentEncoding = mb_detect_encoding($value, $this->mb_detect_order)) { $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); @@ -149,7 +149,7 @@ protected function serializeString($value) $value = mb_substr($value, 0, $this->messageLimit - 10, 'UTF-8') . ' {clipped}'; } } else { - if (strlen($value) > $this->messageLimit) { + if (\strlen($value) > $this->messageLimit) { $value = substr($value, 0, $this->messageLimit - 10) . ' {clipped}'; } } @@ -164,14 +164,14 @@ protected function serializeString($value) */ protected function serializeValue($value) { - if ((null === $value) || is_bool($value) || is_float($value) || is_int($value)) { + if ((null === $value) || \is_bool($value) || \is_float($value) || \is_int($value)) { return $value; - } elseif (is_object($value) || 'object' == gettype($value)) { - return 'Object ' . get_class($value); - } elseif (is_resource($value)) { + } elseif (\is_object($value) || 'object' == \gettype($value)) { + return 'Object ' . \get_class($value); + } elseif (\is_resource($value)) { return 'Resource ' . get_resource_type($value); - } elseif (is_array($value)) { - return 'Array of length ' . count($value); + } elseif (\is_array($value)) { + return 'Array of length ' . \count($value); } else { return $this->serializeString($value); } @@ -184,7 +184,7 @@ protected function serializeValue($value) */ public function serializeCallable($callable) { - if (is_array($callable)) { + if (\is_array($callable)) { $reflection = new \ReflectionMethod($callable[0], $callable[1]); $class = $reflection->getDeclaringClass(); } else { diff --git a/lib/Raven/Stacktrace.php b/lib/Raven/Stacktrace.php index c3f0382db..6941b8ae3 100644 --- a/lib/Raven/Stacktrace.php +++ b/lib/Raven/Stacktrace.php @@ -179,7 +179,7 @@ public function addFrame($file, $line, array $backtraceFrame) foreach ($frameArguments as $argumentName => $argumentValue) { $argumentValue = $this->reprSerializer->serialize($argumentValue); - if (is_string($argumentValue) || is_numeric($argumentValue)) { + if (\is_string($argumentValue) || is_numeric($argumentValue)) { $frameArguments[(string) $argumentName] = substr($argumentValue, 0, Client::MESSAGE_MAX_LENGTH_LIMIT); } else { $frameArguments[(string) $argumentName] = $argumentValue; @@ -297,7 +297,7 @@ protected function stripPrefixFromFilePath($filePath) { foreach ($this->client->getConfig()->getPrefixes() as $prefix) { if (0 === strpos($filePath, $prefix)) { - return substr($filePath, strlen($prefix)); + return substr($filePath, \strlen($prefix)); } } @@ -320,7 +320,7 @@ protected static function getFrameArgumentsValues($frame, $maxValueLength = Clie $result = []; - for ($i = 0; $i < count($frame['args']); ++$i) { + for ($i = 0; $i < \count($frame['args']); ++$i) { $result['param' . ($i + 1)] = self::serializeArgument($frame['args'][$i], $maxValueLength); } @@ -359,7 +359,7 @@ public static function getFrameArguments($frame, $maxValueLength = Client::MESSA return self::getFrameArgumentsValues($frame, $maxValueLength); } - if (in_array($frame['function'], static::$importStatements, true)) { + if (\in_array($frame['function'], static::$importStatements, true)) { if (empty($frame['args'])) { return []; } @@ -378,7 +378,7 @@ public static function getFrameArguments($frame, $maxValueLength = Client::MESSA } else { $reflection = new \ReflectionMethod($frame['class'], '__call'); } - } elseif (function_exists($frame['function'])) { + } elseif (\function_exists($frame['function'])) { $reflection = new \ReflectionFunction($frame['function']); } else { return self::getFrameArgumentsValues($frame, $maxValueLength); @@ -406,11 +406,11 @@ public static function getFrameArguments($frame, $maxValueLength = Client::MESSA protected static function serializeArgument($arg, $maxValueLength) { - if (is_array($arg)) { + if (\is_array($arg)) { $result = []; foreach ($arg as $key => $value) { - if (is_string($value) || is_numeric($value)) { + if (\is_string($value) || is_numeric($value)) { $result[$key] = substr($value, 0, $maxValueLength); } else { $result[$key] = $value; @@ -418,7 +418,7 @@ protected static function serializeArgument($arg, $maxValueLength) } return $result; - } elseif (is_string($arg) || is_numeric($arg)) { + } elseif (\is_string($arg) || is_numeric($arg)) { return substr($arg, 0, $maxValueLength); } else { return $arg; diff --git a/lib/Raven/TransactionStack.php b/lib/Raven/TransactionStack.php index f02a3a501..bd9b44a37 100644 --- a/lib/Raven/TransactionStack.php +++ b/lib/Raven/TransactionStack.php @@ -62,7 +62,7 @@ public function isEmpty() public function push(...$values) { foreach ($values as $value) { - if (!is_string($value)) { + if (!\is_string($value)) { throw new \InvalidArgumentException(sprintf('The $values argument must contain string values only.')); } @@ -81,7 +81,7 @@ public function peek() return null; } - return $this->transactions[count($this->transactions) - 1]; + return $this->transactions[\count($this->transactions) - 1]; } /** @@ -103,6 +103,6 @@ public function pop() */ public function count() { - return count($this->transactions); + return \count($this->transactions); } } diff --git a/tests/AbstractSerializerTest.php b/tests/AbstractSerializerTest.php index 859bea2f7..5b006c2e7 100644 --- a/tests/AbstractSerializerTest.php +++ b/tests/AbstractSerializerTest.php @@ -259,9 +259,9 @@ public function testRecursionInObjects($object, $expectedResult) $result1 = $serializer->serialize($object, 3); $result2 = $serializer->serializeObject($object, 3); $this->assertEquals($expectedResult, $result1); - $this->assertContains(gettype($result1), ['array', 'string', 'null', 'float', 'integer', 'object']); + $this->assertContains(\gettype($result1), ['array', 'string', 'null', 'float', 'integer', 'object']); $this->assertEquals($expectedResult, $result2); - $this->assertContains(gettype($result2), ['array', 'string']); + $this->assertContains(\gettype($result2), ['array', 'string']); } public function testRecursionMaxDepthForObject() @@ -313,7 +313,7 @@ public function testBrokenEncoding($serializeAllObjects) $input = pack('H*', $key); $result = $serializer->serialize($input); $this->assertInternalType('string', $result); - if (function_exists('mb_detect_encoding')) { + if (\function_exists('mb_detect_encoding')) { $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); } } @@ -334,7 +334,7 @@ public function testLongString($serializeAllObjects) $input = str_repeat('x', $length); $result = $serializer->serialize($input); $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(1024, strlen($result)); + $this->assertLessThanOrEqual(1024, \strlen($result)); } } @@ -347,7 +347,7 @@ public function testLongStringWithOverwrittenMessageLength() $input = str_repeat('x', $length); $result = $serializer->serialize($input); $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(500, strlen($result)); + $this->assertLessThanOrEqual(500, \strlen($result)); } } @@ -380,7 +380,7 @@ public function testSetAllObjectSerialize() public function testClippingUTF8Characters() { - if (!extension_loaded('mbstring')) { + if (!\extension_loaded('mbstring')) { $this->markTestSkipped('mbstring extension is not enabled.'); } @@ -524,7 +524,7 @@ public function testSerializeCallable($callable, $expected) $actual2 = $serializer->serialize($callable); $actual3 = $serializer->serialize([$callable]); - if (is_array($callable)) { + if (\is_array($callable)) { $this->assertInternalType('array', $actual2); $this->assertInternalType('array', $actual3); } else { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 36d045023..8ac014ec0 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -355,16 +355,16 @@ private function assertMixedValueAndArray($expected_value, $actual_value) $this->assertTrue($actual_value); } elseif (false === $expected_value) { $this->assertFalse($actual_value); - } elseif (is_string($expected_value) or is_int($expected_value) or is_float($expected_value)) { + } elseif (\is_string($expected_value) or \is_int($expected_value) or \is_float($expected_value)) { $this->assertEquals($expected_value, $actual_value); - } elseif (is_array($expected_value)) { + } elseif (\is_array($expected_value)) { $this->assertInternalType('array', $actual_value); - $this->assertEquals(count($expected_value), count($actual_value)); + $this->assertEquals(\count($expected_value), \count($actual_value)); foreach ($expected_value as $key => $value) { $this->assertArrayHasKey($key, $actual_value); $this->assertMixedValueAndArray($value, $actual_value[$key]); } - } elseif (is_callable($expected_value) or is_object($expected_value)) { + } elseif (\is_callable($expected_value) or \is_object($expected_value)) { $this->assertEquals(spl_object_hash($expected_value), spl_object_hash($actual_value)); } } diff --git a/tests/EventTest.php b/tests/EventTest.php index 1fb4f75b0..2ecc71d84 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -157,16 +157,16 @@ public function testGettersAndSetters($propertyName, $propertyValue, $expectedVa $setterMethod = 'with' . ucfirst($propertyName); $event = new Event($this->configuration); - $newEvent = call_user_func([$event, $setterMethod], call_user_func([$event, $getterMethod])); + $newEvent = \call_user_func([$event, $setterMethod], \call_user_func([$event, $getterMethod])); $this->assertSame($event, $newEvent); - $newEvent = call_user_func([$event, $setterMethod], $propertyValue); + $newEvent = \call_user_func([$event, $setterMethod], $propertyValue); $this->assertNotSame($event, $newEvent); - $value = call_user_func([$event, $getterMethod]); - $newValue = call_user_func([$newEvent, $getterMethod]); + $value = \call_user_func([$event, $getterMethod]); + $newValue = \call_user_func([$newEvent, $getterMethod]); $this->assertNotSame($value, $newValue); $this->assertSame($newValue, $propertyValue); diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php index e35cba23c..035aa88ab 100644 --- a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php +++ b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php @@ -222,7 +222,7 @@ public function testInvokeWithExceptionThrownInLatin1File() /** @var \Raven\Frame $frame */ foreach ($result['values'][0]['stacktrace']->getFrames() as $frame) { - if (null !== $frame->getPreContext() && in_array('// äöü', $frame->getPreContext(), true)) { + if (null !== $frame->getPreContext() && \in_array('// äöü', $frame->getPreContext(), true)) { $latin1StringFound = true; break; diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index d774723e8..703475bbc 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -48,7 +48,7 @@ public function encodeDataProvider() */ public function testEncodeThrowsIfValueIsResource() { - $resource = fopen('php://memory', 'r'); + $resource = fopen('php://memory', 'rb'); fclose($resource); From 1e66065be8060f86fd9f20e1810ba47220409ca6 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 24 Aug 2018 17:24:57 +0200 Subject: [PATCH 0363/1161] Make the Event class mutable --- lib/Raven/Client.php | 6 +- lib/Raven/Event.php | 198 +++++------------- .../BreadcrumbInterfaceMiddleware.php | 2 +- .../Middleware/ContextInterfaceMiddleware.php | 10 +- .../ExceptionInterfaceMiddleware.php | 6 +- .../Middleware/MessageInterfaceMiddleware.php | 2 +- lib/Raven/Middleware/ModulesMiddleware.php | 2 +- .../Middleware/RequestInterfaceMiddleware.php | 2 +- lib/Raven/Middleware/SanitizerMiddleware.php | 12 +- .../Middleware/UserInterfaceMiddleware.php | 2 +- .../Processor/RemoveHttpBodyProcessor.php | 2 +- .../Processor/SanitizeCookiesProcessor.php | 2 +- lib/Raven/Processor/SanitizeDataProcessor.php | 8 +- .../SanitizeHttpHeadersProcessor.php | 2 +- .../Processor/SanitizeStacktraceProcessor.php | 2 +- tests/EventTest.php | 27 +-- .../BreadcrumbInterfaceMiddlewareTest.php | 9 +- .../ContextInterfaceMiddlewareTest.php | 9 +- .../ExceptionInterfaceMiddlewareTest.php | 49 ++--- .../MessageInterfaceMiddlewareTest.php | 10 +- tests/Middleware/ModulesMiddlewareTest.php | 21 +- tests/Middleware/SanitizerMiddlewareTest.php | 15 +- .../UserInterfaceMiddlewareTest.php | 13 +- .../Processor/RemoveHttpBodyProcessorTest.php | 2 +- .../SanitizeCookiesProcessorTest.php | 2 +- tests/Processor/SanitizeDataProcessorTest.php | 6 +- .../SanitizeHttpHeadersProcessorTest.php | 2 +- .../SanitizeStacktraceProcessorTest.php | 4 +- 28 files changed, 145 insertions(+), 282 deletions(-) diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 177ed6e0d..48baafe05 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -353,13 +353,13 @@ public function capture(array $payload) $event = new Event($this->config); if (isset($payload['transaction'])) { - $event = $event->withTransaction($payload['transaction']); + $event->setTransaction($payload['transaction']); } else { - $event = $event->withTransaction($this->transactionStack->peek()); + $event->setTransaction($this->transactionStack->peek()); } if (isset($payload['logger'])) { - $event = $event->withLogger($payload['logger']); + $event->setLogger($payload['logger']); } $event = $this->middlewareStack->executeStack( diff --git a/lib/Raven/Event.php b/lib/Raven/Event.php index fe28404b0..965aa4f9d 100644 --- a/lib/Raven/Event.php +++ b/lib/Raven/Event.php @@ -191,16 +191,11 @@ public function getLevel() * * @return static */ - public function withLevel($level) + public function setLevel($level) { - if ($level === $this->level) { - return $this; - } - - $new = clone $this; - $new->level = $level; + $this->level = $level; - return $new; + return $this; } /** @@ -220,16 +215,11 @@ public function getLogger() * * @return static */ - public function withLogger($logger) + public function setLogger($logger) { - if ($logger === $this->logger) { - return $this; - } + $this->logger = $logger; - $new = clone $this; - $new->logger = $logger; - - return $new; + return $this; } /** @@ -248,19 +238,10 @@ public function getTransaction() * exception. * * @param string $transaction The transaction name - * - * @return static */ - public function withTransaction($transaction) + public function setTransaction($transaction) { - if ($transaction === $this->transaction) { - return $this; - } - - $new = clone $this; - $new->transaction = $transaction; - - return $new; + $this->transaction = $transaction; } /** @@ -280,16 +261,11 @@ public function getServerName() * * @return static */ - public function withServerName($serverName) + public function setServerName($serverName) { - if ($serverName === $this->serverName) { - return $this; - } + $this->serverName = $serverName; - $new = clone $this; - $new->serverName = $serverName; - - return $new; + return $this; } /** @@ -309,16 +285,11 @@ public function getRelease() * * @return static */ - public function withRelease($release) + public function setRelease($release) { - if ($release === $this->release) { - return $this; - } - - $new = clone $this; - $new->release = $release; + $this->release = $release; - return $new; + return $this; } /** @@ -349,17 +320,12 @@ public function getMessageParams() * * @return static */ - public function withMessage($message, array $params = []) + public function setMessage($message, array $params = []) { - if ($message === $this->message && $params === $this->messageParams) { - return $this; - } - - $new = clone $this; - $new->message = $message; - $new->messageParams = $params; + $this->message = $message; + $this->messageParams = $params; - return $new; + return $this; } /** @@ -379,16 +345,11 @@ public function getModules() * * @return static */ - public function withModules(array $modules) + public function setModules(array $modules) { - if ($modules === $this->modules) { - return $this; - } - - $new = clone $this; - $new->modules = $modules; + $this->modules = $modules; - return $new; + return $this; } /** @@ -408,16 +369,11 @@ public function getRequest() * * @return static */ - public function withRequest(array $request) + public function setRequest(array $request) { - if ($request === $this->request) { - return $this; - } - - $new = clone $this; - $new->request = $request; + $this->request = $request; - return $new; + return $this; } /** @@ -438,16 +394,11 @@ public function getExtraContext() * * @return static */ - public function withExtraContext(array $extraContext, $replace = true) + public function setExtraContext(array $extraContext, $replace = true) { - if ($extraContext === $this->extraContext) { - return $this; - } - - $new = clone $this; - $new->extraContext = $replace ? $extraContext : array_merge($this->extraContext, $extraContext); + $this->extraContext = $replace ? $extraContext : array_merge($this->extraContext, $extraContext); - return $new; + return $this; } /** @@ -468,16 +419,11 @@ public function getTagsContext() * * @return static */ - public function withTagsContext(array $tagsContext, $replace = true) + public function setTagsContext(array $tagsContext, $replace = true) { - if ($tagsContext === $this->tagsContext) { - return $this; - } + $this->tagsContext = $replace ? $tagsContext : array_merge($this->tagsContext, $tagsContext); - $new = clone $this; - $new->tagsContext = $replace ? $tagsContext : array_merge($this->tagsContext, $tagsContext); - - return $new; + return $this; } /** @@ -498,16 +444,11 @@ public function getUserContext() * * @return static */ - public function withUserContext(array $userContext, $replace = true) + public function setUserContext(array $userContext, $replace = true) { - if ($userContext === $this->userContext) { - return $this; - } + $this->userContext = $replace ? $userContext : array_merge($this->userContext, $userContext); - $new = clone $this; - $new->userContext = $replace ? $userContext : array_merge($this->userContext, $userContext); - - return $new; + return $this; } /** @@ -528,16 +469,11 @@ public function getServerOsContext() * * @return static */ - public function withServerOsContext(array $serverOsContext, $replace = true) + public function setServerOsContext(array $serverOsContext, $replace = true) { - if ($serverOsContext === $this->serverOsContext) { - return $this; - } + $this->serverOsContext = $replace ? $serverOsContext : array_merge($this->serverOsContext, $serverOsContext); - $new = clone $this; - $new->serverOsContext = $replace ? $serverOsContext : array_merge($this->serverOsContext, $serverOsContext); - - return $new; + return $this; } /** @@ -558,16 +494,11 @@ public function getRuntimeContext() * * @return static */ - public function withRuntimeContext(array $runtimeContext, $replace = true) + public function setRuntimeContext(array $runtimeContext, $replace = true) { - if ($runtimeContext === $this->runtimeContext) { - return $this; - } + $this->runtimeContext = $replace ? $runtimeContext : array_merge($this->runtimeContext, $runtimeContext); - $new = clone $this; - $new->runtimeContext = $replace ? $runtimeContext : array_merge($this->runtimeContext, $runtimeContext); - - return $new; + return $this; } /** @@ -589,16 +520,11 @@ public function getFingerprint() * * @return static */ - public function withFingerprint(array $fingerprint) + public function setFingerprint(array $fingerprint) { - if ($fingerprint === $this->fingerprint) { - return $this; - } - - $new = clone $this; - $new->fingerprint = $fingerprint; + $this->fingerprint = $fingerprint; - return $new; + return $this; } /** @@ -618,16 +544,11 @@ public function getEnvironment() * * @return static */ - public function withEnvironment($environment) + public function setEnvironment($environment) { - if ($environment === $this->environment) { - return $this; - } - - $new = clone $this; - $new->environment = $environment; + $this->environment = $environment; - return $new; + return $this; } /** @@ -647,12 +568,11 @@ public function getBreadcrumbs() * * @return static */ - public function withBreadcrumb(Breadcrumb $breadcrumb) + public function setBreadcrumb(Breadcrumb $breadcrumb) { - $new = clone $this; - $new->breadcrumbs[] = $breadcrumb; + $this->breadcrumbs[] = $breadcrumb; - return $new; + return $this; } /** @@ -672,16 +592,11 @@ public function getException() * * @return static */ - public function withException(array $exception) + public function setException(array $exception) { - if ($exception === $this->exception) { - return $this; - } - - $new = clone $this; - $new->exception = $exception; + $this->exception = $exception; - return $new; + return $this; } /** @@ -701,16 +616,11 @@ public function getStacktrace() * * @return static */ - public function withStacktrace(Stacktrace $stacktrace) + public function setStacktrace(Stacktrace $stacktrace) { - if ($stacktrace === $this->stacktrace) { - return $this; - } - - $new = clone $this; - $new->stacktrace = $stacktrace; + $this->stacktrace = $stacktrace; - return $new; + return $this; } /** diff --git a/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php b/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php index c97ed8ef5..f92b75dcb 100644 --- a/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php +++ b/lib/Raven/Middleware/BreadcrumbInterfaceMiddleware.php @@ -52,7 +52,7 @@ public function __construct(Recorder $recorder) public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { foreach ($this->recorder as $breadcrumb) { - $event = $event->withBreadcrumb($breadcrumb); + $event->setBreadcrumb($breadcrumb); } return $next($event, $request, $exception, $payload); diff --git a/lib/Raven/Middleware/ContextInterfaceMiddleware.php b/lib/Raven/Middleware/ContextInterfaceMiddleware.php index 85e12c2aa..b0a5f29c3 100644 --- a/lib/Raven/Middleware/ContextInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ContextInterfaceMiddleware.php @@ -63,19 +63,19 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r switch ($this->contextName) { case Context::CONTEXT_USER: - $event = $event->withUserContext($context, false); + $event->setUserContext($context, false); break; case Context::CONTEXT_RUNTIME: - $event = $event->withRuntimeContext($context, false); + $event->setRuntimeContext($context, false); break; case Context::CONTEXT_TAGS: - $event = $event->withTagsContext($context, false); + $event->setTagsContext($context, false); break; case Context::CONTEXT_EXTRA: - $event = $event->withExtraContext($context, false); + $event->setExtraContext($context, false); break; case Context::CONTEXT_SERVER_OS: - $event = $event->withServerOsContext($context, false); + $event->setServerOsContext($context, false); break; default: throw new \RuntimeException(sprintf('The "%s" context is not supported.', $this->contextName)); diff --git a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php index 4921f8f4d..e91e453b5 100644 --- a/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ExceptionInterfaceMiddleware.php @@ -52,9 +52,9 @@ public function __construct(ClientInterface $client) public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { if (isset($payload['level'])) { - $event = $event->withLevel($payload['level']); + $event->setLevel($payload['level']); } elseif ($exception instanceof \ErrorException) { - $event = $event->withLevel($this->client->translateSeverity($exception->getSeverity())); + $event->setLevel($this->client->translateSeverity($exception->getSeverity())); } if (null !== $exception) { @@ -82,7 +82,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r 'values' => array_reverse($exceptions), ]; - $event = $event->withException($exceptions); + $event->setException($exceptions); } return $next($event, $request, $exception, $payload); diff --git a/lib/Raven/Middleware/MessageInterfaceMiddleware.php b/lib/Raven/Middleware/MessageInterfaceMiddleware.php index f3a6a090d..65ce19942 100644 --- a/lib/Raven/Middleware/MessageInterfaceMiddleware.php +++ b/lib/Raven/Middleware/MessageInterfaceMiddleware.php @@ -40,7 +40,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $messageParams = isset($payload['message_params']) ? $payload['message_params'] : []; if (null !== $message) { - $event = $event->withMessage(substr($message, 0, Client::MESSAGE_MAX_LENGTH_LIMIT), $messageParams); + $event->setMessage(substr($message, 0, Client::MESSAGE_MAX_LENGTH_LIMIT), $messageParams); } return $next($event, $request, $exception, $payload); diff --git a/lib/Raven/Middleware/ModulesMiddleware.php b/lib/Raven/Middleware/ModulesMiddleware.php index 40c75ab4c..b6fb55d2d 100644 --- a/lib/Raven/Middleware/ModulesMiddleware.php +++ b/lib/Raven/Middleware/ModulesMiddleware.php @@ -62,7 +62,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $modules[$package->getName()] = $package->getVersion(); } - $event = $event->withModules($modules); + $event->setModules($modules); } } diff --git a/lib/Raven/Middleware/RequestInterfaceMiddleware.php b/lib/Raven/Middleware/RequestInterfaceMiddleware.php index a82091bcc..8f11c36af 100644 --- a/lib/Raven/Middleware/RequestInterfaceMiddleware.php +++ b/lib/Raven/Middleware/RequestInterfaceMiddleware.php @@ -54,7 +54,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $requestData['env']['REMOTE_ADDR'] = $request->getHeaderLine('REMOTE_ADDR'); } - $event = $event->withRequest($requestData); + $event->setRequest($requestData); return $next($event, $request, $exception, $payload); } diff --git a/lib/Raven/Middleware/SanitizerMiddleware.php b/lib/Raven/Middleware/SanitizerMiddleware.php index 948902ce1..abf998325 100644 --- a/lib/Raven/Middleware/SanitizerMiddleware.php +++ b/lib/Raven/Middleware/SanitizerMiddleware.php @@ -52,27 +52,27 @@ public function __construct(Serializer $serializer) public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { if (!empty($request = $event->getRequest())) { - $event = $event->withRequest($this->serializer->serialize($request, 5)); + $event->setRequest($this->serializer->serialize($request, 5)); } if (!empty($userContext = $event->getUserContext())) { - $event = $event->withUserContext($this->serializer->serialize($userContext)); + $event->setUserContext($this->serializer->serialize($userContext)); } if (!empty($runtimeContext = $event->getRuntimeContext())) { - $event = $event->withRuntimeContext($this->serializer->serialize($runtimeContext)); + $event->setRuntimeContext($this->serializer->serialize($runtimeContext)); } if (!empty($serverOsContext = $event->getServerOsContext())) { - $event = $event->withServerOsContext($this->serializer->serialize($serverOsContext)); + $event->setServerOsContext($this->serializer->serialize($serverOsContext)); } if (!empty($extraContext = $event->getExtraContext())) { - $event = $event->withExtraContext($this->serializer->serialize($extraContext)); + $event->setExtraContext($this->serializer->serialize($extraContext)); } if (!empty($tagsContext = $event->getTagsContext())) { - $event = $event->withTagsContext($this->serializer->serialize($tagsContext)); + $event->setTagsContext($this->serializer->serialize($tagsContext)); } return $next($event, $request, $exception, $payload); diff --git a/lib/Raven/Middleware/UserInterfaceMiddleware.php b/lib/Raven/Middleware/UserInterfaceMiddleware.php index 3b5ad391d..5a5d9946c 100644 --- a/lib/Raven/Middleware/UserInterfaceMiddleware.php +++ b/lib/Raven/Middleware/UserInterfaceMiddleware.php @@ -40,7 +40,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $userContext['ip_address'] = $request->getHeaderLine('REMOTE_ADDR'); } - $event = $event->withUserContext($userContext); + $event->setUserContext($userContext); return $next($event, $request, $exception, $payload); } diff --git a/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/lib/Raven/Processor/RemoveHttpBodyProcessor.php index 7108f4e13..d4f33ac5f 100644 --- a/lib/Raven/Processor/RemoveHttpBodyProcessor.php +++ b/lib/Raven/Processor/RemoveHttpBodyProcessor.php @@ -33,6 +33,6 @@ public function process(Event $event) $request['data'] = self::STRING_MASK; } - return $event->withRequest($request); + return $event->setRequest($request); } } diff --git a/lib/Raven/Processor/SanitizeCookiesProcessor.php b/lib/Raven/Processor/SanitizeCookiesProcessor.php index cfe179d3c..f38d0581d 100644 --- a/lib/Raven/Processor/SanitizeCookiesProcessor.php +++ b/lib/Raven/Processor/SanitizeCookiesProcessor.php @@ -75,7 +75,7 @@ public function process(Event $event) unset($request['headers']['cookie']); - return $event->withRequest($request); + return $event->setRequest($request); } /** diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php index d2b0e9596..3e7352fee 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -156,20 +156,20 @@ public function process(Event $event) $extraContext = $event->getExtraContext(); if (!empty($exception)) { - $event = $event->withException($this->sanitizeException($exception)); + $event->setException($this->sanitizeException($exception)); } if ($stacktrace) { - $event = $event->withStacktrace($this->sanitizeStacktrace($stacktrace)); + $event->setStacktrace($this->sanitizeStacktrace($stacktrace)); } if (!empty($request)) { - $event = $event->withRequest($this->sanitizeHttp($request)); + $event->setRequest($this->sanitizeHttp($request)); } if (!empty($extraContext)) { $this->sanitize($extraContext); - $event = $event->withExtraContext($extraContext); + $event->setExtraContext($extraContext); } return $event; diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php index aec8f06c7..87847cc06 100644 --- a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php +++ b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -58,7 +58,7 @@ public function process(Event $event) } } - return $event->withRequest($request); + return $event->setRequest($request); } /** diff --git a/lib/Raven/Processor/SanitizeStacktraceProcessor.php b/lib/Raven/Processor/SanitizeStacktraceProcessor.php index 76fa9f0ad..76612a61b 100644 --- a/lib/Raven/Processor/SanitizeStacktraceProcessor.php +++ b/lib/Raven/Processor/SanitizeStacktraceProcessor.php @@ -38,6 +38,6 @@ public function process(Event $event) $frame->setPostContext(null); } - return $event->withStacktrace($stacktrace); + return $event->setStacktrace($stacktrace); } } diff --git a/tests/EventTest.php b/tests/EventTest.php index 2ecc71d84..e5efbd8f7 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -102,7 +102,7 @@ public function testToArray() public function testToArrayWithMessage() { $event = Event::create($this->configuration) - ->withMessage('foo bar'); + ->setMessage('foo bar'); $data = $event->toArray(); @@ -119,7 +119,7 @@ public function testToArrayWithMessageWithParams() ]; $event = Event::create($this->configuration) - ->withMessage('foo %s', ['bar']); + ->setMessage('foo %s', ['bar']); $data = $event->toArray(); @@ -137,7 +137,7 @@ public function testToArrayWithBreadcrumbs() $event = new Event($this->configuration); foreach ($breadcrumbs as $breadcrumb) { - $event = $event->withBreadcrumb($breadcrumb); + $event->setBreadcrumb($breadcrumb); } $this->assertSame($breadcrumbs, $event->getBreadcrumbs()); @@ -154,26 +154,13 @@ public function testToArrayWithBreadcrumbs() public function testGettersAndSetters($propertyName, $propertyValue, $expectedValue) { $getterMethod = 'get' . ucfirst($propertyName); - $setterMethod = 'with' . ucfirst($propertyName); + $setterMethod = 'set' . ucfirst($propertyName); $event = new Event($this->configuration); - $newEvent = \call_user_func([$event, $setterMethod], \call_user_func([$event, $getterMethod])); + $event->$setterMethod($propertyValue); - $this->assertSame($event, $newEvent); - - $newEvent = \call_user_func([$event, $setterMethod], $propertyValue); - - $this->assertNotSame($event, $newEvent); - - $value = \call_user_func([$event, $getterMethod]); - $newValue = \call_user_func([$newEvent, $getterMethod]); - - $this->assertNotSame($value, $newValue); - $this->assertSame($newValue, $propertyValue); - - $data = $newEvent->toArray(); - - $this->assertArraySubset($expectedValue, $data); + $this->assertSame($event->$getterMethod(), $propertyValue); + $this->assertArraySubset($expectedValue, $event->toArray()); } public function gettersAndSettersDataProvider() diff --git a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php index f9f5e58a5..8fbfb5e8a 100644 --- a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php +++ b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php @@ -33,17 +33,16 @@ public function testInvoke() $configuration = new Configuration(); $event = new Event($configuration); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, $breadcrumb, $breadcrumb2, &$invokationCount) { - $this->assertNotSame($event, $eventArg); + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($breadcrumb, $breadcrumb2, &$callbackInvoked) { $this->assertEquals([$breadcrumb, $breadcrumb2], $eventArg->getBreadcrumbs()); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new BreadcrumbInterfaceMiddleware($recorder); $middleware($event, $callback); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } } diff --git a/tests/Middleware/ContextInterfaceMiddlewareTest.php b/tests/Middleware/ContextInterfaceMiddlewareTest.php index 338d9da6f..be1cc14be 100644 --- a/tests/Middleware/ContextInterfaceMiddlewareTest.php +++ b/tests/Middleware/ContextInterfaceMiddlewareTest.php @@ -35,16 +35,15 @@ public function testInvoke($contextName, $initialData, $payloadData, $expectedDa $configuration = new Configuration(); $event = new Event($configuration); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, $contextName, $expectedData, &$invokationCount) { + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($contextName, $expectedData, &$callbackInvoked) { $method = preg_replace_callback('/_[a-zA-Z]/', function ($matches) { return strtoupper($matches[0][1]); }, 'get_' . $contextName . '_context'); - $this->assertNotSame($event, $eventArg); $this->assertEquals($expectedData, $eventArg->$method()); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new ContextInterfaceMiddleware($context, $contextName); @@ -52,7 +51,7 @@ public function testInvoke($contextName, $initialData, $payloadData, $expectedDa $contextName . '_context' => $payloadData, ]); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } public function invokeDataProvider() diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php index 035aa88ab..1d5f5db78 100644 --- a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php +++ b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php @@ -30,9 +30,8 @@ public function testInvoke($exception, $clientConfig, $payload, $expectedResult) $event = new Event($client->getConfig()); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, $assertHasStacktrace, $expectedResult, &$invokationCount) { - $this->assertNotSame($event, $eventArg); + $callbackInvoked = 0; + $callback = function (Event $eventArg) use ($assertHasStacktrace, $expectedResult, &$callbackInvoked) { $this->assertArraySubset($expectedResult, $eventArg->toArray()); foreach ($eventArg->getException()['values'] as $exception) { @@ -44,13 +43,13 @@ public function testInvoke($exception, $clientConfig, $payload, $expectedResult) } } - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new ExceptionInterfaceMiddleware($client); $middleware($event, $callback, null, $exception, $payload); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } public function invokeDataProvider() @@ -139,11 +138,9 @@ public function testInvokeWithExceptionContainingLatin1Characters() $event = new Event($client->getConfig()); $utf8String = 'äöü'; $latin1String = utf8_decode($utf8String); - $invokationCount = 0; - - $callback = function (Event $eventArg) use ($event, &$invokationCount, $utf8String) { - $this->assertNotSame($event, $eventArg); + $callbackInvoked = 0; + $callback = function (Event $eventArg) use (&$callbackInvoked, $utf8String) { $expectedValue = [ 'values' => [ [ @@ -155,24 +152,22 @@ public function testInvokeWithExceptionContainingLatin1Characters() $this->assertArraySubset($expectedValue, $eventArg->getException()); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new ExceptionInterfaceMiddleware($client); $middleware($event, $callback, null, new \Exception($latin1String)); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } public function testInvokeWithExceptionContainingInvalidUtf8Characters() { $client = ClientBuilder::create()->getClient(); $event = new Event($client->getConfig()); - $invokationCount = 0; - - $callback = function (Event $eventArg) use ($event, &$invokationCount) { - $this->assertNotSame($event, $eventArg); + $callbackInvoked = 0; + $callback = function (Event $eventArg) use (&$callbackInvoked) { $expectedValue = [ 'values' => [ [ @@ -184,13 +179,13 @@ public function testInvokeWithExceptionContainingInvalidUtf8Characters() $this->assertArraySubset($expectedValue, $eventArg->getException()); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new ExceptionInterfaceMiddleware($client); $middleware($event, $callback, null, new \Exception("\xC2\xA2\xC2")); // ill-formed 2-byte character U+00A2 (CENT SIGN) - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } public function testInvokeWithExceptionThrownInLatin1File() @@ -202,11 +197,9 @@ public function testInvokeWithExceptionThrownInLatin1File() $event = new Event($client->getConfig()); - $callback = function (Event $eventArg) use ($event, &$invokationCount) { - $this->assertNotSame($event, $eventArg); - + $callbackInvoked = false; + $callback = function (Event $eventArg) use (&$callbackInvoked) { $result = $eventArg->getException(); - $expectedValue = [ 'values' => [ [ @@ -231,13 +224,13 @@ public function testInvokeWithExceptionThrownInLatin1File() $this->assertTrue($latin1StringFound); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new ExceptionInterfaceMiddleware($client); $middleware($event, $callback, null, require_once __DIR__ . '/../Fixtures/code/Latin1File.php'); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } public function testInvokeWithAutoLogStacksDisabled() @@ -245,10 +238,8 @@ public function testInvokeWithAutoLogStacksDisabled() $client = ClientBuilder::create(['auto_log_stacks' => false])->getClient(); $event = new Event($client->getConfig()); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, &$invokationCount) { - $this->assertNotSame($event, $eventArg); - + $callbackInvoked = false; + $callback = function (Event $eventArg) use (&$callbackInvoked) { $result = $eventArg->getException(); $this->assertNotEmpty($result); @@ -257,12 +248,12 @@ public function testInvokeWithAutoLogStacksDisabled() $this->assertEquals('foo', $result['values'][0]['value']); $this->assertArrayNotHasKey('stacktrace', $result['values'][0]); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new ExceptionInterfaceMiddleware($client); $middleware($event, $callback, null, new \Exception('foo')); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } } diff --git a/tests/Middleware/MessageInterfaceMiddlewareTest.php b/tests/Middleware/MessageInterfaceMiddlewareTest.php index 93f68e1f0..69733a0fc 100644 --- a/tests/Middleware/MessageInterfaceMiddlewareTest.php +++ b/tests/Middleware/MessageInterfaceMiddlewareTest.php @@ -44,20 +44,18 @@ public function testInvoke($payload) $configuration = new Configuration(); $event = new Event($configuration); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, $payload, &$invokationCount) { - $this->assertNotSame($event, $eventArg); - + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($payload, &$callbackInvoked) { $this->assertEquals($payload['message'], $eventArg->getMessage()); $this->assertEquals($payload['message_params'], $eventArg->getMessageParams()); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new MessageInterfaceMiddleware(); $middleware($event, $callback, null, null, $payload); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } public function invokeDataProvider() diff --git a/tests/Middleware/ModulesMiddlewareTest.php b/tests/Middleware/ModulesMiddlewareTest.php index 5998931c8..c06b0ec1c 100644 --- a/tests/Middleware/ModulesMiddlewareTest.php +++ b/tests/Middleware/ModulesMiddlewareTest.php @@ -24,8 +24,7 @@ public function testInvoke() $event = new Event($configuration); $callbackInvoked = false; - $callback = function (Event $eventArg) use ($event, &$callbackInvoked) { - $this->assertNotSame($event, $eventArg); + $callback = function (Event $eventArg) use (&$callbackInvoked) { $this->assertEquals(['foo/bar' => '1.2.3.0', 'foo/baz' => '4.5.6.0'], $eventArg->getModules()); $callbackInvoked = true; @@ -36,22 +35,4 @@ public function testInvoke() $this->assertTrue($callbackInvoked); } - - public function testInvokeDoesNothingWhenNoComposerLockFileExists() - { - $configuration = new Configuration(['project_root' => __DIR__]); - $event = new Event($configuration); - - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($event, &$callbackInvoked) { - $this->assertSame($event, $eventArg); - - $callbackInvoked = true; - }; - - $middleware = new ModulesMiddleware($configuration); - $middleware($event, $callback); - - $this->assertTrue($callbackInvoked); - } } diff --git a/tests/Middleware/SanitizerMiddlewareTest.php b/tests/Middleware/SanitizerMiddlewareTest.php index e5d57ed7d..6f5c87960 100644 --- a/tests/Middleware/SanitizerMiddlewareTest.php +++ b/tests/Middleware/SanitizerMiddlewareTest.php @@ -22,12 +22,12 @@ class SanitizerMiddlewareTest extends TestCase public function testInvoke() { $event = new Event(new Configuration()); - $event = $event->withRequest(['bar' => 'baz']); - $event = $event->withUserContext(['foo' => 'bar']); - $event = $event->withTagsContext(['foo', 'bar']); - $event = $event->withServerOsContext(['bar' => 'foo']); - $event = $event->withRuntimeContext(['foo' => 'baz']); - $event = $event->withExtraContext(['baz' => 'foo']); + $event->setRequest(['bar' => 'baz']); + $event->setUserContext(['foo' => 'bar']); + $event->setTagsContext(['foo', 'bar']); + $event->setServerOsContext(['bar' => 'foo']); + $event->setRuntimeContext(['foo' => 'baz']); + $event->setExtraContext(['baz' => 'foo']); /** @var Serializer|\PHPUnit_Framework_MockObject_MockObject $sanitizer */ $sanitizer = $this->createMock(Serializer::class); @@ -62,8 +62,7 @@ public function testInvoke() }); $callbackInvoked = false; - $callback = function (Event $eventArg) use ($event, &$callbackInvoked) { - $this->assertNotSame($event, $eventArg); + $callback = function (Event $eventArg) use (&$callbackInvoked) { $this->assertEquals(['baz' => 'bar'], $eventArg->getRequest()); $this->assertEquals(['bar' => 'foo'], $eventArg->getUserContext()); $this->assertEquals(['baz' => 'foo'], $eventArg->getRuntimeContext()); diff --git a/tests/Middleware/UserInterfaceMiddlewareTest.php b/tests/Middleware/UserInterfaceMiddlewareTest.php index ecafdb5e4..43b4d1e39 100644 --- a/tests/Middleware/UserInterfaceMiddlewareTest.php +++ b/tests/Middleware/UserInterfaceMiddlewareTest.php @@ -22,7 +22,7 @@ class UserInterfaceMiddlewareTest extends TestCase public function testInvoke() { $event = new Event(new Configuration()); - $event = $event->withUserContext(['foo' => 'bar']); + $event->setUserContext(['foo' => 'bar']); $invokationCount = 0; $callback = function (Event $eventArg) use ($event, &$invokationCount) { @@ -40,22 +40,21 @@ public function testInvoke() public function testInvokeWithRequest() { $event = new Event(new Configuration()); - $event = $event->withUserContext(['foo' => 'bar']); + $event->setUserContext(['foo' => 'bar']); $request = new ServerRequest(); $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, &$invokationCount) { - $this->assertNotSame($event, $eventArg); + $callbackInvoked = false; + $callback = function (Event $eventArg) use (&$callbackInvoked) { $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $eventArg->getUserContext()); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new UserInterfaceMiddleware(); $middleware($event, $callback, $request); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } } diff --git a/tests/Processor/RemoveHttpBodyProcessorTest.php b/tests/Processor/RemoveHttpBodyProcessorTest.php index f1660dff8..eec56b10a 100644 --- a/tests/Processor/RemoveHttpBodyProcessorTest.php +++ b/tests/Processor/RemoveHttpBodyProcessorTest.php @@ -41,7 +41,7 @@ protected function setUp() public function testProcess($inputData, $expectedData) { $event = new Event($this->client->getConfig()); - $event = $event->withRequest($inputData); + $event->setRequest($inputData); $event = $this->processor->process($event); diff --git a/tests/Processor/SanitizeCookiesProcessorTest.php b/tests/Processor/SanitizeCookiesProcessorTest.php index 6d498ab0b..d85bb728b 100644 --- a/tests/Processor/SanitizeCookiesProcessorTest.php +++ b/tests/Processor/SanitizeCookiesProcessorTest.php @@ -47,7 +47,7 @@ public function testConstructorThrowsIfBothOnlyAndExceptOptionsAreSet() public function testProcess($options, $expectedData) { $event = new Event($this->client->getConfig()); - $event = $event->withRequest([ + $event->setRequest([ 'foo' => 'bar', 'cookies' => [ 'foo' => 'bar', diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index bc945385c..f8d70e2ac 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -44,18 +44,18 @@ public function testProcess($inputData, $expectedData) $event = new Event($this->client->getConfig()); if (isset($inputData['request'])) { - $event = $event->withRequest($inputData['request']); + $event->setRequest($inputData['request']); } if (isset($inputData['extra_context'])) { - $event = $event->withExtraContext($inputData['extra_context']); + $event->setExtraContext($inputData['extra_context']); } if (isset($inputData['exception'])) { // We must convert the backtrace to a Stacktrace instance here because // PHPUnit executes the data provider before the setUp method and so // the client instance cannot be accessed from there - $event = $event->withException($this->convertExceptionValuesToStacktrace($expectedData['exception'])); + $event->setException($this->convertExceptionValuesToStacktrace($expectedData['exception'])); } $event = $this->processor->process($event); diff --git a/tests/Processor/SanitizeHttpHeadersProcessorTest.php b/tests/Processor/SanitizeHttpHeadersProcessorTest.php index 2fb1192bc..9021c63bb 100644 --- a/tests/Processor/SanitizeHttpHeadersProcessorTest.php +++ b/tests/Processor/SanitizeHttpHeadersProcessorTest.php @@ -43,7 +43,7 @@ protected function setUp() public function testProcess($inputData, $expectedData) { $event = new Event($this->client->getConfig()); - $event = $event->withRequest($inputData); + $event->setRequest($inputData); $event = $this->processor->process($event); diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php index 21478b8f2..36cf85d6f 100644 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ b/tests/Processor/SanitizeStacktraceProcessorTest.php @@ -42,7 +42,7 @@ public function testProcess() $exception = new \Exception(); $event = new Event($this->client->getConfig()); - $event = $event->withStacktrace(Stacktrace::createFromBacktrace($this->client, $exception->getTrace(), $exception->getFile(), $exception->getLine())); + $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception->getTrace(), $exception->getFile(), $exception->getLine())); $event = $this->processor->process($event); @@ -59,7 +59,7 @@ public function testProcessWithPreviousException() $exception2 = new \Exception('', 0, $exception1); $event = new Event($this->client->getConfig()); - $event = $event->withStacktrace(Stacktrace::createFromBacktrace($this->client, $exception2->getTrace(), $exception2->getFile(), $exception2->getLine())); + $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception2->getTrace(), $exception2->getFile(), $exception2->getLine())); $event = $this->processor->process($event); From c5c8cd954316457d8f0234df724d2d985f1e8e89 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 24 Aug 2018 19:32:30 +0200 Subject: [PATCH 0364/1161] Use the Context classes instead of plain arrays in the Event class --- lib/Raven/Event.php | 194 +++--------------- .../Middleware/ContextInterfaceMiddleware.php | 14 +- lib/Raven/Middleware/SanitizerMiddleware.php | 24 +-- .../Middleware/UserInterfaceMiddleware.php | 2 - .../Processor/RemoveHttpBodyProcessor.php | 4 +- .../Processor/SanitizeCookiesProcessor.php | 4 +- lib/Raven/Processor/SanitizeDataProcessor.php | 5 +- .../SanitizeHttpHeadersProcessor.php | 4 +- .../Processor/SanitizeStacktraceProcessor.php | 16 +- tests/ClientTest.php | 6 +- tests/EventTest.php | 64 +++++- .../ContextInterfaceMiddlewareTest.php | 64 ++++-- tests/Middleware/SanitizerMiddlewareTest.php | 52 ++--- .../UserInterfaceMiddlewareTest.php | 16 +- tests/Processor/SanitizeDataProcessorTest.php | 5 +- 15 files changed, 196 insertions(+), 278 deletions(-) diff --git a/lib/Raven/Event.php b/lib/Raven/Event.php index 965aa4f9d..da56f0e41 100644 --- a/lib/Raven/Event.php +++ b/lib/Raven/Event.php @@ -14,6 +14,10 @@ use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Raven\Breadcrumbs\Breadcrumb; +use Raven\Context\Context; +use Raven\Context\RuntimeContext; +use Raven\Context\ServerOsContext; +use Raven\Context\TagsContext; /** * This is the base class for classes containing event data. @@ -83,29 +87,29 @@ final class Event implements \JsonSerializable private $request = []; /** - * @var array The server OS context data + * @var ServerOsContext The server OS context data */ - private $serverOsContext = []; + private $serverOsContext; /** - * @var array The runtime context data + * @var RuntimeContext The runtime context data */ - private $runtimeContext = []; + private $runtimeContext; /** - * @var array The user context data + * @var Context The user context data */ - private $userContext = []; + private $userContext; /** - * @var array An arbitrary mapping of additional metadata + * @var Context An arbitrary mapping of additional metadata */ - private $extraContext = []; + private $extraContext; /** - * @var string[] A List of tags associated to this event + * @var TagsContext A List of tags associated to this event */ - private $tagsContext = []; + private $tagsContext; /** * @var string[] An array of strings used to dictate the deduplication of this event @@ -140,18 +144,11 @@ public function __construct(Configuration $config) $this->serverName = $config->getServerName(); $this->release = $config->getRelease(); $this->environment = $config->getCurrentEnvironment(); - } - - /** - * Creates a new instance of this class. - * - * @param Configuration $config The client configuration - * - * @return static - */ - public static function create(Configuration $config) - { - return new static($config); + $this->serverOsContext = new ServerOsContext(); + $this->runtimeContext = new RuntimeContext(); + $this->userContext = new Context(); + $this->extraContext = new Context(); + $this->tagsContext = new TagsContext(); } /** @@ -188,14 +185,10 @@ public function getLevel() * Sets the severity of this event. * * @param string $level The severity - * - * @return static */ public function setLevel($level) { $this->level = $level; - - return $this; } /** @@ -212,14 +205,10 @@ public function getLogger() * Sets the name of the logger which created the event. * * @param string $logger The logger name - * - * @return static */ public function setLogger($logger) { $this->logger = $logger; - - return $this; } /** @@ -258,14 +247,10 @@ public function getServerName() * Sets the name of the server. * * @param string $serverName The server name - * - * @return static */ public function setServerName($serverName) { $this->serverName = $serverName; - - return $this; } /** @@ -282,14 +267,10 @@ public function getRelease() * Sets the release of the program. * * @param string $release The release - * - * @return static */ public function setRelease($release) { $this->release = $release; - - return $this; } /** @@ -317,15 +298,11 @@ public function getMessageParams() * * @param string $message The message * @param array $params The parameters to use to format the message - * - * @return static */ public function setMessage($message, array $params = []) { $this->message = $message; $this->messageParams = $params; - - return $this; } /** @@ -342,14 +319,10 @@ public function getModules() * Sets a list of relevant modules and their versions. * * @param array $modules - * - * @return static */ public function setModules(array $modules) { $this->modules = $modules; - - return $this; } /** @@ -366,141 +339,62 @@ public function getRequest() * Sets the request data. * * @param array $request The request data - * - * @return static */ public function setRequest(array $request) { $this->request = $request; - - return $this; } /** * Gets an arbitrary mapping of additional metadata. * - * @return array + * @return Context */ public function getExtraContext() { return $this->extraContext; } - /** - * Sets an arbitrary mapping of additional metadata. - * - * @param array $extraContext Additional metadata - * @param bool $replace Whether to merge the old data with the new one or replace entirely it - * - * @return static - */ - public function setExtraContext(array $extraContext, $replace = true) - { - $this->extraContext = $replace ? $extraContext : array_merge($this->extraContext, $extraContext); - - return $this; - } - /** * Gets a list of tags. * - * @return string[] + * @return TagsContext */ public function getTagsContext() { return $this->tagsContext; } - /** - * Sets a list of tags. - * - * @param string[] $tagsContext The tags - * @param bool $replace Whether to merge the old data with the new one or replace entirely it - * - * @return static - */ - public function setTagsContext(array $tagsContext, $replace = true) - { - $this->tagsContext = $replace ? $tagsContext : array_merge($this->tagsContext, $tagsContext); - - return $this; - } - /** * Gets the user context. * - * @return array + * @return Context */ public function getUserContext() { return $this->userContext; } - /** - * Sets the user context. - * - * @param array $userContext The context data - * @param bool $replace Whether to merge the old data with the new one or replace entirely it - * - * @return static - */ - public function setUserContext(array $userContext, $replace = true) - { - $this->userContext = $replace ? $userContext : array_merge($this->userContext, $userContext); - - return $this; - } - /** * Gets the server OS context. * - * @return array + * @return ServerOsContext */ public function getServerOsContext() { return $this->serverOsContext; } - /** - * Gets the server OS context. - * - * @param array $serverOsContext The context data - * @param bool $replace Whether to merge the old data with the new one or replace entirely it - * - * @return static - */ - public function setServerOsContext(array $serverOsContext, $replace = true) - { - $this->serverOsContext = $replace ? $serverOsContext : array_merge($this->serverOsContext, $serverOsContext); - - return $this; - } - /** * Gets the runtime context data. * - * @return array + * @return RuntimeContext */ public function getRuntimeContext() { return $this->runtimeContext; } - /** - * Sets the runtime context data. - * - * @param array $runtimeContext The context data - * @param bool $replace Whether to merge the old data with the new one or replace entirely it - * - * @return static - */ - public function setRuntimeContext(array $runtimeContext, $replace = true) - { - $this->runtimeContext = $replace ? $runtimeContext : array_merge($this->runtimeContext, $runtimeContext); - - return $this; - } - /** * Gets an array of strings used to dictate the deduplication of this * event. @@ -517,14 +411,10 @@ public function getFingerprint() * event. * * @param string[] $fingerprint The strings - * - * @return static */ public function setFingerprint(array $fingerprint) { $this->fingerprint = $fingerprint; - - return $this; } /** @@ -541,14 +431,10 @@ public function getEnvironment() * Sets the environment in which this event was generated. * * @param string $environment The name of the environment - * - * @return static */ public function setEnvironment($environment) { $this->environment = $environment; - - return $this; } /** @@ -565,14 +451,10 @@ public function getBreadcrumbs() * Adds a new breadcrumb to the event. * * @param Breadcrumb $breadcrumb The breadcrumb - * - * @return static */ public function setBreadcrumb(Breadcrumb $breadcrumb) { $this->breadcrumbs[] = $breadcrumb; - - return $this; } /** @@ -589,14 +471,10 @@ public function getException() * Sets the exception. * * @param array $exception The exception - * - * @return static */ public function setException(array $exception) { $this->exception = $exception; - - return $this; } /** @@ -613,14 +491,10 @@ public function getStacktrace() * Sets the stacktrace that generated this event. * * @param Stacktrace $stacktrace The stacktrace instance - * - * @return static */ public function setStacktrace(Stacktrace $stacktrace) { $this->stacktrace = $stacktrace; - - return $this; } /** @@ -669,24 +543,24 @@ public function toArray() $data['modules'] = $this->modules; } - if (!empty($this->extraContext)) { - $data['extra'] = $this->extraContext; + if (!$this->extraContext->isEmpty()) { + $data['extra'] = $this->extraContext->toArray(); } - if (!empty($this->tagsContext)) { - $data['tags'] = $this->tagsContext; + if (!$this->tagsContext->isEmpty()) { + $data['tags'] = $this->tagsContext->toArray(); } - if (!empty($this->userContext)) { - $data['user'] = $this->userContext; + if (!$this->userContext->isEmpty()) { + $data['user'] = $this->userContext->toArray(); } - if (!empty($this->serverOsContext)) { - $data['contexts']['os'] = $this->serverOsContext; + if (!$this->serverOsContext->isEmpty()) { + $data['contexts']['os'] = $this->serverOsContext->toArray(); } - if (!empty($this->runtimeContext)) { - $data['contexts']['runtime'] = $this->runtimeContext; + if (!$this->runtimeContext->isEmpty()) { + $data['contexts']['runtime'] = $this->runtimeContext->toArray(); } if (!empty($this->breadcrumbs)) { diff --git a/lib/Raven/Middleware/ContextInterfaceMiddleware.php b/lib/Raven/Middleware/ContextInterfaceMiddleware.php index b0a5f29c3..c853870bc 100644 --- a/lib/Raven/Middleware/ContextInterfaceMiddleware.php +++ b/lib/Raven/Middleware/ContextInterfaceMiddleware.php @@ -58,24 +58,24 @@ public function __construct(Context $context, $contextName) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - $context = isset($payload[$this->contextName . '_context']) ? $payload[$this->contextName . '_context'] : []; - $context = array_merge($this->context->toArray(), $context); + $contextData = isset($payload[$this->contextName . '_context']) ? $payload[$this->contextName . '_context'] : []; + $contextData = array_merge($this->context->toArray(), $contextData); switch ($this->contextName) { case Context::CONTEXT_USER: - $event->setUserContext($context, false); + $event->getUserContext()->setData($contextData); break; case Context::CONTEXT_RUNTIME: - $event->setRuntimeContext($context, false); + $event->getRuntimeContext()->setData($contextData); break; case Context::CONTEXT_TAGS: - $event->setTagsContext($context, false); + $event->getTagsContext()->setData($contextData); break; case Context::CONTEXT_EXTRA: - $event->setExtraContext($context, false); + $event->getExtraContext()->setData($contextData); break; case Context::CONTEXT_SERVER_OS: - $event->setServerOsContext($context, false); + $event->getServerOsContext()->setData($contextData); break; default: throw new \RuntimeException(sprintf('The "%s" context is not supported.', $this->contextName)); diff --git a/lib/Raven/Middleware/SanitizerMiddleware.php b/lib/Raven/Middleware/SanitizerMiddleware.php index abf998325..a4b7631a6 100644 --- a/lib/Raven/Middleware/SanitizerMiddleware.php +++ b/lib/Raven/Middleware/SanitizerMiddleware.php @@ -55,25 +55,11 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $event->setRequest($this->serializer->serialize($request, 5)); } - if (!empty($userContext = $event->getUserContext())) { - $event->setUserContext($this->serializer->serialize($userContext)); - } - - if (!empty($runtimeContext = $event->getRuntimeContext())) { - $event->setRuntimeContext($this->serializer->serialize($runtimeContext)); - } - - if (!empty($serverOsContext = $event->getServerOsContext())) { - $event->setServerOsContext($this->serializer->serialize($serverOsContext)); - } - - if (!empty($extraContext = $event->getExtraContext())) { - $event->setExtraContext($this->serializer->serialize($extraContext)); - } - - if (!empty($tagsContext = $event->getTagsContext())) { - $event->setTagsContext($this->serializer->serialize($tagsContext)); - } + $event->getUserContext()->replaceData($this->serializer->serialize($event->getUserContext()->toArray())); + $event->getRuntimeContext()->replaceData($this->serializer->serialize($event->getRuntimeContext()->toArray())); + $event->getServerOsContext()->replaceData($this->serializer->serialize($event->getServerOsContext()->toArray())); + $event->getExtraContext()->replaceData($this->serializer->serialize($event->getExtraContext()->toArray())); + $event->getTagsContext()->replaceData($this->serializer->serialize($event->getTagsContext()->toArray())); return $next($event, $request, $exception, $payload); } diff --git a/lib/Raven/Middleware/UserInterfaceMiddleware.php b/lib/Raven/Middleware/UserInterfaceMiddleware.php index 5a5d9946c..3b6e74367 100644 --- a/lib/Raven/Middleware/UserInterfaceMiddleware.php +++ b/lib/Raven/Middleware/UserInterfaceMiddleware.php @@ -40,8 +40,6 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $userContext['ip_address'] = $request->getHeaderLine('REMOTE_ADDR'); } - $event->setUserContext($userContext); - return $next($event, $request, $exception, $payload); } } diff --git a/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/lib/Raven/Processor/RemoveHttpBodyProcessor.php index d4f33ac5f..8bc76316e 100644 --- a/lib/Raven/Processor/RemoveHttpBodyProcessor.php +++ b/lib/Raven/Processor/RemoveHttpBodyProcessor.php @@ -33,6 +33,8 @@ public function process(Event $event) $request['data'] = self::STRING_MASK; } - return $event->setRequest($request); + $event->setRequest($request); + + return $event; } } diff --git a/lib/Raven/Processor/SanitizeCookiesProcessor.php b/lib/Raven/Processor/SanitizeCookiesProcessor.php index f38d0581d..9b415cbd2 100644 --- a/lib/Raven/Processor/SanitizeCookiesProcessor.php +++ b/lib/Raven/Processor/SanitizeCookiesProcessor.php @@ -75,7 +75,9 @@ public function process(Event $event) unset($request['headers']['cookie']); - return $event->setRequest($request); + $event->setRequest($request); + + return $event; } /** diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Processor/SanitizeDataProcessor.php index 3e7352fee..a3c96d388 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Processor/SanitizeDataProcessor.php @@ -153,7 +153,7 @@ public function process(Event $event) $exception = $event->getException(); $stacktrace = $event->getStacktrace(); $request = $event->getRequest(); - $extraContext = $event->getExtraContext(); + $extraContext = $event->getExtraContext()->toArray(); if (!empty($exception)) { $event->setException($this->sanitizeException($exception)); @@ -169,7 +169,8 @@ public function process(Event $event) if (!empty($extraContext)) { $this->sanitize($extraContext); - $event->setExtraContext($extraContext); + + $event->getExtraContext()->replaceData($extraContext); } return $event; diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php index 87847cc06..54f70ae4e 100644 --- a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php +++ b/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -58,7 +58,9 @@ public function process(Event $event) } } - return $event->setRequest($request); + $event->setRequest($request); + + return $event; } /** diff --git a/lib/Raven/Processor/SanitizeStacktraceProcessor.php b/lib/Raven/Processor/SanitizeStacktraceProcessor.php index 76612a61b..e548ef309 100644 --- a/lib/Raven/Processor/SanitizeStacktraceProcessor.php +++ b/lib/Raven/Processor/SanitizeStacktraceProcessor.php @@ -28,16 +28,16 @@ public function process(Event $event) { $stacktrace = $event->getStacktrace(); - if (null === $stacktrace) { - return $event; - } + if (null !== $stacktrace) { + foreach ($stacktrace->getFrames() as $frame) { + $frame->setPreContext(null); + $frame->setContextLine(null); + $frame->setPostContext(null); + } - foreach ($stacktrace->getFrames() as $frame) { - $frame->setPreContext(null); - $frame->setContextLine(null); - $frame->setPostContext(null); + $event->setStacktrace($stacktrace); } - return $event->setStacktrace($stacktrace); + return $event; } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 8ac014ec0..795c5de07 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -249,9 +249,9 @@ public function testCapture() $this->assertEquals($inputData['transaction'], $event->getTransaction()); $this->assertEquals($inputData['level'], $event->getLevel()); $this->assertEquals($inputData['logger'], $event->getLogger()); - $this->assertEquals($inputData['tags_context'], $event->getTagsContext()); - $this->assertEquals($inputData['extra_context'], $event->getExtraContext()); - $this->assertEquals($inputData['user_context'], $event->getUserContext()); + $this->assertEquals($inputData['tags_context'], $event->getTagsContext()->toArray()); + $this->assertEquals($inputData['extra_context'], $event->getExtraContext()->toArray()); + $this->assertEquals($inputData['user_context'], $event->getUserContext()->toArray()); } public function testGetLastEvent() diff --git a/tests/EventTest.php b/tests/EventTest.php index e5efbd8f7..7bf2e7940 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -11,6 +11,10 @@ use Raven\ClientBuilder; use Raven\ClientInterface; use Raven\Configuration; +use Raven\Context\Context; +use Raven\Context\RuntimeContext; +use Raven\Context\ServerOsContext; +use Raven\Context\TagsContext; use Raven\Event; /** @@ -92,6 +96,18 @@ public function testToArray() 'server_name' => $this->configuration->getServerName(), 'release' => $this->configuration->getRelease(), 'environment' => $this->configuration->getCurrentEnvironment(), + 'contexts' => [ + 'os' => [ + 'name' => php_uname('s'), + 'version' => php_uname('r'), + 'build' => php_uname('v'), + 'kernel_version' => php_uname('a'), + ], + 'runtime' => [ + 'name' => 'php', + 'version' => PHP_VERSION, + ], + ], ]; $event = new Event($this->configuration); @@ -101,8 +117,8 @@ public function testToArray() public function testToArrayWithMessage() { - $event = Event::create($this->configuration) - ->setMessage('foo bar'); + $event = new Event($this->configuration); + $event->setMessage('foo bar'); $data = $event->toArray(); @@ -118,8 +134,8 @@ public function testToArrayWithMessageWithParams() 'formatted' => 'foo bar', ]; - $event = Event::create($this->configuration) - ->setMessage('foo %s', ['bar']); + $event = new Event($this->configuration); + $event->setMessage('foo %s', ['bar']); $data = $event->toArray(); @@ -148,6 +164,41 @@ public function testToArrayWithBreadcrumbs() $this->assertSame($breadcrumbs, $data['breadcrumbs']); } + public function testGetServerOsContext() + { + $event = new Event($this->configuration); + + $this->assertInstanceOf(ServerOsContext::class, $event->getServerOsContext()); + } + + public function testGetRuntimeContext() + { + $event = new Event($this->configuration); + + $this->assertInstanceOf(RuntimeContext::class, $event->getRuntimeContext()); + } + + public function testGetUserContext() + { + $event = new Event($this->configuration); + + $this->assertInstanceOf(Context::class, $event->getUserContext()); + } + + public function testGetExtraContext() + { + $event = new Event($this->configuration); + + $this->assertInstanceOf(Context::class, $event->getExtraContext()); + } + + public function getTagsContext() + { + $event = new Event($this->configuration); + + $this->assertInstanceOf(TagsContext::class, $event->getTagsContext()); + } + /** * @dataProvider gettersAndSettersDataProvider */ @@ -172,11 +223,6 @@ public function gettersAndSettersDataProvider() ['serverName', 'local.host', ['server_name' => 'local.host']], ['release', '0.0.1', ['release' => '0.0.1']], ['modules', ['foo' => '0.0.1', 'bar' => '0.0.2'], ['modules' => ['foo' => '0.0.1', 'bar' => '0.0.2']]], - ['extraContext', ['foo' => 'bar'], ['extra' => ['foo' => 'bar']]], - ['tagsContext', ['bar' => 'foo'], ['tags' => ['bar' => 'foo']]], - ['userContext', ['bar' => 'baz'], ['user' => ['bar' => 'baz']]], - ['serverOsContext', ['foobar' => 'barfoo'], ['contexts' => ['os' => ['foobar' => 'barfoo']]]], - ['runtimeContext', ['barfoo' => 'foobar'], ['contexts' => ['runtime' => ['barfoo' => 'foobar']]]], ['fingerprint', ['foo', 'bar'], ['fingerprint' => ['foo', 'bar']]], ['environment', 'foo', ['environment' => 'foo']], ]; diff --git a/tests/Middleware/ContextInterfaceMiddlewareTest.php b/tests/Middleware/ContextInterfaceMiddlewareTest.php index be1cc14be..d5e34a86a 100644 --- a/tests/Middleware/ContextInterfaceMiddlewareTest.php +++ b/tests/Middleware/ContextInterfaceMiddlewareTest.php @@ -29,11 +29,8 @@ public function testInvoke($contextName, $initialData, $payloadData, $expectedDa $this->expectExceptionMessage($expectedExceptionMessage); } - $context = new Context(); - $context->setData($initialData); - - $configuration = new Configuration(); - $event = new Event($configuration); + $context = new Context($initialData); + $event = new Event(new Configuration()); $callbackInvoked = false; $callback = function (Event $eventArg) use ($contextName, $expectedData, &$callbackInvoked) { @@ -41,7 +38,7 @@ public function testInvoke($contextName, $initialData, $payloadData, $expectedDa return strtoupper($matches[0][1]); }, 'get_' . $contextName . '_context'); - $this->assertEquals($expectedData, $eventArg->$method()); + $this->assertEquals($expectedData, $eventArg->$method()->toArray()); $callbackInvoked = true; }; @@ -59,16 +56,31 @@ public function invokeDataProvider() return [ [ Context::CONTEXT_USER, - ['foo' => 'bar', 'foobaz' => 'bazfoo'], - ['foobaz' => 'bazfoo'], - ['foo' => 'bar', 'foobaz' => 'bazfoo'], + [ + 'foo' => 'bar', + 'foobaz' => 'bazfoo', + ], + [ + 'foobaz' => 'bazfoo', + ], + [ + 'foo' => 'bar', + 'foobaz' => 'bazfoo', + ], null, ], [ Context::CONTEXT_RUNTIME, - ['baz' => 'foo'], - ['barfoo' => 'foobar'], - ['baz' => 'foo', 'barfoo' => 'foobar'], + [ + 'name' => 'foo', + ], + [ + 'name' => 'foobar', + ], + [ + 'name' => 'foobar', + 'version' => PHP_VERSION, + ], null, ], [ @@ -80,16 +92,32 @@ public function invokeDataProvider() ], [ Context::CONTEXT_EXTRA, - ['bar' => 'foo'], - ['barbaz' => 'bazbar'], - ['bar' => 'foo', 'barbaz' => 'bazbar'], + [ + 'bar' => 'foo', + ], + [ + 'barbaz' => 'bazbar', + ], + [ + 'bar' => 'foo', + 'barbaz' => 'bazbar', + ], null, ], [ Context::CONTEXT_SERVER_OS, - ['foo' => 'baz'], - ['bazfoo' => 'foobaz'], - ['foo' => 'baz', 'bazfoo' => 'foobaz'], + [ + 'name' => 'baz', + ], + [ + 'name' => 'foobaz', + ], + [ + 'name' => 'foobaz', + 'version' => php_uname('r'), + 'build' => php_uname('v'), + 'kernel_version' => php_uname('a'), + ], null, ], [ diff --git a/tests/Middleware/SanitizerMiddlewareTest.php b/tests/Middleware/SanitizerMiddlewareTest.php index 6f5c87960..0febb53d8 100644 --- a/tests/Middleware/SanitizerMiddlewareTest.php +++ b/tests/Middleware/SanitizerMiddlewareTest.php @@ -23,52 +23,32 @@ public function testInvoke() { $event = new Event(new Configuration()); $event->setRequest(['bar' => 'baz']); - $event->setUserContext(['foo' => 'bar']); - $event->setTagsContext(['foo', 'bar']); - $event->setServerOsContext(['bar' => 'foo']); - $event->setRuntimeContext(['foo' => 'baz']); - $event->setExtraContext(['baz' => 'foo']); + $event->getUserContext()->replaceData(['foo' => 'bar']); + $event->getTagsContext()->replaceData(['foo', 'bar']); + $event->getServerOsContext()->replaceData(['name' => 'foo']); + $event->getRuntimeContext()->replaceData(['name' => 'baz']); + $event->getExtraContext()->replaceData(['baz' => 'foo']); /** @var Serializer|\PHPUnit_Framework_MockObject_MockObject $sanitizer */ $sanitizer = $this->createMock(Serializer::class); $sanitizer->expects($this->exactly(6)) ->method('serialize') - ->withConsecutive( - [ - $event->getRequest(), - 5, - ], - [ - $event->getUserContext(), - ], - [ - $event->getRuntimeContext(), - ], - [ - $event->getServerOsContext(), - ], - [ - $event->getExtraContext(), - ], - [ - $event->getTagsContext(), - ] - ) ->willReturnCallback(function ($eventData) { - // This is here just because otherwise the event object will - // not be updated if the new value being set is the same as - // the previous one - return array_flip($eventData); + foreach ($eventData as $key => $value) { + $eventData[$key] = strrev($value); + } + + return $eventData; }); $callbackInvoked = false; $callback = function (Event $eventArg) use (&$callbackInvoked) { - $this->assertEquals(['baz' => 'bar'], $eventArg->getRequest()); - $this->assertEquals(['bar' => 'foo'], $eventArg->getUserContext()); - $this->assertEquals(['baz' => 'foo'], $eventArg->getRuntimeContext()); - $this->assertEquals(['foo' => 'bar'], $eventArg->getServerOsContext()); - $this->assertEquals(['foo' => 'baz'], $eventArg->getExtraContext()); - $this->assertEquals(['foo' => 0, 'bar' => 1], $eventArg->getTagsContext()); + $this->assertArraySubset(['bar' => 'zab'], $eventArg->getRequest()); + $this->assertArraySubset(['foo' => 'rab'], $eventArg->getUserContext()); + $this->assertArraySubset(['name' => 'zab'], $eventArg->getRuntimeContext()); + $this->assertArraySubset(['name' => 'oof'], $eventArg->getServerOsContext()); + $this->assertArraySubset(['baz' => 'oof'], $eventArg->getExtraContext()); + $this->assertArraySubset(['oof', 'rab'], $eventArg->getTagsContext()); $callbackInvoked = true; }; diff --git a/tests/Middleware/UserInterfaceMiddlewareTest.php b/tests/Middleware/UserInterfaceMiddlewareTest.php index 43b4d1e39..be539540e 100644 --- a/tests/Middleware/UserInterfaceMiddlewareTest.php +++ b/tests/Middleware/UserInterfaceMiddlewareTest.php @@ -22,32 +22,32 @@ class UserInterfaceMiddlewareTest extends TestCase public function testInvoke() { $event = new Event(new Configuration()); - $event->setUserContext(['foo' => 'bar']); + $event->getUserContext()->setData(['foo' => 'bar']); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, &$invokationCount) { - $this->assertSame($event, $eventArg); + $callbackInvoked = false; + $callback = function (Event $eventArg) use (&$callbackInvoked) { + $this->assertArrayNotHasKey('ip_address', $eventArg->getUserContext()); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new UserInterfaceMiddleware(); $middleware($event, $callback); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked); } public function testInvokeWithRequest() { $event = new Event(new Configuration()); - $event->setUserContext(['foo' => 'bar']); + $event->getUserContext()->setData(['foo' => 'bar']); $request = new ServerRequest(); $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); $callbackInvoked = false; $callback = function (Event $eventArg) use (&$callbackInvoked) { - $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $eventArg->getUserContext()); + $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $eventArg->getUserContext()->toArray()); $callbackInvoked = true; }; diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Processor/SanitizeDataProcessorTest.php index f8d70e2ac..ed6bec26a 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Processor/SanitizeDataProcessorTest.php @@ -48,7 +48,7 @@ public function testProcess($inputData, $expectedData) } if (isset($inputData['extra_context'])) { - $event->setExtraContext($inputData['extra_context']); + $event->getExtraContext()->replaceData($inputData['extra_context']); } if (isset($inputData['exception'])) { @@ -72,8 +72,7 @@ public function testProcess($inputData, $expectedData) // We must convert the backtrace to a Stacktrace instance here because // PHPUnit executes the data provider before the setUp method and so // the client instance cannot be accessed from there - $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), - $event->getException()); + $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), $event->getException()); } } From 9d6750fc84b9ed626ae50accee7db826dbf41f97 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 29 Aug 2018 11:12:20 +0200 Subject: [PATCH 0365/1161] Cleanup the PHP version captured in the runtime context (#641) --- lib/Raven/Context/RuntimeContext.php | 3 +- lib/Raven/Util/PHPVersion.php | 27 ++++++++++++++++++ tests/Context/RuntimeContextTest.php | 5 ++-- tests/Util/PHPVersionTest.php | 41 ++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 lib/Raven/Util/PHPVersion.php create mode 100644 tests/Util/PHPVersionTest.php diff --git a/lib/Raven/Context/RuntimeContext.php b/lib/Raven/Context/RuntimeContext.php index 4abb2da1e..15dbd4175 100644 --- a/lib/Raven/Context/RuntimeContext.php +++ b/lib/Raven/Context/RuntimeContext.php @@ -11,6 +11,7 @@ namespace Raven\Context; +use Raven\Util\PHPVersion; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -154,7 +155,7 @@ private function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'name' => 'php', - 'version' => PHP_VERSION, + 'version' => PHPVersion::parseVersion(), ]); $resolver->setAllowedTypes('name', 'string'); diff --git a/lib/Raven/Util/PHPVersion.php b/lib/Raven/Util/PHPVersion.php new file mode 100644 index 000000000..d8d7b5918 --- /dev/null +++ b/lib/Raven/Util/PHPVersion.php @@ -0,0 +1,27 @@ +\d\.\d\.\d{1,2})(?-(beta|rc)-?(\d+)?(-dev)?)?/i'; + + /** + * @param string $version + * + * @return string + */ + public static function parseVersion($version = PHP_VERSION) + { + if (!preg_match(self::VERSION_PARSING_REGEX, $version, $matches)) { + return $version; + } + + $version = $matches['base']; + if (isset($matches['extra'])) { + $version .= $matches['extra']; + } + + return $version; + } +} diff --git a/tests/Context/RuntimeContextTest.php b/tests/Context/RuntimeContextTest.php index 35b738ebc..8d49a85a3 100644 --- a/tests/Context/RuntimeContextTest.php +++ b/tests/Context/RuntimeContextTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Raven\Context\RuntimeContext; +use Raven\Util\PHPVersion; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; @@ -88,7 +89,7 @@ public function valuesDataProvider() [], [ 'name' => 'php', - 'version' => PHP_VERSION, + 'version' => PHPVersion::parseVersion(), ], null, null, @@ -99,7 +100,7 @@ public function valuesDataProvider() ], [ 'name' => 'foo', - 'version' => PHP_VERSION, + 'version' => PHPVersion::parseVersion(), ], null, null, diff --git a/tests/Util/PHPVersionTest.php b/tests/Util/PHPVersionTest.php new file mode 100644 index 000000000..f553a3370 --- /dev/null +++ b/tests/Util/PHPVersionTest.php @@ -0,0 +1,41 @@ +assertSame($expected, PHPVersion::parseVersion($rawVersion)); + } + + public function versionProvider() + { + $baseVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; + $phpExtraVersions = [ + '' => $baseVersion, + '-1+ubuntu17.04.1+deb.sury.org+1' => $baseVersion, + '-beta3-1+ubuntu17.04.1+deb.sury.org+1' => "{$baseVersion}-beta3", + '-beta5-dev-1+ubuntu17.04.1+deb.sury.org+1' => "{$baseVersion}-beta5-dev", + '-rc-9-1+ubuntu17.04.1+deb.sury.org+1' => "{$baseVersion}-rc-9", + '-2~ubuntu16.04.1+deb.sury.org+1' => $baseVersion, + '-beta1-dev' => "{$baseVersion}-beta1-dev", + '-rc10' => "{$baseVersion}-rc10", + '-RC10' => "{$baseVersion}-RC10", + '-rc2-dev' => "{$baseVersion}-rc2-dev", + '-beta-2-dev' => "{$baseVersion}-beta-2-dev", + '-beta2' => "{$baseVersion}-beta2", + '-beta-9' => "{$baseVersion}-beta-9", + ]; + + foreach ($phpExtraVersions as $fullVersion => $parsedVersion) { + yield [$parsedVersion, $baseVersion . $fullVersion]; + } + } +} From 478586c083ba7cc6db87dd8923c166db15a6af8d Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 29 Aug 2018 11:52:22 +0200 Subject: [PATCH 0366/1161] Address review comments --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2b38c0614..a19e6c906 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,9 @@ $ git checkout -b releases/2.1.x 3. Update the hardcoded version tag in ``Client.php``: ```php -class Raven_Client +namespace Raven; + +class Client { const VERSION = '2.1.0'; } @@ -165,15 +167,17 @@ git checkout master 8. Add the next minor release to the ``CHANGES`` file: ``` -## 1.11.0 (unreleased) +## 2.1.0 (unreleased) ``` 9. Update the version in ``Client.php``: ```php -class Raven_Client +namespace Raven; + +class Client implements ClientInterface { - const VERSION = '1.11.x-dev'; + const VERSION = '2.1.x-dev'; } ``` @@ -182,7 +186,7 @@ class Raven_Client ```json "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "2.1.x-dev" } } ``` From 9b7f164cf7f09338e0c445009c3a10c132a9d487 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 12 Aug 2018 01:12:16 +0200 Subject: [PATCH 0367/1161] Remove the processors and transform them to middlewares --- composer.json | 1 + docs/middleware.rst | 29 +++++- docs/processor.rst | 77 --------------- lib/Raven/Client.php | 61 ++---------- lib/Raven/ClientBuilder.php | 93 ++++++------------- lib/Raven/ClientBuilderInterface.php | 30 ------ lib/Raven/ClientInterface.php | 18 +--- lib/Raven/Middleware/MiddlewareStack.php | 2 +- .../ProcessorMiddlewareInterface.php | 26 ++++++ .../SanitizeCookiesMiddleware.php} | 21 +++-- .../SanitizeDataMiddleware.php} | 93 ++++++++++--------- .../Middleware/SanitizeHttpBodyMiddleware.php | 49 ++++++++++ .../SanitizeHttpHeadersMiddleware.php} | 24 +++-- ...e.php => SanitizeStacktraceMiddleware.php} | 36 +++---- lib/Raven/Processor/ProcessorInterface.php | 38 -------- lib/Raven/Processor/ProcessorRegistry.php | 80 ---------------- .../Processor/RemoveHttpBodyProcessor.php | 40 -------- .../Processor/SanitizeStacktraceProcessor.php | 43 --------- tests/ClientBuilderTest.php | 24 ----- tests/ClientTest.php | 57 +++--------- tests/Middleware/ProcessorMiddlewareTest.php | 64 ------------- .../SanitizeCookiesMiddlewareTest.php} | 54 +++++------ .../SanitizeDataMiddlewareTest.php} | 82 ++++++++-------- .../SanitizeHttpBodyMiddlewareTest.php} | 38 +++++--- .../SanitizeHttpHeadersMiddlewareTest.php} | 48 ++++------ .../SanitizeStacktraceMiddlewareTest.php | 82 ++++++++++++++++ tests/Processor/ProcessorRegistryTest.php | 69 -------------- .../SanitizeStacktraceProcessorTest.php | 79 ---------------- 28 files changed, 446 insertions(+), 912 deletions(-) delete mode 100644 docs/processor.rst create mode 100644 lib/Raven/Middleware/ProcessorMiddlewareInterface.php rename lib/Raven/{Processor/SanitizeCookiesProcessor.php => Middleware/SanitizeCookiesMiddleware.php} (71%) rename lib/Raven/{Processor/SanitizeDataProcessor.php => Middleware/SanitizeDataMiddleware.php} (80%) create mode 100644 lib/Raven/Middleware/SanitizeHttpBodyMiddleware.php rename lib/Raven/{Processor/SanitizeHttpHeadersProcessor.php => Middleware/SanitizeHttpHeadersMiddleware.php} (60%) rename lib/Raven/Middleware/{ProcessorMiddleware.php => SanitizeStacktraceMiddleware.php} (53%) delete mode 100644 lib/Raven/Processor/ProcessorInterface.php delete mode 100644 lib/Raven/Processor/ProcessorRegistry.php delete mode 100644 lib/Raven/Processor/RemoveHttpBodyProcessor.php delete mode 100644 lib/Raven/Processor/SanitizeStacktraceProcessor.php delete mode 100644 tests/Middleware/ProcessorMiddlewareTest.php rename tests/{Processor/SanitizeCookiesProcessorTest.php => Middleware/SanitizeCookiesMiddlewareTest.php} (63%) rename tests/{Processor/SanitizeDataProcessorTest.php => Middleware/SanitizeDataMiddlewareTest.php} (74%) rename tests/{Processor/RemoveHttpBodyProcessorTest.php => Middleware/SanitizeHttpBodyMiddlewareTest.php} (63%) rename tests/{Processor/SanitizeHttpHeadersProcessorTest.php => Middleware/SanitizeHttpHeadersMiddlewareTest.php} (52%) create mode 100644 tests/Middleware/SanitizeStacktraceMiddlewareTest.php delete mode 100644 tests/Processor/ProcessorRegistryTest.php delete mode 100644 tests/Processor/SanitizeStacktraceProcessorTest.php diff --git a/composer.json b/composer.json index b8419eadb..b77f25c90 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ ], "require": { "php": "^5.6|^7.0", + "ext-json": "*", "ext-mbstring": "*", "php-http/async-client-implementation": "~1.0", "php-http/client-common": "~1.5", diff --git a/docs/middleware.rst b/docs/middleware.rst index c532503c1..da95d346e 100644 --- a/docs/middleware.rst +++ b/docs/middleware.rst @@ -27,6 +27,21 @@ several built-in middlewares whose list is: - ``UserInterfaceMiddleware``: adds some user-related information like the client IP address to the event. +There are also some "special" middlewares that should be executed after all the +other middlewares so that they can sanitize and remove sensitive information before +they reach the Sentry server (not all of them are enabled by default): + +- ``SanitizeHttpBodyMiddleware``: sanitizes the data sent as body of a POST + request. +- ``SanitizeCookiesMiddleware``: sanitizes the cookies sent with the request + by hiding sensitive information. +- ``SanitizeDataMiddleware``: sanitizes the data of the event by removing + sensitive information. +- ``SanitizeHttpHeadersMiddleware``: sanitizes the headers of the request by + hiding sensitive information. +- ``SanitizeStacktraceMiddleware``: sanitizes the captured stacktrace by + removing the excerpts of source code attached to each frame. + Writing a middleware ==================== @@ -53,7 +68,7 @@ middleware that customizes the message captured with an event can be written: class CustomMiddleware { - public function (Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { $event = $event->withMessage('hello world'); @@ -79,11 +94,19 @@ have the following priorities: - ``ExceptionInterfaceMiddleware``: 0 - ``MessageInterfaceMiddleware``: 0 - ``ModulesMiddleware``: 0 -- ``ProcessorMiddleware``: -250 (this middleware should always be at the end of - the chain) - ``RequestInterfaceMiddleware``: 0 - ``SanitizerMiddleware``: -255 (this middleware should always be the last one) - ``UserInterfaceMiddleware``: 0 +- ``SanitizeHttpBodyMiddleware``: -200 (this middleware should always be after + all "standard" middlewares) +- ``SanitizeCookiesMiddleware``: -200 (this middleware should always be after + all "standard" middlewares) +- ``SanitizeDataMiddleware``: -200 (this middleware should always be after + all "standard" middlewares) +- ``SanitizeHttpHeadersMiddleware``: -200 (this middleware should always be after + all "standard" middlewares) +- ``SanitizeStacktraceMiddleware``: -200 (this middleware should always be after + all "standard" middlewares) The higher the priority value is, the earlier a middleware will be executed in the chain. To add the middleware to the stack you can use the ``addMiddleware`` diff --git a/docs/processor.rst b/docs/processor.rst deleted file mode 100644 index 34941223d..000000000 --- a/docs/processor.rst +++ /dev/null @@ -1,77 +0,0 @@ -Processors -########## - -The processors are classes that are executed as the last step of the event -sending lifecycle before the event data is serialized and sent using the -configured transport. There are several built-in processors (not all are -enabled by default) whose list is: - -- ``RemoveHttpBodyProcessor``: sanitizes the data sent as body of a POST - request. -- ``SanitizeCookiesProcessor``: sanitizes the cookies sent with the request - by hiding sensitive information. -- ``SanitizeDataProcessor``: sanitizes the data of the event by removing - sensitive information. -- ``SanitizeHttpHeadersProcessor``: sanitizes the headers of the request by - hiding sensitive information. -- ``SanitizeStacktraceProcessor``: sanitizes the captured stacktrace by - removing the excerpts of source code attached to each frame. - -Writing a processor -=================== - -You can write your own processor by creating a class that implements the -``ProcessorInterface`` interface. - -.. code-block:: php - - use Raven\Event; - use Raven\Processor\ProcessorInterface; - - class MyCustomProcessor implements ProcessorInterface - { - public function process(Event $event) - { - // Do something on the event object instance - - return $event; - } - } - -Using a processor -================= - -The processors needs to be registered with the client instance before they are -used. Each one can have a priority which defines in which order they will run. -By default they have a priority of 0. The higher the priority value is, the -earlier a processor will be executed: this is similar to how the middlewares -work. You can add or remove the processors at runtime, and they will be executed -sequentially one after the other. The built-in processors have the following -priorities: - -- ``SanitizeCookiesProcessor``: 0 -- ``RemoveHttpBodyProcessor``: 0 -- ``SanitizeHttpHeadersProcessor``: 0 -- ``SanitizeDataProcessor``: -255 - -It's important to leave the ``SanitizeDataProcessor`` as the last processor so -that any sensitive information present in the event data will be sanitized -before being sent out from your network according to the configuration you -specified. To add a middleware to the client you can use the ``addProcessor`` -method which can be found in both the ``Client`` and ``ClientBuilder`` classes -while the ``removeProcessor`` can be used to remove the processors instead. - -.. code-block:: php - - use Raven\ClientBuilder; - use Raven\Processor\RemoveHttpBodyProcessor; - - $processor = new RemoveHttpBodyProcessor(); - - $clientBuilder = new ClientBuilder(); - $clientBuilder->addProcessor($processor, 10); - $clientBuilder->removeProcessor($processor); - - $client = $clientBuilder->getClient(); - $client->addProcessor($processor, -10); - $client->removeProcessor($processor); diff --git a/lib/Raven/Client.php b/lib/Raven/Client.php index 48baafe05..498838bb2 100644 --- a/lib/Raven/Client.php +++ b/lib/Raven/Client.php @@ -17,17 +17,7 @@ use Raven\Context\RuntimeContext; use Raven\Context\ServerOsContext; use Raven\Context\TagsContext; -use Raven\Middleware\BreadcrumbInterfaceMiddleware; -use Raven\Middleware\ContextInterfaceMiddleware; -use Raven\Middleware\ExceptionInterfaceMiddleware; -use Raven\Middleware\MessageInterfaceMiddleware; use Raven\Middleware\MiddlewareStack; -use Raven\Middleware\ProcessorMiddleware; -use Raven\Middleware\RequestInterfaceMiddleware; -use Raven\Middleware\SanitizerMiddleware; -use Raven\Middleware\UserInterfaceMiddleware; -use Raven\Processor\ProcessorInterface; -use Raven\Processor\ProcessorRegistry; use Raven\Transport\TransportInterface; use Zend\Diactoros\ServerRequestFactory; @@ -103,11 +93,6 @@ class Client implements ClientInterface */ private $transport; - /** - * @var ProcessorRegistry The registry of processors - */ - private $processorRegistry; - /** * @var TagsContext The tags context */ @@ -153,7 +138,6 @@ public function __construct(Configuration $config, TransportInterface $transport { $this->config = $config; $this->transport = $transport; - $this->processorRegistry = new ProcessorRegistry(); $this->tagsContext = new TagsContext(); $this->userContext = new Context(); $this->extraContext = new Context(); @@ -167,8 +151,6 @@ public function __construct(Configuration $config, TransportInterface $transport return $event; }); - $this->addDefaultMiddlewares(); - $request = ServerRequestFactory::fromGlobals(); $serverParams = $request->getServerParams(); @@ -181,6 +163,14 @@ public function __construct(Configuration $config, TransportInterface $transport } } + /** + * {@inheritdoc} + */ + public function getBreadcrumbsRecorder() + { + return $this->breadcrumbRecorder; + } + /** * {@inheritdoc} */ @@ -229,22 +219,6 @@ public function removeMiddleware(callable $middleware) $this->middlewareStack->removeMiddleware($middleware); } - /** - * {@inheritdoc} - */ - public function addProcessor(ProcessorInterface $processor, $priority = 0) - { - $this->processorRegistry->addProcessor($processor, $priority); - } - - /** - * {@inheritdoc} - */ - public function removeProcessor(ProcessorInterface $processor) - { - $this->processorRegistry->removeProcessor($processor); - } - /** * {@inheritdoc} */ @@ -474,23 +448,4 @@ public function getServerOsContext() { return $this->serverOsContext; } - - /** - * Adds the default middlewares to this client instance. - */ - private function addDefaultMiddlewares() - { - $this->addMiddleware(new SanitizerMiddleware($this->serializer), -255); - $this->addMiddleware(new ProcessorMiddleware($this->processorRegistry), -250); - $this->addMiddleware(new MessageInterfaceMiddleware()); - $this->addMiddleware(new RequestInterfaceMiddleware()); - $this->addMiddleware(new UserInterfaceMiddleware()); - $this->addMiddleware(new ContextInterfaceMiddleware($this->tagsContext, Context::CONTEXT_TAGS)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->userContext, Context::CONTEXT_USER)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->extraContext, Context::CONTEXT_EXTRA)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->runtimeContext, Context::CONTEXT_RUNTIME)); - $this->addMiddleware(new ContextInterfaceMiddleware($this->serverOsContext, Context::CONTEXT_SERVER_OS)); - $this->addMiddleware(new BreadcrumbInterfaceMiddleware($this->breadcrumbRecorder)); - $this->addMiddleware(new ExceptionInterfaceMiddleware($this)); - } } diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index f8c4b6119..d47cd26c4 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -24,12 +24,19 @@ use Http\Discovery\UriFactoryDiscovery; use Http\Message\MessageFactory; use Http\Message\UriFactory; +use Raven\Context\Context; use Raven\HttpClient\Authentication\SentryAuth; -use Raven\Processor\ProcessorInterface; -use Raven\Processor\RemoveHttpBodyProcessor; -use Raven\Processor\SanitizeCookiesProcessor; -use Raven\Processor\SanitizeDataProcessor; -use Raven\Processor\SanitizeHttpHeadersProcessor; +use Raven\Middleware\BreadcrumbInterfaceMiddleware; +use Raven\Middleware\ContextInterfaceMiddleware; +use Raven\Middleware\ExceptionInterfaceMiddleware; +use Raven\Middleware\MessageInterfaceMiddleware; +use Raven\Middleware\RequestInterfaceMiddleware; +use Raven\Middleware\SanitizeCookiesMiddleware; +use Raven\Middleware\SanitizeDataMiddleware; +use Raven\Middleware\SanitizeHttpBodyMiddleware; +use Raven\Middleware\SanitizeHttpHeadersMiddleware; +use Raven\Middleware\SanitizerMiddleware; +use Raven\Middleware\UserInterfaceMiddleware; use Raven\Transport\HttpTransport; use Raven\Transport\NullTransport; use Raven\Transport\TransportInterface; @@ -112,11 +119,6 @@ final class ClientBuilder implements ClientBuilderInterface */ private $middlewares = []; - /** - * @var array List of processors and their priorities - */ - private $processors = []; - /** * Class constructor. * @@ -125,7 +127,6 @@ final class ClientBuilder implements ClientBuilderInterface public function __construct(array $options = []) { $this->configuration = new Configuration($options); - $this->processors = self::getDefaultProcessors(); } /** @@ -236,40 +237,6 @@ public function getMiddlewares() return $this->middlewares; } - /** - * {@inheritdoc} - */ - public function addProcessor(ProcessorInterface $processor, $priority = 0) - { - $this->processors[] = [$processor, $priority]; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function removeProcessor(ProcessorInterface $processor) - { - foreach ($this->processors as $key => $value) { - if ($value[0] !== $processor) { - continue; - } - - unset($this->processors[$key]); - } - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProcessors() - { - return $this->processors; - } - /** * {@inheritdoc} */ @@ -281,9 +248,24 @@ public function getClient() $this->transport = $this->createTransportInstance(); $client = new Client($this->configuration, $this->transport); - - foreach ($this->processors as $value) { - $client->addProcessor($value[0], $value[1]); + $client->addMiddleware(new SanitizerMiddleware($client->getSerializer()), -255); + $client->addMiddleware(new SanitizeDataMiddleware(), -200); + $client->addMiddleware(new SanitizeCookiesMiddleware(), -200); + $client->addMiddleware(new SanitizeHttpBodyMiddleware(), -200); + $client->addMiddleware(new SanitizeHttpHeadersMiddleware(), -200); + $client->addMiddleware(new MessageInterfaceMiddleware()); + $client->addMiddleware(new RequestInterfaceMiddleware()); + $client->addMiddleware(new UserInterfaceMiddleware()); + $client->addMiddleware(new ContextInterfaceMiddleware($client->getTagsContext(), Context::CONTEXT_TAGS)); + $client->addMiddleware(new ContextInterfaceMiddleware($client->getUserContext(), Context::CONTEXT_USER)); + $client->addMiddleware(new ContextInterfaceMiddleware($client->getExtraContext(), Context::CONTEXT_EXTRA)); + $client->addMiddleware(new ContextInterfaceMiddleware($client->getRuntimeContext(), Context::CONTEXT_RUNTIME)); + $client->addMiddleware(new ContextInterfaceMiddleware($client->getServerOsContext(), Context::CONTEXT_SERVER_OS)); + $client->addMiddleware(new BreadcrumbInterfaceMiddleware($client->getBreadcrumbsRecorder())); + $client->addMiddleware(new ExceptionInterfaceMiddleware($client)); + + foreach ($this->middlewares as $middleware) { + $client->addMiddleware($middleware[0], $middleware[1]); } return $client; @@ -346,19 +328,4 @@ private function createTransportInstance() return new NullTransport(); } - - /** - * Returns a list of processors that are enabled by default. - * - * @return array - */ - private static function getDefaultProcessors() - { - return [ - [new SanitizeCookiesProcessor(), 0], - [new RemoveHttpBodyProcessor(), 0], - [new SanitizeHttpHeadersProcessor(), 0], - [new SanitizeDataProcessor(), -255], - ]; - } } diff --git a/lib/Raven/ClientBuilderInterface.php b/lib/Raven/ClientBuilderInterface.php index a972ad454..26899d07a 100644 --- a/lib/Raven/ClientBuilderInterface.php +++ b/lib/Raven/ClientBuilderInterface.php @@ -15,7 +15,6 @@ use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory; use Http\Message\UriFactory; -use Raven\Processor\ProcessorInterface; use Raven\Transport\TransportInterface; /** @@ -117,35 +116,6 @@ public function removeMiddleware(callable $middleware); */ public function getMiddlewares(); - /** - * Adds a new processor to the processors chain with the specified priority. - * - * @param ProcessorInterface $processor The processor instance - * @param int $priority The priority. The higher this value, - * the earlier a processor will be - * executed in the chain (defaults to 0) - * - * @return $this - */ - public function addProcessor(ProcessorInterface $processor, $priority = 0); - - /** - * Removes the given processor from the list. - * - * @param ProcessorInterface $processor The processor instance - * - * @return $this - */ - public function removeProcessor(ProcessorInterface $processor); - - /** - * Gets the list of processors that will be added to the client at the - * given priority. - * - * @return array - */ - public function getProcessors(); - /** * Gets the instance of the client built using the configured options. * diff --git a/lib/Raven/ClientInterface.php b/lib/Raven/ClientInterface.php index 61d5d7885..8c1325ad4 100644 --- a/lib/Raven/ClientInterface.php +++ b/lib/Raven/ClientInterface.php @@ -12,11 +12,11 @@ namespace Raven; use Raven\Breadcrumbs\Breadcrumb; +use Raven\Breadcrumbs\Recorder as BreadcrumbRecorder; use Raven\Context\Context; use Raven\Context\RuntimeContext; use Raven\Context\ServerOsContext; use Raven\Context\TagsContext; -use Raven\Processor\ProcessorInterface; /** * This interface must be implemented by all Raven client classes. @@ -57,21 +57,11 @@ public function addMiddleware(callable $middleware, $priority = 0); public function removeMiddleware(callable $middleware); /** - * Adds a new processor to the processors chain with the specified priority. + * Gets the breadcrumbs recorder. * - * @param ProcessorInterface $processor The processor instance - * @param int $priority The priority. The higher this value, - * the earlier a processor will be - * executed in the chain (defaults to 0) + * @return BreadcrumbRecorder */ - public function addProcessor(ProcessorInterface $processor, $priority = 0); - - /** - * Removes the given processor from the list. - * - * @param ProcessorInterface $processor The processor instance - */ - public function removeProcessor(ProcessorInterface $processor); + public function getBreadcrumbsRecorder(); /** * Records the given breadcrumb. diff --git a/lib/Raven/Middleware/MiddlewareStack.php b/lib/Raven/Middleware/MiddlewareStack.php index 7c2dc5b5a..2b9b35305 100644 --- a/lib/Raven/Middleware/MiddlewareStack.php +++ b/lib/Raven/Middleware/MiddlewareStack.php @@ -83,7 +83,7 @@ public function executeStack(Event $event, ServerRequestInterface $request = nul * * @param callable $middleware The middleware instance * @param int $priority The priority. The higher this value, the - * earlier a processor will be executed in + * earlier a middleware will be executed in * the chain (defaults to 0) * * @throws \RuntimeException If the method is called while the stack is dequeuing diff --git a/lib/Raven/Middleware/ProcessorMiddlewareInterface.php b/lib/Raven/Middleware/ProcessorMiddlewareInterface.php new file mode 100644 index 000000000..80c614f74 --- /dev/null +++ b/lib/Raven/Middleware/ProcessorMiddlewareInterface.php @@ -0,0 +1,26 @@ + + */ +interface ProcessorMiddlewareInterface +{ + /** + * This constant defines the mask string used to strip sensitive information. + */ + const STRING_MASK = '********'; +} diff --git a/lib/Raven/Processor/SanitizeCookiesProcessor.php b/lib/Raven/Middleware/SanitizeCookiesMiddleware.php similarity index 71% rename from lib/Raven/Processor/SanitizeCookiesProcessor.php rename to lib/Raven/Middleware/SanitizeCookiesMiddleware.php index 9b415cbd2..2930c3bd9 100644 --- a/lib/Raven/Processor/SanitizeCookiesProcessor.php +++ b/lib/Raven/Middleware/SanitizeCookiesMiddleware.php @@ -9,19 +9,20 @@ * file that was distributed with this source code. */ -namespace Raven\Processor; +namespace Raven\Middleware; +use Psr\Http\Message\ServerRequestInterface; use Raven\Event; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\OptionsResolver; /** - * This processor sanitizes the cookies to ensure no sensitive information are + * This middleware sanitizes the cookies to ensure no sensitive information are * sent to the server. * * @author Stefano Arlandini */ -final class SanitizeCookiesProcessor implements ProcessorInterface +final class SanitizeCookiesMiddleware implements ProcessorMiddlewareInterface { /** * @var array The configuration options @@ -47,9 +48,17 @@ public function __construct(array $options = []) } /** - * {@inheritdoc} + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event */ - public function process(Event $event) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { $request = $event->getRequest(); @@ -77,7 +86,7 @@ public function process(Event $event) $event->setRequest($request); - return $event; + return $next($event, $request, $exception, $payload); } /** diff --git a/lib/Raven/Processor/SanitizeDataProcessor.php b/lib/Raven/Middleware/SanitizeDataMiddleware.php similarity index 80% rename from lib/Raven/Processor/SanitizeDataProcessor.php rename to lib/Raven/Middleware/SanitizeDataMiddleware.php index a3c96d388..a713ea55a 100644 --- a/lib/Raven/Processor/SanitizeDataProcessor.php +++ b/lib/Raven/Middleware/SanitizeDataMiddleware.php @@ -11,8 +11,9 @@ * file that was distributed with this source code. */ -namespace Raven\Processor; +namespace Raven\Middleware; +use Psr\Http\Message\ServerRequestInterface; use Raven\Event; use Raven\Stacktrace; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -24,7 +25,7 @@ * @author David Cramer * @author Stefano Arlandini */ -final class SanitizeDataProcessor implements ProcessorInterface +final class SanitizeDataMiddleware implements ProcessorMiddlewareInterface { /** * @var array The configuration options @@ -46,21 +47,42 @@ public function __construct(array $options = []) } /** - * Configures the options for this processor. + * Collects the needed data and sets it in the given event object. * - * @param OptionsResolver $resolver The resolver for the options + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event */ - private function configureOptions(OptionsResolver $resolver) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - $resolver->setDefaults([ - 'fields_re' => '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', - 'values_re' => '/^(?:\d[ -]*?){13,19}$/', - 'session_cookie_name' => ini_get('session.name'), - ]); + $exception = $event->getException(); + $stacktrace = $event->getStacktrace(); + $request = $event->getRequest(); + $extraContext = $event->getExtraContext()->toArray(); - $resolver->setAllowedTypes('fields_re', 'string'); - $resolver->setAllowedTypes('values_re', 'string'); - $resolver->setAllowedTypes('session_cookie_name', 'string'); + if (!empty($exception)) { + $event->setException($this->sanitizeException($exception)); + } + + if ($stacktrace) { + $event->setStacktrace($this->sanitizeStacktrace($stacktrace)); + } + + if (!empty($request)) { + $event->setRequest($this->sanitizeHttp($request)); + } + + if (!empty($extraContext)) { + $this->sanitize($extraContext); + + $event->getExtraContext()->replaceData($extraContext); + } + + return $next($event, $request, $exception, $payload); } /** @@ -68,7 +90,7 @@ private function configureOptions(OptionsResolver $resolver) * * @param array $data Associative array to be sanitized */ - public function sanitize(&$data) + private function sanitize(&$data) { foreach ($data as $key => &$item) { if (preg_match($this->options['fields_re'], $key)) { @@ -95,7 +117,7 @@ public function sanitize(&$data) } } - public function sanitizeException(&$data) + private function sanitizeException(&$data) { foreach ($data['values'] as &$value) { if (!isset($value['stacktrace'])) { @@ -108,7 +130,7 @@ public function sanitizeException(&$data) return $data; } - public function sanitizeHttp(&$data) + private function sanitizeHttp(&$data) { if (!empty($data['cookies']) && \is_array($data['cookies'])) { $cookies = &$data['cookies']; @@ -129,7 +151,7 @@ public function sanitizeHttp(&$data) * * @return Stacktrace */ - public function sanitizeStacktrace($data) + private function sanitizeStacktrace($data) { foreach ($data->getFrames() as &$frame) { if (empty($frame->getVars())) { @@ -146,33 +168,20 @@ public function sanitizeStacktrace($data) } /** - * {@inheritdoc} + * Configures the options for this processor. + * + * @param OptionsResolver $resolver The resolver for the options */ - public function process(Event $event) + private function configureOptions(OptionsResolver $resolver) { - $exception = $event->getException(); - $stacktrace = $event->getStacktrace(); - $request = $event->getRequest(); - $extraContext = $event->getExtraContext()->toArray(); - - if (!empty($exception)) { - $event->setException($this->sanitizeException($exception)); - } - - if ($stacktrace) { - $event->setStacktrace($this->sanitizeStacktrace($stacktrace)); - } - - if (!empty($request)) { - $event->setRequest($this->sanitizeHttp($request)); - } - - if (!empty($extraContext)) { - $this->sanitize($extraContext); - - $event->getExtraContext()->replaceData($extraContext); - } + $resolver->setDefaults([ + 'fields_re' => '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', + 'values_re' => '/^(?:\d[ -]*?){13,19}$/', + 'session_cookie_name' => ini_get('session.name'), + ]); - return $event; + $resolver->setAllowedTypes('fields_re', 'string'); + $resolver->setAllowedTypes('values_re', 'string'); + $resolver->setAllowedTypes('session_cookie_name', 'string'); } } diff --git a/lib/Raven/Middleware/SanitizeHttpBodyMiddleware.php b/lib/Raven/Middleware/SanitizeHttpBodyMiddleware.php new file mode 100644 index 000000000..ab89ef2ff --- /dev/null +++ b/lib/Raven/Middleware/SanitizeHttpBodyMiddleware.php @@ -0,0 +1,49 @@ + + */ +final class SanitizeHttpBodyMiddleware implements ProcessorMiddlewareInterface +{ + /** + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event + */ + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) + { + $request = $event->getRequest(); + + if (isset($request['method']) && \in_array(strtoupper($request['method']), ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { + $request['data'] = self::STRING_MASK; + } + + $event->setRequest($request); + + return $next($event, $request, $exception, $payload); + } +} diff --git a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/lib/Raven/Middleware/SanitizeHttpHeadersMiddleware.php similarity index 60% rename from lib/Raven/Processor/SanitizeHttpHeadersProcessor.php rename to lib/Raven/Middleware/SanitizeHttpHeadersMiddleware.php index 54f70ae4e..81ff9e940 100644 --- a/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php +++ b/lib/Raven/Middleware/SanitizeHttpHeadersMiddleware.php @@ -9,18 +9,19 @@ * file that was distributed with this source code. */ -namespace Raven\Processor; +namespace Raven\Middleware; +use Psr\Http\Message\ServerRequestInterface; use Raven\Event; use Symfony\Component\OptionsResolver\OptionsResolver; /** - * This processor sanitizes the configured HTTP headers to ensure no sensitive + * This middleware sanitizes the configured HTTP headers to ensure no sensitive * informations are sent to the server. * * @author Stefano Arlandini */ -final class SanitizeHttpHeadersProcessor implements ProcessorInterface +final class SanitizeHttpHeadersMiddleware implements ProcessorMiddlewareInterface { /** * @var array The configuration options @@ -42,9 +43,17 @@ public function __construct(array $options = []) } /** - * {@inheritdoc} + * Collects the needed data and sets it in the given event object. + * + * @param Event $event The event being processed + * @param callable $next The next middleware to call + * @param ServerRequestInterface|null $request The request, if available + * @param \Exception|\Throwable|null $exception The thrown exception, if available + * @param array $payload Additional data + * + * @return Event */ - public function process(Event $event) + public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { $request = $event->getRequest(); @@ -58,9 +67,12 @@ public function process(Event $event) } } + // Break the reference and free some memory + unset($value); + $event->setRequest($request); - return $event; + return $next($event, $request, $exception, $payload); } /** diff --git a/lib/Raven/Middleware/ProcessorMiddleware.php b/lib/Raven/Middleware/SanitizeStacktraceMiddleware.php similarity index 53% rename from lib/Raven/Middleware/ProcessorMiddleware.php rename to lib/Raven/Middleware/SanitizeStacktraceMiddleware.php index 100351143..d9c040822 100644 --- a/lib/Raven/Middleware/ProcessorMiddleware.php +++ b/lib/Raven/Middleware/SanitizeStacktraceMiddleware.php @@ -13,33 +13,17 @@ use Psr\Http\Message\ServerRequestInterface; use Raven\Event; -use Raven\Processor\ProcessorRegistry; /** - * This middleware loops through all registered processors and execute them - * in their order. + * This middleware removes the `pre_context`, `context_line` and `post_context` + * information from all exception frames captured by an event. * * @author Stefano Arlandini */ -final class ProcessorMiddleware +final class SanitizeStacktraceMiddleware implements ProcessorMiddlewareInterface { /** - * @var ProcessorRegistry The registry of processors - */ - private $processorRegistry; - - /** - * Constructor. - * - * @param ProcessorRegistry $processorRegistry The registry of processors - */ - public function __construct(ProcessorRegistry $processorRegistry) - { - $this->processorRegistry = $processorRegistry; - } - - /** - * Invokes all the processors to process the event before it's sent. + * Collects the needed data and sets it in the given event object. * * @param Event $event The event being processed * @param callable $next The next middleware to call @@ -51,12 +35,16 @@ public function __construct(ProcessorRegistry $processorRegistry) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - foreach ($this->processorRegistry->getProcessors() as $processor) { - $event = $processor->process($event); + $stacktrace = $event->getStacktrace(); - if (!$event instanceof Event) { - throw new \UnexpectedValueException(sprintf('The processor must return an instance of the "%s" class.', Event::class)); + if (null !== $stacktrace) { + foreach ($stacktrace->getFrames() as $frame) { + $frame->setPreContext(null); + $frame->setContextLine(null); + $frame->setPostContext(null); } + + $event->setStacktrace($stacktrace); } return $next($event, $request, $exception, $payload); diff --git a/lib/Raven/Processor/ProcessorInterface.php b/lib/Raven/Processor/ProcessorInterface.php deleted file mode 100644 index c203a0816..000000000 --- a/lib/Raven/Processor/ProcessorInterface.php +++ /dev/null @@ -1,38 +0,0 @@ - - */ -interface ProcessorInterface -{ - /** - * This constant defines the mask string used to strip sensitive informations. - */ - const STRING_MASK = '********'; - - /** - * Process and sanitize data, modifying the existing value if necessary. - * - * @param Event $event The event object - * - * @return Event - */ - public function process(Event $event); -} diff --git a/lib/Raven/Processor/ProcessorRegistry.php b/lib/Raven/Processor/ProcessorRegistry.php deleted file mode 100644 index acef94295..000000000 --- a/lib/Raven/Processor/ProcessorRegistry.php +++ /dev/null @@ -1,80 +0,0 @@ - - */ -class ProcessorRegistry -{ - /** - * @var array List of processors sorted by priority - */ - private $processors = []; - - /** - * @var array - */ - private $sortedProcessors = []; - - /** - * Registers the given processor. - * - * @param ProcessorInterface $processor The processor instance - * @param int $priority The priority at which the processor must run - */ - public function addProcessor(ProcessorInterface $processor, $priority = 0) - { - $this->processors[$priority][] = $processor; - - unset($this->sortedProcessors); - } - - /** - * Removes the given processor from the list of available ones. - * - * @param ProcessorInterface $processor The processor instance - */ - public function removeProcessor(ProcessorInterface $processor) - { - foreach ($this->processors as $priority => $processors) { - foreach ($processors as $key => $value) { - if ($value === $processor) { - unset($this->processors[$priority][$key], $this->sortedProcessors); - } - } - } - } - - /** - * Gets the processors sorted by priority. - * - * @return ProcessorInterface[] - */ - public function getProcessors() - { - if (empty($this->processors)) { - return []; - } - - if (empty($this->sortedProcessors)) { - krsort($this->processors); - - $this->sortedProcessors = array_merge(...$this->processors); - } - - return $this->sortedProcessors; - } -} diff --git a/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/lib/Raven/Processor/RemoveHttpBodyProcessor.php deleted file mode 100644 index 8bc76316e..000000000 --- a/lib/Raven/Processor/RemoveHttpBodyProcessor.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ -final class RemoveHttpBodyProcessor implements ProcessorInterface -{ - /** - * {@inheritdoc} - */ - public function process(Event $event) - { - $request = $event->getRequest(); - - if (isset($request['method']) && \in_array(strtoupper($request['method']), ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { - $request['data'] = self::STRING_MASK; - } - - $event->setRequest($request); - - return $event; - } -} diff --git a/lib/Raven/Processor/SanitizeStacktraceProcessor.php b/lib/Raven/Processor/SanitizeStacktraceProcessor.php deleted file mode 100644 index e548ef309..000000000 --- a/lib/Raven/Processor/SanitizeStacktraceProcessor.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ -final class SanitizeStacktraceProcessor implements ProcessorInterface -{ - /** - * {@inheritdoc} - */ - public function process(Event $event) - { - $stacktrace = $event->getStacktrace(); - - if (null !== $stacktrace) { - foreach ($stacktrace->getFrames() as $frame) { - $frame->setPreContext(null); - $frame->setContextLine(null); - $frame->setPostContext(null); - } - - $event->setStacktrace($stacktrace); - } - - return $event; - } -} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 60e107fa0..225b3788c 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -21,7 +21,6 @@ use Raven\Client; use Raven\ClientBuilder; use Raven\Configuration; -use Raven\Processor\ProcessorInterface; use Raven\Transport\HttpTransport; use Raven\Transport\NullTransport; use Raven\Transport\TransportInterface; @@ -166,29 +165,6 @@ public function testRemoveMiddleware() $this->assertEquals([[$middleware1, -10]], $clientBuilder->getMiddlewares()); } - public function testAddProcessor() - { - /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ - $processor = $this->createMock(ProcessorInterface::class); - - $clientBuilder = new ClientBuilder(); - $clientBuilder->addProcessor($processor, -10); - - $this->assertContains([$processor, -10], $clientBuilder->getProcessors()); - } - - public function testRemoveProcessor() - { - /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ - $processor = $this->createMock(ProcessorInterface::class); - - $clientBuilder = new ClientBuilder(); - $clientBuilder->addProcessor($processor, -10); - $clientBuilder->removeProcessor($processor); - - $this->assertNotContains([$processor, -10], $clientBuilder->getProcessors()); - } - public function testGetClient() { $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 795c5de07..7e87a44d8 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidFactory; +use Raven\Breadcrumbs\Recorder as BreadcrumbsRecorder; use Raven\Client; use Raven\ClientBuilder; use Raven\Context\Context; @@ -22,11 +23,10 @@ use Raven\Context\TagsContext; use Raven\Event; use Raven\Middleware\MiddlewareStack; -use Raven\Processor\ProcessorInterface; -use Raven\Processor\ProcessorRegistry; use Raven\ReprSerializer; use Raven\Serializer; use Raven\Tests\Fixtures\classes\CarelessException; +use Raven\TransactionStack; use Raven\Transport\TransportInterface; class ClientTest extends TestCase @@ -50,11 +50,18 @@ public function testConstructorInitializesTransactionStackInCli() $this->assertEmpty($client->getTransactionStack()); } + public function testGetBreadcrumbsRecorder() + { + $client = ClientBuilder::create()->getClient(); + + $this->assertInstanceOf(BreadcrumbsRecorder::class, $client->getBreadcrumbsRecorder()); + } + public function testGetTransactionStack() { $client = ClientBuilder::create()->getClient(); - $this->assertAttributeSame($client->getTransactionStack(), 'transactionStack', $client); + $this->assertInstanceOf(TransactionStack::class, $client->getTransactionStack()); } public function testAddMiddleware() @@ -97,46 +104,6 @@ public function testRemoveMiddleware() $client->removeMiddleware($middleware); } - public function testAddProcessor() - { - /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ - $processor = $this->createMock(ProcessorInterface::class); - - $processorRegistry = $this->createMock(ProcessorRegistry::class); - $processorRegistry->expects($this->once()) - ->method('addProcessor') - ->with($processor, -10); - - $client = ClientBuilder::create()->getClient(); - - $reflectionProperty = new \ReflectionProperty($client, 'processorRegistry'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($client, $processorRegistry); - $reflectionProperty->setAccessible(false); - - $client->addProcessor($processor, -10); - } - - public function testRemoveProcessor() - { - /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ - $processor = $this->createMock(ProcessorInterface::class); - - $processorRegistry = $this->createMock(ProcessorRegistry::class); - $processorRegistry->expects($this->once()) - ->method('removeProcessor') - ->with($processor); - - $client = ClientBuilder::create()->getClient(); - - $reflectionProperty = new \ReflectionProperty($client, 'processorRegistry'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($client, $processorRegistry); - $reflectionProperty->setAccessible(false); - - $client->removeProcessor($processor); - } - public function testCaptureMessage() { /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ @@ -561,7 +528,9 @@ private function createCarelessExceptionWithStacktrace() */ private function clearLastError() { - $handler = function () { return false; }; + $handler = function () { + return false; + }; set_error_handler($handler); @trigger_error(''); diff --git a/tests/Middleware/ProcessorMiddlewareTest.php b/tests/Middleware/ProcessorMiddlewareTest.php deleted file mode 100644 index 4d642f4c5..000000000 --- a/tests/Middleware/ProcessorMiddlewareTest.php +++ /dev/null @@ -1,64 +0,0 @@ -getClient(); - $event = new Event($client->getConfig()); - $processorRegistry = $this->getObjectAttribute($client, 'processorRegistry'); - - /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ - $processor = $this->createMock(ProcessorInterface::class); - $processor->expects($this->once()) - ->method('process') - ->willReturnArgument(0); - - $client->addProcessor($processor); - - $middleware = new ProcessorMiddleware($processorRegistry); - $middleware($event, function () { - // Do nothing, it's just a middleware added to end the chain - }); - } - - /** - * @expectedException \UnexpectedValueException - * @expectedExceptionMessage The processor must return an instance of the "Raven\Event" class. - */ - public function testInvokeProcessorThatReturnsNothingThrows() - { - $client = ClientBuilder::create()->getClient(); - $event = new Event($client->getConfig()); - $processorRegistry = $this->getObjectAttribute($client, 'processorRegistry'); - - /** @var ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject $processor */ - $processor = $this->createMock(ProcessorInterface::class); - $processor->expects($this->once()) - ->method('process'); - - $client->addProcessor($processor); - - $middleware = new ProcessorMiddleware($processorRegistry); - $middleware($event, function () { - // Do nothing, it's just a middleware added to end the chain - }); - } -} diff --git a/tests/Processor/SanitizeCookiesProcessorTest.php b/tests/Middleware/SanitizeCookiesMiddlewareTest.php similarity index 63% rename from tests/Processor/SanitizeCookiesProcessorTest.php rename to tests/Middleware/SanitizeCookiesMiddlewareTest.php index d85bb728b..cbe2f351f 100644 --- a/tests/Processor/SanitizeCookiesProcessorTest.php +++ b/tests/Middleware/SanitizeCookiesMiddlewareTest.php @@ -9,44 +9,33 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Processor; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\ClientBuilder; -use Raven\ClientInterface; +use Raven\Configuration; use Raven\Event; -use Raven\Processor\SanitizeCookiesProcessor; +use Raven\Middleware\SanitizeCookiesMiddleware; -class SanitizeCookiesProcessorTest extends TestCase +class SanitizeCookiesMiddlewareTest extends TestCase { - /** - * @var ClientInterface - */ - protected $client; - - protected function setUp() - { - $this->client = ClientBuilder::create()->getClient(); - } - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException * @expectedExceptionMessage You can configure only one of "only" and "except" options. */ public function testConstructorThrowsIfBothOnlyAndExceptOptionsAreSet() { - new SanitizeCookiesProcessor([ + new SanitizeCookiesMiddleware([ 'only' => ['foo'], 'except' => ['bar'], ]); } /** - * @dataProvider processDataProvider + * @dataProvider invokeDataProvider */ - public function testProcess($options, $expectedData) + public function testInvoke($options, $expectedData) { - $event = new Event($this->client->getConfig()); + $event = new Event(new Configuration()); $event->setRequest([ 'foo' => 'bar', 'cookies' => [ @@ -59,16 +48,23 @@ public function testProcess($options, $expectedData) ], ]); - $processor = new SanitizeCookiesProcessor($options); - $event = $processor->process($event); + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($expectedData, &$callbackInvoked) { + $request = $eventArg->getRequest(); + + $this->assertArraySubset($expectedData, $request); + $this->assertArrayNotHasKey('cookie', $request['headers']); + + $callbackInvoked = true; + }; - $requestData = $event->getRequest(); + $middleware = new SanitizeCookiesMiddleware($options); + $middleware($event, $callback); - $this->assertArraySubset($expectedData, $requestData); - $this->assertArrayNotHasKey('cookie', $requestData['headers']); + $this->assertTrue($callbackInvoked); } - public function processDataProvider() + public function invokeDataProvider() { return [ [ @@ -76,8 +72,8 @@ public function processDataProvider() [ 'foo' => 'bar', 'cookies' => [ - 'foo' => SanitizeCookiesProcessor::STRING_MASK, - 'bar' => SanitizeCookiesProcessor::STRING_MASK, + 'foo' => SanitizeCookiesMiddleware::STRING_MASK, + 'bar' => SanitizeCookiesMiddleware::STRING_MASK, ], 'headers' => [ 'another-header' => 'foo', @@ -91,7 +87,7 @@ public function processDataProvider() [ 'foo' => 'bar', 'cookies' => [ - 'foo' => SanitizeCookiesProcessor::STRING_MASK, + 'foo' => SanitizeCookiesMiddleware::STRING_MASK, 'bar' => 'foo', ], 'headers' => [ @@ -107,7 +103,7 @@ public function processDataProvider() 'foo' => 'bar', 'cookies' => [ 'foo' => 'bar', - 'bar' => SanitizeCookiesProcessor::STRING_MASK, + 'bar' => SanitizeCookiesMiddleware::STRING_MASK, ], 'headers' => [ 'another-header' => 'foo', diff --git a/tests/Processor/SanitizeDataProcessorTest.php b/tests/Middleware/SanitizeDataMiddlewareTest.php similarity index 74% rename from tests/Processor/SanitizeDataProcessorTest.php rename to tests/Middleware/SanitizeDataMiddlewareTest.php index ed6bec26a..04d2c4152 100644 --- a/tests/Processor/SanitizeDataProcessorTest.php +++ b/tests/Middleware/SanitizeDataMiddlewareTest.php @@ -9,37 +9,31 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Processor; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\ClientBuilder; use Raven\ClientInterface; use Raven\Event; -use Raven\Processor\SanitizeDataProcessor; +use Raven\Middleware\SanitizeDataMiddleware; use Raven\Stacktrace; -class SanitizeDataProcessorTest extends TestCase +class SanitizeDataMiddlewareTest extends TestCase { /** * @var ClientInterface */ protected $client; - /** - * @var SanitizeDataProcessor - */ - protected $processor; - protected function setUp() { $this->client = ClientBuilder::create()->getClient(); - $this->processor = new SanitizeDataProcessor(); } /** - * @dataProvider processDataProvider + * @dataProvider invokeDataProvider */ - public function testProcess($inputData, $expectedData) + public function testInvoke($inputData, $expectedData) { $event = new Event($this->client->getConfig()); @@ -58,25 +52,33 @@ public function testProcess($inputData, $expectedData) $event->setException($this->convertExceptionValuesToStacktrace($expectedData['exception'])); } - $event = $this->processor->process($event); + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($expectedData, &$callbackInvoked) { + if (isset($expectedData['request'])) { + $this->assertArraySubset($expectedData['request'], $eventArg->getRequest()); + } - if (isset($expectedData['request'])) { - $this->assertArraySubset($expectedData['request'], $event->getRequest()); - } + if (isset($expectedData['extra_context'])) { + $this->assertArraySubset($expectedData['extra_context'], $eventArg->getExtraContext()); + } - if (isset($expectedData['extra_context'])) { - $this->assertArraySubset($expectedData['extra_context'], $event->getExtraContext()); - } + if (isset($expectedData['exception'])) { + // We must convert the backtrace to a Stacktrace instance here because + // PHPUnit executes the data provider before the setUp method and so + // the client instance cannot be accessed from there + $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), $eventArg->getException()); + } - if (isset($expectedData['exception'])) { - // We must convert the backtrace to a Stacktrace instance here because - // PHPUnit executes the data provider before the setUp method and so - // the client instance cannot be accessed from there - $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), $event->getException()); - } + $callbackInvoked = true; + }; + + $middleware = new SanitizeDataMiddleware(); + $middleware($event, $callback); + + $this->assertTrue($callbackInvoked); } - public function processDataProvider() + public function invokeDataProvider() { return [ [ @@ -96,11 +98,11 @@ public function processDataProvider() 'request' => [ 'data' => [ 'foo' => 'bar', - 'password' => SanitizeDataProcessor::STRING_MASK, - 'the_secret' => SanitizeDataProcessor::STRING_MASK, - 'a_password_here' => SanitizeDataProcessor::STRING_MASK, - 'mypasswd' => SanitizeDataProcessor::STRING_MASK, - 'authorization' => SanitizeDataProcessor::STRING_MASK, + 'password' => SanitizeDataMiddleware::STRING_MASK, + 'the_secret' => SanitizeDataMiddleware::STRING_MASK, + 'a_password_here' => SanitizeDataMiddleware::STRING_MASK, + 'mypasswd' => SanitizeDataMiddleware::STRING_MASK, + 'authorization' => SanitizeDataMiddleware::STRING_MASK, ], ], ], @@ -116,7 +118,7 @@ public function processDataProvider() [ 'request' => [ 'cookies' => [ - ini_get('session.name') => SanitizeDataProcessor::STRING_MASK, + ini_get('session.name') => SanitizeDataMiddleware::STRING_MASK, ], ], ], @@ -129,7 +131,7 @@ public function processDataProvider() ], [ 'extra_context' => [ - 'ccnumba' => SanitizeDataProcessor::STRING_MASK, + 'ccnumba' => SanitizeDataMiddleware::STRING_MASK, ], ], ], @@ -141,7 +143,7 @@ public function processDataProvider() ], [ 'extra_context' => [ - 'ccnumba' => SanitizeDataProcessor::STRING_MASK, + 'ccnumba' => SanitizeDataMiddleware::STRING_MASK, ], ], ], @@ -182,7 +184,7 @@ public function processDataProvider() [ 'args' => [ [ - 'password' => SanitizeDataProcessor::STRING_MASK, + 'password' => SanitizeDataMiddleware::STRING_MASK, ], ], ], @@ -193,7 +195,7 @@ public function processDataProvider() [ 'args' => [ [ - 'password' => SanitizeDataProcessor::STRING_MASK, + 'password' => SanitizeDataMiddleware::STRING_MASK, ], ], ], @@ -225,13 +227,13 @@ public function processDataProvider() 'extra_context' => [ 'foobar' => 'some-data', 'authorization' => [ - 'foo' => SanitizeDataProcessor::STRING_MASK, - 'bar' => SanitizeDataProcessor::STRING_MASK, + 'foo' => SanitizeDataMiddleware::STRING_MASK, + 'bar' => SanitizeDataMiddleware::STRING_MASK, 'baz' => [ - 'nested1' => SanitizeDataProcessor::STRING_MASK, - 'nested2' => SanitizeDataProcessor::STRING_MASK, + 'nested1' => SanitizeDataMiddleware::STRING_MASK, + 'nested2' => SanitizeDataMiddleware::STRING_MASK, 'nested3' => [ - 'deep' => SanitizeDataProcessor::STRING_MASK, + 'deep' => SanitizeDataMiddleware::STRING_MASK, ], ], ], diff --git a/tests/Processor/RemoveHttpBodyProcessorTest.php b/tests/Middleware/SanitizeHttpBodyMiddlewareTest.php similarity index 63% rename from tests/Processor/RemoveHttpBodyProcessorTest.php rename to tests/Middleware/SanitizeHttpBodyMiddlewareTest.php index eec56b10a..9efd578e6 100644 --- a/tests/Processor/RemoveHttpBodyProcessorTest.php +++ b/tests/Middleware/SanitizeHttpBodyMiddlewareTest.php @@ -9,15 +9,15 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Processor; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; use Raven\ClientBuilder; use Raven\ClientInterface; use Raven\Event; -use Raven\Processor\RemoveHttpBodyProcessor; +use Raven\Middleware\SanitizeHttpBodyMiddleware; -class RemoveHttpBodyProcessorTest extends TestCase +class SanitizeHttpBodyMiddlewareTest extends TestCase { /** * @var ClientInterface @@ -25,30 +25,38 @@ class RemoveHttpBodyProcessorTest extends TestCase protected $client; /** - * @var RemoveHttpBodyProcessor|\PHPUnit_Framework_MockObject_MockObject + * @var SanitizeHttpBodyMiddleware|\PHPUnit_Framework_MockObject_MockObject */ - protected $processor; + protected $middleware; protected function setUp() { $this->client = ClientBuilder::create()->getClient(); - $this->processor = new RemoveHttpBodyProcessor(); + $this->middleware = new SanitizeHttpBodyMiddleware(); } /** - * @dataProvider processDataProvider + * @dataProvider invokeDataProvider */ - public function testProcess($inputData, $expectedData) + public function testInvoke($inputData, $expectedData) { $event = new Event($this->client->getConfig()); $event->setRequest($inputData); - $event = $this->processor->process($event); + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($expectedData, &$callbackInvoked) { + $this->assertArraySubset($expectedData, $eventArg->getRequest()); - $this->assertArraySubset($expectedData, $event->getRequest()); + $callbackInvoked = true; + }; + + $middleware = new SanitizeHttpBodyMiddleware(); + $middleware($event, $callback); + + $this->assertTrue($callbackInvoked); } - public function processDataProvider() + public function invokeDataProvider() { return [ [ @@ -59,7 +67,7 @@ public function processDataProvider() ], ], [ - 'data' => RemoveHttpBodyProcessor::STRING_MASK, + 'data' => SanitizeHttpBodyMiddleware::STRING_MASK, ], ], [ @@ -70,7 +78,7 @@ public function processDataProvider() ], ], [ - 'data' => RemoveHttpBodyProcessor::STRING_MASK, + 'data' => SanitizeHttpBodyMiddleware::STRING_MASK, ], ], [ @@ -81,7 +89,7 @@ public function processDataProvider() ], ], [ - 'data' => RemoveHttpBodyProcessor::STRING_MASK, + 'data' => SanitizeHttpBodyMiddleware::STRING_MASK, ], ], [ @@ -92,7 +100,7 @@ public function processDataProvider() ], ], [ - 'data' => RemoveHttpBodyProcessor::STRING_MASK, + 'data' => SanitizeHttpBodyMiddleware::STRING_MASK, ], ], [ diff --git a/tests/Processor/SanitizeHttpHeadersProcessorTest.php b/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php similarity index 52% rename from tests/Processor/SanitizeHttpHeadersProcessorTest.php rename to tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php index 9021c63bb..efb1ac87d 100644 --- a/tests/Processor/SanitizeHttpHeadersProcessorTest.php +++ b/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php @@ -9,48 +9,40 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Processor; +namespace Raven\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\ClientBuilder; -use Raven\ClientInterface; +use Raven\Configuration; use Raven\Event; -use Raven\Processor\SanitizeHttpHeadersProcessor; +use Raven\Middleware\SanitizeHttpHeadersMiddleware; -class SanitizeHttpHeadersProcessorTest extends TestCase +class SanitizeHttpHeadersMiddlewareTest extends TestCase { /** - * @var ClientInterface + * @dataProvider invokeDataProvider */ - protected $client; + public function testInvoke($inputData, $expectedData) + { + $event = new Event(new Configuration()); + $event->setRequest($inputData); - /** - * @var SanitizeHttpHeadersProcessor|\PHPUnit_Framework_MockObject_MockObject - */ - protected $processor; + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($expectedData, &$callbackInvoked) { + $this->assertArraySubset($expectedData, $eventArg->getRequest()); - protected function setUp() - { - $this->client = ClientBuilder::create()->getClient(); - $this->processor = new SanitizeHttpHeadersProcessor([ + $callbackInvoked = true; + }; + + $middleware = new SanitizeHttpHeadersMiddleware([ 'sanitize_http_headers' => ['User-Defined-Header'], ]); - } - - /** - * @dataProvider processDataProvider - */ - public function testProcess($inputData, $expectedData) - { - $event = new Event($this->client->getConfig()); - $event->setRequest($inputData); - $event = $this->processor->process($event); + $middleware($event, $callback); - $this->assertArraySubset($expectedData, $event->getRequest()); + $this->assertTrue($callbackInvoked); } - public function processDataProvider() + public function invokeDataProvider() { return [ [ @@ -65,7 +57,7 @@ public function processDataProvider() 'headers' => [ 'Authorization' => 'foo', 'AnotherHeader' => 'bar', - 'User-Defined-Header' => SanitizeHttpHeadersProcessor::STRING_MASK, + 'User-Defined-Header' => SanitizeHttpHeadersMiddleware::STRING_MASK, ], ], ], diff --git a/tests/Middleware/SanitizeStacktraceMiddlewareTest.php b/tests/Middleware/SanitizeStacktraceMiddlewareTest.php new file mode 100644 index 000000000..c99e45ae9 --- /dev/null +++ b/tests/Middleware/SanitizeStacktraceMiddlewareTest.php @@ -0,0 +1,82 @@ +client = ClientBuilder::create(['auto_log_stacks' => true]) + ->getClient(); + } + + public function testInvoke() + { + $exception = new \Exception(); + + $event = new Event($this->client->getConfig()); + $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception->getTrace(), $exception->getFile(), $exception->getLine())); + + $callbackInvoked = false; + $callback = function (Event $eventArg) use (&$callbackInvoked) { + foreach ($eventArg->getStacktrace()->getFrames() as $frame) { + $this->assertNull($frame->getPreContext()); + $this->assertNull($frame->getContextLine()); + $this->assertNull($frame->getPostContext()); + } + + $callbackInvoked = true; + }; + + $middleware = new SanitizeStacktraceMiddleware(); + $middleware($event, $callback); + + $this->assertTrue($callbackInvoked); + } + + public function testInvokeWithPreviousException() + { + $exception1 = new \Exception(); + $exception2 = new \Exception('foo', 0, $exception1); + + $event = new Event($this->client->getConfig()); + $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception2->getTrace(), $exception2->getFile(), $exception2->getLine())); + + $callbackInvoked = false; + $callback = function (Event $eventArg) use (&$callbackInvoked) { + foreach ($eventArg->getStacktrace()->getFrames() as $frame) { + $this->assertNull($frame->getPreContext()); + $this->assertNull($frame->getContextLine()); + $this->assertNull($frame->getPostContext()); + } + + $callbackInvoked = true; + }; + + $middleware = new SanitizeStacktraceMiddleware(); + $middleware($event, $callback); + + $this->assertTrue($callbackInvoked); + } +} diff --git a/tests/Processor/ProcessorRegistryTest.php b/tests/Processor/ProcessorRegistryTest.php deleted file mode 100644 index ee11200c1..000000000 --- a/tests/Processor/ProcessorRegistryTest.php +++ /dev/null @@ -1,69 +0,0 @@ -processorRegistry = new ProcessorRegistry(); - } - - public function testAddProcessor() - { - /** @var ProcessorInterface $processor1 */ - $processor1 = $this->createMock(ProcessorInterface::class); - - /** @var ProcessorInterface $processor2 */ - $processor2 = $this->createMock(ProcessorInterface::class); - - /** @var ProcessorInterface $processor3 */ - $processor3 = $this->createMock(ProcessorInterface::class); - - $this->processorRegistry->addProcessor($processor1, -10); - $this->processorRegistry->addProcessor($processor2, 10); - $this->processorRegistry->addProcessor($processor3); - - $processors = $this->processorRegistry->getProcessors(); - - $this->assertCount(3, $processors); - $this->assertSame($processor2, $processors[0]); - $this->assertSame($processor3, $processors[1]); - $this->assertSame($processor1, $processors[2]); - } - - public function testRemoveProcessor() - { - /** @var ProcessorInterface $processor1 */ - $processor1 = $this->createMock(ProcessorInterface::class); - - /** @var ProcessorInterface $processor2 */ - $processor2 = $this->createMock(ProcessorInterface::class); - - /** @var ProcessorInterface $processor3 */ - $processor3 = $this->createMock(ProcessorInterface::class); - - $this->processorRegistry->addProcessor($processor1, -10); - $this->processorRegistry->addProcessor($processor2, 10); - $this->processorRegistry->addProcessor($processor3); - - $this->assertCount(3, $this->processorRegistry->getProcessors()); - - $this->processorRegistry->removeProcessor($processor1); - - $processors = $this->processorRegistry->getProcessors(); - - $this->assertCount(2, $processors); - $this->assertSame($processor2, $processors[0]); - $this->assertSame($processor3, $processors[1]); - } -} diff --git a/tests/Processor/SanitizeStacktraceProcessorTest.php b/tests/Processor/SanitizeStacktraceProcessorTest.php deleted file mode 100644 index 36cf85d6f..000000000 --- a/tests/Processor/SanitizeStacktraceProcessorTest.php +++ /dev/null @@ -1,79 +0,0 @@ -processor = new SanitizeStacktraceProcessor(); - $this->client = ClientBuilder::create(['auto_log_stacks' => true]) - ->getClient(); - } - - public function testProcess() - { - $exception = new \Exception(); - - $event = new Event($this->client->getConfig()); - $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception->getTrace(), $exception->getFile(), $exception->getLine())); - - $event = $this->processor->process($event); - - foreach ($event->getStacktrace()->getFrames() as $frame) { - $this->assertNull($frame->getPreContext()); - $this->assertNull($frame->getContextLine()); - $this->assertNull($frame->getPostContext()); - } - } - - public function testProcessWithPreviousException() - { - $exception1 = new \Exception(); - $exception2 = new \Exception('', 0, $exception1); - - $event = new Event($this->client->getConfig()); - $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception2->getTrace(), $exception2->getFile(), $exception2->getLine())); - - $event = $this->processor->process($event); - - foreach ($event->getStacktrace()->toArray() as $frame) { - $this->assertNull($frame->getPreContext()); - $this->assertNull($frame->getContextLine()); - $this->assertNull($frame->getPostContext()); - } - } - - public function testProcessWithNoStacktrace() - { - $event = new Event($this->client->getConfig()); - - $this->assertSame($event, $this->processor->process($event)); - } -} From 96bc409db20ff9f866cffe0799a35343bb270118 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 29 Aug 2018 19:41:30 +0200 Subject: [PATCH 0368/1161] Fix issues reported by code review --- docs/middleware.rst | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/middleware.rst b/docs/middleware.rst index da95d346e..490e1e531 100644 --- a/docs/middleware.rst +++ b/docs/middleware.rst @@ -29,7 +29,7 @@ several built-in middlewares whose list is: There are also some "special" middlewares that should be executed after all the other middlewares so that they can sanitize and remove sensitive information before -they reach the Sentry server (not all of them are enabled by default): +they reach the Sentry server: - ``SanitizeHttpBodyMiddleware``: sanitizes the data sent as body of a POST request. @@ -40,7 +40,8 @@ they reach the Sentry server (not all of them are enabled by default): - ``SanitizeHttpHeadersMiddleware``: sanitizes the headers of the request by hiding sensitive information. - ``SanitizeStacktraceMiddleware``: sanitizes the captured stacktrace by - removing the excerpts of source code attached to each frame. + removing the excerpts of source code attached to each frame. This middleware + is not enabled by default. Writing a middleware ==================== @@ -89,24 +90,33 @@ can have a priority which defines in which order they will run. If you don't specify a priority the default one of 0 will be assigned. The built-in middlewares have the following priorities: -- ``BreadcrumbInterfaceMiddleware``: 0 -- ``ContextInterfaceMiddleware``: 0 -- ``ExceptionInterfaceMiddleware``: 0 -- ``MessageInterfaceMiddleware``: 0 -- ``ModulesMiddleware``: 0 -- ``RequestInterfaceMiddleware``: 0 - ``SanitizerMiddleware``: -255 (this middleware should always be the last one) -- ``UserInterfaceMiddleware``: 0 -- ``SanitizeHttpBodyMiddleware``: -200 (this middleware should always be after +- ``SanitizeDataMiddleware``: -200 (this middleware should always be after all "standard" middlewares) - ``SanitizeCookiesMiddleware``: -200 (this middleware should always be after all "standard" middlewares) -- ``SanitizeDataMiddleware``: -200 (this middleware should always be after +- ``SanitizeHttpBodyMiddleware``: -200 (this middleware should always be after all "standard" middlewares) - ``SanitizeHttpHeadersMiddleware``: -200 (this middleware should always be after all "standard" middlewares) - ``SanitizeStacktraceMiddleware``: -200 (this middleware should always be after all "standard" middlewares) +- ``MessageInterfaceMiddleware``: 0 +- ``RequestInterfaceMiddleware``: 0 +- ``UserInterfaceMiddleware``: 0 +- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about + the tags context) +- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about + the user context) +- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about + the extra context) +- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about + the runtime context) +- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about + the server OS context) +- ``BreadcrumbInterfaceMiddleware``: 0 +- ``ExceptionInterfaceMiddleware``: 0 +- ``ModulesMiddleware``: 0 (this middleware is not enabled by default) The higher the priority value is, the earlier a middleware will be executed in the chain. To add the middleware to the stack you can use the ``addMiddleware`` From 4a81b82fc6233fa76c9d9674d41f913c76a9ce8a Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 30 Aug 2018 00:01:32 +0200 Subject: [PATCH 0369/1161] Update the documentation index of the hosted Sentry to reflect the v2 changes --- bin/sentry | 66 -------- docs/config.rst | 296 ------------------------------------ docs/index.rst | 73 +++++---- docs/sentry-doc-config.json | 3 +- examples/vanilla/README.md | 6 - examples/vanilla/index.php | 35 ----- 6 files changed, 41 insertions(+), 438 deletions(-) delete mode 100755 bin/sentry delete mode 100644 docs/config.rst delete mode 100644 examples/vanilla/README.md delete mode 100644 examples/vanilla/index.php diff --git a/bin/sentry b/bin/sentry deleted file mode 100755 index a15a4cde1..000000000 --- a/bin/sentry +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env php - $dsn, - 'project_root' => realpath(__DIR__ . '/../'), - ])->getClient(); - - echo "Sending a test event:\n"; - - $ex = raven_cli_test("command name", ["foo" => "bar"]); - $event_id = $client->captureException($ex); - - echo "-> event ID: $event_id\n"; - - $last_error = $client->getLastError(); - if (!empty($last_error)) { - exit("ERROR: There was an error sending the test event:\n " . $last_error); - } - - echo "\n"; - echo "Done!"; -} - - -function main() -{ - global $argv; - - if (!isset($argv[1])) { - exit('Usage: sentry test '); - } - - $cmd = $argv[1]; - - switch ($cmd) { - case 'test': - cmd_test(@$argv[2]); - break; - default: - exit('Usage: sentry test '); - } -} - -main(); diff --git a/docs/config.rst b/docs/config.rst deleted file mode 100644 index 6e193db0d..000000000 --- a/docs/config.rst +++ /dev/null @@ -1,296 +0,0 @@ -Configuration -============= - -Several options exist that allow you to configure the behavior of the -``\Raven\Client``. These are passed as the second parameter of the -constructor, and is expected to be an array of key value pairs: - -.. code-block:: php - - $client = new \Raven\Client($dsn, array( - 'option_name' => 'value', - )); - -Available Settings ------------------- - -The following settings are available for the client: - -.. describe:: name - - A string to override the default value for the server's hostname. - - Defaults to ``gethostname()``. - -.. describe:: tags - - An array of tags to apply to events in this context. - - .. code-block:: php - - 'tags' => array( - 'php_version' => phpversion(), - ) - - .. code-block:: php - - $client->tags_context(array( - 'php_version' => phpversion(), - )); - -.. describe:: release - - The version of your application (e.g. git SHA) - - .. code-block:: php - - 'release' => MyApp::getReleaseVersion(), - - .. code-block:: php - - $client->setRelease(MyApp::getReleaseVersion()); - -.. describe:: environment - - The environment your application is running in. - - .. code-block:: php - - 'environment' => 'production', - - .. code-block:: php - - $client->setEnvironment('production'); - -.. describe:: app_path - - The root path to your application code. - - .. code-block:: php - - 'app_path' => app_root(), - - .. code-block:: php - - $client->setAppPath(app_root()); - -.. describe:: excluded_app_paths - - Paths to exclude from app_path detection. - - .. code-block:: php - - 'excluded_app_paths' => array(app_root() . '/cache'), - - .. code-block:: php - - $client->setExcludedAppPaths(array(app_root() . '/cache')); - -.. describe:: prefixes - - Prefixes which should be stripped from filenames to create relative - paths. - - .. code-block:: php - - 'prefixes' => array( - '/www/php/lib', - ), - - .. code-block:: php - - $client->setPrefixes(array( - '/www/php/lib', - )); - -.. describe:: sample_rate - - The sampling factor to apply to events. A value of 0.00 will deny sending - any events, and a value of 1.00 will send 100% of events. - - .. code-block:: php - - // send 50% of events - 'sample_rate' => 0.5, - -.. describe:: send_callback - - A function which will be called whenever data is ready to be sent. Within - the function you can mutate the data, or alternatively return ``false`` to - instruct the SDK to not send the event. - - .. code-block:: php - - 'send_callback' => function($data) { - // strip HTTP data - @unset($data['request']); - }, - - .. code-block:: php - - $client->setSendCallback(function($data) { - // dont send events if POST - if ($_SERVER['REQUEST_METHOD'] === 'POST') - { - return false; - } - }); - -.. describe:: curl_method - - Defaults to 'sync'. - - Available methods: - - - ``sync`` (default): send requests immediately when they're made - - ``async``: uses a curl_multi handler for best-effort asynchronous - submissions - - ``exec``: asynchronously send events by forking a curl - process for each item - -.. describe:: curl_path - - Defaults to 'curl'. - - Specify the path to the curl binary to be used with the 'exec' curl - method. - -.. describe:: transport - - Set a custom transport to override how Sentry events are sent upstream. - - .. code-block:: php - - 'transport' => function($client, $data) { - $myHttpClient->send(array( - 'url' => $client->getServerEndpoint(), - 'method' => 'POST', - 'headers' => array( - 'Content-Encoding' => 'gzip', - 'Content-Type' => 'application/octet-stream', - 'User-Agent' => $client->getUserAgent(), - 'X-Sentry-Auth' => $client->getAuthHeader(), - ), - 'body' => gzcompress(jsonEncode($data)), - )) - }, - - .. code-block:: php - - $client->setTransport(...); - -.. describe:: trace - - Set this to ``false`` to disable reflection tracing (function calling - arguments) in stacktraces. - - -.. describe:: logger - - Adjust the default logger name for messages. - - Defaults to ``php``. - -.. describe:: ca_cert - - The path to the CA certificate bundle. - - Defaults to the common bundle which includes getsentry.com: - ./data/cacert.pem - - Caveats: - - - The CA bundle is ignored unless curl throws an error suggesting it - needs a cert. - - The option is only currently used within the synchronous curl - transport. - -.. describe:: curl_ssl_version - - The SSL version (2 or 3) to use. By default PHP will try to determine - this itself, although in some cases this must be set manually. - -.. describe:: message_limit - - Defaults to 1024 characters. - - This value is used to truncate message and frame variables. However it - is not guarantee that length of whole message will be restricted by - this value. - -.. describe:: processors - - An array of classes to use to process data before it is sent to - Sentry. By default, ``\Raven\SanitizeDataProcessor`` is used - -.. describe:: processorOptions - - Options that will be passed on to a ``setProcessorOptions()`` function - in a ``\Raven\Processor`` sub-class before that Processor is added to - the list of processors used by ``\Raven\Client`` - - An example of overriding the regular expressions in - ``\Raven\SanitizeDataProcessor`` is below: - - .. code-block:: php - - 'processorOptions' => array( - '\Raven\SanitizeDataProcessor' => array( - 'fields_re' => '/(user_password|user_token|user_secret)/i', - 'values_re' => '/^(?:\d[ -]*?){15,16}$/' - ) - ) - -.. describe:: timeout - - The timeout for sending requests to the Sentry server in seconds, default is 2 seconds. - - .. code-block:: php - - 'timeout' => 2, - -.. describe:: excluded_exceptions - - Exception that should not be reported, exceptions extending exceptions in this list will also - be excluded, default is an empty array. - - In the example below, when you exclude ``LogicException`` you will also exclude ``BadFunctionCallException`` - since it extends ``LogicException``. - - .. code-block:: php - - 'excluded_exceptions' => array('LogicException'), - -.. _sentry-php-request-context: - -Providing Request Context -------------------------- - -Most of the time you're not actually calling out to Raven directly, but -you still want to provide some additional context. This lifecycle -generally constists of something like the following: - -- Set some context via a middleware (e.g. the logged in user) -- Send all given context with any events during the request lifecycle -- Cleanup context - -There are three primary methods for providing request context: - -.. code-block:: php - - // bind the logged in user - $client->setUserContext(array('email' => 'foo@example.com')); - - // tag the request with something interesting - $client->tags_context(array('interesting' => 'yes')); - - // provide a bit of additional context - $client->extra_context(array('happiness' => 'very')); - - -If you're performing additional requests during the lifecycle, you'll also -need to ensure you cleanup the context (to reset its state): - -.. code-block:: php - - $client->context->clear(); diff --git a/docs/index.rst b/docs/index.rst index 80642228a..15032afbc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,53 +10,49 @@ PHP === -The PHP SDK for Sentry supports PHP 5.3 and higher. It's -available as a BSD licensed Open Source library. +The PHP SDK for Sentry supports PHP 5.6 and higher and is available as a BSD +licensed Open Source library. Installation ------------ +To install the SDK you will need to be using Composer_ in your project. To install +it please see the `docs `_. -There are various ways to install the PHP integration for Sentry. The -recommended way is to use `Composer `__:: +Sentry PHP is not tied to any specific library that sends HTTP messages. Instead, +it uses Httplug_ to let users choose whichever PSR-7 implementation and HTTP client +they want to use. - $ composer require "sentry/sentry" +If you just want to get started quickly you should run the following command: -Configuration -------------- +.. code-block:: bash -The most important part is the creation of the raven client. Create it -once and reference it from anywhere you want to interface with Sentry: + php composer.phar require sentry/sentry php-http/curl-client guzzlehttp/psr7 -.. code-block:: php - - $client = new \Raven\Client('___PUBLIC_DSN___'); +This will install the library itself along with an HTTP client adapter that uses +cURL as transport method (provided by Httplug) and a PSR-7 implementation +(provided by Guzzle). You do not have to use those packages if you do not want to. +The SDK does not care about which transport method you want to use because it's +an implementation detail of your application. You may use any package that provides +`php-http/async-client-implementation`_ and `http-message-implementation`_. -Once you have the client you can either use it manually or enable the -automatic error and exception capturing which is recomended: +Usage +----- .. code-block:: php - $error_handler = new \Raven\ErrorHandler($client); - $error_handler->registerExceptionHandler(); - $error_handler->registerErrorHandler(); - $error_handler->registerShutdownFunction(); - -Adding Context --------------- + use Raven\ClientBuilder; -Much of the usefulness of Sentry comes from additional context data with -the events. The PHP client makes this very convenient by providing -methods to set thread local context data that is then submitted -automatically with all events. For instance you can use the -``setUserContext`` method to add information about the current user: + require 'vendor/autoload.php'; -.. sourcecode:: php + // Instantiate the SDK with your DSN + $client = ClientBuilder::create(['server' => 'http://___PUBLIC_DSN___@example.com/1'])->getClient(); - $client->setUserContext(array( - 'email' => $USER->getEmail() - )); + // Capture an exception + $eventId = $client->captureException(new \RuntimeException('Hello World!')); -For more information see :ref:`sentry-php-request-context`. + // Give the user feedback + echo 'Sorry, there was an error!'; + echo 'Your reference ID is ' . $eventId; Deep Dive --------- @@ -67,11 +63,22 @@ Want more? Have a look at the full documentation for more information. :maxdepth: 2 :titlesonly: - usage - config + quickstart + configuration + transport + middleware + error_handling integrations/index Resources: * `Bug Tracker `_ * `Github Project `_ +* `Code `_ +* `Mailing List `_ +* `IRC (irc.freenode.net, #sentry) `_ + +.. _Httplug: https://github.com/php-http/httplug +.. _Composer: https://getcomposer.org +.. _php-http/async-client-implementation: https://packagist.org/providers/php-http/async-client-implementation +.. _http-message-implementation: https://packagist.org/providers/psr/http-message-implementation diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 972fa207d..cd92a4793 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -7,8 +7,7 @@ "doc_link": "", "wizard": [ "index#installation", - "index#configuration", - "usage#capturing-errors" + "quickstart#creating-a-client" ] }, "php.laravel": { diff --git a/examples/vanilla/README.md b/examples/vanilla/README.md deleted file mode 100644 index 5a32cd67d..000000000 --- a/examples/vanilla/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Running this example: - -``` -# Run webserver -php -S localhost:8000 -``` diff --git a/examples/vanilla/index.php b/examples/vanilla/index.php deleted file mode 100644 index ad96189b3..000000000 --- a/examples/vanilla/index.php +++ /dev/null @@ -1,35 +0,0 @@ -setAppPath(__DIR__) - ->setRelease(\Raven\Client::VERSION) - ->setPrefixes([__DIR__]) - ->install(); -} - -function createCrumbs() -{ - echo $undefined['foobar']; - echo $undefined['bizbaz']; -} - -function createError() -{ - 1 / 0; -} - -function createException() -{ - throw new Exception('example exception'); -} - -setupSentry(); -createCrumbs(); -createError(); -createException(); From b1b6208c9dd3a2ac6fb2f52ef8135b029094f153 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 16 Sep 2018 00:32:34 +0200 Subject: [PATCH 0370/1161] Apply code review changes --- docs/index.rst | 35 ++++++++++++++++++++- docs/middleware.rst | 9 ++---- docs/processor.rst | 77 --------------------------------------------- 3 files changed, 36 insertions(+), 85 deletions(-) delete mode 100644 docs/processor.rst diff --git a/docs/index.rst b/docs/index.rst index 15032afbc..263dac115 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,19 +35,51 @@ The SDK does not care about which transport method you want to use because it's an implementation detail of your application. You may use any package that provides `php-http/async-client-implementation`_ and `http-message-implementation`_. +If you want to use Guzzle as underlying HTTP client, you just need to run the +following command to install the adapter and Guzzle itself: + +.. code-block:: bash + + php composer.phar require php-http/guzzle6-adapter + +You can then use the client builder to create a Raven client instance that will +use the configured HTTP client based on Guzzle. The code looks like the one +below: + +.. code-block:: php + + use Http\Adapter\Guzzle6\Client as GuzzleClientAdapter; + use Raven\ClientBuilder; + + require 'vendor/autoload.php'; + + // The client will use a Guzzle client to send the HTTP requests + $client = ClientBuilder::create(['server' => 'http://___PUBLIC_DSN___@example.com/1']) + ->setHttpClient(new GuzzleClientAdapter()) + ->getClient(); + +If you want to have more control on the Guzzle HTTP client you can create the +instance yourself and tell the adapter to use it instead of creating one +on-the-fly. Please refer to the `HTTPlug Guzzle 6 Adapter documentation`_ for +instructions on how to do it. + Usage ----- .. code-block:: php use Raven\ClientBuilder; + use Raven\ErrorHandler; require 'vendor/autoload.php'; // Instantiate the SDK with your DSN $client = ClientBuilder::create(['server' => 'http://___PUBLIC_DSN___@example.com/1'])->getClient(); - // Capture an exception + // Register error handler to automatically capture errors and exceptions + ErrorHandler::register($client); + + // Capture an exception manually $eventId = $client->captureException(new \RuntimeException('Hello World!')); // Give the user feedback @@ -82,3 +114,4 @@ Resources: .. _Composer: https://getcomposer.org .. _php-http/async-client-implementation: https://packagist.org/providers/php-http/async-client-implementation .. _http-message-implementation: https://packagist.org/providers/psr/http-message-implementation +.. _HTTPlug Guzzle 6 Adapter documentation: http://docs.php-http.org/en/latest/clients/guzzle6-adapter.html diff --git a/docs/middleware.rst b/docs/middleware.rst index c532503c1..23f274f33 100644 --- a/docs/middleware.rst +++ b/docs/middleware.rst @@ -51,21 +51,16 @@ middleware that customizes the message captured with an event can be written: use Raven\ClientBuilder; use Raven\Event; - class CustomMiddleware + final class CustomMiddleware { public function (Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - $event = $event->withMessage('hello world'); + $event->setMessage('hello world'); return $next($event, $request, $exception, $payload); } } -As you can see the ``$event`` variable must always be reassigned to the result of -the called ``with*`` method: this is because the ``Event`` class is immutable by -design and any method that edit its data will act on a new instance of the event -instead of the original one. - Using a middleware ================== diff --git a/docs/processor.rst b/docs/processor.rst deleted file mode 100644 index 34941223d..000000000 --- a/docs/processor.rst +++ /dev/null @@ -1,77 +0,0 @@ -Processors -########## - -The processors are classes that are executed as the last step of the event -sending lifecycle before the event data is serialized and sent using the -configured transport. There are several built-in processors (not all are -enabled by default) whose list is: - -- ``RemoveHttpBodyProcessor``: sanitizes the data sent as body of a POST - request. -- ``SanitizeCookiesProcessor``: sanitizes the cookies sent with the request - by hiding sensitive information. -- ``SanitizeDataProcessor``: sanitizes the data of the event by removing - sensitive information. -- ``SanitizeHttpHeadersProcessor``: sanitizes the headers of the request by - hiding sensitive information. -- ``SanitizeStacktraceProcessor``: sanitizes the captured stacktrace by - removing the excerpts of source code attached to each frame. - -Writing a processor -=================== - -You can write your own processor by creating a class that implements the -``ProcessorInterface`` interface. - -.. code-block:: php - - use Raven\Event; - use Raven\Processor\ProcessorInterface; - - class MyCustomProcessor implements ProcessorInterface - { - public function process(Event $event) - { - // Do something on the event object instance - - return $event; - } - } - -Using a processor -================= - -The processors needs to be registered with the client instance before they are -used. Each one can have a priority which defines in which order they will run. -By default they have a priority of 0. The higher the priority value is, the -earlier a processor will be executed: this is similar to how the middlewares -work. You can add or remove the processors at runtime, and they will be executed -sequentially one after the other. The built-in processors have the following -priorities: - -- ``SanitizeCookiesProcessor``: 0 -- ``RemoveHttpBodyProcessor``: 0 -- ``SanitizeHttpHeadersProcessor``: 0 -- ``SanitizeDataProcessor``: -255 - -It's important to leave the ``SanitizeDataProcessor`` as the last processor so -that any sensitive information present in the event data will be sanitized -before being sent out from your network according to the configuration you -specified. To add a middleware to the client you can use the ``addProcessor`` -method which can be found in both the ``Client`` and ``ClientBuilder`` classes -while the ``removeProcessor`` can be used to remove the processors instead. - -.. code-block:: php - - use Raven\ClientBuilder; - use Raven\Processor\RemoveHttpBodyProcessor; - - $processor = new RemoveHttpBodyProcessor(); - - $clientBuilder = new ClientBuilder(); - $clientBuilder->addProcessor($processor, 10); - $clientBuilder->removeProcessor($processor); - - $client = $clientBuilder->getClient(); - $client->addProcessor($processor, -10); - $client->removeProcessor($processor); From 039f31b7f107d1c177039412257691b3df943d9c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 26 Sep 2018 21:58:04 +0200 Subject: [PATCH 0371/1161] Rename two middlewares to better reflect their purpose --- UPGRADE-2.0.md | 21 ++++++++++++------- docs/middleware.rst | 8 +++---- lib/Raven/ClientBuilder.php | 4 ++-- ...eware.php => RemoveHttpBodyMiddleware.php} | 2 +- ... => RemoveStacktraceContextMiddleware.php} | 2 +- ...t.php => RemoveHttpBodyMiddlewareTest.php} | 18 ++++++++-------- ...RemoveStacktraceContextMiddlewareTest.php} | 8 +++---- 7 files changed, 34 insertions(+), 29 deletions(-) rename lib/Raven/Middleware/{SanitizeHttpBodyMiddleware.php => RemoveHttpBodyMiddleware.php} (95%) rename lib/Raven/Middleware/{SanitizeStacktraceMiddleware.php => RemoveStacktraceContextMiddleware.php} (94%) rename tests/Middleware/{SanitizeHttpBodyMiddlewareTest.php => RemoveHttpBodyMiddlewareTest.php} (80%) rename tests/Middleware/{SanitizeStacktraceMiddlewareTest.php => RemoveStacktraceContextMiddlewareTest.php} (90%) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 2d0c685e1..c33fde739 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -461,24 +461,29 @@ ### Processors -- The `Raven_Processor_RemoveCookiesProcessor` class has been renamed to `SanitizeCookiesProcessor` - to better reflect its purpose. The constructor accepts an array of options to - make the behaviour of which cookies to sanitize configurable. +All processor have been refactored into middlewares, and have changed names +to follow PSR-4 convention. + +- The `Raven_Processor_RemoveCookiesProcessor` class has been renamed to + `SanitizeCookiesMiddleware` to better reflect its purpose. The constructor + accepts an array of options to make the behaviour of which cookies to + sanitize configurable. - The `Raven_Processor_SanitizeStacktraceProcessor` class has been renamed to - `SanitizeStacktraceProcessor` to follow the PSR-4 convention. + `RemoveStacktraceContextMiddleware` to follow the PSR-4 convention and better + reflect its purpose. - The `Raven_Processor_SanitizeHttpHeadersProcessor` class has been renamed to - `SanitizeHttpHeadersProcessor` to follow the PSR-4 convention. + `SanitizeHttpHeadersMiddleware` to follow the PSR-4 convention. - The `Raven_Processor_RemoveHttpBodyProcessor` class has been renamed to - `RemoveHttpBodyProcessor` to follow the PSR-4 convention. + `RemoveHttpBodyMiddleware` to follow the PSR-4 convention. - The `Raven_Processor_SanitizeDataProcessor` class has been renamed to - `SanitizeDataProcessor` to follow the PSR-4 convention. + `SanitizeDataMiddleware` to follow the PSR-4 convention. - The `Raven_Processor` class has been removed. There is not anymore a base - abstract class for the processors, but a `ProcessorInterface` interface has + abstract class for the processors, but a `ProcessorMiddlewareInterface` interface has been introduced. ### Context diff --git a/docs/middleware.rst b/docs/middleware.rst index e4769ec82..772b4709f 100644 --- a/docs/middleware.rst +++ b/docs/middleware.rst @@ -31,7 +31,7 @@ There are also some "special" middlewares that should be executed after all the other middlewares so that they can sanitize and remove sensitive information before they reach the Sentry server: -- ``SanitizeHttpBodyMiddleware``: sanitizes the data sent as body of a POST +- ``RemoveHttpBodyMiddleware``: sanitizes the data sent as body of a POST request. - ``SanitizeCookiesMiddleware``: sanitizes the cookies sent with the request by hiding sensitive information. @@ -39,7 +39,7 @@ they reach the Sentry server: sensitive information. - ``SanitizeHttpHeadersMiddleware``: sanitizes the headers of the request by hiding sensitive information. -- ``SanitizeStacktraceMiddleware``: sanitizes the captured stacktrace by +- ``RemoveStacktraceContextMiddleware``: sanitizes the captured stacktrace by removing the excerpts of source code attached to each frame. This middleware is not enabled by default. @@ -90,11 +90,11 @@ have the following priorities: all "standard" middlewares) - ``SanitizeCookiesMiddleware``: -200 (this middleware should always be after all "standard" middlewares) -- ``SanitizeHttpBodyMiddleware``: -200 (this middleware should always be after +- ``RemoveHttpBodyMiddleware``: -200 (this middleware should always be after all "standard" middlewares) - ``SanitizeHttpHeadersMiddleware``: -200 (this middleware should always be after all "standard" middlewares) -- ``SanitizeStacktraceMiddleware``: -200 (this middleware should always be after +- ``RemoveStacktraceContextMiddleware``: -200 (this middleware should always be after all "standard" middlewares) - ``MessageInterfaceMiddleware``: 0 - ``RequestInterfaceMiddleware``: 0 diff --git a/lib/Raven/ClientBuilder.php b/lib/Raven/ClientBuilder.php index d47cd26c4..a10c21eb0 100644 --- a/lib/Raven/ClientBuilder.php +++ b/lib/Raven/ClientBuilder.php @@ -30,10 +30,10 @@ use Raven\Middleware\ContextInterfaceMiddleware; use Raven\Middleware\ExceptionInterfaceMiddleware; use Raven\Middleware\MessageInterfaceMiddleware; +use Raven\Middleware\RemoveHttpBodyMiddleware; use Raven\Middleware\RequestInterfaceMiddleware; use Raven\Middleware\SanitizeCookiesMiddleware; use Raven\Middleware\SanitizeDataMiddleware; -use Raven\Middleware\SanitizeHttpBodyMiddleware; use Raven\Middleware\SanitizeHttpHeadersMiddleware; use Raven\Middleware\SanitizerMiddleware; use Raven\Middleware\UserInterfaceMiddleware; @@ -251,7 +251,7 @@ public function getClient() $client->addMiddleware(new SanitizerMiddleware($client->getSerializer()), -255); $client->addMiddleware(new SanitizeDataMiddleware(), -200); $client->addMiddleware(new SanitizeCookiesMiddleware(), -200); - $client->addMiddleware(new SanitizeHttpBodyMiddleware(), -200); + $client->addMiddleware(new RemoveHttpBodyMiddleware(), -200); $client->addMiddleware(new SanitizeHttpHeadersMiddleware(), -200); $client->addMiddleware(new MessageInterfaceMiddleware()); $client->addMiddleware(new RequestInterfaceMiddleware()); diff --git a/lib/Raven/Middleware/SanitizeHttpBodyMiddleware.php b/lib/Raven/Middleware/RemoveHttpBodyMiddleware.php similarity index 95% rename from lib/Raven/Middleware/SanitizeHttpBodyMiddleware.php rename to lib/Raven/Middleware/RemoveHttpBodyMiddleware.php index ab89ef2ff..efaac7f54 100644 --- a/lib/Raven/Middleware/SanitizeHttpBodyMiddleware.php +++ b/lib/Raven/Middleware/RemoveHttpBodyMiddleware.php @@ -21,7 +21,7 @@ * * @author Stefano Arlandini */ -final class SanitizeHttpBodyMiddleware implements ProcessorMiddlewareInterface +final class RemoveHttpBodyMiddleware implements ProcessorMiddlewareInterface { /** * Collects the needed data and sets it in the given event object. diff --git a/lib/Raven/Middleware/SanitizeStacktraceMiddleware.php b/lib/Raven/Middleware/RemoveStacktraceContextMiddleware.php similarity index 94% rename from lib/Raven/Middleware/SanitizeStacktraceMiddleware.php rename to lib/Raven/Middleware/RemoveStacktraceContextMiddleware.php index d9c040822..37d561bcd 100644 --- a/lib/Raven/Middleware/SanitizeStacktraceMiddleware.php +++ b/lib/Raven/Middleware/RemoveStacktraceContextMiddleware.php @@ -20,7 +20,7 @@ * * @author Stefano Arlandini */ -final class SanitizeStacktraceMiddleware implements ProcessorMiddlewareInterface +final class RemoveStacktraceContextMiddleware implements ProcessorMiddlewareInterface { /** * Collects the needed data and sets it in the given event object. diff --git a/tests/Middleware/SanitizeHttpBodyMiddlewareTest.php b/tests/Middleware/RemoveHttpBodyMiddlewareTest.php similarity index 80% rename from tests/Middleware/SanitizeHttpBodyMiddlewareTest.php rename to tests/Middleware/RemoveHttpBodyMiddlewareTest.php index 9efd578e6..974495d94 100644 --- a/tests/Middleware/SanitizeHttpBodyMiddlewareTest.php +++ b/tests/Middleware/RemoveHttpBodyMiddlewareTest.php @@ -15,9 +15,9 @@ use Raven\ClientBuilder; use Raven\ClientInterface; use Raven\Event; -use Raven\Middleware\SanitizeHttpBodyMiddleware; +use Raven\Middleware\RemoveHttpBodyMiddleware; -class SanitizeHttpBodyMiddlewareTest extends TestCase +class RemoveHttpBodyMiddlewareTest extends TestCase { /** * @var ClientInterface @@ -25,14 +25,14 @@ class SanitizeHttpBodyMiddlewareTest extends TestCase protected $client; /** - * @var SanitizeHttpBodyMiddleware|\PHPUnit_Framework_MockObject_MockObject + * @var RemoveHttpBodyMiddleware|\PHPUnit_Framework_MockObject_MockObject */ protected $middleware; protected function setUp() { $this->client = ClientBuilder::create()->getClient(); - $this->middleware = new SanitizeHttpBodyMiddleware(); + $this->middleware = new RemoveHttpBodyMiddleware(); } /** @@ -50,7 +50,7 @@ public function testInvoke($inputData, $expectedData) $callbackInvoked = true; }; - $middleware = new SanitizeHttpBodyMiddleware(); + $middleware = new RemoveHttpBodyMiddleware(); $middleware($event, $callback); $this->assertTrue($callbackInvoked); @@ -67,7 +67,7 @@ public function invokeDataProvider() ], ], [ - 'data' => SanitizeHttpBodyMiddleware::STRING_MASK, + 'data' => RemoveHttpBodyMiddleware::STRING_MASK, ], ], [ @@ -78,7 +78,7 @@ public function invokeDataProvider() ], ], [ - 'data' => SanitizeHttpBodyMiddleware::STRING_MASK, + 'data' => RemoveHttpBodyMiddleware::STRING_MASK, ], ], [ @@ -89,7 +89,7 @@ public function invokeDataProvider() ], ], [ - 'data' => SanitizeHttpBodyMiddleware::STRING_MASK, + 'data' => RemoveHttpBodyMiddleware::STRING_MASK, ], ], [ @@ -100,7 +100,7 @@ public function invokeDataProvider() ], ], [ - 'data' => SanitizeHttpBodyMiddleware::STRING_MASK, + 'data' => RemoveHttpBodyMiddleware::STRING_MASK, ], ], [ diff --git a/tests/Middleware/SanitizeStacktraceMiddlewareTest.php b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php similarity index 90% rename from tests/Middleware/SanitizeStacktraceMiddlewareTest.php rename to tests/Middleware/RemoveStacktraceContextMiddlewareTest.php index c99e45ae9..87a3f8e18 100644 --- a/tests/Middleware/SanitizeStacktraceMiddlewareTest.php +++ b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php @@ -15,10 +15,10 @@ use Raven\ClientBuilder; use Raven\ClientInterface; use Raven\Event; -use Raven\Middleware\SanitizeStacktraceMiddleware; +use Raven\Middleware\RemoveStacktraceContextMiddleware; use Raven\Stacktrace; -class SanitizeStacktraceMiddlewareTest extends TestCase +class RemoveStacktraceContextMiddlewareTest extends TestCase { /** * @var ClientInterface @@ -49,7 +49,7 @@ public function testInvoke() $callbackInvoked = true; }; - $middleware = new SanitizeStacktraceMiddleware(); + $middleware = new RemoveStacktraceContextMiddleware(); $middleware($event, $callback); $this->assertTrue($callbackInvoked); @@ -74,7 +74,7 @@ public function testInvokeWithPreviousException() $callbackInvoked = true; }; - $middleware = new SanitizeStacktraceMiddleware(); + $middleware = new RemoveStacktraceContextMiddleware(); $middleware($event, $callback); $this->assertTrue($callbackInvoked); From 21d3a0b207bbab7345ece83a8194149562a56946 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 26 Sep 2018 22:50:15 +0200 Subject: [PATCH 0372/1161] Add conflict with php-http/client-common 1.8 to fix the build --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index b77f25c90..1fbdb413e 100644 --- a/composer.json +++ b/composer.json @@ -38,6 +38,7 @@ "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" }, "conflict": { + "php-http/client-common": "^1.8.0", "raven/raven": "*" }, "bin": [ From 263e0adbcfbb8e818ba1b937001263d134a02338 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 27 Sep 2018 19:02:30 +0200 Subject: [PATCH 0373/1161] Change vendor name in namespaces from Raven to Sentry (#659) --- .php_cs | 2 + README.md | 6 +- UPGRADE-2.0.md | 10 +-- composer.json | 11 ++-- docs/error_handling.rst | 10 +-- docs/index.rst | 6 +- docs/integrations/monolog.rst | 6 +- docs/middleware.rst | 10 +-- docs/quickstart.rst | 4 +- docs/transport.rst | 18 +++--- phpstan.neon | 4 +- {lib/Raven => src}/AbstractErrorHandler.php | 2 +- {lib/Raven => src}/BreadcrumbErrorHandler.php | 4 +- {lib/Raven => src}/Breadcrumbs/Breadcrumb.php | 10 +-- .../Breadcrumbs/MonologHandler.php | 6 +- {lib/Raven => src}/Breadcrumbs/Recorder.php | 4 +- {lib/Raven => src}/Client.php | 20 +++--- {lib/Raven => src}/ClientBuilder.php | 34 +++++----- {lib/Raven => src}/ClientBuilderInterface.php | 4 +- {lib/Raven => src}/ClientInterface.php | 16 ++--- {lib/Raven => src}/Configuration.php | 2 +- {lib/Raven => src}/Context/Context.php | 2 +- {lib/Raven => src}/Context/RuntimeContext.php | 4 +- .../Raven => src}/Context/ServerOsContext.php | 2 +- {lib/Raven => src}/Context/TagsContext.php | 2 +- {lib/Raven => src}/ErrorHandler.php | 2 +- {lib/Raven => src}/Event.php | 12 ++-- {lib/Raven => src}/Exception.php | 2 +- .../Exception/ExceptionInterface.php | 2 +- .../Exception/InvalidArgumentException.php | 2 +- {lib/Raven => src}/Frame.php | 2 +- .../HttpClient/Authentication/SentryAuth.php | 6 +- .../Encoding/Base64EncodingStream.php | 2 +- .../BreadcrumbInterfaceMiddleware.php | 6 +- .../Middleware/ContextInterfaceMiddleware.php | 6 +- .../ExceptionInterfaceMiddleware.php | 8 +-- .../Middleware/MessageInterfaceMiddleware.php | 6 +- .../Middleware/MiddlewareStack.php | 4 +- .../Middleware/ModulesMiddleware.php | 6 +- .../ProcessorMiddlewareInterface.php | 2 +- .../Middleware/RemoveHttpBodyMiddleware.php | 4 +- .../RemoveStacktraceContextMiddleware.php | 4 +- .../Middleware/RequestInterfaceMiddleware.php | 4 +- .../Middleware/SanitizeCookiesMiddleware.php | 4 +- .../Middleware/SanitizeDataMiddleware.php | 6 +- .../SanitizeHttpHeadersMiddleware.php | 4 +- .../Middleware/SanitizerMiddleware.php | 6 +- .../Middleware/UserInterfaceMiddleware.php | 4 +- {lib/Raven => src}/ReprSerializer.php | 4 +- {lib/Raven => src}/Serializer.php | 4 +- {lib/Raven => src}/Spool/MemorySpool.php | 6 +- {lib/Raven => src}/Spool/SpoolInterface.php | 6 +- {lib/Raven => src}/Stacktrace.php | 2 +- {lib/Raven => src}/TransactionStack.php | 2 +- .../Raven => src}/Transport/HttpTransport.php | 10 +-- .../Raven => src}/Transport/NullTransport.php | 4 +- .../Transport/SpoolTransport.php | 6 +- .../Transport/TransportInterface.php | 4 +- {lib/Raven => src}/Util/JSON.php | 2 +- {lib/Raven => src}/Util/PHPVersion.php | 2 +- tests/AbstractErrorHandlerTest.php | 4 +- tests/AbstractSerializerTest.php | 64 +++++++++---------- .../BreadcrumbErrorHandlerTest.php | 10 +-- tests/Breadcrumbs/BreadcrumbTest.php | 14 ++-- tests/Breadcrumbs/MonologHandlerTest.php | 12 ++-- tests/Breadcrumbs/RecorderTest.php | 6 +- tests/ClientBuilderTest.php | 14 ++-- tests/ClientTest.php | 40 ++++++------ tests/ConfigurationTest.php | 6 +- tests/Context/ContextTest.php | 4 +- tests/Context/RuntimeContextTest.php | 6 +- tests/Context/ServerOsContextTest.php | 4 +- tests/Context/TagsContextTest.php | 4 +- tests/ErrorHandlerTest.php | 6 +- tests/EventTest.php | 22 +++---- tests/Fixtures/classes/CarelessException.php | 2 +- tests/FrameTest.php | 4 +- .../Authentication/SentryAuthTest.php | 8 +-- .../Encoding/Base64EncodingStreamTest.php | 4 +- .../BreadcrumbInterfaceMiddlewareTest.php | 14 ++-- .../ContextInterfaceMiddlewareTest.php | 12 ++-- .../ExceptionInterfaceMiddlewareTest.php | 14 ++-- .../MessageInterfaceMiddlewareTest.php | 8 +-- tests/Middleware/MiddlewareStackTest.php | 12 ++-- tests/Middleware/ModulesMiddlewareTest.php | 8 +-- .../RemoveHttpBodyMiddlewareTest.php | 10 +-- .../RemoveStacktraceContextMiddlewareTest.php | 12 ++-- .../RequestInterfaceMiddlewareTest.php | 8 +-- .../SanitizeCookiesMiddlewareTest.php | 8 +-- .../Middleware/SanitizeDataMiddlewareTest.php | 12 ++-- .../SanitizeHttpHeadersMiddlewareTest.php | 8 +-- tests/Middleware/SanitizerMiddlewareTest.php | 10 +-- .../UserInterfaceMiddlewareTest.php | 8 +-- tests/ReprSerializerTest.php | 6 +- tests/SerializerTest.php | 4 +- tests/Spool/MemorySpoolTest.php | 10 +-- tests/StacktraceTest.php | 10 +-- tests/TransactionStackTest.php | 4 +- tests/Transport/HttpTransportTest.php | 10 +-- tests/Transport/NullTransportTest.php | 8 +-- tests/Transport/SpoolTransportTest.php | 10 +-- tests/Util/Fixtures/JsonSerializableClass.php | 2 +- tests/Util/Fixtures/SimpleClass.php | 2 +- tests/Util/JSONTest.php | 8 +-- tests/Util/PHPVersionTest.php | 4 +- tests/phpt/exception_rethrown.phpt | 8 +-- tests/phpt/exception_thrown.phpt | 8 +-- tests/phpt/fatal_error.phpt | 12 ++-- .../phpt/fatal_error_not_captured_twice.phpt | 12 ++-- tests/phpt/out_of_memory.phpt | 10 +-- 110 files changed, 430 insertions(+), 431 deletions(-) rename {lib/Raven => src}/AbstractErrorHandler.php (99%) rename {lib/Raven => src}/BreadcrumbErrorHandler.php (96%) rename {lib/Raven => src}/Breadcrumbs/Breadcrumb.php (96%) rename {lib/Raven => src}/Breadcrumbs/MonologHandler.php (97%) rename {lib/Raven => src}/Breadcrumbs/Recorder.php (97%) rename {lib/Raven => src}/Client.php (97%) rename {lib/Raven => src}/ClientBuilder.php (92%) rename {lib/Raven => src}/ClientBuilderInterface.php (98%) rename {lib/Raven => src}/ClientInterface.php (95%) rename {lib/Raven => src}/Configuration.php (99%) rename {lib/Raven => src}/Context/Context.php (99%) rename {lib/Raven => src}/Context/RuntimeContext.php (98%) rename {lib/Raven => src}/Context/ServerOsContext.php (99%) rename {lib/Raven => src}/Context/TagsContext.php (98%) rename {lib/Raven => src}/ErrorHandler.php (98%) rename {lib/Raven => src}/Event.php (98%) rename {lib/Raven => src}/Exception.php (72%) rename {lib/Raven => src}/Exception/ExceptionInterface.php (93%) rename {lib/Raven => src}/Exception/InvalidArgumentException.php (94%) rename {lib/Raven => src}/Frame.php (99%) rename {lib/Raven => src}/HttpClient/Authentication/SentryAuth.php (94%) rename {lib/Raven => src}/HttpClient/Encoding/Base64EncodingStream.php (97%) rename {lib/Raven => src}/Middleware/BreadcrumbInterfaceMiddleware.php (95%) rename {lib/Raven => src}/Middleware/ContextInterfaceMiddleware.php (97%) rename {lib/Raven => src}/Middleware/ExceptionInterfaceMiddleware.php (96%) rename {lib/Raven => src}/Middleware/MessageInterfaceMiddleware.php (95%) rename {lib/Raven => src}/Middleware/MiddlewareStack.php (99%) rename {lib/Raven => src}/Middleware/ModulesMiddleware.php (96%) rename {lib/Raven => src}/Middleware/ProcessorMiddlewareInterface.php (94%) rename {lib/Raven => src}/Middleware/RemoveHttpBodyMiddleware.php (97%) rename {lib/Raven => src}/Middleware/RemoveStacktraceContextMiddleware.php (97%) rename {lib/Raven => src}/Middleware/RequestInterfaceMiddleware.php (97%) rename {lib/Raven => src}/Middleware/SanitizeCookiesMiddleware.php (98%) rename {lib/Raven => src}/Middleware/SanitizeDataMiddleware.php (98%) rename {lib/Raven => src}/Middleware/SanitizeHttpHeadersMiddleware.php (98%) rename {lib/Raven => src}/Middleware/SanitizerMiddleware.php (96%) rename {lib/Raven => src}/Middleware/UserInterfaceMiddleware.php (96%) rename {lib/Raven => src}/ReprSerializer.php (94%) rename {lib/Raven => src}/Serializer.php (99%) rename {lib/Raven => src}/Spool/MemorySpool.php (91%) rename {lib/Raven => src}/Spool/SpoolInterface.php (90%) rename {lib/Raven => src}/Stacktrace.php (99%) rename {lib/Raven => src}/TransactionStack.php (99%) rename {lib/Raven => src}/Transport/HttpTransport.php (96%) rename {lib/Raven => src}/Transport/NullTransport.php (91%) rename {lib/Raven => src}/Transport/SpoolTransport.php (92%) rename {lib/Raven => src}/Transport/TransportInterface.php (93%) rename {lib/Raven => src}/Util/JSON.php (97%) rename {lib/Raven => src}/Util/PHPVersion.php (96%) diff --git a/.php_cs b/.php_cs index 724043b6a..74f7b090a 100644 --- a/.php_cs +++ b/.php_cs @@ -8,6 +8,8 @@ return PhpCsFixer\Config::create() 'array_syntax' => ['syntax' => 'short'], 'concat_space' => ['spacing' => 'one'], 'ordered_imports' => true, + 'psr0' => true, + 'psr4' => true, 'random_api_migration' => true, 'yoda_style' => true, 'phpdoc_align' => [ diff --git a/README.md b/README.md index a19e6c906..d777d65aa 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ and [`http-message-implementation`](https://packagist.org/providers/psr/http-mes ```php namespace XXX; -use Raven\ClientBuilder; +use Sentry\ClientBuilder; require 'vendor/autoload.php'; @@ -132,7 +132,7 @@ $ git checkout -b releases/2.1.x 3. Update the hardcoded version tag in ``Client.php``: ```php -namespace Raven; +namespace Sentry; class Client { @@ -173,7 +173,7 @@ git checkout master 9. Update the version in ``Client.php``: ```php -namespace Raven; +namespace Sentry; class Client implements ClientInterface { diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index c33fde739..eafc6e9e6 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -411,7 +411,7 @@ After: ```php - use Raven\ErrorHandler; + use Sentry\ErrorHandler; ErrorHandler::register($client); ``` @@ -429,7 +429,7 @@ After: ```php - use Raven\BreadcrumbErrorHandler; + use Sentry\BreadcrumbErrorHandler; $client = new Client([...]); @@ -568,7 +568,7 @@ to follow PSR-4 convention. After: ```php - use Raven\BreadcrumbErrorHandler; + use Sentry\BreadcrumbErrorHandler; $errorHandler = BreadcrumbErrorHandler::register($client); ``` @@ -589,7 +589,7 @@ to follow PSR-4 convention. After: ```php - use Raven\ErrorHandler; + use Sentry\ErrorHandler; $errorHandler = ErrorHandler::register($client); ``` @@ -617,7 +617,7 @@ to follow PSR-4 convention. After: ```php - use Raven\ErrorHandler; + use Sentry\ErrorHandler; $errorHandler = ErrorHandler::register($client); ``` diff --git a/composer.json b/composer.json index 1fbdb413e..777f681e1 100644 --- a/composer.json +++ b/composer.json @@ -46,15 +46,12 @@ ], "autoload": { "psr-4" : { - "Raven\\" : "lib/Raven/" - }, - "exclude-from-classmap": [ - "/tests/" - ] + "Sentry\\" : "src/" + } }, "autoload-dev": { "psr-4": { - "Raven\\Tests\\": "tests/" + "Sentry\\Tests\\": "tests/" } }, "scripts": { @@ -69,7 +66,7 @@ ], "phpstan": [ "composer require --dev phpstan/phpstan-shim ^0.9.2 phpstan/phpstan-phpunit ^0.9.2", - "vendor/bin/phpstan.phar analyse lib tests -c phpstan.neon -l 3" + "vendor/bin/phpstan.phar analyse src tests -c phpstan.neon -l 3" ] }, "config": { diff --git a/docs/error_handling.rst b/docs/error_handling.rst index 50ca89d00..3d0d0c373 100644 --- a/docs/error_handling.rst +++ b/docs/error_handling.rst @@ -8,7 +8,7 @@ client instance as first argument. .. code-block:: php - use Raven\ErrorHandler; + use Sentry\ErrorHandler; // Initialize the client @@ -20,7 +20,7 @@ argument. For example, the code below will reserve 20 megabytes of memory. .. code-block:: php - use Raven\ErrorHandler; + use Sentry\ErrorHandler; // Initialize the client @@ -42,7 +42,7 @@ the Sentry error handler was registered will still be called regardeless. .. code-block:: php - use Raven\ErrorHandler; + use Sentry\ErrorHandler; // Initialize the client @@ -60,7 +60,7 @@ the default value) and the new value will be appended to the existing mask: .. code-block:: php - use Raven\ErrorHandler; + use Sentry\ErrorHandler; // Initialize the client @@ -83,7 +83,7 @@ handler the same way as before. .. code-block:: php - use Raven\BreadcrumbErrorHandler; + use Sentry\BreadcrumbErrorHandler; // Initialize the client diff --git a/docs/index.rst b/docs/index.rst index 263dac115..58b996c02 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,7 +49,7 @@ below: .. code-block:: php use Http\Adapter\Guzzle6\Client as GuzzleClientAdapter; - use Raven\ClientBuilder; + use Sentry\ClientBuilder; require 'vendor/autoload.php'; @@ -68,8 +68,8 @@ Usage .. code-block:: php - use Raven\ClientBuilder; - use Raven\ErrorHandler; + use Sentry\ClientBuilder; + use Sentry\ErrorHandler; require 'vendor/autoload.php'; diff --git a/docs/integrations/monolog.rst b/docs/integrations/monolog.rst index 97ce9dfe8..475bcb536 100644 --- a/docs/integrations/monolog.rst +++ b/docs/integrations/monolog.rst @@ -8,7 +8,7 @@ Monolog supports Sentry out of the box, so you'll just need to configure a handl .. sourcecode:: php - $client = new \Raven\Client('___PUBLIC_DSN___'); + $client = new \Sentry\Client('___PUBLIC_DSN___'); $handler = new Monolog\Handler\RavenHandler($client); $handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n")); @@ -48,7 +48,7 @@ Sentry provides a breadcrumb handler to automatically send logs along as crumbs: .. sourcecode:: php - $client = new \Raven\Client('___PUBLIC_DSN___'); + $client = new \Sentry\Client('___PUBLIC_DSN___'); - $handler = new \Raven\Breadcrumbs\MonologHandler($client); + $handler = new \Sentry\Breadcrumbs\MonologHandler($client); $monolog->pushHandler($handler); diff --git a/docs/middleware.rst b/docs/middleware.rst index 772b4709f..ada697a6e 100644 --- a/docs/middleware.rst +++ b/docs/middleware.rst @@ -53,7 +53,7 @@ The signature of the function that will be called must be the following: .. code-block:: php - function (\Raven\Event $event, callable $next, \Psr\Http\Message\ServerRequestInterface $request = null, $exception = null, array $payload = []) + function (\Sentry\Event $event, callable $next, \Psr\Http\Message\ServerRequestInterface $request = null, $exception = null, array $payload = []) The middleware can call the next one in the chain or can directly return the event instance and break the chain. Additional data supplied by the user while @@ -64,8 +64,8 @@ middleware that customizes the message captured with an event can be written: .. code-block:: php use Psr\Http\Message\ServerRequestInterface; - use Raven\ClientBuilder; - use Raven\Event; + use Sentry\ClientBuilder; + use Sentry\Event; final class CustomMiddleware { @@ -122,8 +122,8 @@ can manage the middlewares at runtime and the chain will be recomputed according .. code-block:: php use Psr\Http\Message\ServerRequestInterface; - use Raven\ClientBuilder; - use Raven\Event; + use Sentry\ClientBuilder; + use Sentry\Event; $middleware = function (Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { // Do something here diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 79aaa957d..054724261 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -30,7 +30,7 @@ before the client instance is initialized. .. code-block:: php - use Raven\ClientBuilder; + use Sentry\ClientBuilder; $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1'])->getClient(); @@ -113,7 +113,7 @@ methods to report and clear the breadcrumbs. .. code-block:: php - use Raven\Breadcrumbs\Breadcrumb; + use Sentry\Breadcrumbs\Breadcrumb; $client->leaveBreadcrumb(Breadcrumb::create('debug', 'error', 'error_reporting', 'foo bar')); $client->clearBreadcrumbs(); diff --git a/docs/transport.rst b/docs/transport.rst index 120bd7341..2fd1056e7 100644 --- a/docs/transport.rst +++ b/docs/transport.rst @@ -29,9 +29,9 @@ the events, but report them as sent. .. code-block:: php - use Raven\Client; - use Raven\Configuration; - use Raven\Transport\NullTransport; + use Sentry\Client; + use Sentry\Configuration; + use Sentry\Transport\NullTransport; // Even though the server is configured for the client, using the NullTransport // transport won't send any event @@ -48,9 +48,9 @@ through the client builder, but you can override it using the appropriate method .. code-block:: php - use Raven\Client; - use Raven\Configuration; - use Raven\Transport\HttpTransport; + use Sentry\Client; + use Sentry\Configuration; + use Sentry\Transport\HttpTransport; $configuration = new Configuration(['server' => 'http://public:secret@example.com/1']); $transport = new HttpTransport($configuration, HttpAsyncClientDiscovery::find(), MessageFactoryDiscovery::find()); @@ -67,9 +67,9 @@ care of sending them. Currently only spooling to memory is supported. .. code-block:: php - use Raven\Client; - use Raven\Configuration; - use Raven\Transport\SpoolTransport; + use Sentry\Client; + use Sentry\Configuration; + use Sentry\Transport\SpoolTransport; // The transport used by the client to send the events uses the memory spool // which stores the events in a queue in-memory diff --git a/phpstan.neon b/phpstan.neon index 3c99d7013..1f0a623c5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,7 @@ parameters: ignoreErrors: - - '/Constructor of class Raven\\HttpClient\\Encoding\\Base64EncodingStream has an unused parameter \$(readFilterOptions|writeFilterOptions)/' - - '/Call to an undefined method Raven\\ClientBuilder::methodThatDoesNotExists\(\)/' + - '/Constructor of class Sentry\\HttpClient\\Encoding\\Base64EncodingStream has an unused parameter \$(readFilterOptions|writeFilterOptions)/' + - '/Call to an undefined method Sentry\\ClientBuilder::methodThatDoesNotExists\(\)/' - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' excludes_analyse: - tests/resources diff --git a/lib/Raven/AbstractErrorHandler.php b/src/AbstractErrorHandler.php similarity index 99% rename from lib/Raven/AbstractErrorHandler.php rename to src/AbstractErrorHandler.php index 97eb57b46..f4b9ee288 100644 --- a/lib/Raven/AbstractErrorHandler.php +++ b/src/AbstractErrorHandler.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven; +namespace Sentry; /** * This class is a nearly-complete implementation of an error handler. diff --git a/lib/Raven/BreadcrumbErrorHandler.php b/src/BreadcrumbErrorHandler.php similarity index 96% rename from lib/Raven/BreadcrumbErrorHandler.php rename to src/BreadcrumbErrorHandler.php index 6a3a83973..02b0fa4c6 100644 --- a/lib/Raven/BreadcrumbErrorHandler.php +++ b/src/BreadcrumbErrorHandler.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -namespace Raven; +namespace Sentry; -use Raven\Breadcrumbs\Breadcrumb; +use Sentry\Breadcrumbs\Breadcrumb; /** * This error handler records a breadcrumb for any raised error. diff --git a/lib/Raven/Breadcrumbs/Breadcrumb.php b/src/Breadcrumbs/Breadcrumb.php similarity index 96% rename from lib/Raven/Breadcrumbs/Breadcrumb.php rename to src/Breadcrumbs/Breadcrumb.php index 5939c932a..0e9165078 100644 --- a/lib/Raven/Breadcrumbs/Breadcrumb.php +++ b/src/Breadcrumbs/Breadcrumb.php @@ -9,10 +9,10 @@ * file that was distributed with this source code. */ -namespace Raven\Breadcrumbs; +namespace Sentry\Breadcrumbs; -use Raven\Client; -use Raven\Exception\InvalidArgumentException; +use Sentry\Client; +use Sentry\Exception\InvalidArgumentException; /** * This class stores all the informations about a breadcrumb. @@ -83,7 +83,7 @@ final class Breadcrumb implements \JsonSerializable public function __construct($level, $type, $category, $message = null, array $metadata = []) { if (!\in_array($level, self::getLevels(), true)) { - throw new InvalidArgumentException('The value of the $level argument must be one of the Raven\Client::LEVEL_* constants.'); + throw new InvalidArgumentException('The value of the $level argument must be one of the Sentry\Client::LEVEL_* constants.'); } $this->type = $type; @@ -159,7 +159,7 @@ public function getLevel() public function withLevel($level) { if (!\in_array($level, self::getLevels(), true)) { - throw new InvalidArgumentException('The value of the $level argument must be one of the Raven\Client::LEVEL_* constants.'); + throw new InvalidArgumentException('The value of the $level argument must be one of the Sentry\Client::LEVEL_* constants.'); } if ($level === $this->level) { diff --git a/lib/Raven/Breadcrumbs/MonologHandler.php b/src/Breadcrumbs/MonologHandler.php similarity index 97% rename from lib/Raven/Breadcrumbs/MonologHandler.php rename to src/Breadcrumbs/MonologHandler.php index 2b74c8102..2cdec2fbf 100644 --- a/lib/Raven/Breadcrumbs/MonologHandler.php +++ b/src/Breadcrumbs/MonologHandler.php @@ -1,11 +1,11 @@ getSerializerUnderTest(); $input = new SerializerTestObject(); $result = $serializer->serialize($input); - $this->assertEquals('Object Raven\Tests\SerializerTestObject', $result); + $this->assertEquals('Object Sentry\Tests\SerializerTestObject', $result); } public function testObjectsAreNotStrings() @@ -187,7 +187,7 @@ public function dataRecursionInObjectsDataProvider() $object->key = $object; yield [ 'object' => $object, - 'expectedResult' => ['key' => 'Object Raven\Tests\SerializerTestObject'], + 'expectedResult' => ['key' => 'Object Sentry\Tests\SerializerTestObject'], ]; $object = new SerializerTestObject(); @@ -196,7 +196,7 @@ public function dataRecursionInObjectsDataProvider() $object->key = $object2; yield [ 'object' => $object, - 'expectedResult' => ['key' => ['key' => 'Object Raven\Tests\SerializerTestObject']], + 'expectedResult' => ['key' => ['key' => 'Object Sentry\Tests\SerializerTestObject']], ]; $object = new SerializerTestObject(); @@ -229,7 +229,7 @@ public function dataRecursionInObjectsDataProvider() $object->key = $object2; yield [ 'object' => $object, - 'expectedResult' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject']]], + 'expectedResult' => ['key' => ['key' => ['key' => 'Object Sentry\\Tests\\SerializerTestObject']]], ]; $object3 = new SerializerTestObject(); @@ -241,7 +241,7 @@ public function dataRecursionInObjectsDataProvider() $object3->key = $object2; yield [ 'object' => $object, - 'expectedResult' => ['key' => ['key' => ['key' => 'Object Raven\\Tests\\SerializerTestObject'], 'keys' => 'keys']], + 'expectedResult' => ['key' => ['key' => ['key' => 'Object Sentry\\Tests\\SerializerTestObject'], 'keys' => 'keys']], ]; } @@ -287,7 +287,7 @@ public function testObjectInArray() $serializer = $this->getSerializerUnderTest(); $input = ['foo' => new SerializerTestObject()]; $result = $serializer->serialize($input); - $this->assertEquals(['foo' => 'Object Raven\\Tests\\SerializerTestObject'], $result); + $this->assertEquals(['foo' => 'Object Sentry\\Tests\\SerializerTestObject'], $result); } public function testObjectInArraySerializeAll() @@ -386,7 +386,7 @@ public function testClippingUTF8Characters() $testString = 'Прекратите надеÑтьÑÑ, что ваши пользователи будут Ñообщать об ошибках'; $class_name = static::getSerializerUnderTest(); - /** @var \Raven\Serializer $serializer */ + /** @var \Sentry\Serializer $serializer */ $serializer = new $class_name(null, 19); $clipped = $serializer->serialize($testString); @@ -424,40 +424,40 @@ public function serializableCallableProvider() $data = [ [ 'callable' => $closure1, - 'expected' => 'Lambda Raven\\Tests\\{closure} [array param1]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [array param1]', ], [ 'callable' => $closure2, - 'expected' => 'Lambda Raven\\Tests\\{closure} [mixed|null param1a]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [mixed|null param1a]', ], [ 'callable' => $closure4, - 'expected' => 'Lambda Raven\\Tests\\{closure} [callable param1c]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [callable param1c]', ], [ 'callable' => $closure5, - 'expected' => 'Lambda Raven\\Tests\\{closure} [stdClass param1d]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [stdClass param1d]', ], [ 'callable' => $closure6, - 'expected' => 'Lambda Raven\\Tests\\{closure} [stdClass|null [param1e]]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [stdClass|null [param1e]]', ], [ 'callable' => $closure7, - 'expected' => 'Lambda Raven\\Tests\\{closure} [array ¶m1f]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [array ¶m1f]', ], [ 'callable' => $closure8, - 'expected' => 'Lambda Raven\\Tests\\{closure} [array|null [¶m1g]]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [array|null [¶m1g]]', ], [ 'callable' => [$this, 'serializableCallableProvider'], - 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::serializableCallableProvider []', + 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::serializableCallableProvider []', ], [ 'callable' => [Client::class, 'getConfig'], - 'expected' => 'Callable Raven\Client::getConfig []', + 'expected' => 'Callable Sentry\Client::getConfig []', ], [ 'callable' => [TestCase::class, 'setUpBeforeClass'], 'expected' => 'Callable PHPUnit\\Framework\\TestCase::setUpBeforeClass []', ], [ 'callable' => [$this, 'setUpBeforeClass'], - 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::setUpBeforeClass []', + 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', ], [ 'callable' => [self::class, 'setUpBeforeClass'], - 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::setUpBeforeClass []', + 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', ], ]; require_once 'resources/php70_serializing.inc'; @@ -472,40 +472,40 @@ public function serializableCallableProvider() return [ [ 'callable' => $closure1, - 'expected' => 'Lambda Raven\\Tests\\{closure} [array param1]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [array param1]', ], [ 'callable' => $closure2, - 'expected' => 'Lambda Raven\\Tests\\{closure} [param1a]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [param1a]', ], [ 'callable' => $closure4, - 'expected' => 'Lambda Raven\\Tests\\{closure} [callable param1c]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [callable param1c]', ], [ 'callable' => $closure5, - 'expected' => 'Lambda Raven\\Tests\\{closure} [param1d]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [param1d]', ], [ 'callable' => $closure6, - 'expected' => 'Lambda Raven\\Tests\\{closure} [[param1e]]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [[param1e]]', ], [ 'callable' => $closure7, - 'expected' => 'Lambda Raven\\Tests\\{closure} [array ¶m1f]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [array ¶m1f]', ], [ 'callable' => $closure8, - 'expected' => 'Lambda Raven\\Tests\\{closure} [array|null [¶m1g]]', + 'expected' => 'Lambda Sentry\\Tests\\{closure} [array|null [¶m1g]]', ], [ 'callable' => [$this, 'serializableCallableProvider'], - 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::serializableCallableProvider []', + 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::serializableCallableProvider []', ], [ 'callable' => [Client::class, 'getConfig'], - 'expected' => 'Callable Raven\Client::getConfig []', + 'expected' => 'Callable Sentry\Client::getConfig []', ], [ 'callable' => [TestCase::class, 'setUpBeforeClass'], 'expected' => 'Callable PHPUnit_Framework_TestCase::setUpBeforeClass []', ], [ 'callable' => [$this, 'setUpBeforeClass'], - 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::setUpBeforeClass []', + 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', ], [ 'callable' => [self::class, 'setUpBeforeClass'], - 'expected' => 'Callable Raven\Tests\AbstractSerializerTest::setUpBeforeClass []', + 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', ], ]; } diff --git a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php index 069e3e872..bed93242f 100644 --- a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php +++ b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Sentry\Tests\Breadcrumbs; -use Raven\BreadcrumbErrorHandler; -use Raven\Breadcrumbs\Breadcrumb; -use Raven\Client; -use Raven\Tests\AbstractErrorHandlerTest; +use Sentry\BreadcrumbErrorHandler; +use Sentry\Breadcrumbs\Breadcrumb; +use Sentry\Client; +use Sentry\Tests\AbstractErrorHandlerTest; class BreadcrumbErrorHandlerTest extends AbstractErrorHandlerTest { diff --git a/tests/Breadcrumbs/BreadcrumbTest.php b/tests/Breadcrumbs/BreadcrumbTest.php index f38a38d70..975f06dd1 100644 --- a/tests/Breadcrumbs/BreadcrumbTest.php +++ b/tests/Breadcrumbs/BreadcrumbTest.php @@ -9,11 +9,11 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Sentry\Tests\Breadcrumbs; use PHPUnit\Framework\TestCase; -use Raven\Breadcrumbs\Breadcrumb; -use Raven\Client; +use Sentry\Breadcrumbs\Breadcrumb; +use Sentry\Client; /** * @group time-sensitive @@ -21,8 +21,8 @@ class BreadcrumbTest extends TestCase { /** - * @expectedException \Raven\Exception\InvalidArgumentException - * @expectedExceptionMessage The value of the $level argument must be one of the Raven\Client::LEVEL_* constants. + * @expectedException \Sentry\Exception\InvalidArgumentException + * @expectedExceptionMessage The value of the $level argument must be one of the Sentry\Client::LEVEL_* constants. */ public function testConstructorThrowsOnInvalidLevel() { @@ -30,8 +30,8 @@ public function testConstructorThrowsOnInvalidLevel() } /** - * @expectedException \Raven\Exception\InvalidArgumentException - * @expectedExceptionMessage The value of the $level argument must be one of the Raven\Client::LEVEL_* constants. + * @expectedException \Sentry\Exception\InvalidArgumentException + * @expectedExceptionMessage The value of the $level argument must be one of the Sentry\Client::LEVEL_* constants. */ public function testSetLevelThrowsOnInvalidLevel() { diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php index 79749c243..7b679ab49 100644 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -9,15 +9,15 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Sentry\Tests\Breadcrumbs; use Monolog\Logger; use PHPUnit\Framework\TestCase; -use Raven\Breadcrumbs\Breadcrumb; -use Raven\Breadcrumbs\MonologHandler; -use Raven\Client; -use Raven\ClientBuilder; -use Raven\ClientInterface; +use Sentry\Breadcrumbs\Breadcrumb; +use Sentry\Breadcrumbs\MonologHandler; +use Sentry\Client; +use Sentry\ClientBuilder; +use Sentry\ClientInterface; class MonologHandlerTest extends TestCase { diff --git a/tests/Breadcrumbs/RecorderTest.php b/tests/Breadcrumbs/RecorderTest.php index 69bfd8809..ade2cde2f 100644 --- a/tests/Breadcrumbs/RecorderTest.php +++ b/tests/Breadcrumbs/RecorderTest.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Breadcrumbs; +namespace Sentry\Tests\Breadcrumbs; use PHPUnit\Framework\TestCase; use Psr\Log\LogLevel; -use Raven\Breadcrumbs\Breadcrumb; -use Raven\Breadcrumbs\Recorder; +use Sentry\Breadcrumbs\Breadcrumb; +use Sentry\Breadcrumbs\Recorder; /** * @group time-sensitive diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 225b3788c..73674e0ef 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Raven\Tests; +namespace Sentry\Tests; use Http\Client\Common\Plugin; use Http\Client\Common\PluginClient; @@ -18,12 +18,12 @@ use Http\Message\UriFactory; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; -use Raven\Client; -use Raven\ClientBuilder; -use Raven\Configuration; -use Raven\Transport\HttpTransport; -use Raven\Transport\NullTransport; -use Raven\Transport\TransportInterface; +use Sentry\Client; +use Sentry\ClientBuilder; +use Sentry\Configuration; +use Sentry\Transport\HttpTransport; +use Sentry\Transport\NullTransport; +use Sentry\Transport\TransportInterface; class ClientBuilderTest extends TestCase { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 7e87a44d8..14f4eab8f 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -8,26 +8,26 @@ * file that was distributed with this source code. */ -namespace Raven\Tests; +namespace Sentry\Tests; use Http\Mock\Client as MockClient; use PHPUnit\Framework\TestCase; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidFactory; -use Raven\Breadcrumbs\Recorder as BreadcrumbsRecorder; -use Raven\Client; -use Raven\ClientBuilder; -use Raven\Context\Context; -use Raven\Context\RuntimeContext; -use Raven\Context\ServerOsContext; -use Raven\Context\TagsContext; -use Raven\Event; -use Raven\Middleware\MiddlewareStack; -use Raven\ReprSerializer; -use Raven\Serializer; -use Raven\Tests\Fixtures\classes\CarelessException; -use Raven\TransactionStack; -use Raven\Transport\TransportInterface; +use Sentry\Breadcrumbs\Recorder as BreadcrumbsRecorder; +use Sentry\Client; +use Sentry\ClientBuilder; +use Sentry\Context\Context; +use Sentry\Context\RuntimeContext; +use Sentry\Context\ServerOsContext; +use Sentry\Context\TagsContext; +use Sentry\Event; +use Sentry\Middleware\MiddlewareStack; +use Sentry\ReprSerializer; +use Sentry\Serializer; +use Sentry\Tests\Fixtures\classes\CarelessException; +use Sentry\TransactionStack; +use Sentry\Transport\TransportInterface; class ClientTest extends TestCase { @@ -240,7 +240,7 @@ public function testGetLastEvent() /** * @group legacy * - * @expectedDeprecation The Raven\Client::getLastEventId() method is deprecated since version 2.0. Use getLastEvent() instead. + * @expectedDeprecation The Sentry\Client::getLastEventId() method is deprecated since version 2.0. Use getLastEvent() instead. */ public function testGetLastEventId() { @@ -337,8 +337,8 @@ private function assertMixedValueAndArray($expected_value, $actual_value) } /** - * @covers \Raven\Client::translateSeverity - * @covers \Raven\Client::registerSeverityMap + * @covers \Sentry\Client::translateSeverity + * @covers \Sentry\Client::registerSeverityMap */ public function testTranslateSeverity() { @@ -466,8 +466,8 @@ public function testClearBreadcrumb() { $client = ClientBuilder::create()->getClient(); $client->leaveBreadcrumb( - new \Raven\Breadcrumbs\Breadcrumb( - 'warning', \Raven\Breadcrumbs\Breadcrumb::TYPE_ERROR, 'error_reporting', 'message', [ + new \Sentry\Breadcrumbs\Breadcrumb( + 'warning', \Sentry\Breadcrumbs\Breadcrumb::TYPE_ERROR, 'error_reporting', 'message', [ 'code' => 127, 'line' => 10, 'file' => '/tmp/delme.php', diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 42eed584d..2ade23aa3 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -9,11 +9,11 @@ * file that was distributed with this source code. */ -namespace Raven\Tests; +namespace Sentry\Tests; use PHPUnit\Framework\TestCase; -use Raven\Configuration; -use Raven\Event; +use Sentry\Configuration; +use Sentry\Event; class ConfigurationTest extends TestCase { diff --git a/tests/Context/ContextTest.php b/tests/Context/ContextTest.php index 4ea653053..3725d29ab 100644 --- a/tests/Context/ContextTest.php +++ b/tests/Context/ContextTest.php @@ -9,10 +9,10 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Context; +namespace Sentry\Tests\Context; use PHPUnit\Framework\TestCase; -use Raven\Context\Context; +use Sentry\Context\Context; class ContextTest extends TestCase { diff --git a/tests/Context/RuntimeContextTest.php b/tests/Context/RuntimeContextTest.php index 8d49a85a3..09861297b 100644 --- a/tests/Context/RuntimeContextTest.php +++ b/tests/Context/RuntimeContextTest.php @@ -9,11 +9,11 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Context; +namespace Sentry\Tests\Context; use PHPUnit\Framework\TestCase; -use Raven\Context\RuntimeContext; -use Raven\Util\PHPVersion; +use Sentry\Context\RuntimeContext; +use Sentry\Util\PHPVersion; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; diff --git a/tests/Context/ServerOsContextTest.php b/tests/Context/ServerOsContextTest.php index 130c11a0d..9641fe13e 100644 --- a/tests/Context/ServerOsContextTest.php +++ b/tests/Context/ServerOsContextTest.php @@ -9,10 +9,10 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Context; +namespace Sentry\Tests\Context; use PHPUnit\Framework\TestCase; -use Raven\Context\ServerOsContext; +use Sentry\Context\ServerOsContext; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; diff --git a/tests/Context/TagsContextTest.php b/tests/Context/TagsContextTest.php index f93cf7576..09826a429 100644 --- a/tests/Context/TagsContextTest.php +++ b/tests/Context/TagsContextTest.php @@ -9,10 +9,10 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Context; +namespace Sentry\Tests\Context; use PHPUnit\Framework\TestCase; -use Raven\Context\TagsContext; +use Sentry\Context\TagsContext; class TagsContextTest extends TestCase { diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 7d22baa8f..dede8ef88 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -9,10 +9,10 @@ * file that was distributed with this source code. */ -namespace Raven\Tests; +namespace Sentry\Tests; -use Raven\AbstractErrorHandler; -use Raven\ErrorHandler; +use Sentry\AbstractErrorHandler; +use Sentry\ErrorHandler; class ErrorHandlerTest extends AbstractErrorHandlerTest { diff --git a/tests/EventTest.php b/tests/EventTest.php index 7bf2e7940..ec31ac671 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -1,21 +1,21 @@ 'foobar', - 'version' => PHP_VERSION, + 'version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION, ], null, ], diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php index 1d5f5db78..f63bb6d2b 100644 --- a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php +++ b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php @@ -9,14 +9,14 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\Client; -use Raven\ClientBuilder; -use Raven\Event; -use Raven\Middleware\ExceptionInterfaceMiddleware; -use Raven\Stacktrace; +use Sentry\Client; +use Sentry\ClientBuilder; +use Sentry\Event; +use Sentry\Middleware\ExceptionInterfaceMiddleware; +use Sentry\Stacktrace; class ExceptionInterfaceMiddlewareTest extends TestCase { @@ -213,7 +213,7 @@ public function testInvokeWithExceptionThrownInLatin1File() $latin1StringFound = false; - /** @var \Raven\Frame $frame */ + /** @var \Sentry\Frame $frame */ foreach ($result['values'][0]['stacktrace']->getFrames() as $frame) { if (null !== $frame->getPreContext() && \in_array('// äöü', $frame->getPreContext(), true)) { $latin1StringFound = true; diff --git a/tests/Middleware/MessageInterfaceMiddlewareTest.php b/tests/Middleware/MessageInterfaceMiddlewareTest.php index 69733a0fc..536919270 100644 --- a/tests/Middleware/MessageInterfaceMiddlewareTest.php +++ b/tests/Middleware/MessageInterfaceMiddlewareTest.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\Configuration; -use Raven\Event; -use Raven\Middleware\MessageInterfaceMiddleware; +use Sentry\Configuration; +use Sentry\Event; +use Sentry\Middleware\MessageInterfaceMiddleware; class MessageInterfaceMiddlewareTest extends TestCase { diff --git a/tests/Middleware/MiddlewareStackTest.php b/tests/Middleware/MiddlewareStackTest.php index dddd4e5d1..1f773cea9 100644 --- a/tests/Middleware/MiddlewareStackTest.php +++ b/tests/Middleware/MiddlewareStackTest.php @@ -9,13 +9,13 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use Raven\Configuration; -use Raven\Event; -use Raven\Middleware\MiddlewareStack; +use Sentry\Configuration; +use Sentry\Event; +use Sentry\Middleware\MiddlewareStack; class MiddlewareStackTest extends TestCase { @@ -173,7 +173,7 @@ public function testRemoveMiddlewareThrowsWhileStackIsRunning() /** * @expectedException \UnexpectedValueException - * @expectedExceptionMessage Middleware must return an instance of the "Raven\Event" class. + * @expectedExceptionMessage Middleware must return an instance of the "Sentry\Event" class. */ public function testMiddlewareThrowsWhenBadValueIsReturned() { @@ -191,7 +191,7 @@ public function testMiddlewareThrowsWhenBadValueIsReturned() /** * @expectedException \UnexpectedValueException - * @expectedExceptionMessage Middleware must return an instance of the "Raven\Event" class. + * @expectedExceptionMessage Middleware must return an instance of the "Sentry\Event" class. */ public function testMiddlewareThrowsWhenBadValueIsReturnedFromHandler() { diff --git a/tests/Middleware/ModulesMiddlewareTest.php b/tests/Middleware/ModulesMiddlewareTest.php index c06b0ec1c..f54f050ab 100644 --- a/tests/Middleware/ModulesMiddlewareTest.php +++ b/tests/Middleware/ModulesMiddlewareTest.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\Configuration; -use Raven\Event; -use Raven\Middleware\ModulesMiddleware; +use Sentry\Configuration; +use Sentry\Event; +use Sentry\Middleware\ModulesMiddleware; class ModulesMiddlewareTest extends TestCase { diff --git a/tests/Middleware/RemoveHttpBodyMiddlewareTest.php b/tests/Middleware/RemoveHttpBodyMiddlewareTest.php index 974495d94..6cbf0607a 100644 --- a/tests/Middleware/RemoveHttpBodyMiddlewareTest.php +++ b/tests/Middleware/RemoveHttpBodyMiddlewareTest.php @@ -9,13 +9,13 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\ClientBuilder; -use Raven\ClientInterface; -use Raven\Event; -use Raven\Middleware\RemoveHttpBodyMiddleware; +use Sentry\ClientBuilder; +use Sentry\ClientInterface; +use Sentry\Event; +use Sentry\Middleware\RemoveHttpBodyMiddleware; class RemoveHttpBodyMiddlewareTest extends TestCase { diff --git a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php index 87a3f8e18..247096c5b 100644 --- a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php +++ b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php @@ -9,14 +9,14 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\ClientBuilder; -use Raven\ClientInterface; -use Raven\Event; -use Raven\Middleware\RemoveStacktraceContextMiddleware; -use Raven\Stacktrace; +use Sentry\ClientBuilder; +use Sentry\ClientInterface; +use Sentry\Event; +use Sentry\Middleware\RemoveStacktraceContextMiddleware; +use Sentry\Stacktrace; class RemoveStacktraceContextMiddlewareTest extends TestCase { diff --git a/tests/Middleware/RequestInterfaceMiddlewareTest.php b/tests/Middleware/RequestInterfaceMiddlewareTest.php index 7763adaed..b45f416e2 100644 --- a/tests/Middleware/RequestInterfaceMiddlewareTest.php +++ b/tests/Middleware/RequestInterfaceMiddlewareTest.php @@ -9,13 +9,13 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use Raven\Configuration; -use Raven\Event; -use Raven\Middleware\RequestInterfaceMiddleware; +use Sentry\Configuration; +use Sentry\Event; +use Sentry\Middleware\RequestInterfaceMiddleware; use Zend\Diactoros\ServerRequest; use Zend\Diactoros\Uri; diff --git a/tests/Middleware/SanitizeCookiesMiddlewareTest.php b/tests/Middleware/SanitizeCookiesMiddlewareTest.php index cbe2f351f..8b1d91f46 100644 --- a/tests/Middleware/SanitizeCookiesMiddlewareTest.php +++ b/tests/Middleware/SanitizeCookiesMiddlewareTest.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\Configuration; -use Raven\Event; -use Raven\Middleware\SanitizeCookiesMiddleware; +use Sentry\Configuration; +use Sentry\Event; +use Sentry\Middleware\SanitizeCookiesMiddleware; class SanitizeCookiesMiddlewareTest extends TestCase { diff --git a/tests/Middleware/SanitizeDataMiddlewareTest.php b/tests/Middleware/SanitizeDataMiddlewareTest.php index 04d2c4152..a212c9395 100644 --- a/tests/Middleware/SanitizeDataMiddlewareTest.php +++ b/tests/Middleware/SanitizeDataMiddlewareTest.php @@ -9,14 +9,14 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\ClientBuilder; -use Raven\ClientInterface; -use Raven\Event; -use Raven\Middleware\SanitizeDataMiddleware; -use Raven\Stacktrace; +use Sentry\ClientBuilder; +use Sentry\ClientInterface; +use Sentry\Event; +use Sentry\Middleware\SanitizeDataMiddleware; +use Sentry\Stacktrace; class SanitizeDataMiddlewareTest extends TestCase { diff --git a/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php b/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php index efb1ac87d..f4862f3f4 100644 --- a/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php +++ b/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\Configuration; -use Raven\Event; -use Raven\Middleware\SanitizeHttpHeadersMiddleware; +use Sentry\Configuration; +use Sentry\Event; +use Sentry\Middleware\SanitizeHttpHeadersMiddleware; class SanitizeHttpHeadersMiddlewareTest extends TestCase { diff --git a/tests/Middleware/SanitizerMiddlewareTest.php b/tests/Middleware/SanitizerMiddlewareTest.php index 0febb53d8..9b61edb22 100644 --- a/tests/Middleware/SanitizerMiddlewareTest.php +++ b/tests/Middleware/SanitizerMiddlewareTest.php @@ -9,13 +9,13 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\Configuration; -use Raven\Event; -use Raven\Middleware\SanitizerMiddleware; -use Raven\Serializer; +use Sentry\Configuration; +use Sentry\Event; +use Sentry\Middleware\SanitizerMiddleware; +use Sentry\Serializer; class SanitizerMiddlewareTest extends TestCase { diff --git a/tests/Middleware/UserInterfaceMiddlewareTest.php b/tests/Middleware/UserInterfaceMiddlewareTest.php index be539540e..2b4eca5f6 100644 --- a/tests/Middleware/UserInterfaceMiddlewareTest.php +++ b/tests/Middleware/UserInterfaceMiddlewareTest.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Raven\Tests\Middleware; +namespace Sentry\Tests\Middleware; use PHPUnit\Framework\TestCase; -use Raven\Configuration; -use Raven\Event; -use Raven\Middleware\UserInterfaceMiddleware; +use Sentry\Configuration; +use Sentry\Event; +use Sentry\Middleware\UserInterfaceMiddleware; use Zend\Diactoros\ServerRequest; class UserInterfaceMiddlewareTest extends TestCase diff --git a/tests/ReprSerializerTest.php b/tests/ReprSerializerTest.php index 71d5633e5..37058a86a 100644 --- a/tests/ReprSerializerTest.php +++ b/tests/ReprSerializerTest.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -namespace Raven\Tests; +namespace Sentry\Tests; -use Raven\ReprSerializer; +use Sentry\ReprSerializer; class ReprSerializerTest extends AbstractSerializerTest { @@ -91,7 +91,7 @@ public function testNull($serialize_all_objects) /** * @param bool $serialize_all_objects * @dataProvider serializeAllObjectsProvider - * @covers \Raven\ReprSerializer::serializeValue + * @covers \Sentry\ReprSerializer::serializeValue */ public function testSerializeRoundedFloat($serialize_all_objects) { diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index 4a50619a6..2c25434a9 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -1,8 +1,8 @@ getLastEvent()); @@ -39,5 +39,5 @@ class TestClass implements \Serializable } ?> --EXPECTF-- -Fatal error: Class Raven\Tests\TestClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d -Shutdown function called \ No newline at end of file +Fatal error: Class Sentry\Tests\TestClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d +Shutdown function called diff --git a/tests/phpt/fatal_error_not_captured_twice.phpt b/tests/phpt/fatal_error_not_captured_twice.phpt index fdec9a6f4..34fd03bef 100644 --- a/tests/phpt/fatal_error_not_captured_twice.phpt +++ b/tests/phpt/fatal_error_not_captured_twice.phpt @@ -3,13 +3,13 @@ Test catching fatal errors does not capture twice --FILE-- --EXPECTREGEX-- Fatal error: (?:Class 'Foo\\Bar' not found in [^\r\n]+ on line \d+|Uncaught Error: Class 'Foo\\Bar' not found in [^\r\n]+:\d+) -(?:Stack trace:[\s\S]+)?Shutdown function called \ No newline at end of file +(?:Stack trace:[\s\S]+)?Shutdown function called diff --git a/tests/phpt/out_of_memory.phpt b/tests/phpt/out_of_memory.phpt index 1c978eecf..b6c5dfd58 100644 --- a/tests/phpt/out_of_memory.phpt +++ b/tests/phpt/out_of_memory.phpt @@ -3,11 +3,11 @@ Test catching out of memory fatal error --FILE-- getLastEvent()); @@ -40,4 +40,4 @@ $foo = str_repeat('x', 1024 * 1024 * 30); ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d -Shutdown function called \ No newline at end of file +Shutdown function called From 8f72acc6a3df3e04dc940b4e80850d1d9fb64edd Mon Sep 17 00:00:00 2001 From: HazA Date: Tue, 9 Oct 2018 13:31:59 +0200 Subject: [PATCH 0374/1161] fix: cs command in makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9e2840e47..915ce6237 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,10 @@ update-submodules: git submodule update cs: - vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff + vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff cs-dry-run: - vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run + vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run test: cs-dry-run vendor/bin/phpunit From 3fe6a365146303f57329fddc213c17e600ccad58 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 15 Oct 2018 16:12:22 +0200 Subject: [PATCH 0375/1161] Bump the minimum requirements to PHP 7.1 (#672) --- .scrutinizer.yml | 26 ++++++++++++++------------ .travis.yml | 34 +++++++++++++++++----------------- composer.json | 13 +++++++------ phpunit.xml.dist | 2 +- tests/EventTest.php | 3 ++- 5 files changed, 41 insertions(+), 37 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index f8d25d1a9..6620f0be4 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,19 +1,21 @@ tools: - php_sim: false - php_pdepend: true - php_analyzer: true - php_code_coverage: true external_code_coverage: - timeout: 2400 # There can be another pull request in progress runs: 1 build: - environment: - php: - version: 5.6.0 - redis: false - postgresql: false - mongodb: false + nodes: + analysis: + tests: + override: + - php-scrutinizer-run filter: - excluded_paths: [vendor/*, tests/*, bin/*, docs/*, examples/*] + paths: + - src/ + - tests/ + +coding_style: + php: + spaces: + around_operators: + concatenation: true diff --git a/.travis.yml b/.travis.yml index caa517b55..95a5fc6d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,13 @@ language: php php: - - 5.6 - - 7.0 - 7.1 - 7.2 matrix: fast_finish: true - exclude: + allow_failures: - php: nightly - env: REMOVE_XDEBUG="1" env: - REMOVE_XDEBUG="0" @@ -31,30 +28,33 @@ script: jobs: include: - stage: Test - php: 5.6 + php: 7.2 + env: + REMOVE_XDEBUG: "0" + COVERAGE: true + script: + - vendor/bin/phpunit --verbose --configuration phpunit.xml.dist --coverage-clover tests/clover.xml + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT + - php: 7.1 env: COMPOSER_OPTIONS: "--prefer-lowest" REMOVE_XDEBUG: "1" install: travis_retry composer update --no-interaction --prefer-lowest + - php: nightly + allow_failure: true + before_install: + - composer remove --dev friendsofphp/php-cs-fixer + env: + REMOVE_XDEBUG: "0" - stage: Code style & static analysis - php: 7.1 env: CS-FIXER: true REMOVE_XDEBUG: "1" script: - composer phpcs - - php: 7.1 - env: + - env: PHPSTAN: true REMOVE_XDEBUG: "1" script: - composer phpstan - - stage: Coverage - php: 7.1 - env: - REMOVE_XDEBUG: "0" - script: - - vendor/bin/phpunit --verbose --configuration phpunit.xml.dist --coverage-clover tests/clover.xml - after_success: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT diff --git a/composer.json b/composer.json index 777f681e1..3042cb2cd 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": "^5.6|^7.0", + "php": "^7.1", "ext-json": "*", "ext-mbstring": "*", "php-http/async-client-implementation": "~1.0", @@ -26,13 +26,15 @@ }, "require-dev": { "composer/composer": "^1.6", - "friendsofphp/php-cs-fixer": "~2.1", + "friendsofphp/php-cs-fixer": "^2.13", "monolog/monolog": "^1.3", "php-http/curl-client": "^1.7.1", "php-http/message": "^1.5", "php-http/mock-client": "~1.0", - "phpunit/phpunit": "^5.7.27|^6.0", - "symfony/phpunit-bridge": "^4.0" + "phpstan/phpstan-phpunit": "^0.9.2", + "phpstan/phpstan": "^0.9.2", + "phpunit/phpunit": "^7.0", + "symfony/phpunit-bridge": "^4.1.6" }, "suggest": { "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" @@ -65,8 +67,7 @@ "vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run" ], "phpstan": [ - "composer require --dev phpstan/phpstan-shim ^0.9.2 phpstan/phpstan-phpunit ^0.9.2", - "vendor/bin/phpstan.phar analyse src tests -c phpstan.neon -l 3" + "vendor/bin/phpstan analyse src tests -c phpstan.neon -l 3" ] }, "config": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 89cadfc81..6a8588bcb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -25,7 +25,7 @@ - lib + src diff --git a/tests/EventTest.php b/tests/EventTest.php index ec31ac671..8a8348d88 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -16,6 +16,7 @@ use Sentry\Context\ServerOsContext; use Sentry\Context\TagsContext; use Sentry\Event; +use Sentry\Util\PHPVersion; /** * @group time-sensitive @@ -105,7 +106,7 @@ public function testToArray() ], 'runtime' => [ 'name' => 'php', - 'version' => PHP_VERSION, + 'version' => PHPVersion::parseVersion(), ], ], ]; From ee62f511e670e23e86d94547dd83aba4861d2a89 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 15 Oct 2018 23:23:50 +0200 Subject: [PATCH 0376/1161] Ensure that the middleware arguments are forwarded correctly to the next in the chain (#666) --- src/Middleware/RemoveHttpBodyMiddleware.php | 8 +- src/Middleware/SanitizeCookiesMiddleware.php | 14 +- src/Middleware/SanitizeDataMiddleware.php | 12 +- .../SanitizeHttpHeadersMiddleware.php | 22 ++- src/Middleware/SanitizerMiddleware.php | 4 +- .../BreadcrumbInterfaceMiddlewareTest.php | 20 +-- .../ContextInterfaceMiddlewareTest.php | 31 ++-- .../ExceptionInterfaceMiddlewareTest.php | 166 ++++++++---------- .../MessageInterfaceMiddlewareTest.php | 31 +--- tests/Middleware/MiddlewareTestCase.php | 53 ++++++ tests/Middleware/ModulesMiddlewareTest.php | 15 +- .../RemoveHttpBodyMiddlewareTest.php | 15 +- .../RemoveStacktraceContextMiddlewareTest.php | 43 ++--- .../RequestInterfaceMiddlewareTest.php | 27 +-- .../SanitizeCookiesMiddlewareTest.php | 21 +-- .../Middleware/SanitizeDataMiddlewareTest.php | 39 ++-- .../SanitizeHttpHeadersMiddlewareTest.php | 14 +- tests/Middleware/SanitizerMiddlewareTest.php | 25 +-- .../UserInterfaceMiddlewareTest.php | 27 +-- 19 files changed, 249 insertions(+), 338 deletions(-) create mode 100644 tests/Middleware/MiddlewareTestCase.php diff --git a/src/Middleware/RemoveHttpBodyMiddleware.php b/src/Middleware/RemoveHttpBodyMiddleware.php index a77780dbc..bb1f3b95b 100644 --- a/src/Middleware/RemoveHttpBodyMiddleware.php +++ b/src/Middleware/RemoveHttpBodyMiddleware.php @@ -36,13 +36,13 @@ final class RemoveHttpBodyMiddleware implements ProcessorMiddlewareInterface */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - $request = $event->getRequest(); + $requestData = $event->getRequest(); - if (isset($request['method']) && \in_array(strtoupper($request['method']), ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { - $request['data'] = self::STRING_MASK; + if (isset($requestData['method']) && \in_array(strtoupper($requestData['method']), ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { + $requestData['data'] = self::STRING_MASK; } - $event->setRequest($request); + $event->setRequest($requestData); return $next($event, $request, $exception, $payload); } diff --git a/src/Middleware/SanitizeCookiesMiddleware.php b/src/Middleware/SanitizeCookiesMiddleware.php index e41700d7d..979242021 100644 --- a/src/Middleware/SanitizeCookiesMiddleware.php +++ b/src/Middleware/SanitizeCookiesMiddleware.php @@ -60,10 +60,10 @@ public function __construct(array $options = []) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - $request = $event->getRequest(); + $requestData = $event->getRequest(); - if (isset($request['cookies'])) { - $cookiesToSanitize = array_keys($request['cookies']); + if (isset($requestData['cookies'])) { + $cookiesToSanitize = array_keys($requestData['cookies']); if (!empty($this->options['only'])) { $cookiesToSanitize = $this->options['only']; @@ -73,18 +73,18 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r $cookiesToSanitize = array_diff($cookiesToSanitize, $this->options['except']); } - foreach ($request['cookies'] as $name => $value) { + foreach ($requestData['cookies'] as $name => $value) { if (!\in_array($name, $cookiesToSanitize)) { continue; } - $request['cookies'][$name] = self::STRING_MASK; + $requestData['cookies'][$name] = self::STRING_MASK; } } - unset($request['headers']['cookie']); + unset($requestData['headers']['cookie']); - $event->setRequest($request); + $event->setRequest($requestData); return $next($event, $request, $exception, $payload); } diff --git a/src/Middleware/SanitizeDataMiddleware.php b/src/Middleware/SanitizeDataMiddleware.php index a5968ff27..f0d7d3f33 100644 --- a/src/Middleware/SanitizeDataMiddleware.php +++ b/src/Middleware/SanitizeDataMiddleware.php @@ -59,21 +59,21 @@ public function __construct(array $options = []) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - $exception = $event->getException(); + $exceptionData = $event->getException(); $stacktrace = $event->getStacktrace(); - $request = $event->getRequest(); + $requestData = $event->getRequest(); $extraContext = $event->getExtraContext()->toArray(); - if (!empty($exception)) { - $event->setException($this->sanitizeException($exception)); + if (!empty($exceptionData)) { + $event->setException($this->sanitizeException($exceptionData)); } if ($stacktrace) { $event->setStacktrace($this->sanitizeStacktrace($stacktrace)); } - if (!empty($request)) { - $event->setRequest($this->sanitizeHttp($request)); + if (!empty($requestData)) { + $event->setRequest($this->sanitizeHttp($requestData)); } if (!empty($extraContext)) { diff --git a/src/Middleware/SanitizeHttpHeadersMiddleware.php b/src/Middleware/SanitizeHttpHeadersMiddleware.php index f87e97f98..61156fa4d 100644 --- a/src/Middleware/SanitizeHttpHeadersMiddleware.php +++ b/src/Middleware/SanitizeHttpHeadersMiddleware.php @@ -55,22 +55,20 @@ public function __construct(array $options = []) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - $request = $event->getRequest(); + $requestData = $event->getRequest(); - if (!isset($request['headers'])) { - return $event; - } - - foreach ($request['headers'] as $header => &$value) { - if (\in_array($header, $this->options['sanitize_http_headers'], true)) { - $value = self::STRING_MASK; + if (isset($requestData['headers'])) { + foreach ($requestData['headers'] as $header => &$value) { + if (\in_array($header, $this->options['sanitize_http_headers'], true)) { + $value = self::STRING_MASK; + } } - } - // Break the reference and free some memory - unset($value); + // Break the reference and free some memory + unset($value); - $event->setRequest($request); + $event->setRequest($requestData); + } return $next($event, $request, $exception, $payload); } diff --git a/src/Middleware/SanitizerMiddleware.php b/src/Middleware/SanitizerMiddleware.php index 126d3f281..14a5751d9 100644 --- a/src/Middleware/SanitizerMiddleware.php +++ b/src/Middleware/SanitizerMiddleware.php @@ -51,8 +51,8 @@ public function __construct(Serializer $serializer) */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - if (!empty($request = $event->getRequest())) { - $event->setRequest($this->serializer->serialize($request, 5)); + if (!empty($requestData = $event->getRequest())) { + $event->setRequest($this->serializer->serialize($requestData, 5)); } $event->getUserContext()->replaceData($this->serializer->serialize($event->getUserContext()->toArray())); diff --git a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php index d561ac3f7..0a9235761 100644 --- a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php +++ b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php @@ -11,15 +11,12 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\Breadcrumbs\Breadcrumb; use Sentry\Breadcrumbs\Recorder; use Sentry\Client; -use Sentry\Configuration; -use Sentry\Event; use Sentry\Middleware\BreadcrumbInterfaceMiddleware; -class BreadcrumbInterfaceMiddlewareTest extends TestCase +class BreadcrumbInterfaceMiddlewareTest extends MiddlewareTestCase { public function testInvoke() { @@ -30,19 +27,10 @@ public function testInvoke() $recorder->record($breadcrumb); $recorder->record($breadcrumb2); - $configuration = new Configuration(); - $event = new Event($configuration); - - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($breadcrumb, $breadcrumb2, &$callbackInvoked) { - $this->assertEquals([$breadcrumb, $breadcrumb2], $eventArg->getBreadcrumbs()); - - $callbackInvoked = true; - }; - $middleware = new BreadcrumbInterfaceMiddleware($recorder); - $middleware($event, $callback); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware); + + $this->assertEquals([$breadcrumb, $breadcrumb2], $returnedEvent->getBreadcrumbs()); } } diff --git a/tests/Middleware/ContextInterfaceMiddlewareTest.php b/tests/Middleware/ContextInterfaceMiddlewareTest.php index a75f4b658..80898500c 100644 --- a/tests/Middleware/ContextInterfaceMiddlewareTest.php +++ b/tests/Middleware/ContextInterfaceMiddlewareTest.php @@ -11,13 +11,10 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; -use Sentry\Configuration; use Sentry\Context\Context; -use Sentry\Event; use Sentry\Middleware\ContextInterfaceMiddleware; -class ContextInterfaceMiddlewareTest extends TestCase +class ContextInterfaceMiddlewareTest extends MiddlewareTestCase { /** * @dataProvider invokeDataProvider @@ -29,26 +26,18 @@ public function testInvoke($contextName, $initialData, $payloadData, $expectedDa $this->expectExceptionMessage($expectedExceptionMessage); } - $context = new Context($initialData); - $event = new Event(new Configuration()); - - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($contextName, $expectedData, &$callbackInvoked) { - $method = preg_replace_callback('/_[a-zA-Z]/', function ($matches) { - return strtoupper($matches[0][1]); - }, 'get_' . $contextName . '_context'); - - $this->assertEquals($expectedData, $eventArg->$method()->toArray()); + $middleware = new ContextInterfaceMiddleware(new Context($initialData), $contextName); + $payload = [ + $contextName . '_context' => $payloadData, + ]; - $callbackInvoked = true; - }; + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, null, null, null, $payload); - $middleware = new ContextInterfaceMiddleware($context, $contextName); - $middleware($event, $callback, null, null, [ - $contextName . '_context' => $payloadData, - ]); + $method = preg_replace_callback('/_[a-zA-Z]/', function ($matches) { + return strtoupper($matches[0][1]); + }, 'get_' . $contextName . '_context'); - $this->assertTrue($callbackInvoked); + $this->assertEquals($expectedData, $returnedEvent->$method()->toArray()); } public function invokeDataProvider() diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php index f63bb6d2b..17e02e67f 100644 --- a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php +++ b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php @@ -11,45 +11,38 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Middleware\ExceptionInterfaceMiddleware; use Sentry\Stacktrace; -class ExceptionInterfaceMiddlewareTest extends TestCase +class ExceptionInterfaceMiddlewareTest extends MiddlewareTestCase { /** * @dataProvider invokeDataProvider */ - public function testInvoke($exception, $clientConfig, $payload, $expectedResult) + public function testInvoke(\Exception $exception, array $clientConfig, array $payload, array $expectedResult) { $client = ClientBuilder::create($clientConfig)->getClient(); $assertHasStacktrace = $client->getConfig()->getAutoLogStacks(); $event = new Event($client->getConfig()); - $callbackInvoked = 0; - $callback = function (Event $eventArg) use ($assertHasStacktrace, $expectedResult, &$callbackInvoked) { - $this->assertArraySubset($expectedResult, $eventArg->toArray()); - - foreach ($eventArg->getException()['values'] as $exception) { - if ($assertHasStacktrace) { - $this->assertArrayHasKey('stacktrace', $exception); - $this->assertInstanceOf(Stacktrace::class, $exception['stacktrace']); - } else { - $this->assertArrayNotHasKey('stacktrace', $exception); - } - } + $middleware = new ExceptionInterfaceMiddleware($client); - $callbackInvoked = true; - }; + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, $exception, $payload); - $middleware = new ExceptionInterfaceMiddleware($client); - $middleware($event, $callback, null, $exception, $payload); + $this->assertArraySubset($expectedResult, $returnedEvent->toArray()); - $this->assertTrue($callbackInvoked); + foreach ($returnedEvent->getException()['values'] as $exceptionData) { + if ($assertHasStacktrace) { + $this->assertArrayHasKey('stacktrace', $exceptionData); + $this->assertInstanceOf(Stacktrace::class, $exceptionData['stacktrace']); + } else { + $this->assertArrayNotHasKey('stacktrace', $exceptionData); + } + } } public function invokeDataProvider() @@ -139,26 +132,20 @@ public function testInvokeWithExceptionContainingLatin1Characters() $utf8String = 'äöü'; $latin1String = utf8_decode($utf8String); - $callbackInvoked = 0; - $callback = function (Event $eventArg) use (&$callbackInvoked, $utf8String) { - $expectedValue = [ - 'values' => [ - [ - 'type' => \Exception::class, - 'value' => $utf8String, - ], - ], - ]; - - $this->assertArraySubset($expectedValue, $eventArg->getException()); + $middleware = new ExceptionInterfaceMiddleware($client); - $callbackInvoked = true; - }; + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, new \Exception($latin1String)); - $middleware = new ExceptionInterfaceMiddleware($client); - $middleware($event, $callback, null, new \Exception($latin1String)); + $expectedValue = [ + 'values' => [ + [ + 'type' => \Exception::class, + 'value' => $utf8String, + ], + ], + ]; - $this->assertTrue($callbackInvoked); + $this->assertArraySubset($expectedValue, $returnedEvent->getException()); } public function testInvokeWithExceptionContainingInvalidUtf8Characters() @@ -166,26 +153,21 @@ public function testInvokeWithExceptionContainingInvalidUtf8Characters() $client = ClientBuilder::create()->getClient(); $event = new Event($client->getConfig()); - $callbackInvoked = 0; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - $expectedValue = [ - 'values' => [ - [ - 'type' => \Exception::class, - 'value' => "\xC2\xA2\x3F", - ], - ], - ]; - - $this->assertArraySubset($expectedValue, $eventArg->getException()); + $middleware = new ExceptionInterfaceMiddleware($client); - $callbackInvoked = true; - }; + $malformedString = "\xC2\xA2\xC2"; // ill-formed 2-byte character U+00A2 (CENT SIGN) + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, new \Exception($malformedString)); - $middleware = new ExceptionInterfaceMiddleware($client); - $middleware($event, $callback, null, new \Exception("\xC2\xA2\xC2")); // ill-formed 2-byte character U+00A2 (CENT SIGN) + $expectedValue = [ + 'values' => [ + [ + 'type' => \Exception::class, + 'value' => "\xC2\xA2\x3F", + ], + ], + ]; - $this->assertTrue($callbackInvoked); + $this->assertArraySubset($expectedValue, $returnedEvent->getException()); } public function testInvokeWithExceptionThrownInLatin1File() @@ -197,40 +179,39 @@ public function testInvokeWithExceptionThrownInLatin1File() $event = new Event($client->getConfig()); - $callbackInvoked = false; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - $result = $eventArg->getException(); - $expectedValue = [ - 'values' => [ - [ - 'type' => \Exception::class, - 'value' => 'foo', - ], - ], - ]; - - $this->assertArraySubset($expectedValue, $result); + $middleware = new ExceptionInterfaceMiddleware($client); - $latin1StringFound = false; + $returnedEvent = $this->assertMiddlewareInvokesNext( + $middleware, + $event, + null, + require_once __DIR__ . '/../Fixtures/code/Latin1File.php' + ); - /** @var \Sentry\Frame $frame */ - foreach ($result['values'][0]['stacktrace']->getFrames() as $frame) { - if (null !== $frame->getPreContext() && \in_array('// äöü', $frame->getPreContext(), true)) { - $latin1StringFound = true; + $result = $returnedEvent->getException(); + $expectedValue = [ + 'values' => [ + [ + 'type' => \Exception::class, + 'value' => 'foo', + ], + ], + ]; - break; - } - } + $this->assertArraySubset($expectedValue, $result); - $this->assertTrue($latin1StringFound); + $latin1StringFound = false; - $callbackInvoked = true; - }; + /** @var \Sentry\Frame $frame */ + foreach ($result['values'][0]['stacktrace']->getFrames() as $frame) { + if (null !== $frame->getPreContext() && \in_array('// äöü', $frame->getPreContext(), true)) { + $latin1StringFound = true; - $middleware = new ExceptionInterfaceMiddleware($client); - $middleware($event, $callback, null, require_once __DIR__ . '/../Fixtures/code/Latin1File.php'); + break; + } + } - $this->assertTrue($callbackInvoked); + $this->assertTrue($latin1StringFound); } public function testInvokeWithAutoLogStacksDisabled() @@ -238,22 +219,15 @@ public function testInvokeWithAutoLogStacksDisabled() $client = ClientBuilder::create(['auto_log_stacks' => false])->getClient(); $event = new Event($client->getConfig()); - $callbackInvoked = false; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - $result = $eventArg->getException(); - - $this->assertNotEmpty($result); - $this->assertInternalType('array', $result['values'][0]); - $this->assertEquals(\Exception::class, $result['values'][0]['type']); - $this->assertEquals('foo', $result['values'][0]['value']); - $this->assertArrayNotHasKey('stacktrace', $result['values'][0]); - - $callbackInvoked = true; - }; - $middleware = new ExceptionInterfaceMiddleware($client); - $middleware($event, $callback, null, new \Exception('foo')); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, new \Exception('foo')); + + $result = $returnedEvent->getException(); + $this->assertNotEmpty($result); + $this->assertInternalType('array', $result['values'][0]); + $this->assertEquals(\Exception::class, $result['values'][0]['type']); + $this->assertEquals('foo', $result['values'][0]['value']); + $this->assertArrayNotHasKey('stacktrace', $result['values'][0]); } } diff --git a/tests/Middleware/MessageInterfaceMiddlewareTest.php b/tests/Middleware/MessageInterfaceMiddlewareTest.php index 536919270..089c2b5a4 100644 --- a/tests/Middleware/MessageInterfaceMiddlewareTest.php +++ b/tests/Middleware/MessageInterfaceMiddlewareTest.php @@ -11,51 +11,38 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\Configuration; use Sentry\Event; use Sentry\Middleware\MessageInterfaceMiddleware; -class MessageInterfaceMiddlewareTest extends TestCase +class MessageInterfaceMiddlewareTest extends MiddlewareTestCase { public function testInvokeWithoutMessage() { $configuration = new Configuration(); $event = new Event($configuration); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, &$invokationCount) { - $this->assertSame($event, $eventArg); - - ++$invokationCount; - }; - $middleware = new MessageInterfaceMiddleware(); - $middleware($event, $callback); - $this->assertEquals(1, $invokationCount); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + + $this->assertSame($event, $returnedEvent); } /** * @dataProvider invokeDataProvider */ - public function testInvoke($payload) + public function testInvoke(array $payload) { $configuration = new Configuration(); $event = new Event($configuration); - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($payload, &$callbackInvoked) { - $this->assertEquals($payload['message'], $eventArg->getMessage()); - $this->assertEquals($payload['message_params'], $eventArg->getMessageParams()); - - $callbackInvoked = true; - }; - $middleware = new MessageInterfaceMiddleware(); - $middleware($event, $callback, null, null, $payload); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, null, $payload); + + $this->assertEquals($payload['message'], $returnedEvent->getMessage()); + $this->assertEquals($payload['message_params'], $returnedEvent->getMessageParams()); } public function invokeDataProvider() diff --git a/tests/Middleware/MiddlewareTestCase.php b/tests/Middleware/MiddlewareTestCase.php new file mode 100644 index 000000000..ea2f604b7 --- /dev/null +++ b/tests/Middleware/MiddlewareTestCase.php @@ -0,0 +1,53 @@ +assertInstanceOf(\Exception::class, $passedException); + } + + $callbackInvoked = true; + + return $passedEvent; + }; + + if (null === $event) { + $event = new Event($this->createMock(Configuration::class)); + } + + if (null === $request) { + $request = $this->createMock(ServerRequestInterface::class); + } + + if (null === $exception) { + $exception = new \Exception(); + } + + $returnedEvent = $middleware($event, $callback, $request, $exception, $payload); + + $this->assertTrue($callbackInvoked, 'Next middleware was not invoked'); + $this->assertSame($event, $returnedEvent); + + return $returnedEvent; + } +} diff --git a/tests/Middleware/ModulesMiddlewareTest.php b/tests/Middleware/ModulesMiddlewareTest.php index f54f050ab..c538c8d50 100644 --- a/tests/Middleware/ModulesMiddlewareTest.php +++ b/tests/Middleware/ModulesMiddlewareTest.php @@ -11,28 +11,21 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\Configuration; use Sentry\Event; use Sentry\Middleware\ModulesMiddleware; -class ModulesMiddlewareTest extends TestCase +class ModulesMiddlewareTest extends MiddlewareTestCase { public function testInvoke() { $configuration = new Configuration(['project_root' => __DIR__ . '/../Fixtures']); $event = new Event($configuration); - $callbackInvoked = false; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - $this->assertEquals(['foo/bar' => '1.2.3.0', 'foo/baz' => '4.5.6.0'], $eventArg->getModules()); - - $callbackInvoked = true; - }; - $middleware = new ModulesMiddleware($configuration); - $middleware($event, $callback); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + + $this->assertEquals(['foo/bar' => '1.2.3.0', 'foo/baz' => '4.5.6.0'], $returnedEvent->getModules()); } } diff --git a/tests/Middleware/RemoveHttpBodyMiddlewareTest.php b/tests/Middleware/RemoveHttpBodyMiddlewareTest.php index 6cbf0607a..d8274d4c4 100644 --- a/tests/Middleware/RemoveHttpBodyMiddlewareTest.php +++ b/tests/Middleware/RemoveHttpBodyMiddlewareTest.php @@ -11,13 +11,12 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\ClientBuilder; use Sentry\ClientInterface; use Sentry\Event; use Sentry\Middleware\RemoveHttpBodyMiddleware; -class RemoveHttpBodyMiddlewareTest extends TestCase +class RemoveHttpBodyMiddlewareTest extends MiddlewareTestCase { /** * @var ClientInterface @@ -43,17 +42,11 @@ public function testInvoke($inputData, $expectedData) $event = new Event($this->client->getConfig()); $event->setRequest($inputData); - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($expectedData, &$callbackInvoked) { - $this->assertArraySubset($expectedData, $eventArg->getRequest()); - - $callbackInvoked = true; - }; - $middleware = new RemoveHttpBodyMiddleware(); - $middleware($event, $callback); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + + $this->assertArraySubset($expectedData, $returnedEvent->getRequest()); } public function invokeDataProvider() diff --git a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php index 247096c5b..191f6f840 100644 --- a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php +++ b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php @@ -11,14 +11,13 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\ClientBuilder; use Sentry\ClientInterface; use Sentry\Event; use Sentry\Middleware\RemoveStacktraceContextMiddleware; use Sentry\Stacktrace; -class RemoveStacktraceContextMiddlewareTest extends TestCase +class RemoveStacktraceContextMiddlewareTest extends MiddlewareTestCase { /** * @var ClientInterface @@ -38,21 +37,15 @@ public function testInvoke() $event = new Event($this->client->getConfig()); $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception->getTrace(), $exception->getFile(), $exception->getLine())); - $callbackInvoked = false; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - foreach ($eventArg->getStacktrace()->getFrames() as $frame) { - $this->assertNull($frame->getPreContext()); - $this->assertNull($frame->getContextLine()); - $this->assertNull($frame->getPostContext()); - } - - $callbackInvoked = true; - }; - $middleware = new RemoveStacktraceContextMiddleware(); - $middleware($event, $callback); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + + foreach ($returnedEvent->getStacktrace()->getFrames() as $frame) { + $this->assertNull($frame->getPreContext()); + $this->assertNull($frame->getContextLine()); + $this->assertNull($frame->getPostContext()); + } } public function testInvokeWithPreviousException() @@ -63,20 +56,14 @@ public function testInvokeWithPreviousException() $event = new Event($this->client->getConfig()); $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception2->getTrace(), $exception2->getFile(), $exception2->getLine())); - $callbackInvoked = false; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - foreach ($eventArg->getStacktrace()->getFrames() as $frame) { - $this->assertNull($frame->getPreContext()); - $this->assertNull($frame->getContextLine()); - $this->assertNull($frame->getPostContext()); - } - - $callbackInvoked = true; - }; - $middleware = new RemoveStacktraceContextMiddleware(); - $middleware($event, $callback); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + + foreach ($returnedEvent->getStacktrace()->getFrames() as $frame) { + $this->assertNull($frame->getPreContext()); + $this->assertNull($frame->getContextLine()); + $this->assertNull($frame->getPostContext()); + } } } diff --git a/tests/Middleware/RequestInterfaceMiddlewareTest.php b/tests/Middleware/RequestInterfaceMiddlewareTest.php index b45f416e2..29f9f1b10 100644 --- a/tests/Middleware/RequestInterfaceMiddlewareTest.php +++ b/tests/Middleware/RequestInterfaceMiddlewareTest.php @@ -11,38 +11,36 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ServerRequestInterface; use Sentry\Configuration; use Sentry\Event; use Sentry\Middleware\RequestInterfaceMiddleware; use Zend\Diactoros\ServerRequest; use Zend\Diactoros\Uri; -class RequestInterfaceMiddlewareTest extends TestCase +class RequestInterfaceMiddlewareTest extends MiddlewareTestCase { public function testInvokeWithNoRequest() { $configuration = new Configuration(); $event = new Event($configuration); - $invokationCount = 0; - $callback = function (Event $eventArg) use ($event, &$invokationCount) { + $callbackInvoked = false; + $callback = function (Event $eventArg) use ($event, &$callbackInvoked) { $this->assertSame($event, $eventArg); - ++$invokationCount; + $callbackInvoked = true; }; $middleware = new RequestInterfaceMiddleware(); $middleware($event, $callback); - $this->assertEquals(1, $invokationCount); + $this->assertTrue($callbackInvoked, 'Next middleware NOT invoked'); } /** * @dataProvider invokeDataProvider */ - public function testInvoke($requestData, $expectedValue) + public function testInvoke(array $requestData, array $expectedValue) { $configuration = new Configuration(); $event = new Event($configuration); @@ -56,18 +54,11 @@ public function testInvoke($requestData, $expectedValue) $request = $request->withHeader($name, $value); } - $invokationCount = 0; - $callback = function (Event $eventArg, ServerRequestInterface $requestArg) use ($request, $expectedValue, &$invokationCount) { - $this->assertSame($request, $requestArg); - $this->assertEquals($expectedValue, $eventArg->getRequest()); - - ++$invokationCount; - }; - $middleware = new RequestInterfaceMiddleware(); - $middleware($event, $callback, $request); - $this->assertEquals(1, $invokationCount); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, $request); + + $this->assertEquals($expectedValue, $returnedEvent->getRequest()); } public function invokeDataProvider() diff --git a/tests/Middleware/SanitizeCookiesMiddlewareTest.php b/tests/Middleware/SanitizeCookiesMiddlewareTest.php index 8b1d91f46..9f658b6c7 100644 --- a/tests/Middleware/SanitizeCookiesMiddlewareTest.php +++ b/tests/Middleware/SanitizeCookiesMiddlewareTest.php @@ -11,12 +11,11 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\Configuration; use Sentry\Event; use Sentry\Middleware\SanitizeCookiesMiddleware; -class SanitizeCookiesMiddlewareTest extends TestCase +class SanitizeCookiesMiddlewareTest extends MiddlewareTestCase { /** * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException @@ -33,7 +32,7 @@ public function testConstructorThrowsIfBothOnlyAndExceptOptionsAreSet() /** * @dataProvider invokeDataProvider */ - public function testInvoke($options, $expectedData) + public function testInvoke(array $options, array $expectedData) { $event = new Event(new Configuration()); $event->setRequest([ @@ -48,20 +47,14 @@ public function testInvoke($options, $expectedData) ], ]); - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($expectedData, &$callbackInvoked) { - $request = $eventArg->getRequest(); - - $this->assertArraySubset($expectedData, $request); - $this->assertArrayNotHasKey('cookie', $request['headers']); + $middleware = new SanitizeCookiesMiddleware($options); - $callbackInvoked = true; - }; + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - $middleware = new SanitizeCookiesMiddleware($options); - $middleware($event, $callback); + $request = $returnedEvent->getRequest(); - $this->assertTrue($callbackInvoked); + $this->assertArraySubset($expectedData, $request); + $this->assertArrayNotHasKey('cookie', $request['headers']); } public function invokeDataProvider() diff --git a/tests/Middleware/SanitizeDataMiddlewareTest.php b/tests/Middleware/SanitizeDataMiddlewareTest.php index a212c9395..40440691e 100644 --- a/tests/Middleware/SanitizeDataMiddlewareTest.php +++ b/tests/Middleware/SanitizeDataMiddlewareTest.php @@ -11,14 +11,13 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\ClientBuilder; use Sentry\ClientInterface; use Sentry\Event; use Sentry\Middleware\SanitizeDataMiddleware; use Sentry\Stacktrace; -class SanitizeDataMiddlewareTest extends TestCase +class SanitizeDataMiddlewareTest extends MiddlewareTestCase { /** * @var ClientInterface @@ -33,7 +32,7 @@ protected function setUp() /** * @dataProvider invokeDataProvider */ - public function testInvoke($inputData, $expectedData) + public function testInvoke(array $inputData, array $expectedData) { $event = new Event($this->client->getConfig()); @@ -52,30 +51,24 @@ public function testInvoke($inputData, $expectedData) $event->setException($this->convertExceptionValuesToStacktrace($expectedData['exception'])); } - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($expectedData, &$callbackInvoked) { - if (isset($expectedData['request'])) { - $this->assertArraySubset($expectedData['request'], $eventArg->getRequest()); - } - - if (isset($expectedData['extra_context'])) { - $this->assertArraySubset($expectedData['extra_context'], $eventArg->getExtraContext()); - } + $middleware = new SanitizeDataMiddleware(); - if (isset($expectedData['exception'])) { - // We must convert the backtrace to a Stacktrace instance here because - // PHPUnit executes the data provider before the setUp method and so - // the client instance cannot be accessed from there - $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), $eventArg->getException()); - } + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - $callbackInvoked = true; - }; + if (isset($expectedData['request'])) { + $this->assertArraySubset($expectedData['request'], $returnedEvent->getRequest()); + } - $middleware = new SanitizeDataMiddleware(); - $middleware($event, $callback); + if (isset($expectedData['extra_context'])) { + $this->assertArraySubset($expectedData['extra_context'], $returnedEvent->getExtraContext()); + } - $this->assertTrue($callbackInvoked); + if (isset($expectedData['exception'])) { + // We must convert the backtrace to a Stacktrace instance here because + // PHPUnit executes the data provider before the setUp method and so + // the client instance cannot be accessed from there + $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), $returnedEvent->getException()); + } } public function invokeDataProvider() diff --git a/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php b/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php index f4862f3f4..8a2efae8c 100644 --- a/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php +++ b/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php @@ -11,12 +11,11 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\Configuration; use Sentry\Event; use Sentry\Middleware\SanitizeHttpHeadersMiddleware; -class SanitizeHttpHeadersMiddlewareTest extends TestCase +class SanitizeHttpHeadersMiddlewareTest extends MiddlewareTestCase { /** * @dataProvider invokeDataProvider @@ -26,20 +25,13 @@ public function testInvoke($inputData, $expectedData) $event = new Event(new Configuration()); $event->setRequest($inputData); - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($expectedData, &$callbackInvoked) { - $this->assertArraySubset($expectedData, $eventArg->getRequest()); - - $callbackInvoked = true; - }; - $middleware = new SanitizeHttpHeadersMiddleware([ 'sanitize_http_headers' => ['User-Defined-Header'], ]); - $middleware($event, $callback); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - $this->assertTrue($callbackInvoked); + $this->assertArraySubset($expectedData, $returnedEvent->getRequest()); } public function invokeDataProvider() diff --git a/tests/Middleware/SanitizerMiddlewareTest.php b/tests/Middleware/SanitizerMiddlewareTest.php index 9b61edb22..515904e62 100644 --- a/tests/Middleware/SanitizerMiddlewareTest.php +++ b/tests/Middleware/SanitizerMiddlewareTest.php @@ -11,13 +11,12 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\Configuration; use Sentry\Event; use Sentry\Middleware\SanitizerMiddleware; use Sentry\Serializer; -class SanitizerMiddlewareTest extends TestCase +class SanitizerMiddlewareTest extends MiddlewareTestCase { public function testInvoke() { @@ -41,21 +40,15 @@ public function testInvoke() return $eventData; }); - $callbackInvoked = false; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - $this->assertArraySubset(['bar' => 'zab'], $eventArg->getRequest()); - $this->assertArraySubset(['foo' => 'rab'], $eventArg->getUserContext()); - $this->assertArraySubset(['name' => 'zab'], $eventArg->getRuntimeContext()); - $this->assertArraySubset(['name' => 'oof'], $eventArg->getServerOsContext()); - $this->assertArraySubset(['baz' => 'oof'], $eventArg->getExtraContext()); - $this->assertArraySubset(['oof', 'rab'], $eventArg->getTagsContext()); - - $callbackInvoked = true; - }; - $middleware = new SanitizerMiddleware($sanitizer); - $middleware($event, $callback); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + + $this->assertArraySubset(['bar' => 'zab'], $returnedEvent->getRequest()); + $this->assertArraySubset(['foo' => 'rab'], $returnedEvent->getUserContext()); + $this->assertArraySubset(['name' => 'zab'], $returnedEvent->getRuntimeContext()); + $this->assertArraySubset(['name' => 'oof'], $returnedEvent->getServerOsContext()); + $this->assertArraySubset(['baz' => 'oof'], $returnedEvent->getExtraContext()); + $this->assertArraySubset(['oof', 'rab'], $returnedEvent->getTagsContext()); } } diff --git a/tests/Middleware/UserInterfaceMiddlewareTest.php b/tests/Middleware/UserInterfaceMiddlewareTest.php index 2b4eca5f6..f257116f4 100644 --- a/tests/Middleware/UserInterfaceMiddlewareTest.php +++ b/tests/Middleware/UserInterfaceMiddlewareTest.php @@ -11,30 +11,23 @@ namespace Sentry\Tests\Middleware; -use PHPUnit\Framework\TestCase; use Sentry\Configuration; use Sentry\Event; use Sentry\Middleware\UserInterfaceMiddleware; use Zend\Diactoros\ServerRequest; -class UserInterfaceMiddlewareTest extends TestCase +class UserInterfaceMiddlewareTest extends MiddlewareTestCase { public function testInvoke() { $event = new Event(new Configuration()); $event->getUserContext()->setData(['foo' => 'bar']); - $callbackInvoked = false; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - $this->assertArrayNotHasKey('ip_address', $eventArg->getUserContext()); - - $callbackInvoked = true; - }; - $middleware = new UserInterfaceMiddleware(); - $middleware($event, $callback); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + + $this->assertArrayNotHasKey('ip_address', $returnedEvent->getUserContext()); } public function testInvokeWithRequest() @@ -45,16 +38,10 @@ public function testInvokeWithRequest() $request = new ServerRequest(); $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); - $callbackInvoked = false; - $callback = function (Event $eventArg) use (&$callbackInvoked) { - $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $eventArg->getUserContext()->toArray()); - - $callbackInvoked = true; - }; - $middleware = new UserInterfaceMiddleware(); - $middleware($event, $callback, $request); - $this->assertTrue($callbackInvoked); + $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, $request); + + $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $returnedEvent->getUserContext()->toArray()); } } From ecf1fd79c751ac5ff13c02fc41618e975f79773c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 18 Oct 2018 22:25:00 +0200 Subject: [PATCH 0377/1161] Upgrade PHPStan to version 0.10 and level 4 (#673) --- composer.json | 6 ++-- phpstan.neon | 6 ++++ src/AbstractErrorHandler.php | 7 ++-- src/Breadcrumbs/Breadcrumb.php | 4 +-- src/ClientBuilder.php | 12 +++---- src/Configuration.php | 6 ++-- src/Event.php | 2 +- src/Frame.php | 4 +-- src/Middleware/UserInterfaceMiddleware.php | 3 ++ tests/AbstractErrorHandlerTest.php | 32 +++++++++++++++++++ .../BreadcrumbErrorHandlerTest.php | 4 +-- tests/Context/ContextTest.php | 2 +- tests/ErrorHandlerTest.php | 4 +-- 13 files changed, 66 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 3042cb2cd..b0e87eeae 100644 --- a/composer.json +++ b/composer.json @@ -31,8 +31,8 @@ "php-http/curl-client": "^1.7.1", "php-http/message": "^1.5", "php-http/mock-client": "~1.0", - "phpstan/phpstan-phpunit": "^0.9.2", - "phpstan/phpstan": "^0.9.2", + "phpstan/phpstan-phpunit": "^0.10", + "phpstan/phpstan": "^0.10.3", "phpunit/phpunit": "^7.0", "symfony/phpunit-bridge": "^4.1.6" }, @@ -67,7 +67,7 @@ "vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run" ], "phpstan": [ - "vendor/bin/phpstan analyse src tests -c phpstan.neon -l 3" + "vendor/bin/phpstan analyse" ] }, "config": { diff --git a/phpstan.neon b/phpstan.neon index 1f0a623c5..c465f751d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,14 @@ parameters: + level: 4 + paths: + - src + - tests ignoreErrors: - '/Constructor of class Sentry\\HttpClient\\Encoding\\Base64EncodingStream has an unused parameter \$(readFilterOptions|writeFilterOptions)/' - '/Call to an undefined method Sentry\\ClientBuilder::methodThatDoesNotExists\(\)/' - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' + - '/Binary operation "\*" between array and 2 results in an error\./' + - '/Method Sentry\\ClientBuilder::\w+\(\) should return \$this\(Sentry\\ClientBuilderInterface\) but returns \$this\(Sentry\\ClientBuilder\)\./' excludes_analyse: - tests/resources includes: diff --git a/src/AbstractErrorHandler.php b/src/AbstractErrorHandler.php index f4b9ee288..b705a74ea 100644 --- a/src/AbstractErrorHandler.php +++ b/src/AbstractErrorHandler.php @@ -150,16 +150,15 @@ public function captureAt($levels, $replace = false) * @param string $file The filename the error was raised in * @param int $line The line number the error was raised at * - * @return bool Whether the standard PHP error handler should be called + * @return bool If the function returns FALSE then the normal error handler continues * * @internal */ public function handleError($level, $message, $file, $line) { - $shouldReportError = (bool) (error_reporting() & $level); $shouldCaptureError = (bool) ($this->capturedErrors & $level); - if (!$shouldCaptureError || (!$shouldCaptureError && !$shouldReportError)) { + if (!$shouldCaptureError) { return false; } @@ -178,7 +177,7 @@ public function handleError($level, $message, $file, $line) return \call_user_func($this->previousErrorHandler, $level, $message, $file, $line); } - return $shouldReportError; + return false; } /** diff --git a/src/Breadcrumbs/Breadcrumb.php b/src/Breadcrumbs/Breadcrumb.php index 0e9165078..9e923c9d9 100644 --- a/src/Breadcrumbs/Breadcrumb.php +++ b/src/Breadcrumbs/Breadcrumb.php @@ -204,7 +204,7 @@ public function withCategory($category) /** * Gets the breadcrumb message. * - * @return string + * @return string|null */ public function getMessage() { @@ -251,7 +251,7 @@ public function getMetadata() */ public function withMetadata($name, $value) { - if (isset($this->metadata[$name]) && $value === $this->message[$name]) { + if (isset($this->metadata[$name]) && $value === $this->metadata[$name]) { return $this; } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index e3bd91569..940df8762 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -90,12 +90,12 @@ final class ClientBuilder implements ClientBuilderInterface private $configuration; /** - * @var UriFactory The PSR-7 URI factory + * @var UriFactory|null The PSR-7 URI factory */ private $uriFactory; /** - * @var MessageFactory The PSR-7 message factory + * @var MessageFactory|null The PSR-7 message factory */ private $messageFactory; @@ -105,7 +105,7 @@ final class ClientBuilder implements ClientBuilderInterface private $transport; /** - * @var HttpAsyncClient The HTTP client + * @var HttpAsyncClient|null The HTTP client */ private $httpClient; @@ -242,9 +242,9 @@ public function getMiddlewares() */ public function getClient() { - $this->messageFactory = $this->messageFactory ?: MessageFactoryDiscovery::find(); - $this->uriFactory = $this->uriFactory ?: UriFactoryDiscovery::find(); - $this->httpClient = $this->httpClient ?: HttpAsyncClientDiscovery::find(); + $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); + $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); + $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); $this->transport = $this->createTransportInstance(); $client = new Client($this->configuration, $this->transport); diff --git a/src/Configuration.php b/src/Configuration.php index 99cfdf824..78e64aace 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -32,7 +32,7 @@ class Configuration private $dsn; /** - * @var string The project ID number to send to the Sentry server + * @var string|null The project ID number to send to the Sentry server */ private $projectId; @@ -381,7 +381,7 @@ public function setExcludedProjectPaths(array $paths) /** * Gets the project ID number to send to the Sentry server. * - * @return string + * @return string|null */ public function getProjectId() { @@ -477,7 +477,7 @@ public function setRelease($release) /** * Gets the DSN of the Sentry server the authenticated user is bound to. * - * @return string + * @return string|null */ public function getDsn() { diff --git a/src/Event.php b/src/Event.php index 91513257a..a11c2d744 100644 --- a/src/Event.php +++ b/src/Event.php @@ -480,7 +480,7 @@ public function setException(array $exception) /** * Gets the stacktrace that generated this event. * - * @return Stacktrace + * @return Stacktrace|null */ public function getStacktrace() { diff --git a/src/Frame.php b/src/Frame.php index 706998881..a026e989e 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -186,9 +186,9 @@ public function isInApp() * * @param bool $inApp flag indicating whether the frame is application-related */ - public function setIsInApp($inApp) + public function setIsInApp(bool $inApp) { - $this->inApp = (bool) $inApp; + $this->inApp = $inApp; } /** diff --git a/src/Middleware/UserInterfaceMiddleware.php b/src/Middleware/UserInterfaceMiddleware.php index b28259b72..458b5e37b 100644 --- a/src/Middleware/UserInterfaceMiddleware.php +++ b/src/Middleware/UserInterfaceMiddleware.php @@ -12,6 +12,7 @@ namespace Sentry\Middleware; use Psr\Http\Message\ServerRequestInterface; +use Sentry\Context\Context; use Sentry\Event; /** @@ -34,6 +35,8 @@ final class UserInterfaceMiddleware */ public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { + // following PHPDoc is incorrect, workaround for https://github.com/phpstan/phpstan/issues/1377 + /** @var array|Context $userContext */ $userContext = $event->getUserContext(); if (!isset($userContext['ip_address']) && null !== $request && $request->hasHeader('REMOTE_ADDR')) { diff --git a/tests/AbstractErrorHandlerTest.php b/tests/AbstractErrorHandlerTest.php index b290783a6..f9eb6b96b 100644 --- a/tests/AbstractErrorHandlerTest.php +++ b/tests/AbstractErrorHandlerTest.php @@ -64,6 +64,38 @@ public function constructorThrowsWhenReservedMemorySizeIsWrongDataProvider() ]; } + /** + * @dataProvider handleErrorShouldNotCaptureDataProvider + */ + public function testHandleErrorShouldNotCapture(bool $expectedToCapture, int $captureAt, int $errorReporting) + { + if (!$expectedToCapture) { + $this->client->expects($this->never()) + ->method('capture'); + } + + $errorHandler = $this->createErrorHandler($this->client); + $errorHandler->captureAt($captureAt, true); + + $prevErrorReporting = error_reporting($errorReporting); + + try { + $this->assertFalse($errorHandler->handleError(E_WARNING, 'Test', __FILE__, __LINE__)); + } finally { + error_reporting($prevErrorReporting); + } + } + + public function handleErrorShouldNotCaptureDataProvider() + { + return [ + [false, E_ERROR, E_ERROR], + [false, E_ALL, E_ERROR], + [true, E_ERROR, E_ALL], + [true, E_ALL, E_ALL], + ]; + } + /** * @dataProvider captureAtDataProvider */ diff --git a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php index bed93242f..3a9415c0e 100644 --- a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php +++ b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php @@ -52,7 +52,7 @@ public function testHandleError() $errorHandler->captureAt(E_USER_NOTICE, true); $this->assertFalse($errorHandler->handleError(E_USER_WARNING, 'foo bar', __FILE__, __LINE__)); - $this->assertTrue($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__)); + $this->assertFalse($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__)); } finally { restore_error_handler(); restore_exception_handler(); @@ -318,7 +318,7 @@ public function testThrownErrorLeavesBreadcrumb() } } - protected function createErrorHandler(...$arguments) + protected function createErrorHandler(...$arguments): BreadcrumbErrorHandler { return BreadcrumbErrorHandler::register(...$arguments); } diff --git a/tests/Context/ContextTest.php b/tests/Context/ContextTest.php index 3725d29ab..3903892b6 100644 --- a/tests/Context/ContextTest.php +++ b/tests/Context/ContextTest.php @@ -102,7 +102,7 @@ public function testArrayLikeBehaviour() $context = new Context(); $this->assertAttributeEquals([], 'data', $context); - $this->assertFalse(isset($context['foo'])); + $this->assertArrayNotHasKey('foo', $context); // Accessing a key that does not exists in the data object should behave // like accessing a non-existent key of an array diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index dede8ef88..09d6e8e27 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -57,7 +57,7 @@ public function testHandleError() $errorHandler->captureAt(E_USER_NOTICE, true); $this->assertFalse($errorHandler->handleError(E_USER_WARNING, 'foo bar', __FILE__, __LINE__)); - $this->assertTrue($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, 123)); + $this->assertFalse($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, 123)); } finally { restore_error_handler(); restore_exception_handler(); @@ -254,7 +254,7 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler() } } - protected function createErrorHandler(...$arguments) + protected function createErrorHandler(...$arguments): ErrorHandler { return ErrorHandler::register(...$arguments); } From 545cbc7af2da15dc8c18b7d3fb0661a3fe3f7119 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 18 Oct 2018 23:59:41 +0200 Subject: [PATCH 0378/1161] Raise the level of PHPStan to 6 (#675) --- phpstan.neon | 5 ++++- phpunit.xml.dist | 8 +------ src/Client.php | 2 +- src/ClientBuilder.php | 2 +- src/ClientInterface.php | 4 ++-- src/Configuration.php | 10 ++++----- .../Encoding/Base64EncodingStream.php | 4 ++-- src/Middleware/SanitizerMiddleware.php | 5 ++++- src/Serializer.php | 16 +++++++------- src/Stacktrace.php | 10 +++++---- src/TransactionStack.php | 18 ++++++---------- src/Util/JSON.php | 6 +++--- tests/AbstractSerializerTest.php | 1 + tests/Breadcrumbs/MonologHandlerTest.php | 12 +++++------ tests/ClientTest.php | 10 +++++---- tests/ConfigurationTest.php | 2 ++ tests/EventTest.php | 7 ++++++- tests/StacktraceTest.php | 7 +++++-- tests/TransactionStackTest.php | 21 +++---------------- tests/Transport/HttpTransportTest.php | 6 +++++- tests/Util/JSONTest.php | 2 ++ 21 files changed, 79 insertions(+), 79 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index c465f751d..3eb5f5961 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 4 + level: 6 paths: - src - tests @@ -9,6 +9,9 @@ parameters: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' - '/Method Sentry\\ClientBuilder::\w+\(\) should return \$this\(Sentry\\ClientBuilderInterface\) but returns \$this\(Sentry\\ClientBuilder\)\./' + - '/Parameter #1 \$values of class Sentry\\TransactionStack constructor expects array, array given\./' + # to be removed with serializer refactoring + - '/Parameter #1 \$data of method Sentry\\Context\\\w*Context::replaceData\(\) expects array, array\|bool\|float\|int\|object\|string\|null given\./' excludes_analyse: - tests/resources includes: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6a8588bcb..058f0a8d1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,9 @@ diff --git a/src/Client.php b/src/Client.php index ef1048bc4..d1d642b36 100644 --- a/src/Client.php +++ b/src/Client.php @@ -370,7 +370,7 @@ public function send(Event $event) /** * {@inheritdoc} */ - public function translateSeverity($severity) + public function translateSeverity(int $severity) { if (\is_array($this->severityMap) && isset($this->severityMap[$severity])) { return $this->severityMap[$severity]; diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 940df8762..ae7c156d4 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -287,7 +287,7 @@ public function __call($name, $arguments) throw new \BadMethodCallException(sprintf('The method named "%s" does not exists.', $name)); } - \call_user_func_array([$this->configuration, $name], $arguments); + $this->configuration->$name(...$arguments); return $this; } diff --git a/src/ClientInterface.php b/src/ClientInterface.php index efcef2139..3034872b5 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -141,11 +141,11 @@ public function send(Event $event); /** * Translate a PHP Error constant into a Sentry log level group. * - * @param string $severity PHP E_$x error constant + * @param int $severity PHP E_$x error constant * * @return string Sentry log level group */ - public function translateSeverity($severity); + public function translateSeverity(int $severity); /** * Provide a map of PHP Error constants to Sentry logging groups to use instead diff --git a/src/Configuration.php b/src/Configuration.php index 78e64aace..354580518 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -160,7 +160,7 @@ public function setSampleRate($sampleRate) /** * Gets the character encoding detection order. * - * @return string[]|null + * @return string|string[]|null */ public function getMbDetectOrder() { @@ -170,7 +170,7 @@ public function getMbDetectOrder() /** * Sets the character encoding detection order. * - * @param string[]|null $detectOrder The detection order + * @param string|string[]|null $detectOrder The detection order */ public function setMbDetectOrder($detectOrder) { @@ -624,7 +624,7 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('prefixes', 'array'); $resolver->setAllowedTypes('serialize_all_object', 'bool'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); - $resolver->setAllowedTypes('mb_detect_order', ['null', 'array']); + $resolver->setAllowedTypes('mb_detect_order', ['null', 'array', 'string']); $resolver->setAllowedTypes('auto_log_stacks', 'bool'); $resolver->setAllowedTypes('context_lines', 'int'); $resolver->setAllowedTypes('encoding', 'string'); @@ -698,9 +698,9 @@ private function configureOptions(OptionsResolver $resolver) $this->dsn .= ':' . $parsed['port']; } - $this->dsn .= substr($parsed['path'], 0, strripos($parsed['path'], '/')); + $this->dsn .= substr($parsed['path'], 0, strrpos($parsed['path'], '/') ?: null); $this->publicKey = $parsed['user']; - $this->secretKey = isset($parsed['pass']) ? $parsed['pass'] : null; + $this->secretKey = $parsed['pass'] ?? null; $parts = explode('/', $parsed['path']); diff --git a/src/HttpClient/Encoding/Base64EncodingStream.php b/src/HttpClient/Encoding/Base64EncodingStream.php index 298b7c1b9..51bd7d778 100644 --- a/src/HttpClient/Encoding/Base64EncodingStream.php +++ b/src/HttpClient/Encoding/Base64EncodingStream.php @@ -40,13 +40,13 @@ public function getSize() $inputSize = $this->stream->getSize(); if (null === $inputSize) { - return $inputSize; + return null; } // See https://stackoverflow.com/questions/1533113/calculate-the-size-to-a-base-64-encoded-message $adjustment = (($inputSize % 3) ? (3 - ($inputSize % 3)) : 0); - return (($inputSize + $adjustment) / 3) * 4; + return (int) ((($inputSize + $adjustment) / 3) * 4); } /** diff --git a/src/Middleware/SanitizerMiddleware.php b/src/Middleware/SanitizerMiddleware.php index 14a5751d9..c5663951f 100644 --- a/src/Middleware/SanitizerMiddleware.php +++ b/src/Middleware/SanitizerMiddleware.php @@ -52,7 +52,10 @@ public function __construct(Serializer $serializer) public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { if (!empty($requestData = $event->getRequest())) { - $event->setRequest($this->serializer->serialize($requestData, 5)); + /** @var array $serializedRequest */ + $serializedRequest = $this->serializer->serialize($requestData, 5); + + $event->setRequest($serializedRequest); } $event->getUserContext()->replaceData($this->serializer->serialize($event->getUserContext()->toArray())); diff --git a/src/Serializer.php b/src/Serializer.php index 3e579811f..99599984f 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -41,7 +41,7 @@ class Serializer /** * This is the default mb detect order for the detection of encoding. * - * @var string + * @var string[]|string */ protected $mb_detect_order = self::DEFAULT_MB_DETECT_ORDER; @@ -58,8 +58,8 @@ class Serializer protected $messageLimit; /** - * @param null|string $mb_detect_order - * @param null|int $messageLimit + * @param null|string|string[] $mb_detect_order + * @param null|int $messageLimit */ public function __construct($mb_detect_order = null, $messageLimit = Client::MESSAGE_MAX_LENGTH_LIMIT) { @@ -182,19 +182,21 @@ protected function serializeValue($value) * * @return string */ - public function serializeCallable($callable) + public function serializeCallable(callable $callable) { if (\is_array($callable)) { $reflection = new \ReflectionMethod($callable[0], $callable[1]); $class = $reflection->getDeclaringClass(); - } else { + } elseif ($callable instanceof \Closure || \is_string($callable)) { $reflection = new \ReflectionFunction($callable); $class = null; + } else { + throw new \InvalidArgumentException('Unrecognized type of callable'); } $value = $reflection->isClosure() ? 'Lambda ' : 'Callable '; - if (version_compare(PHP_VERSION, '7.0.0') >= 0 && $reflection->getReturnType()) { + if ($reflection->getReturnType()) { $value .= $reflection->getReturnType() . ' '; } @@ -244,7 +246,7 @@ private function serializeCallableParameters(\ReflectionFunctionAbstract $reflec } /** - * @return string + * @return string|string[] * @codeCoverageIgnore */ public function getMbDetectOrder() diff --git a/src/Stacktrace.php b/src/Stacktrace.php index b55d738d9..a7e38bece 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -180,7 +180,7 @@ public function addFrame($file, $line, array $backtraceFrame) $argumentValue = $this->reprSerializer->serialize($argumentValue); if (\is_string($argumentValue) || is_numeric($argumentValue)) { - $frameArguments[(string) $argumentName] = substr($argumentValue, 0, Client::MESSAGE_MAX_LENGTH_LIMIT); + $frameArguments[(string) $argumentName] = substr((string) $argumentValue, 0, Client::MESSAGE_MAX_LENGTH_LIMIT); } else { $frameArguments[(string) $argumentName] = $argumentValue; } @@ -256,7 +256,9 @@ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) $file->seek($target); while (!$file->eof()) { - $line = rtrim($file->current(), "\r\n"); + /** @var string $row */ + $row = $file->current(); + $line = rtrim($row, "\r\n"); if ($currentLineNumber == $lineNumber) { $frame['context_line'] = $line; @@ -411,7 +413,7 @@ protected static function serializeArgument($arg, $maxValueLength) foreach ($arg as $key => $value) { if (\is_string($value) || is_numeric($value)) { - $result[$key] = substr($value, 0, $maxValueLength); + $result[$key] = substr((string) $value, 0, $maxValueLength); } else { $result[$key] = $value; } @@ -419,7 +421,7 @@ protected static function serializeArgument($arg, $maxValueLength) return $result; } elseif (\is_string($arg) || is_numeric($arg)) { - return substr($arg, 0, $maxValueLength); + return substr((string) $arg, 0, $maxValueLength); } else { return $arg; } diff --git a/src/TransactionStack.php b/src/TransactionStack.php index de24c4e27..d3a00c2f2 100644 --- a/src/TransactionStack.php +++ b/src/TransactionStack.php @@ -11,6 +11,8 @@ namespace Sentry; +use phpDocumentor\Reflection\Types\This; + /** * This class is a LIFO collection that only allows access to the value at the * top of the stack. @@ -27,7 +29,7 @@ final class TransactionStack implements \Countable /** * Class constructor. * - * @param array $values An array of initial values + * @param string[] $values An array of initial values */ public function __construct(array $values = []) { @@ -55,19 +57,11 @@ public function isEmpty() /** * Pushes the given values onto the stack. * - * @param array $values The values to push - * - * @throws \InvalidArgumentException If any of the values is not a string + * @param string ...$values The values to push */ - public function push(...$values) + public function push(string ...$values) { - foreach ($values as $value) { - if (!\is_string($value)) { - throw new \InvalidArgumentException(sprintf('The $values argument must contain string values only.')); - } - - $this->transactions[] = $value; - } + $this->transactions = array_merge($this->transactions, $values); } /** diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 4a2cc699c..07c754e5b 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -27,12 +27,12 @@ final class JSON */ public static function encode($data) { - $encoded = json_encode($data, JSON_UNESCAPED_UNICODE); + $encodedData = json_encode($data, JSON_UNESCAPED_UNICODE); - if (JSON_ERROR_NONE !== json_last_error()) { + if (JSON_ERROR_NONE !== json_last_error() || false === $encodedData) { throw new \InvalidArgumentException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); } - return $encoded; + return $encodedData; } } diff --git a/tests/AbstractSerializerTest.php b/tests/AbstractSerializerTest.php index 51f0baab7..163a6957c 100644 --- a/tests/AbstractSerializerTest.php +++ b/tests/AbstractSerializerTest.php @@ -362,6 +362,7 @@ public function testSerializeValueResource($serializeAllObjects) $serializer->setAllObjectSerialize(true); } $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); + $this->assertNotFalse($filename, 'Temp file creation failed'); $fo = fopen($filename, 'wb'); $result = $serializer->serialize($fo); diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php index 7b679ab49..16432e39b 100644 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ b/tests/Breadcrumbs/MonologHandlerTest.php @@ -114,17 +114,15 @@ public function testThrowableBeingParsedAsException() */ private function createClient() { - $client = $client = ClientBuilder::create()->getClient(); - - return $client; + return ClientBuilder::create()->getClient(); } /** - * @param Client $client + * @param ClientInterface $client * * @return Logger */ - private function createLoggerWithHandler(Client $client) + private function createLoggerWithHandler(ClientInterface $client) { $handler = new MonologHandler($client); $logger = new Logger('sentry'); @@ -134,11 +132,11 @@ private function createLoggerWithHandler(Client $client) } /** - * @param Client $client + * @param ClientInterface $client * * @return Breadcrumb[] */ - private function getBreadcrumbs(Client $client) + private function getBreadcrumbs(ClientInterface $client) { $breadcrumbsRecorder = $this->getObjectAttribute($client, 'breadcrumbRecorder'); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 14f4eab8f..78aae407e 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -322,7 +322,7 @@ private function assertMixedValueAndArray($expected_value, $actual_value) $this->assertTrue($actual_value); } elseif (false === $expected_value) { $this->assertFalse($actual_value); - } elseif (\is_string($expected_value) or \is_int($expected_value) or \is_float($expected_value)) { + } elseif (\is_string($expected_value) || \is_numeric($expected_value)) { $this->assertEquals($expected_value, $actual_value); } elseif (\is_array($expected_value)) { $this->assertInternalType('array', $actual_value); @@ -331,7 +331,9 @@ private function assertMixedValueAndArray($expected_value, $actual_value) $this->assertArrayHasKey($key, $actual_value); $this->assertMixedValueAndArray($value, $actual_value[$key]); } - } elseif (\is_callable($expected_value) or \is_object($expected_value)) { + } elseif (\is_callable($expected_value)) { + $this->assertEquals($expected_value, $actual_value); + } elseif (\is_object($expected_value)) { $this->assertEquals(spl_object_hash($expected_value), spl_object_hash($actual_value)); } } @@ -487,7 +489,7 @@ public function testClearBreadcrumb() public function testSetSerializer() { $client = ClientBuilder::create()->getClient(); - $serializer = $this->prophesize(Serializer::class)->reveal(); + $serializer = $this->createMock(Serializer::class); $client->setSerializer($serializer); @@ -497,7 +499,7 @@ public function testSetSerializer() public function testSetReprSerializer() { $client = ClientBuilder::create()->getClient(); - $serializer = $this->prophesize(ReprSerializer::class)->reveal(); + $serializer = $this->createMock(ReprSerializer::class); $client->setRepresentationSerializer($serializer); diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 2ade23aa3..661cb64b9 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -49,6 +49,8 @@ public function optionsDataProvider() ['serialize_all_object', false, 'getSerializeAllObjects', 'setSerializeAllObjects'], ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], ['mb_detect_order', null, 'getMbDetectOrder', 'setMbDetectOrder'], + ['mb_detect_order', 'UTF-8', 'getMbDetectOrder', 'setMbDetectOrder'], + ['mb_detect_order', ['UTF-8'], 'getMbDetectOrder', 'setMbDetectOrder'], ['auto_log_stacks', false, 'getAutoLogStacks', 'setAutoLogStacks'], ['context_lines', 3, 'getContextLines', 'setContextLines'], ['encoding', 'json', 'getEncoding', 'setEncoding'], diff --git a/tests/EventTest.php b/tests/EventTest.php index 8a8348d88..fe477a281 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -233,6 +233,11 @@ public function testEventJsonSerialization() { $event = new Event($this->configuration); - $this->assertJsonStringEqualsJsonString(json_encode($event->toArray()), json_encode($event)); + $encodingOfToArray = json_encode($event->toArray()); + $serializedEvent = json_encode($event); + + $this->assertNotFalse($encodingOfToArray); + $this->assertNotFalse($serializedEvent); + $this->assertJsonStringEqualsJsonString($encodingOfToArray, $serializedEvent); } } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 554c8e4f1..5b694993c 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -51,9 +51,12 @@ public function testStacktraceJsonSerialization() $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); - $frames = $stacktrace->getFrames(); + $frames = json_encode($stacktrace->getFrames()); + $serializedStacktrace = json_encode($stacktrace); - $this->assertJsonStringEqualsJsonString(json_encode($frames), json_encode($stacktrace)); + $this->assertNotFalse($frames); + $this->assertNotFalse($serializedStacktrace); + $this->assertJsonStringEqualsJsonString($frames, $serializedStacktrace); } public function testAddFrame() diff --git a/tests/TransactionStackTest.php b/tests/TransactionStackTest.php index 6ce879b2e..dcc98035d 100644 --- a/tests/TransactionStackTest.php +++ b/tests/TransactionStackTest.php @@ -23,13 +23,11 @@ public function testConstructor() $this->assertAttributeEquals(['a', 'b'], 'transactions', $stack); } - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage The $values argument must contain string values only. - */ public function testConstructorThrowsIfValuesAreNotAllStrings() { - new TransactionStack(['a', 1]); + $this->expectException(\TypeError::class); + + new TransactionStack(['a', new \stdClass()]); } public function testClear() @@ -68,19 +66,6 @@ public function testPush() $this->assertAttributeEquals(['a', 'b', 'c'], 'transactions', $stack); } - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage The $values argument must contain string values only. - */ - public function testPushThrowsIfValuesAreNotAllStrings() - { - $stack = new TransactionStack(); - - $this->assertEmpty($stack); - - $stack->push('a', 1); - } - /** * @dataProvider peekDataProvider */ diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index e5c1e864b..7f67f89ba 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -130,8 +130,12 @@ public function testSendWithCompressedEncoding() ->with($this->callback(function (RequestInterface $request) use ($event) { $request->getBody()->rewind(); + $compressedPayload = gzcompress(JSON::encode($event)); + + $this->assertNotFalse($compressedPayload); + return 'application/octet-stream' === $request->getHeaderLine('Content-Type') - && base64_encode(gzcompress(JSON::encode($event))) === $request->getBody()->getContents(); + && base64_encode($compressedPayload) === $request->getBody()->getContents(); })) ->willReturn($promise); diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index 499ba4205..b7486600c 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -50,6 +50,8 @@ public function testEncodeThrowsIfValueIsResource() { $resource = fopen('php://memory', 'rb'); + $this->assertNotFalse($resource); + fclose($resource); JSON::encode($resource); From 56f14ce6691d095c2ebfaa99adf1c6ca87d0501d Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 19 Oct 2018 09:40:40 +0200 Subject: [PATCH 0379/1161] Increase PHPStan level to 7 (#676) --- phpstan.neon | 2 +- src/Breadcrumbs/Breadcrumb.php | 2 +- src/ClientBuilder.php | 18 ++++++++++++++--- src/Configuration.php | 9 ++++++++- src/Event.php | 6 +++--- src/Frame.php | 20 +++++++++---------- .../RemoveStacktraceContextMiddleware.php | 4 ++-- .../RemoveStacktraceContextMiddlewareTest.php | 8 ++++---- 8 files changed, 44 insertions(+), 25 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 3eb5f5961..cdf0e5a67 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 6 + level: 7 paths: - src - tests diff --git a/src/Breadcrumbs/Breadcrumb.php b/src/Breadcrumbs/Breadcrumb.php index 9e923c9d9..d108ad4a7 100644 --- a/src/Breadcrumbs/Breadcrumb.php +++ b/src/Breadcrumbs/Breadcrumb.php @@ -52,7 +52,7 @@ final class Breadcrumb implements \JsonSerializable private $type; /** - * @var string The message of the breadcrumb + * @var string|null The message of the breadcrumb */ private $message; diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index ae7c156d4..ec5ca6829 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -299,6 +299,14 @@ public function __call($name, $arguments) */ private function createHttpClientInstance() { + if (null === $this->uriFactory) { + throw new \RuntimeException('The PSR-7 URI factory must be set.'); + } + + if (null === $this->httpClient) { + throw new \RuntimeException('The PSR-18 HTTP client must be set.'); + } + if (null !== $this->configuration->getDsn()) { $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->configuration->getDsn()))); } @@ -322,10 +330,14 @@ private function createTransportInstance() return $this->transport; } - if (null !== $this->configuration->getDsn()) { - return new HttpTransport($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); + if (null === $this->configuration->getDsn()) { + return new NullTransport(); + } + + if (null === $this->messageFactory) { + throw new \RuntimeException('The PSR-7 message factory must be set.'); } - return new NullTransport(); + return new HttpTransport($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); } } diff --git a/src/Configuration.php b/src/Configuration.php index 354580518..50e71085b 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -698,7 +698,14 @@ private function configureOptions(OptionsResolver $resolver) $this->dsn .= ':' . $parsed['port']; } - $this->dsn .= substr($parsed['path'], 0, strrpos($parsed['path'], '/') ?: null); + $lastSlashPosition = strrpos($parsed['path'], '/'); + + if (false !== $lastSlashPosition) { + $this->dsn .= substr($parsed['path'], 0, $lastSlashPosition); + } else { + $this->dsn .= $parsed['path']; + } + $this->publicKey = $parsed['user']; $this->secretKey = $parsed['pass'] ?? null; diff --git a/src/Event.php b/src/Event.php index a11c2d744..7bf40f49a 100644 --- a/src/Event.php +++ b/src/Event.php @@ -47,7 +47,7 @@ final class Event implements \JsonSerializable private $logger; /** - * @var string the name of the transaction (or culprit) which caused this exception + * @var string|null the name of the transaction (or culprit) which caused this exception */ private $transaction; @@ -215,7 +215,7 @@ public function setLogger($logger) * Gets the name of the transaction (or culprit) which caused this * exception. * - * @return string + * @return string|null */ public function getTransaction() { @@ -226,7 +226,7 @@ public function getTransaction() * Sets the name of the transaction (or culprit) which caused this * exception. * - * @param string $transaction The transaction name + * @param string|null $transaction The transaction name */ public function setTransaction($transaction) { diff --git a/src/Frame.php b/src/Frame.php index a026e989e..8f5eac293 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -37,7 +37,7 @@ final class Frame implements \JsonSerializable * @var string[] A list of source code lines before the one where the frame * originated */ - private $preContext; + private $preContext = []; /** * @var string|null The source code written at the line number of the file that @@ -49,7 +49,7 @@ final class Frame implements \JsonSerializable * @var string[] A list of source code lines after the one where the frame * originated */ - private $postContext; + private $postContext = []; /** * @var bool Flag telling whether the frame is related to the execution of @@ -110,7 +110,7 @@ public function getLine() /** * Gets a list of source code lines before the one where the frame originated. * - * @return string[]|null + * @return string[] */ public function getPreContext() { @@ -120,9 +120,9 @@ public function getPreContext() /** * Sets a list of source code lines before the one where the frame originated. * - * @param string[]|null $preContext The source code lines + * @param string[] $preContext The source code lines */ - public function setPreContext(array $preContext = null) + public function setPreContext(array $preContext) { $this->preContext = $preContext; } @@ -152,7 +152,7 @@ public function setContextLine($contextLine) /** * Gets a list of source code lines after the one where the frame originated. * - * @return string[]|null + * @return string[] */ public function getPostContext() { @@ -162,9 +162,9 @@ public function getPostContext() /** * Sets a list of source code lines after the one where the frame originated. * - * @param string[]|null $postContext The source code lines + * @param string[] $postContext The source code lines */ - public function setPostContext(array $postContext = null) + public function setPostContext(array $postContext) { $this->postContext = $postContext; } @@ -228,7 +228,7 @@ public function toArray() 'in_app' => $this->inApp, ]; - if (null !== $this->preContext) { + if (0 !== \count($this->preContext)) { $result['pre_context'] = $this->preContext; } @@ -236,7 +236,7 @@ public function toArray() $result['context_line'] = $this->contextLine; } - if (null !== $this->postContext) { + if (0 !== \count($this->postContext)) { $result['post_context'] = $this->postContext; } diff --git a/src/Middleware/RemoveStacktraceContextMiddleware.php b/src/Middleware/RemoveStacktraceContextMiddleware.php index c844d3699..dd06cc26b 100644 --- a/src/Middleware/RemoveStacktraceContextMiddleware.php +++ b/src/Middleware/RemoveStacktraceContextMiddleware.php @@ -39,9 +39,9 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r if (null !== $stacktrace) { foreach ($stacktrace->getFrames() as $frame) { - $frame->setPreContext(null); + $frame->setPreContext([]); $frame->setContextLine(null); - $frame->setPostContext(null); + $frame->setPostContext([]); } $event->setStacktrace($stacktrace); diff --git a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php index 191f6f840..0c5ca0f64 100644 --- a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php +++ b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php @@ -42,9 +42,9 @@ public function testInvoke() $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); foreach ($returnedEvent->getStacktrace()->getFrames() as $frame) { - $this->assertNull($frame->getPreContext()); + $this->assertEmpty($frame->getPreContext()); $this->assertNull($frame->getContextLine()); - $this->assertNull($frame->getPostContext()); + $this->assertEmpty($frame->getPostContext()); } } @@ -61,9 +61,9 @@ public function testInvokeWithPreviousException() $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); foreach ($returnedEvent->getStacktrace()->getFrames() as $frame) { - $this->assertNull($frame->getPreContext()); + $this->assertEmpty($frame->getPreContext()); $this->assertNull($frame->getContextLine()); - $this->assertNull($frame->getPostContext()); + $this->assertEmpty($frame->getPostContext()); } } } From 00e5d081f85f0e022d0161f4376ee0a6e6f5dafe Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 29 Oct 2018 10:08:42 +0100 Subject: [PATCH 0380/1161] Implement the Scope and Hub of the unified API (#679) --- .php_cs | 1 + Makefile | 8 +- docs/middleware.rst | 3 - docs/quickstart.rst | 3 +- src/BreadcrumbErrorHandler.php | 39 ++- src/Breadcrumbs/Breadcrumb.php | 70 ++-- src/Breadcrumbs/MonologHandler.php | 104 ------ src/Breadcrumbs/Recorder.php | 139 -------- src/Client.php | 44 +-- src/ClientBuilder.php | 2 - src/ClientInterface.php | 25 +- src/Configuration.php | 76 +++++ src/Context/UserContext.php | 81 +++++ src/Event.php | 14 +- .../BreadcrumbInterfaceMiddleware.php | 60 ---- .../ExceptionInterfaceMiddleware.php | 3 +- src/Severity.php | 148 ++++++++ src/State/Hub.php | 224 ++++++++++++ src/State/Layer.php | 95 ++++++ src/State/Scope.php | 322 ++++++++++++++++++ .../BreadcrumbErrorHandlerTest.php | 18 +- tests/Breadcrumbs/BreadcrumbTest.php | 4 +- tests/Breadcrumbs/MonologHandlerTest.php | 148 -------- tests/Breadcrumbs/RecorderTest.php | 91 ----- tests/ClientTest.php | 98 ++++-- tests/ConfigurationTest.php | 2 + tests/Context/AbstractContextTest.php | 133 ++++++++ tests/Context/RuntimeContextTest.php | 105 +----- tests/Context/ServerOsContextTest.php | 105 +----- tests/Context/UserContextTest.php | 94 +++++ tests/EventTest.php | 9 +- .../BreadcrumbInterfaceMiddlewareTest.php | 36 -- .../ExceptionInterfaceMiddlewareTest.php | 1 - tests/Middleware/MiddlewareTestCase.php | 2 +- .../RemoveStacktraceContextMiddlewareTest.php | 4 + tests/SeverityTest.php | 74 ++++ tests/State/HubTest.php | 265 ++++++++++++++ tests/State/LayerTest.php | 46 +++ tests/State/ScopeTest.php | 206 +++++++++++ 39 files changed, 2004 insertions(+), 898 deletions(-) delete mode 100644 src/Breadcrumbs/MonologHandler.php delete mode 100644 src/Breadcrumbs/Recorder.php create mode 100644 src/Context/UserContext.php delete mode 100644 src/Middleware/BreadcrumbInterfaceMiddleware.php create mode 100644 src/Severity.php create mode 100644 src/State/Hub.php create mode 100644 src/State/Layer.php create mode 100644 src/State/Scope.php delete mode 100644 tests/Breadcrumbs/MonologHandlerTest.php delete mode 100644 tests/Breadcrumbs/RecorderTest.php create mode 100644 tests/Context/AbstractContextTest.php create mode 100644 tests/Context/UserContextTest.php delete mode 100644 tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php create mode 100644 tests/SeverityTest.php create mode 100644 tests/State/HubTest.php create mode 100644 tests/State/LayerTest.php create mode 100644 tests/State/ScopeTest.php diff --git a/.php_cs b/.php_cs index 74f7b090a..23f13efb4 100644 --- a/.php_cs +++ b/.php_cs @@ -12,6 +12,7 @@ return PhpCsFixer\Config::create() 'psr4' => true, 'random_api_migration' => true, 'yoda_style' => true, + 'phpdoc_no_useless_inheritdoc' => false, 'phpdoc_align' => [ 'tags' => ['param', 'return', 'throws', 'type', 'var'], ], diff --git a/Makefile b/Makefile index 915ce6237..f742ddedc 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,13 @@ cs: cs-dry-run: vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run -test: cs-dry-run +cs-fix: + vendor/bin/php-cs-fixer fix --config=.php_cs + +phpstan: + vendor/bin/phpstan analyse + +test: cs-fix phpstan vendor/bin/phpunit setup-git: diff --git a/docs/middleware.rst b/docs/middleware.rst index ada697a6e..a98439380 100644 --- a/docs/middleware.rst +++ b/docs/middleware.rst @@ -6,8 +6,6 @@ event is passed through the middleware chain before being sent to the server and each middleware can edit the data to add, change or remove information. There are several built-in middlewares whose list is: -- ``BreadcrumbInterfaceMiddleware``: adds all the recorded breadcrumbs up to the - point the event was generated. - ``ContextInterfaceMiddleware``: adds the data stored in the contexts to the event. - ``ExceptionInterfaceMiddleware``: fetches the stacktrace for the captured event @@ -109,7 +107,6 @@ have the following priorities: the runtime context) - ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about the server OS context) -- ``BreadcrumbInterfaceMiddleware``: 0 - ``ExceptionInterfaceMiddleware``: 0 - ``ModulesMiddleware``: 0 (this middleware is not enabled by default) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 054724261..0d849783b 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -115,8 +115,7 @@ methods to report and clear the breadcrumbs. use Sentry\Breadcrumbs\Breadcrumb; - $client->leaveBreadcrumb(Breadcrumb::create('debug', 'error', 'error_reporting', 'foo bar')); - $client->clearBreadcrumbs(); + $client->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting', 'foo bar')); The default implementation of the breadcrumbs recorder is a circular buffer, so when you reach out the maximum number of items that it can store at the same time diff --git a/src/BreadcrumbErrorHandler.php b/src/BreadcrumbErrorHandler.php index 02b0fa4c6..8dcd25706 100644 --- a/src/BreadcrumbErrorHandler.php +++ b/src/BreadcrumbErrorHandler.php @@ -43,8 +43,8 @@ public function doHandleException($exception) return; } - $this->client->leaveBreadcrumb(new Breadcrumb( - $this->client->translateSeverity($exception->getSeverity()), + $this->client->addBreadcrumb(new Breadcrumb( + $this->getSeverityFromErrorException($exception), Breadcrumb::TYPE_ERROR, 'error_reporting', $exception->getMessage(), @@ -55,4 +55,39 @@ public function doHandleException($exception) ] )); } + + /** + * Maps the severity of the error to one of the levels supported by the + * breadcrumbs. + * + * @param \ErrorException $exception The exception + * + * @return string + */ + private function getSeverityFromErrorException(\ErrorException $exception): string + { + switch ($exception->getSeverity()) { + case E_DEPRECATED: + case E_USER_DEPRECATED: + case E_WARNING: + case E_USER_WARNING: + case E_RECOVERABLE_ERROR: + return Breadcrumb::LEVEL_WARNING; + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + return Breadcrumb::LEVEL_CRITICAL; + case E_USER_ERROR: + return Breadcrumb::LEVEL_ERROR; + case E_NOTICE: + case E_USER_NOTICE: + case E_STRICT: + return Breadcrumb::LEVEL_INFO; + default: + return Breadcrumb::LEVEL_ERROR; + } + } } diff --git a/src/Breadcrumbs/Breadcrumb.php b/src/Breadcrumbs/Breadcrumb.php index d108ad4a7..7ecbd87e6 100644 --- a/src/Breadcrumbs/Breadcrumb.php +++ b/src/Breadcrumbs/Breadcrumb.php @@ -11,7 +11,6 @@ namespace Sentry\Breadcrumbs; -use Sentry\Client; use Sentry\Exception\InvalidArgumentException; /** @@ -24,22 +23,59 @@ final class Breadcrumb implements \JsonSerializable /** * This constant defines the http breadcrumb type. */ - const TYPE_HTTP = 'http'; + public const TYPE_HTTP = 'http'; /** * This constant defines the user breadcrumb type. */ - const TYPE_USER = 'user'; + public const TYPE_USER = 'user'; /** * This constant defines the navigation breadcrumb type. */ - const TYPE_NAVIGATION = 'navigation'; + public const TYPE_NAVIGATION = 'navigation'; /** * This constant defines the error breadcrumb type. */ - const TYPE_ERROR = 'error'; + public const TYPE_ERROR = 'error'; + + /** + * This constant defines the debug level for a breadcrumb. + */ + public const LEVEL_DEBUG = 'debug'; + + /** + * This constant defines the info level for a breadcrumb. + */ + public const LEVEL_INFO = 'info'; + + /** + * This constant defines the warning level for a breadcrumb. + */ + public const LEVEL_WARNING = 'warning'; + + /** + * This constant defines the error level for a breadcrumb. + */ + public const LEVEL_ERROR = 'error'; + + /** + * This constant defines the critical level for a breadcrumb. + */ + public const LEVEL_CRITICAL = 'critical'; + + /** + * This constant defines the list of values allowed to be set as severity + * level of the breadcrumb. + */ + private const ALLOWED_LEVELS = [ + self::LEVEL_DEBUG, + self::LEVEL_INFO, + self::LEVEL_WARNING, + self::LEVEL_ERROR, + self::LEVEL_CRITICAL, + ]; /** * @var string The category of the breadcrumb @@ -82,8 +118,8 @@ final class Breadcrumb implements \JsonSerializable */ public function __construct($level, $type, $category, $message = null, array $metadata = []) { - if (!\in_array($level, self::getLevels(), true)) { - throw new InvalidArgumentException('The value of the $level argument must be one of the Sentry\Client::LEVEL_* constants.'); + if (!\in_array($level, self::ALLOWED_LEVELS, true)) { + throw new InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); } $this->type = $type; @@ -158,8 +194,8 @@ public function getLevel() */ public function withLevel($level) { - if (!\in_array($level, self::getLevels(), true)) { - throw new InvalidArgumentException('The value of the $level argument must be one of the Sentry\Client::LEVEL_* constants.'); + if (!\in_array($level, self::ALLOWED_LEVELS, true)) { + throw new InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); } if ($level === $this->level) { @@ -335,20 +371,4 @@ public function jsonSerialize() { return $this->toArray(); } - - /** - * Gets the list of allowed breadcrumb error levels. - * - * @return string[] - */ - private static function getLevels() - { - return [ - Client::LEVEL_DEBUG, - Client::LEVEL_INFO, - Client::LEVEL_WARNING, - Client::LEVEL_ERROR, - Client::LEVEL_FATAL, - ]; - } } diff --git a/src/Breadcrumbs/MonologHandler.php b/src/Breadcrumbs/MonologHandler.php deleted file mode 100644 index 2cdec2fbf..000000000 --- a/src/Breadcrumbs/MonologHandler.php +++ /dev/null @@ -1,104 +0,0 @@ - Client::LEVEL_DEBUG, - Logger::INFO => Client::LEVEL_INFO, - Logger::NOTICE => Client::LEVEL_INFO, - Logger::WARNING => Client::LEVEL_WARNING, - Logger::ERROR => Client::LEVEL_ERROR, - Logger::CRITICAL => Client::LEVEL_FATAL, - Logger::ALERT => Client::LEVEL_FATAL, - Logger::EMERGENCY => Client::LEVEL_FATAL, - ]; - - protected $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; - - /** - * @var ClientInterface the client object that sends the message to the server - */ - protected $ravenClient; - - /** - * @param ClientInterface $ravenClient The Raven client - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - */ - public function __construct(ClientInterface $ravenClient, $level = Logger::DEBUG, $bubble = true) - { - parent::__construct($level, $bubble); - - $this->ravenClient = $ravenClient; - } - - /** - * @param string $message - * - * @return array|null - */ - protected function parseException($message) - { - if (preg_match($this->excMatch, $message, $matches)) { - return [$matches[1], $matches[2]]; - } - - return null; - } - - /** - * {@inheritdoc} - */ - protected function write(array $record) - { - // sentry uses the 'nobreadcrumb' attribute to skip reporting - if (!empty($record['context']['nobreadcrumb'])) { - return; - } - - if ( - isset($record['context']['exception']) - && ( - $record['context']['exception'] instanceof \Exception - || (\PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable) - ) - ) { - /** - * @var \Exception|\Throwable - */ - $exc = $record['context']['exception']; - - /** @noinspection PhpUndefinedMethodInspection */ - $breadcrumb = new Breadcrumb($this->logLevels[$record['level']], Breadcrumb::TYPE_ERROR, $record['channel'], null, [ - 'type' => \get_class($exc), - 'value' => $exc->getMessage(), - ]); - - $this->ravenClient->leaveBreadcrumb($breadcrumb); - } else { - // TODO(dcramer): parse exceptions out of messages and format as above - if ($error = $this->parseException($record['message'])) { - $breadcrumb = new Breadcrumb($this->logLevels[$record['level']], Breadcrumb::TYPE_ERROR, $record['channel'], null, [ - 'type' => $error[0], - 'value' => $error[1], - ]); - - $this->ravenClient->leaveBreadcrumb($breadcrumb); - } else { - $breadcrumb = new Breadcrumb($this->logLevels[$record['level']], Breadcrumb::TYPE_ERROR, $record['channel'], $record['message']); - - $this->ravenClient->leaveBreadcrumb($breadcrumb); - } - } - } -} diff --git a/src/Breadcrumbs/Recorder.php b/src/Breadcrumbs/Recorder.php deleted file mode 100644 index 8679cd2b9..000000000 --- a/src/Breadcrumbs/Recorder.php +++ /dev/null @@ -1,139 +0,0 @@ - - */ -final class Recorder implements \Countable, \Iterator -{ - /** - * This constant defines the maximum number of breadcrumbs to store. - */ - const MAX_ITEMS = 100; - - /** - * @var int The current position of the iterator - */ - private $position = 0; - - /** - * @var int The current head position - */ - private $head = 0; - - /** - * @var int Current number of stored breadcrumbs - */ - private $size = 0; - - /** - * @var \SplFixedArray|Breadcrumb[] The list of recorded breadcrumbs - */ - private $breadcrumbs; - - /** - * @var int The maximum number of breadcrumbs to store - */ - private $maxSize; - - /** - * Constructor. - * - * @param int $maxSize The maximum number of breadcrumbs to store - */ - public function __construct($maxSize = self::MAX_ITEMS) - { - if (!\is_int($maxSize) || $maxSize < 1) { - throw new InvalidArgumentException(sprintf('The $maxSize argument must be an integer greater than 0.')); - } - - $this->breadcrumbs = new \SplFixedArray($maxSize); - $this->maxSize = $maxSize; - } - - /** - * Records a new breadcrumb. - * - * @param Breadcrumb $breadcrumb The breadcrumb object - */ - public function record(Breadcrumb $breadcrumb) - { - $this->breadcrumbs[$this->head] = $breadcrumb; - $this->head = ($this->head + 1) % $this->maxSize; - $this->size = min($this->size + 1, $this->maxSize); - } - - /** - * Clears all recorded breadcrumbs. - */ - public function clear() - { - $this->breadcrumbs = new \SplFixedArray($this->maxSize); - $this->position = 0; - $this->head = 0; - $this->size = 0; - } - - /** - * {@inheritdoc} - */ - public function current() - { - return $this->breadcrumbs[($this->head + $this->position) % $this->size]; - } - - /** - * {@inheritdoc} - */ - public function next() - { - ++$this->position; - } - - /** - * {@inheritdoc} - */ - public function key() - { - return $this->position; - } - - /** - * {@inheritdoc} - */ - public function valid() - { - return $this->position < $this->size; - } - - /** - * {@inheritdoc} - */ - public function rewind() - { - $this->position = 0; - } - - /** - * {@inheritdoc} - */ - public function count() - { - return $this->size; - } -} diff --git a/src/Client.php b/src/Client.php index d1d642b36..4e20412e8 100644 --- a/src/Client.php +++ b/src/Client.php @@ -12,12 +12,13 @@ namespace Sentry; use Sentry\Breadcrumbs\Breadcrumb; -use Sentry\Breadcrumbs\Recorder; use Sentry\Context\Context; use Sentry\Context\RuntimeContext; use Sentry\Context\ServerOsContext; use Sentry\Context\TagsContext; +use Sentry\Context\UserContext; use Sentry\Middleware\MiddlewareStack; +use Sentry\State\Scope; use Sentry\Transport\TransportInterface; use Zend\Diactoros\ServerRequestFactory; @@ -78,11 +79,6 @@ class Client implements ClientInterface */ private $config; - /** - * @var Recorder The breadcrumbs recorder - */ - private $breadcrumbRecorder; - /** * @var TransactionStack The transaction stack */ @@ -99,7 +95,7 @@ class Client implements ClientInterface private $tagsContext; /** - * @var Context The user context + * @var UserContext The user context */ private $userContext; @@ -139,11 +135,10 @@ public function __construct(Configuration $config, TransportInterface $transport $this->config = $config; $this->transport = $transport; $this->tagsContext = new TagsContext(); - $this->userContext = new Context(); + $this->userContext = new UserContext(); $this->extraContext = new Context(); $this->runtimeContext = new RuntimeContext(); $this->serverOsContext = new ServerOsContext(); - $this->breadcrumbRecorder = new Recorder(); $this->transactionStack = new TransactionStack(); $this->serializer = new Serializer($this->config->getMbDetectOrder()); $this->representationSerializer = new ReprSerializer($this->config->getMbDetectOrder()); @@ -166,25 +161,20 @@ public function __construct(Configuration $config, TransportInterface $transport /** * {@inheritdoc} */ - public function getBreadcrumbsRecorder() + public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null) { - return $this->breadcrumbRecorder; - } + $beforeBreadcrumbCallback = $this->config->getBeforeBreadcrumbCallback(); + $maxBreadcrumbs = $this->config->getMaxBreadcrumbs(); - /** - * {@inheritdoc} - */ - public function leaveBreadcrumb(Breadcrumb $breadcrumb) - { - $this->breadcrumbRecorder->record($breadcrumb); - } + if ($maxBreadcrumbs <= 0) { + return; + } - /** - * {@inheritdoc} - */ - public function clearBreadcrumbs() - { - $this->breadcrumbRecorder->clear(); + $breadcrumb = $beforeBreadcrumbCallback($breadcrumb); + + if (null !== $breadcrumb && null !== $scope) { + $scope->addBreadcrumb($breadcrumb, $maxBreadcrumbs); + } } /** @@ -263,7 +253,7 @@ public function setSerializer(Serializer $serializer) /** * {@inheritdoc} */ - public function captureMessage($message, array $params = [], array $payload = []) + public function captureMessage(string $message, array $params = [], array $payload = []) { $payload['message'] = $message; $payload['message_params'] = $params; @@ -274,7 +264,7 @@ public function captureMessage($message, array $params = [], array $payload = [] /** * {@inheritdoc} */ - public function captureException($exception, array $payload = []) + public function captureException(\Throwable $exception, array $payload = []) { $payload['exception'] = $exception; diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index ec5ca6829..8fdbfd7fe 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -26,7 +26,6 @@ use Http\Message\UriFactory; use Sentry\Context\Context; use Sentry\HttpClient\Authentication\SentryAuth; -use Sentry\Middleware\BreadcrumbInterfaceMiddleware; use Sentry\Middleware\ContextInterfaceMiddleware; use Sentry\Middleware\ExceptionInterfaceMiddleware; use Sentry\Middleware\MessageInterfaceMiddleware; @@ -261,7 +260,6 @@ public function getClient() $client->addMiddleware(new ContextInterfaceMiddleware($client->getExtraContext(), Context::CONTEXT_EXTRA)); $client->addMiddleware(new ContextInterfaceMiddleware($client->getRuntimeContext(), Context::CONTEXT_RUNTIME)); $client->addMiddleware(new ContextInterfaceMiddleware($client->getServerOsContext(), Context::CONTEXT_SERVER_OS)); - $client->addMiddleware(new BreadcrumbInterfaceMiddleware($client->getBreadcrumbsRecorder())); $client->addMiddleware(new ExceptionInterfaceMiddleware($client)); foreach ($this->middlewares as $middleware) { diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 3034872b5..70bcd7ca9 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -12,11 +12,11 @@ namespace Sentry; use Sentry\Breadcrumbs\Breadcrumb; -use Sentry\Breadcrumbs\Recorder as BreadcrumbRecorder; use Sentry\Context\Context; use Sentry\Context\RuntimeContext; use Sentry\Context\ServerOsContext; use Sentry\Context\TagsContext; +use Sentry\State\Scope; /** * This interface must be implemented by all Raven client classes. @@ -56,24 +56,13 @@ public function addMiddleware(callable $middleware, $priority = 0); */ public function removeMiddleware(callable $middleware); - /** - * Gets the breadcrumbs recorder. - * - * @return BreadcrumbRecorder - */ - public function getBreadcrumbsRecorder(); - /** * Records the given breadcrumb. * * @param Breadcrumb $breadcrumb The breadcrumb instance + * @param Scope|null $scope an optional scope to store this breadcrumb in */ - public function leaveBreadcrumb(Breadcrumb $breadcrumb); - - /** - * Clears all recorded breadcrumbs. - */ - public function clearBreadcrumbs(); + public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null); /** * Logs a message. @@ -84,17 +73,17 @@ public function clearBreadcrumbs(); * * @return string */ - public function captureMessage($message, array $params = [], array $payload = []); + public function captureMessage(string $message, array $params = [], array $payload = []); /** * Logs an exception. * - * @param \Throwable|\Exception $exception The exception object - * @param array $payload Additional attributes to pass with this event + * @param \Throwable $exception The exception object + * @param array $payload Additional attributes to pass with this event * * @return string */ - public function captureException($exception, array $payload = []); + public function captureException(\Throwable $exception, array $payload = []); /** * Logs the most recent error (obtained with {@link error_get_last}). diff --git a/src/Configuration.php b/src/Configuration.php index 50e71085b..5b6770e44 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -9,8 +9,11 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace Sentry; +use Sentry\Breadcrumbs\Breadcrumb; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -21,6 +24,11 @@ */ class Configuration { + /** + * The default maximum number of breadcrumbs that will be sent with an event. + */ + public const DEFAULT_MAX_BREADCRUMBS = 100; + /** * @var array The configuration options */ @@ -586,6 +594,54 @@ public function setErrorTypes($errorTypes) $this->options = $this->resolver->resolve($options); } + /** + * Gets the maximum number of breadcrumbs sent with events. + * + * @return int + */ + public function getMaxBreadcrumbs(): int + { + return $this->options['max_breadcrumbs']; + } + + /** + * Sets the maximum number of breadcrumbs sent with events. + * + * @param int $maxBreadcrumbs The maximum number of breadcrumbs + */ + public function setMaxBreadcrumbs(int $maxBreadcrumbs): void + { + $options = array_merge($this->options, ['max_breadcrumbs' => $maxBreadcrumbs]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets a callback that will be invoked when adding a breadcrumb. + * + * @return callable + */ + public function getBeforeBreadcrumbCallback(): callable + { + return $this->options['before_breadcrumb']; + } + + /** + * Sets a callback that will be invoked when adding a breadcrumb, allowing + * to optionally modify it before adding it to future events. Note that you + * must return a valid breadcrumb from this callback. If you do not wish to + * modify the breadcrumb, simply return it at the end. Returning `null` will + * cause the breadcrumb to be dropped. + * + * @param callable $callback The callback + */ + public function setBeforeBreadcrumbCallback(callable $callback): void + { + $options = array_merge($this->options, ['before_breadcrumb' => $callback]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -618,6 +674,10 @@ private function configureOptions(OptionsResolver $resolver) 'should_capture' => null, 'tags' => [], 'error_types' => null, + 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, + 'before_breadcrumb' => function (Breadcrumb $breadcrumb): ?Breadcrumb { + return $breadcrumb; + }, ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -641,9 +701,15 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('should_capture', ['null', 'callable']); $resolver->setAllowedTypes('tags', 'array'); $resolver->setAllowedTypes('error_types', ['null', 'int']); + $resolver->setAllowedTypes('max_breadcrumbs', 'int'); + $resolver->setAllowedTypes('before_breadcrumb', ['callable']); $resolver->setAllowedValues('encoding', ['gzip', 'json']); $resolver->setAllowedValues('dsn', function ($value) { + if (empty($value)) { + return true; + } + switch (strtolower($value)) { case '': case 'false': @@ -676,7 +742,17 @@ private function configureOptions(OptionsResolver $resolver) return true; }); + $resolver->setAllowedValues('max_breadcrumbs', function ($value) { + return $value <= self::DEFAULT_MAX_BREADCRUMBS; + }); + $resolver->setNormalizer('dsn', function (Options $options, $value) { + if (empty($value)) { + $this->dsn = null; + + return null; + } + switch (strtolower($value)) { case '': case 'false': diff --git a/src/Context/UserContext.php b/src/Context/UserContext.php new file mode 100644 index 000000000..9657d8b89 --- /dev/null +++ b/src/Context/UserContext.php @@ -0,0 +1,81 @@ +data['id'] ?? null; + } + + /** + * Sets the ID of the user. + * + * @param null|string $id The ID + */ + public function setId(?string $id): void + { + $this->data['id'] = $id; + } + + /** + * Gets the username of the user. + * + * @return null|string + */ + public function getUsername(): ?string + { + return $this->data['username'] ?? null; + } + + /** + * Sets the username of the user. + * + * @param null|string $username The username + */ + public function setUsername(?string $username): void + { + $this->data['username'] = $username; + } + + /** + * Gets the email of the user. + * + * @return null|string + */ + public function getEmail(): ?string + { + return $this->data['email'] ?? null; + } + + /** + * Sets the email of the user. + * + * @param null|string $email The email + */ + public function setEmail(?string $email): void + { + $this->data['email'] = $email; + } +} diff --git a/src/Event.php b/src/Event.php index 7bf40f49a..fbb6a2eae 100644 --- a/src/Event.php +++ b/src/Event.php @@ -37,7 +37,7 @@ final class Event implements \JsonSerializable private $timestamp; /** - * @var string The severity of this event + * @var Severity The severity of this event */ private $level; @@ -140,7 +140,7 @@ public function __construct(Configuration $config) { $this->id = Uuid::uuid4(); $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); - $this->level = Client::LEVEL_ERROR; + $this->level = Severity::error(); $this->serverName = $config->getServerName(); $this->release = $config->getRelease(); $this->environment = $config->getCurrentEnvironment(); @@ -174,9 +174,9 @@ public function getTimestamp() /** * Gets the severity of this event. * - * @return string + * @return Severity */ - public function getLevel() + public function getLevel(): Severity { return $this->level; } @@ -184,9 +184,9 @@ public function getLevel() /** * Sets the severity of this event. * - * @param string $level The severity + * @param Severity $level The severity */ - public function setLevel($level) + public function setLevel(Severity $level) { $this->level = $level; } @@ -507,7 +507,7 @@ public function toArray() $data = [ 'event_id' => str_replace('-', '', $this->id->toString()), 'timestamp' => $this->timestamp, - 'level' => $this->level, + 'level' => (string) $this->level, 'platform' => 'php', 'sdk' => [ 'name' => 'sentry-php', diff --git a/src/Middleware/BreadcrumbInterfaceMiddleware.php b/src/Middleware/BreadcrumbInterfaceMiddleware.php deleted file mode 100644 index 2ab2ebb89..000000000 --- a/src/Middleware/BreadcrumbInterfaceMiddleware.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ -final class BreadcrumbInterfaceMiddleware -{ - /** - * @var Recorder The breadcrumbs recorder - */ - private $recorder; - - /** - * Constructor. - * - * @param Recorder $recorder The breadcrumbs recorder - */ - public function __construct(Recorder $recorder) - { - $this->recorder = $recorder; - } - - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - foreach ($this->recorder as $breadcrumb) { - $event->setBreadcrumb($breadcrumb); - } - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/ExceptionInterfaceMiddleware.php b/src/Middleware/ExceptionInterfaceMiddleware.php index b42e431b4..bcbbf9413 100644 --- a/src/Middleware/ExceptionInterfaceMiddleware.php +++ b/src/Middleware/ExceptionInterfaceMiddleware.php @@ -14,6 +14,7 @@ use Psr\Http\Message\ServerRequestInterface; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\Severity; use Sentry\Stacktrace; /** @@ -54,7 +55,7 @@ public function __invoke(Event $event, callable $next, ServerRequestInterface $r if (isset($payload['level'])) { $event->setLevel($payload['level']); } elseif ($exception instanceof \ErrorException) { - $event->setLevel($this->client->translateSeverity($exception->getSeverity())); + $event->setLevel(new Severity($this->client->translateSeverity($exception->getSeverity()))); } if (null !== $exception) { diff --git a/src/Severity.php b/src/Severity.php new file mode 100644 index 000000000..832e35b11 --- /dev/null +++ b/src/Severity.php @@ -0,0 +1,148 @@ + + */ +final class Severity +{ + /** + * This constant represents the "debug" severity level. + */ + public const DEBUG = 'debug'; + + /** + * This constant represents the "info" severity level. + */ + public const INFO = 'info'; + + /** + * This constant represents the "warning" severity level. + */ + public const WARNING = 'warning'; + + /** + * This constant represents the "error" severity level. + */ + public const ERROR = 'error'; + + /** + * This constant represents the "fatal" severity level. + */ + public const FATAL = 'fatal'; + + /** + * This constant contains the list of allowed enum values. + */ + public const ALLOWED_SEVERITIES = [ + self::DEBUG, + self::INFO, + self::WARNING, + self::ERROR, + self::FATAL, + ]; + + /** + * @var string The value of this enum instance + */ + private $value; + + /** + * Constructor. + * + * @param string $value The value this instance represents + */ + public function __construct(string $value = self::INFO) + { + if (!\in_array($value, self::ALLOWED_SEVERITIES, true)) { + throw new \InvalidArgumentException(sprintf('The "%s" is not a valid enum value.', $value)); + } + + $this->value = $value; + } + + /** + * Creates a new instance of this enum for the "debug" value. + * + * @return self + */ + public static function debug(): self + { + return new self(self::DEBUG); + } + + /** + * Creates a new instance of this enum for the "info" value. + * + * @return self + */ + public static function info(): self + { + return new self(self::INFO); + } + + /** + * Creates a new instance of this enum for the "warning" value. + * + * @return self + */ + public static function warning(): self + { + return new self(self::WARNING); + } + + /** + * Creates a new instance of this enum for the "error" value. + * + * @return self + */ + public static function error(): self + { + return new self(self::ERROR); + } + + /** + * Creates a new instance of this enum for the "fatal" value. + * + * @return self + */ + public static function fatal(): self + { + return new self(self::FATAL); + } + + /** + * Returns whether two object instances of this class are equal. + * + * @param self $other The object to compare + * + * @return bool + */ + public function isEqualTo(self $other): bool + { + return $this->value === (string) $other; + } + + /** + * {@inheritdoc} + */ + public function __toString(): string + { + return $this->value; + } +} diff --git a/src/State/Hub.php b/src/State/Hub.php new file mode 100644 index 000000000..b594b2958 --- /dev/null +++ b/src/State/Hub.php @@ -0,0 +1,224 @@ +stack[] = new Layer($client, $scope); + } + + /** + * Gets the client binded to the top of the stack. + * + * @return ClientInterface|null + */ + public function getClient(): ?ClientInterface + { + return $this->getStackTop()->getClient(); + } + + /** + * Gets the scope binded to the top of the stack. + * + * @return Scope + */ + public function getScope(): Scope + { + return $this->getStackTop()->getScope(); + } + + /** + * Gets the stack of clients and scopes. + * + * @return Layer[] + */ + public function getStack(): array + { + return $this->stack; + } + + /** + * Gets the topmost client/layer pair in the stack. + * + * @return Layer + */ + public function getStackTop(): Layer + { + return $this->stack[\count($this->stack) - 1]; + } + + /** + * Gets the ID of the last captured event. + * + * @return null|string + */ + public function getLastEventId(): ?string + { + return $this->lastEventId; + } + + /** + * Creates a new scope to store context information that will be layered on + * top of the current one. It is isolated, i.e. all breadcrumbs and context + * information added to this scope will be removed once the scope ends. Be + * sure to always remove this scope with {@see Hub::popScope} when the + * operation finishes or throws. + * + * @return Scope + */ + public function pushScope(): Scope + { + $clonedScope = clone $this->getScope(); + + $this->stack[] = new Layer($this->getClient(), $clonedScope); + + return $clonedScope; + } + + /** + * Removes a previously pushed scope from the stack. This restores the state + * before the scope was pushed. All breadcrumbs and context information added + * since the last call to {@see Hub::pushScope} are discarded. + * + * @return bool + */ + public function popScope(): bool + { + if (1 === \count($this->stack)) { + return false; + } + + return null !== \array_pop($this->stack); + } + + /** + * Creates a new scope with and executes the given operation within. The scope + * is automatically removed once the operation finishes or throws. + * + * @param callable $callback The callback to be executed + */ + public function withScope(callable $callback): void + { + $scope = $this->pushScope(); + + try { + $callback($scope); + } finally { + $this->popScope(); + } + } + + /** + * Calls the given callback passing to it the current scope so that any + * operation can be run within its context. + * + * @param callable $callback The callback to be executed + */ + public function configureScope(callable $callback): void + { + $callback($this->getScope()); + } + + /** + * Binds the given client to the current scope. + * + * @param ClientInterface $client The client + */ + public function bindClient(ClientInterface $client): void + { + $layer = $this->getStackTop(); + $layer->setClient($client); + } + + /** + * Captures a message event and sends it to Sentry. + * + * @param string $message The message + * @param Severity $level The severity level of the message + * + * @return null|string + */ + public function captureMessage(string $message, ?Severity $level = null): ?string + { + $client = $this->getClient(); + + if (null !== $client) { + return $this->lastEventId = $client->captureMessage($message, [], ['level' => $level]); + } + + return null; + } + + /** + * Captures an exception event and sends it to Sentry. + * + * @param \Throwable $exception The exception + * + * @return null|string + */ + public function captureException(\Throwable $exception): ?string + { + $client = $this->getClient(); + + if (null !== $client) { + return $this->lastEventId = $client->captureException($exception); + } + + return null; + } + + /** + * Records a new breadcrumb which will be attached to future events. They + * will be added to subsequent events to provide more context on user's + * actions prior to an error or crash. + * + * @param Breadcrumb $breadcrumb The breadcrumb to record + */ + public function addBreadcrumb(Breadcrumb $breadcrumb): void + { + $client = $this->getClient(); + + if (null !== $client) { + $client->addBreadcrumb($breadcrumb, $this->getScope()); + } + } +} diff --git a/src/State/Layer.php b/src/State/Layer.php new file mode 100644 index 000000000..4d31738b6 --- /dev/null +++ b/src/State/Layer.php @@ -0,0 +1,95 @@ +client = $client; + $this->scope = $scope; + } + + /** + * Gets the client held by this layer. + * + * @return ClientInterface|null + */ + public function getClient(): ?ClientInterface + { + return $this->client; + } + + /** + * Sets the client held by this layer. + * + * @param ClientInterface|null $client The client instance + * + * @return $this + */ + public function setClient(?ClientInterface $client): self + { + $this->client = $client; + + return $this; + } + + /** + * Gets the scope held by this layer. + * + * @return Scope + */ + public function getScope(): Scope + { + return $this->scope; + } + + /** + * Sets the scope held by this layer. + * + * @param Scope $scope The scope instance + * + * @return $this + */ + public function setScope(Scope $scope): self + { + $this->scope = $scope; + + return $this; + } +} diff --git a/src/State/Scope.php b/src/State/Scope.php new file mode 100644 index 000000000..3b24ae75a --- /dev/null +++ b/src/State/Scope.php @@ -0,0 +1,322 @@ +user = new UserContext(); + $this->tags = new TagsContext(); + $this->extra = new Context(); + } + + /** + * Sets a new tag in the tags context. + * + * @param string $key The key that uniquely identifies the tag + * @param string $value The value + * + * @return $this + */ + public function setTag(string $key, string $value): self + { + $this->tags[$key] = $value; + + return $this; + } + + /** + * Gets the tags contained in the tags context. + * + * @return array + * + * @internal + */ + public function getTags(): array + { + return $this->tags->toArray(); + } + + /** + * Sets a new information in the extra context. + * + * @param string $key The key that uniquely identifies the information + * @param mixed $value The value + * + * @return $this + */ + public function setExtra(string $key, $value): self + { + $this->extra[$key] = $value; + + return $this; + } + + /** + * Gets the information contained in the extra context. + * + * @return array + * + * @internal + */ + public function getExtra(): array + { + return $this->extra->toArray(); + } + + /** + * Sets the given data in the user context. + * + * @param array $data The data + * + * @return $this + */ + public function setUser(array $data): self + { + $this->user->replaceData($data); + + return $this; + } + + /** + * Gets the information contained in the user context. + * + * @return array + * + * @internal + */ + public function getUser(): array + { + return $this->user->toArray(); + } + + /** + * Sets the list of strings used to dictate the deduplication of this event. + * + * @param string[] $fingerprint The fingerprint values + * + * @return $this + */ + public function setFingerprint(array $fingerprint): self + { + $this->fingerprint = $fingerprint; + + return $this; + } + + /** + * Gets the list of strings used to dictate the deduplication of this event. + * + * @return string[] + * + * @internal + */ + public function getFingerprint(): array + { + return $this->fingerprint; + } + + /** + * Sets the severity to apply to all events captured in this scope. + * + * @param null|Severity $level The severity + * + * @return $this + */ + public function setLevel(?Severity $level): self + { + $this->level = $level; + + return $this; + } + + /** + * Gets the severity to apply to all events captured in this scope. + * + * @return null|Severity + * + * @internal + */ + public function getLevel(): ?Severity + { + return $this->level; + } + + /** + * Add the given breadcrumb to the scope. + * + * @param Breadcrumb $breadcrumb The breadcrumb to add + * @param int $maxBreadcrumbs The maximum number of breadcrumbs to record + * + * @return $this + */ + public function addBreadcrumb(Breadcrumb $breadcrumb, int $maxBreadcrumbs = 100): self + { + $this->breadcrumbs[] = $breadcrumb; + $this->breadcrumbs = \array_slice($this->breadcrumbs, -$maxBreadcrumbs); + + return $this; + } + + /** + * Gets the breadcrumbs. + * + * @return Breadcrumb[] + * + * @internal + */ + public function getBreadcrumbs(): array + { + return $this->breadcrumbs; + } + + /** + * Adds a new event processor that will be called after {@see Scope::applyToEvent} + * finished its work. + * + * @param callable $eventProcessor The event processor + * + * @return $this + */ + public function addEventProcessor(callable $eventProcessor): self + { + $this->eventProcessors[] = $eventProcessor; + + return $this; + } + + /** + * Clears the scope and resets any data it contains. + * + * @return $this + */ + public function clear(): self + { + $this->tags->clear(); + $this->extra->clear(); + $this->user->clear(); + + $this->level = null; + $this->fingerprint = []; + $this->breadcrumbs = []; + + return $this; + } + + /** + * Applies the current context and fingerprint to the event. If the event has + * already some breadcrumbs on it, the ones from this scope won't get merged. + * + * @param Event $event The event object + * @param int $maxBreadcrumbs The maximum number of breadcrumbs to add to + * the event + * + * @return Event|null + */ + public function applyToEvent(Event $event, int $maxBreadcrumbs = 100): ?Event + { + if (empty($event->getFingerprint())) { + $event->setFingerprint($this->fingerprint); + } + + if (empty($event->getBreadcrumbs())) { + $breadcrumbs = \array_slice($this->breadcrumbs, -$maxBreadcrumbs); + + foreach ($breadcrumbs as $breadcrumb) { + $event->setBreadcrumb($breadcrumb); + } + } + + if (null !== $this->level) { + $event->setLevel($this->level); + } + + $event->getTagsContext()->merge($this->tags->toArray()); + $event->getExtraContext()->merge($this->extra->toArray()); + $event->getUserContext()->merge($this->user->toArray()); + + foreach ($this->eventProcessors as $processor) { + $event = $processor($event); + + if (null === $event) { + return null; + } + + if (!$event instanceof Event) { + throw new \InvalidArgumentException(sprintf('The event processor must return null or an instance of the %s class', Event::class)); + } + } + + return $event; + } + + public function __clone() + { + $this->user = clone $this->user; + $this->tags = clone $this->tags; + $this->extra = clone $this->extra; + } +} diff --git a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php index 3a9415c0e..8613a88f7 100644 --- a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php +++ b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php @@ -21,7 +21,7 @@ class BreadcrumbErrorHandlerTest extends AbstractErrorHandlerTest public function testHandleError() { $this->client->expects($this->once()) - ->method('leaveBreadcrumb') + ->method('addBreadcrumb') ->with($this->callback(function ($breadcrumb) { /* @var Breadcrumb $breadcrumb */ $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); @@ -62,7 +62,7 @@ public function testHandleError() public function testHandleErrorWithPreviousErrorHandler() { $this->client->expects($this->once()) - ->method('leaveBreadcrumb') + ->method('addBreadcrumb') ->with($this->callback(function ($breadcrumb) { /* @var Breadcrumb $breadcrumb */ $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); @@ -103,13 +103,13 @@ public function testHandleErrorWithPreviousErrorHandler() public function testHandleFatalError() { $this->client->expects($this->once()) - ->method('leaveBreadcrumb') + ->method('addBreadcrumb') ->with($this->callback(function ($breadcrumb) { /* @var Breadcrumb $breadcrumb */ $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); $this->assertEquals('Parse Error: foo bar', $breadcrumb->getMessage()); $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); - $this->assertEquals(Client::LEVEL_FATAL, $breadcrumb->getLevel()); + $this->assertEquals(Breadcrumb::LEVEL_CRITICAL, $breadcrumb->getLevel()); $this->assertEquals('error_reporting', $breadcrumb->getCategory()); $this->assertArraySubset([ 'code' => 0, @@ -137,7 +137,7 @@ public function testHandleFatalError() public function testHandleFatalErrorWithNonFatalErrorDoesNothing() { $this->client->expects($this->never()) - ->method('leaveBreadcrumb'); + ->method('addBreadcrumb'); try { $errorHandler = $this->createErrorHandler($this->client); @@ -158,7 +158,7 @@ public function testHandleExceptionSkipsNotErrorExceptionException() $exception = new \Exception('foo bar'); $this->client->expects($this->never()) - ->method('leaveBreadcrumb'); + ->method('addBreadcrumb'); try { $errorHandler = $this->createErrorHandler($this->client); @@ -181,7 +181,7 @@ public function testHandleExceptionWithPreviousExceptionHandler() $exception = new \ErrorException('foo bar', 0, E_USER_NOTICE); $this->client->expects($this->once()) - ->method('leaveBreadcrumb') + ->method('addBreadcrumb') ->with($this->callback(function ($breadcrumb) { /* @var Breadcrumb $breadcrumb */ $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); @@ -230,7 +230,7 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler() $exception2 = new \ErrorException('bar foo', 0, E_USER_NOTICE); $this->client->expects($this->exactly(2)) - ->method('leaveBreadcrumb') + ->method('addBreadcrumb') ->withConsecutive($this->callback(function ($breadcrumb) { /* @var Breadcrumb $breadcrumb */ $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); @@ -291,7 +291,7 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler() public function testThrownErrorLeavesBreadcrumb() { $this->client->expects($this->once()) - ->method('leaveBreadcrumb') + ->method('addBreadcrumb') ->with($this->callback(function ($breadcrumb) { /* @var Breadcrumb $breadcrumb */ $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); diff --git a/tests/Breadcrumbs/BreadcrumbTest.php b/tests/Breadcrumbs/BreadcrumbTest.php index 975f06dd1..c888844f6 100644 --- a/tests/Breadcrumbs/BreadcrumbTest.php +++ b/tests/Breadcrumbs/BreadcrumbTest.php @@ -22,7 +22,7 @@ class BreadcrumbTest extends TestCase { /** * @expectedException \Sentry\Exception\InvalidArgumentException - * @expectedExceptionMessage The value of the $level argument must be one of the Sentry\Client::LEVEL_* constants. + * @expectedExceptionMessage The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants. */ public function testConstructorThrowsOnInvalidLevel() { @@ -31,7 +31,7 @@ public function testConstructorThrowsOnInvalidLevel() /** * @expectedException \Sentry\Exception\InvalidArgumentException - * @expectedExceptionMessage The value of the $level argument must be one of the Sentry\Client::LEVEL_* constants. + * @expectedExceptionMessage The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants. */ public function testSetLevelThrowsOnInvalidLevel() { diff --git a/tests/Breadcrumbs/MonologHandlerTest.php b/tests/Breadcrumbs/MonologHandlerTest.php deleted file mode 100644 index 16432e39b..000000000 --- a/tests/Breadcrumbs/MonologHandlerTest.php +++ /dev/null @@ -1,148 +0,0 @@ -run(Object(Illuminate\Http\Request)) -#3 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(5053): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request)) -#4 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(715): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request)) -#5 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(696): Illuminate\Foundation\Application->dispatch(Object(Illuminate\Http\Request)) -#6 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(7825): Illuminate\Foundation\Application->handle(Object(Illuminate\Http\Request), 1, true) -#7 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(8432): Illuminate\Session\Middleware->handle(Object(Illuminate\Http\Request), 1, true) -#8 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(8379): Illuminate\Cookie\Queue->handle(Object(Illuminate\Http\Request), 1, true) -#9 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(11123): Illuminate\Cookie\Guard->handle(Object(Illuminate\Http\Request), 1, true) -#10 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(657): Stack\StackedHttpKernel->handle(Object(Illuminate\Http\Request)) -#11 /sentry-laravel/examples/laravel-4.2/public/index.php(49): Illuminate\Foundation\Application->run() -#12 /sentry-laravel/examples/laravel-4.2/server.php(19): require_once('/Users/dcramer/...') -#13 {main} -EOF; - } - - public function testSimple() - { - $client = $this->createClient(); - $logger = $this->createLoggerWithHandler($client); - - $logger->addWarning('foo'); - - $breadcrumbs = $this->getBreadcrumbs($client); - $this->assertCount(1, $breadcrumbs); - $this->assertEquals('foo', $breadcrumbs[0]->getMessage()); - $this->assertEquals(Client::LEVEL_WARNING, $breadcrumbs[0]->getLevel()); - $this->assertEquals('sentry', $breadcrumbs[0]->getCategory()); - } - - public function testErrorInMessage() - { - $client = $this->createClient(); - $logger = $this->createLoggerWithHandler($client); - - $logger->addError($this->getSampleErrorMessage()); - - $breadcrumbs = $this->getBreadcrumbs($client); - $this->assertCount(1, $breadcrumbs); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumbs[0]->getType()); - $this->assertEquals(Client::LEVEL_ERROR, $breadcrumbs[0]->getLevel()); - $this->assertEquals('sentry', $breadcrumbs[0]->getCategory()); - $this->assertEquals('An unhandled exception', $breadcrumbs[0]->getMetadata()['value']); - } - - public function testExceptionBeingParsed() - { - $client = $this->createClient(); - $logger = $this->createLoggerWithHandler($client); - - $logger->addError('A message', ['exception' => new \Exception('Foo bar')]); - - $breadcrumbs = $this->getBreadcrumbs($client); - $this->assertCount(1, $breadcrumbs); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumbs[0]->getType()); - $this->assertEquals('Foo bar', $breadcrumbs[0]->getMetadata()['value']); - $this->assertEquals('sentry', $breadcrumbs[0]->getCategory()); - $this->assertEquals(Client::LEVEL_ERROR, $breadcrumbs[0]->getLevel()); - $this->assertNull($breadcrumbs[0]->getMessage()); - } - - public function testThrowableBeingParsedAsException() - { - if (\PHP_VERSION_ID <= 70000) { - $this->markTestSkipped('PHP 7.0 introduced Throwable'); - } - - $client = $this->createClient(); - $logger = $this->createLoggerWithHandler($client); - $throwable = new \ParseError('Foo bar'); - - $logger->addError('This is a throwable', ['exception' => $throwable]); - - $breadcrumbs = $this->getBreadcrumbs($client); - $this->assertCount(1, $breadcrumbs); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumbs[0]->getType()); - $this->assertEquals('Foo bar', $breadcrumbs[0]->getMetadata()['value']); - $this->assertEquals('sentry', $breadcrumbs[0]->getCategory()); - $this->assertEquals(Client::LEVEL_ERROR, $breadcrumbs[0]->getLevel()); - $this->assertNull($breadcrumbs[0]->getMessage()); - } - - /** - * @return ClientInterface - */ - private function createClient() - { - return ClientBuilder::create()->getClient(); - } - - /** - * @param ClientInterface $client - * - * @return Logger - */ - private function createLoggerWithHandler(ClientInterface $client) - { - $handler = new MonologHandler($client); - $logger = new Logger('sentry'); - $logger->pushHandler($handler); - - return $logger; - } - - /** - * @param ClientInterface $client - * - * @return Breadcrumb[] - */ - private function getBreadcrumbs(ClientInterface $client) - { - $breadcrumbsRecorder = $this->getObjectAttribute($client, 'breadcrumbRecorder'); - - $breadcrumbs = iterator_to_array($breadcrumbsRecorder); - $this->assertContainsOnlyInstancesOf(Breadcrumb::class, $breadcrumbs); - - return $breadcrumbs; - } -} diff --git a/tests/Breadcrumbs/RecorderTest.php b/tests/Breadcrumbs/RecorderTest.php deleted file mode 100644 index ade2cde2f..000000000 --- a/tests/Breadcrumbs/RecorderTest.php +++ /dev/null @@ -1,91 +0,0 @@ -assertCount(0, $recorder); - $this->assertEquals([], iterator_to_array($recorder)); - - $recorder->record($breadcrumb); - - $this->assertCount(1, $recorder); - $this->assertEquals([$breadcrumb], iterator_to_array($recorder)); - - for ($i = 0; $i < 2; ++$i) { - $recorder->record($breadcrumb); - } - - $this->assertCount(3, $recorder); - $this->assertEquals([$breadcrumb, $breadcrumb, $breadcrumb], iterator_to_array($recorder)); - - for ($i = 0; $i < 2; ++$i) { - $recorder->record($breadcrumb2); - } - - $this->assertCount(3, $recorder); - $this->assertEquals([$breadcrumb, $breadcrumb2, $breadcrumb2], iterator_to_array($recorder)); - } - - public function testClear() - { - $recorder = new Recorder(1); - $breadcrumb = new Breadcrumb(LogLevel::DEBUG, Breadcrumb::TYPE_USER, 'foo'); - - $this->assertCount(0, $recorder); - $this->assertEquals([], iterator_to_array($recorder)); - - $recorder->record($breadcrumb); - - $this->assertCount(1, $recorder); - $this->assertEquals([$breadcrumb], iterator_to_array($recorder)); - - $recorder->clear(); - - $this->assertCount(0, $recorder); - $this->assertEquals([], iterator_to_array($recorder)); - } -} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 78aae407e..b7efb8724 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\TestCase; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidFactory; -use Sentry\Breadcrumbs\Recorder as BreadcrumbsRecorder; +use Sentry\Breadcrumbs\Breadcrumb; use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Context\Context; @@ -25,6 +25,8 @@ use Sentry\Middleware\MiddlewareStack; use Sentry\ReprSerializer; use Sentry\Serializer; +use Sentry\Severity; +use Sentry\State\Scope; use Sentry\Tests\Fixtures\classes\CarelessException; use Sentry\TransactionStack; use Sentry\Transport\TransportInterface; @@ -50,13 +52,6 @@ public function testConstructorInitializesTransactionStackInCli() $this->assertEmpty($client->getTransactionStack()); } - public function testGetBreadcrumbsRecorder() - { - $client = ClientBuilder::create()->getClient(); - - $this->assertInstanceOf(BreadcrumbsRecorder::class, $client->getBreadcrumbsRecorder()); - } - public function testGetTransactionStack() { $client = ClientBuilder::create()->getClient(); @@ -201,7 +196,7 @@ public function testCapture() $inputData = [ 'transaction' => 'foo bar', - 'level' => Client::LEVEL_DEBUG, + 'level' => Severity::debug(), 'logger' => 'foo', 'tags_context' => ['foo', 'bar'], 'extra_context' => ['foo' => 'bar'], @@ -464,26 +459,79 @@ public function testSetAllObjectSerialize() $this->assertFalse($client->getRepresentationSerializer()->getAllObjectSerialize()); } - public function testClearBreadcrumb() + /** + * @dataProvider addBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsTooLowDataProvider + */ + public function testAddBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsTooLow(int $maxBreadcrumbs): void { - $client = ClientBuilder::create()->getClient(); - $client->leaveBreadcrumb( - new \Sentry\Breadcrumbs\Breadcrumb( - 'warning', \Sentry\Breadcrumbs\Breadcrumb::TYPE_ERROR, 'error_reporting', 'message', [ - 'code' => 127, - 'line' => 10, - 'file' => '/tmp/delme.php', - ] - ) - ); - $reflection = new \ReflectionProperty($client, 'breadcrumbRecorder'); - $reflection->setAccessible(true); + $client = ClientBuilder::create(['max_breadcrumbs' => $maxBreadcrumbs])->getClient(); + $scope = new Scope(); + + $client->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'), $scope); + + $this->assertEmpty($scope->getBreadcrumbs()); + } + + public function addBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsTooLowDataProvider(): array + { + return [ + [0], + [-1], + ]; + } - $this->assertNotEmpty(iterator_to_array($reflection->getValue($client))); + public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void + { + $client = ClientBuilder::create(['max_breadcrumbs' => 2])->getClient(); + $scope = new Scope(); + + $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'foo'); + $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'bar'); + $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'baz'); + + $client->addBreadcrumb($breadcrumb1, $scope); + $client->addBreadcrumb($breadcrumb2, $scope); + + $this->assertSame([$breadcrumb1, $breadcrumb2], $scope->getBreadcrumbs()); + + $client->addBreadcrumb($breadcrumb3, $scope); + + $this->assertSame([$breadcrumb2, $breadcrumb3], $scope->getBreadcrumbs()); + } + + public function testAddBreadcrumbDoesNothingWhenBeforeBreadcrumbCallbackReturnsNull(): void + { + $scope = new Scope(); + $client = ClientBuilder::create( + [ + 'before_breadcrumb' => function (): ?Breadcrumb { + return null; + }, + ] + )->getClient(); + + $client->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'), $scope); + + $this->assertEmpty($scope->getBreadcrumbs()); + } + + public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallback(): void + { + $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + + $scope = new Scope(); + $client = ClientBuilder::create( + [ + 'before_breadcrumb' => function () use ($breadcrumb2): ?Breadcrumb { + return $breadcrumb2; + }, + ] + )->getClient(); - $client->clearBreadcrumbs(); + $client->addBreadcrumb($breadcrumb1, $scope); - $this->assertEmpty(iterator_to_array($reflection->getValue($client))); + $this->assertSame([$breadcrumb2], $scope->getBreadcrumbs()); } public function testSetSerializer() diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 661cb64b9..44a0ff56f 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -65,6 +65,8 @@ public function optionsDataProvider() ['server_name', 'foo', 'getServerName', 'setServerName'], ['tags', ['foo', 'bar'], 'getTags', 'setTags'], ['error_types', 0, 'getErrorTypes', 'setErrorTypes'], + ['max_breadcrumbs', 50, 'getMaxBreadcrumbs', 'setMaxBreadcrumbs'], + ['before_breadcrumb', function () {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback'], ]; } diff --git a/tests/Context/AbstractContextTest.php b/tests/Context/AbstractContextTest.php new file mode 100644 index 000000000..d4c16f121 --- /dev/null +++ b/tests/Context/AbstractContextTest.php @@ -0,0 +1,133 @@ +expectException($expectedExceptionClass); + } + + if (null !== $expectedExceptionMessage) { + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = $this->createContext($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testMerge(array $initialData, array $expectedData, ?string $expectedExceptionClass, ?string $expectedExceptionMessage): void + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + } + + if (null !== $expectedExceptionMessage) { + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = $this->createContext(); + $context->merge($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testSetData(array $initialData, array $expectedData, ?string $expectedExceptionClass, ?string $expectedExceptionMessage): void + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + } + + if (null !== $expectedExceptionMessage) { + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = $this->createContext(); + $context->setData($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testReplaceData(array $initialData, array $expectedData, ?string $expectedExceptionClass, ?string $expectedExceptionMessage): void + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + } + + if (null !== $expectedExceptionMessage) { + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = $this->createContext(); + $context->replaceData($initialData); + + $this->assertEquals($expectedData, $context->toArray()); + } + + /** + * @dataProvider offsetSetDataProvider + */ + public function testOffsetSet(string $key, $value, ?string $expectedExceptionClass, ?string $expectedExceptionMessage): void + { + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + } + + if (null !== $expectedExceptionMessage) { + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $context = $this->createContext(); + $context[$key] = $value; + + $this->assertArraySubset([$key => $value], $context->toArray()); + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters(string $getterMethod, string $setterMethod, $value): void + { + $context = $this->createContext(); + $context->$setterMethod($value); + + $this->assertEquals($value, $context->$getterMethod()); + } + + abstract public function valuesDataProvider(): array; + + abstract public function offsetSetDataProvider(): array; + + abstract public function gettersAndSettersDataProvider(): array; + + abstract protected function createContext(array $initialData = []): Context; +} diff --git a/tests/Context/RuntimeContextTest.php b/tests/Context/RuntimeContextTest.php index 09861297b..ad89bd80b 100644 --- a/tests/Context/RuntimeContextTest.php +++ b/tests/Context/RuntimeContextTest.php @@ -11,78 +11,15 @@ namespace Sentry\Tests\Context; -use PHPUnit\Framework\TestCase; +use Sentry\Context\Context; use Sentry\Context\RuntimeContext; use Sentry\Util\PHPVersion; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; -class RuntimeContextTest extends TestCase +class RuntimeContextTest extends AbstractContextTest { - /** - * @dataProvider valuesDataProvider - */ - public function testConstructor($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new RuntimeContext($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testMerge($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new RuntimeContext(); - $context->merge($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testSetData($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new RuntimeContext(); - $context->setData($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testReplaceData($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new RuntimeContext(); - $context->replaceData($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - public function valuesDataProvider() + public function valuesDataProvider(): array { return [ [ @@ -144,23 +81,7 @@ public function valuesDataProvider() ]; } - /** - * @dataProvider offsetSetDataProvider - */ - public function testOffsetSet($key, $value, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new RuntimeContext(); - $context[$key] = $value; - - $this->assertArraySubset([$key => $value], $context->toArray()); - } - - public function offsetSetDataProvider() + public function offsetSetDataProvider(): array { return [ [ @@ -190,18 +111,7 @@ public function offsetSetDataProvider() ]; } - /** - * @dataProvider gettersAndSettersDataProvider - */ - public function testGettersAndSetters($getterMethod, $setterMethod, $value) - { - $context = new RuntimeContext(); - $context->$setterMethod($value); - - $this->assertEquals($value, $context->$getterMethod()); - } - - public function gettersAndSettersDataProvider() + public function gettersAndSettersDataProvider(): array { return [ [ @@ -216,4 +126,9 @@ public function gettersAndSettersDataProvider() ], ]; } + + protected function createContext(array $initialData = []): Context + { + return new RuntimeContext($initialData); + } } diff --git a/tests/Context/ServerOsContextTest.php b/tests/Context/ServerOsContextTest.php index 9641fe13e..97d694790 100644 --- a/tests/Context/ServerOsContextTest.php +++ b/tests/Context/ServerOsContextTest.php @@ -11,77 +11,14 @@ namespace Sentry\Tests\Context; -use PHPUnit\Framework\TestCase; +use Sentry\Context\Context; use Sentry\Context\ServerOsContext; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; -class ServerOsContextTest extends TestCase +class ServerOsContextTest extends AbstractContextTest { - /** - * @dataProvider valuesDataProvider - */ - public function testConstructor($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new ServerOsContext($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testMerge($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new ServerOsContext(); - $context->merge($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testSetData($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new ServerOsContext(); - $context->setData($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testReplaceData($initialData, $expectedData, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new ServerOsContext(); - $context->replaceData($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - public function valuesDataProvider() + public function valuesDataProvider(): array { return [ [ @@ -190,23 +127,7 @@ public function valuesDataProvider() ]; } - /** - * @dataProvider offsetSetDataProvider - */ - public function testOffsetSet($key, $value, $expectedExceptionClass, $expectedExceptionMessage) - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new ServerOsContext(); - $context[$key] = $value; - - $this->assertArraySubset([$key => $value], $context->toArray()); - } - - public function offsetSetDataProvider() + public function offsetSetDataProvider(): array { return [ [ @@ -266,18 +187,7 @@ public function offsetSetDataProvider() ]; } - /** - * @dataProvider gettersAndSettersDataProvider - */ - public function testGettersAndSetters($getterMethod, $setterMethod, $value) - { - $context = new ServerOsContext(); - $context->$setterMethod($value); - - $this->assertEquals($value, $context->$getterMethod()); - } - - public function gettersAndSettersDataProvider() + public function gettersAndSettersDataProvider(): array { return [ [ @@ -302,4 +212,9 @@ public function gettersAndSettersDataProvider() ], ]; } + + protected function createContext(array $initialData = []): Context + { + return new ServerOsContext($initialData); + } } diff --git a/tests/Context/UserContextTest.php b/tests/Context/UserContextTest.php new file mode 100644 index 000000000..800d2205a --- /dev/null +++ b/tests/Context/UserContextTest.php @@ -0,0 +1,94 @@ + 'foo', + 'username' => 'bar', + 'email' => 'foo@bar.baz', + ], + [ + 'id' => 'foo', + 'username' => 'bar', + 'email' => 'foo@bar.baz', + ], + null, + null, + ], + ]; + } + + public function offsetSetDataProvider(): array + { + return [ + [ + 'id', + 'foo', + null, + null, + ], + [ + 'username', + 'bar', + null, + null, + ], + [ + 'email', + 'foo@bar.baz', + null, + null, + ], + [ + 'ip_address', + '127.0.0.1', + null, + null, + ], + ]; + } + + public function gettersAndSettersDataProvider(): array + { + return [ + [ + 'getId', + 'setId', + 'foo', + ], + [ + 'getUsername', + 'setUsername', + 'bar', + ], + [ + 'getEmail', + 'setEmail', + 'foo@bar.baz', + ], + ]; + } + + protected function createContext(array $initialData = []): Context + { + return new UserContext($initialData); + } +} diff --git a/tests/EventTest.php b/tests/EventTest.php index fe477a281..1687ff2c6 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -16,6 +16,7 @@ use Sentry\Context\ServerOsContext; use Sentry\Context\TagsContext; use Sentry\Event; +use Sentry\Severity; use Sentry\Util\PHPVersion; /** @@ -147,8 +148,8 @@ public function testToArrayWithMessageWithParams() public function testToArrayWithBreadcrumbs() { $breadcrumbs = [ - new Breadcrumb(Client::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'foo'), - new Breadcrumb(Client::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'bar'), + new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'foo'), + new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'bar'), ]; $event = new Event($this->configuration); @@ -211,14 +212,14 @@ public function testGettersAndSetters($propertyName, $propertyValue, $expectedVa $event = new Event($this->configuration); $event->$setterMethod($propertyValue); - $this->assertSame($event->$getterMethod(), $propertyValue); + $this->assertEquals($event->$getterMethod(), $propertyValue); $this->assertArraySubset($expectedValue, $event->toArray()); } public function gettersAndSettersDataProvider() { return [ - ['level', 'info', ['level' => 'info']], + ['level', Severity::info(), ['level' => Severity::info()]], ['logger', 'ruby', ['logger' => 'ruby']], ['transaction', 'foo', ['transaction' => 'foo']], ['serverName', 'local.host', ['server_name' => 'local.host']], diff --git a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php b/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php deleted file mode 100644 index 0a9235761..000000000 --- a/tests/Middleware/BreadcrumbInterfaceMiddlewareTest.php +++ /dev/null @@ -1,36 +0,0 @@ -record($breadcrumb); - $recorder->record($breadcrumb2); - - $middleware = new BreadcrumbInterfaceMiddleware($recorder); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware); - - $this->assertEquals([$breadcrumb, $breadcrumb2], $returnedEvent->getBreadcrumbs()); - } -} diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php index 17e02e67f..c8bc613e8 100644 --- a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php +++ b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php @@ -28,7 +28,6 @@ public function testInvoke(\Exception $exception, array $clientConfig, array $pa $assertHasStacktrace = $client->getConfig()->getAutoLogStacks(); $event = new Event($client->getConfig()); - $middleware = new ExceptionInterfaceMiddleware($client); $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, $exception, $payload); diff --git a/tests/Middleware/MiddlewareTestCase.php b/tests/Middleware/MiddlewareTestCase.php index ea2f604b7..ad7938009 100644 --- a/tests/Middleware/MiddlewareTestCase.php +++ b/tests/Middleware/MiddlewareTestCase.php @@ -18,7 +18,7 @@ abstract class MiddlewareTestCase extends TestCase { - protected function assertMiddlewareInvokesNext(callable $middleware, Event $event = null, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []) + protected function assertMiddlewareInvokesNext(callable $middleware, Event $event = null, ServerRequestInterface $request = null, \Exception $exception = null, array $payload = []): Event { $callbackInvoked = false; $callback = function (Event $passedEvent, ServerRequestInterface $passedRequest = null, $passedException = null) use (&$callbackInvoked) { diff --git a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php index 0c5ca0f64..625537903 100644 --- a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php +++ b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php @@ -41,6 +41,8 @@ public function testInvoke() $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + $this->assertNotNull($returnedEvent->getStacktrace()); + foreach ($returnedEvent->getStacktrace()->getFrames() as $frame) { $this->assertEmpty($frame->getPreContext()); $this->assertNull($frame->getContextLine()); @@ -60,6 +62,8 @@ public function testInvokeWithPreviousException() $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); + $this->assertNotNull($returnedEvent->getStacktrace()); + foreach ($returnedEvent->getStacktrace()->getFrames() as $frame) { $this->assertEmpty($frame->getPreContext()); $this->assertNull($frame->getContextLine()); diff --git a/tests/SeverityTest.php b/tests/SeverityTest.php new file mode 100644 index 000000000..2ed4aa752 --- /dev/null +++ b/tests/SeverityTest.php @@ -0,0 +1,74 @@ +assertSame(Severity::DEBUG, (string) $severity); + } + + public function testInfo(): void + { + $severity = Severity::info(); + + $this->assertSame(Severity::INFO, (string) $severity); + } + + public function testWarning(): void + { + $severity = Severity::warning(); + + $this->assertSame(Severity::WARNING, (string) $severity); + } + + public function testError(): void + { + $severity = Severity::error(); + + $this->assertSame(Severity::ERROR, (string) $severity); + } + + public function testFatal(): void + { + $severity = Severity::fatal(); + + $this->assertSame(Severity::FATAL, (string) $severity); + } + + public function testIsEqualTo(): void + { + $severity1 = Severity::error(); + $severity2 = Severity::error(); + $severity3 = Severity::fatal(); + + $this->assertTrue($severity1->isEqualTo($severity2)); + $this->assertFalse($severity1->isEqualTo($severity3)); + } +} diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php new file mode 100644 index 000000000..c85b9e114 --- /dev/null +++ b/tests/State/HubTest.php @@ -0,0 +1,265 @@ +assertNotNull($hub->getScope()); + } + + public function testGetClient(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $hub = new Hub($client); + + $this->assertSame($client, $hub->getClient()); + } + + public function testGetScope(): void + { + $scope = new Scope(); + $hub = new Hub($this->createMock(ClientInterface::class), $scope); + + $this->assertSame($scope, $hub->getScope()); + } + + public function testGetStack(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $scope = new Scope(); + $hub = new Hub($client, $scope); + + $stack = $hub->getStack(); + + $this->assertCount(1, $stack); + $this->assertSame($client, $stack[0]->getClient()); + $this->assertSame($scope, $stack[0]->getScope()); + } + + public function testGetStackTop(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $scope = new Scope(); + $hub = new Hub($client, $scope); + + $stackTop = $hub->getStackTop(); + + $this->assertSame($client, $stackTop->getClient()); + $this->assertSame($scope, $stackTop->getScope()); + + $scope = $hub->pushScope(); + + $stackTop = $hub->getStackTop(); + + $this->assertSame($client, $stackTop->getClient()); + $this->assertSame($scope, $stackTop->getScope()); + } + + public function testGetLastEventId(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('captureMessage') + ->with('foo', [], ['level' => null]) + ->willReturn('92db40a886c0458288c7c83935a350ef'); + + $hub = new Hub($client); + + $this->assertNull($hub->getLastEventId()); + $this->assertEquals($hub->captureMessage('foo'), $hub->getLastEventId()); + } + + public function testPushScope(): void + { + $hub = new Hub($this->createMock(ClientInterface::class)); + + $this->assertCount(1, $hub->getStack()); + + $scope1 = $hub->getScope(); + $scope2 = $hub->pushScope(); + + $layers = $hub->getStack(); + + $this->assertCount(2, $layers); + $this->assertNotSame($scope1, $scope2); + $this->assertSame($scope1, $layers[0]->getScope()); + $this->assertSame($scope2, $layers[1]->getScope()); + } + + public function testPopScope(): void + { + $hub = new Hub($this->createMock(ClientInterface::class)); + + $this->assertCount(1, $hub->getStack()); + + $scope1 = $hub->getScope(); + $scope2 = $hub->pushScope(); + + $this->assertSame($scope2, $hub->getScope()); + + $this->assertTrue($hub->popScope()); + $this->assertSame($scope1, $hub->getScope()); + + $this->assertFalse($hub->popScope()); + $this->assertSame($scope1, $hub->getScope()); + } + + public function testWithScope(): void + { + $scope = new Scope(); + $hub = new Hub($this->createMock(ClientInterface::class), $scope); + + $this->assertSame($scope, $hub->getScope()); + + $callbackInvoked = false; + + try { + $hub->withScope(function (Scope $scopeArg) use ($scope, &$callbackInvoked): void { + $this->assertNotSame($scope, $scopeArg); + + $callbackInvoked = true; + + // We throw to test that the scope is correctly popped form the + // stack regardless + throw new \RuntimeException(); + }); + } catch (\RuntimeException $exception) { + // Do nothing, we catch this exception to not make the test fail + } + + $this->assertTrue($callbackInvoked); + $this->assertSame($scope, $hub->getScope()); + } + + public function testConfigureScope(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + + $hub = new Hub($client); + $scope = $hub->pushScope(); + + $hub->configureScope(function (Scope $scopeArg) use ($scope, &$callbackInvoked): void { + $this->assertSame($scope, $scopeArg); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); + $this->assertSame($scope, $hub->getScope()); + } + + public function testBindClient(): void + { + /** @var ClientInterface|MockObject $client1 */ + $client1 = $this->createMock(ClientInterface::class); + + /** @var ClientInterface|MockObject $client2 */ + $client2 = $this->createMock(ClientInterface::class); + + $hub = new Hub($client1); + + $this->assertSame($client1, $hub->getClient()); + + $hub->bindClient($client2); + + $this->assertSame($client2, $hub->getClient()); + } + + public function testCaptureMessageDoesNothingIfClientIsNotBinded() + { + $hub = new Hub(); + + $this->assertNull($hub->captureMessage('foo')); + } + + public function testCaptureMessage(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('captureMessage') + ->with('foo', [], ['level' => Severity::debug()]) + ->willReturn('2b867534eead412cbdb882fd5d441690'); + + $hub = new Hub($client); + + $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureMessage('foo', Severity::debug())); + } + + public function testCaptureExceptionDoesNothingIfClientIsNotBinded() + { + $hub = new Hub(); + + $this->assertNull($hub->captureException(new \RuntimeException())); + } + + public function testCaptureException(): void + { + $exception = new \RuntimeException('foo'); + + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('captureException') + ->with($exception) + ->willReturn('2b867534eead412cbdb882fd5d441690'); + + $hub = new Hub($client); + + $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureException($exception)); + } + + public function testAddBreadcrumbDoesNothingIfClientIsNotBinded(): void + { + $scope = new Scope(); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + + $hub = new Hub(null, $scope); + $hub->addBreadcrumb($breadcrumb); + + $this->assertEmpty($scope->getBreadcrumbs()); + } + + public function testAddBreadcrumb(): void + { + $scope = new Scope(); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('addBreadcrumb') + ->with($breadcrumb, $scope); + + $hub = new Hub($client, $scope); + $hub->addBreadcrumb($breadcrumb); + } +} diff --git a/tests/State/LayerTest.php b/tests/State/LayerTest.php new file mode 100644 index 000000000..54436208d --- /dev/null +++ b/tests/State/LayerTest.php @@ -0,0 +1,46 @@ +createMock(ClientInterface::class); + + /** @var ClientInterface|MockObject $client2 */ + $client2 = $this->createMock(ClientInterface::class); + + $scope1 = new Scope(); + $scope2 = new Scope(); + + $layer = new Layer($client1, $scope1); + + $this->assertSame($client1, $layer->getClient()); + $this->assertSame($scope1, $layer->getScope()); + + $layer->setClient($client2); + $layer->setScope($scope2); + + $this->assertSame($client2, $layer->getClient()); + $this->assertSame($scope2, $layer->getScope()); + } +} diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php new file mode 100644 index 000000000..420ffd80c --- /dev/null +++ b/tests/State/ScopeTest.php @@ -0,0 +1,206 @@ +assertEquals([], $scope->getTags()); + + $scope->setTag('foo', 'bar'); + $scope->setTag('bar', 'baz'); + + $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $scope->getTags()); + } + + public function testSetExtra(): void + { + $scope = new Scope(); + + $this->assertEquals([], $scope->getExtra()); + + $scope->setExtra('foo', 'bar'); + $scope->setExtra('bar', 'baz'); + + $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $scope->getExtra()); + } + + public function testSetUser(): void + { + $scope = new Scope(); + + $this->assertEquals([], $scope->getUser()); + + $scope->setUser(['foo' => 'bar']); + + $this->assertEquals(['foo' => 'bar'], $scope->getUser()); + + $scope->setUser(['bar' => 'baz']); + + $this->assertEquals(['bar' => 'baz'], $scope->getUser()); + } + + public function testSetFingerprint(): void + { + $scope = new Scope(); + + $this->assertEmpty($scope->getFingerprint()); + + $scope->setFingerprint(['foo', 'bar']); + + $this->assertEquals(['foo', 'bar'], $scope->getFingerprint()); + } + + public function testSetLevel(): void + { + $scope = new Scope(); + + $this->assertNull($scope->getLevel()); + + $scope->setLevel(Severity::debug()); + + $this->assertEquals(Breadcrumb::LEVEL_DEBUG, $scope->getLevel()); + } + + public function testAddBreadcrumb(): void + { + $scope = new Scope(); + $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + + $this->assertEmpty($scope->getBreadcrumbs()); + + $scope->addBreadcrumb($breadcrumb1); + $scope->addBreadcrumb($breadcrumb2); + + $this->assertSame([$breadcrumb1, $breadcrumb2], $scope->getBreadcrumbs()); + + $scope->addBreadcrumb($breadcrumb3, 2); + + $this->assertSame([$breadcrumb2, $breadcrumb3], $scope->getBreadcrumbs()); + } + + public function testAddEventProcessor(): void + { + $callback1Called = false; + $callback2Called = false; + $callback3Called = false; + + $event = new Event(new Configuration()); + $scope = new Scope(); + + $scope->addEventProcessor(function (Event $eventArg) use (&$callback1Called, $callback2Called, $callback3Called): ?Event { + $this->assertFalse($callback2Called); + $this->assertFalse($callback3Called); + + $callback1Called = true; + + return $eventArg; + }); + + $this->assertSame($event, $scope->applyToEvent($event)); + $this->assertTrue($callback1Called); + + $scope->addEventProcessor(function () use ($callback1Called, &$callback2Called, $callback3Called): ?Event { + $this->assertTrue($callback1Called); + $this->assertFalse($callback3Called); + + $callback2Called = true; + + return null; + }); + + $scope->addEventProcessor(function () use (&$callback3Called): ?Event { + $callback3Called = true; + + return null; + }); + + $this->assertNull($scope->applyToEvent($event)); + $this->assertTrue($callback2Called); + $this->assertFalse($callback3Called); + } + + public function testClear(): void + { + $scope = new Scope(); + $scope->setLevel(Severity::error()); + $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); + $scope->setFingerprint(['foo']); + + $this->assertNotNull($scope->getLevel()); + $this->assertNotEmpty($scope->getBreadcrumbs()); + $this->assertNotEmpty($scope->getFingerprint()); + + $scope->clear(); + + $this->assertNull($scope->getLevel()); + $this->assertEmpty($scope->getBreadcrumbs()); + $this->assertEmpty($scope->getFingerprint()); + } + + public function testApplyToEvent(): void + { + $event = new Event(new Configuration()); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + + $scope = new Scope(); + $scope->setLevel(Severity::warning()); + $scope->setFingerprint(['foo']); + $scope->addBreadcrumb($breadcrumb); + $scope->setTag('foo', 'bar'); + $scope->setExtra('bar', 'foo'); + $scope->setUser(['foo' => 'baz']); + + $event = $scope->applyToEvent($event); + + $this->assertNotNull($event); + $this->assertTrue($event->getLevel()->isEqualTo(Severity::warning())); + $this->assertSame(['foo'], $event->getFingerprint()); + $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); + $this->assertEquals(['foo' => 'bar'], $event->getTagsContext()->toArray()); + $this->assertEquals(['bar' => 'foo'], $event->getExtraContext()->toArray()); + $this->assertEquals(['foo' => 'baz'], $event->getUserContext()->toArray()); + + $scope->setFingerprint(['foo', 'bar']); + $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_CRITICAL, Breadcrumb::TYPE_ERROR, 'error_reporting')); + $scope->setLevel(Severity::fatal()); + $scope->setTag('bar', 'foo'); + $scope->setExtra('foo', 'bar'); + $scope->setUser(['baz' => 'foo']); + + $event = $scope->applyToEvent($event); + + $this->assertNotNull($event); + $this->assertTrue($event->getLevel()->isEqualTo(Severity::fatal())); + $this->assertSame(['foo'], $event->getFingerprint()); + $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); + $this->assertEquals(['foo' => 'bar', 'bar' => 'foo'], $event->getTagsContext()->toArray()); + $this->assertEquals(['bar' => 'foo', 'foo' => 'bar'], $event->getExtraContext()->toArray()); + $this->assertEquals(['foo' => 'baz', 'baz' => 'foo'], $event->getUserContext()->toArray()); + + $this->assertSame($event, $scope->applyToEvent($event)); + } +} From 3e9272e041b3462d99c3842588cc701c6c783f32 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 29 Oct 2018 18:05:29 +0100 Subject: [PATCH 0381/1161] feat: [2.0] Add global SDK (#680) --- Makefile | 2 +- composer.json | 1 + src/ClientBuilder.php | 4 +- src/ClientBuilderInterface.php | 2 +- src/Sdk.php | 87 ++++++++++++++++++++++++++ src/State/Hub.php | 53 ++++++++++++++++ tests/SdkTest.php | 111 +++++++++++++++++++++++++++++++++ tests/State/HubTest.php | 14 +++++ 8 files changed, 270 insertions(+), 4 deletions(-) create mode 100644 src/Sdk.php create mode 100644 tests/SdkTest.php diff --git a/Makefile b/Makefile index f742ddedc..1fbb6e9e4 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ phpstan: vendor/bin/phpstan analyse test: cs-fix phpstan - vendor/bin/phpunit + vendor/bin/phpunit --verbose setup-git: git config branch.autosetuprebase always diff --git a/composer.json b/composer.json index b0e87eeae..deb737c9a 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "bin/sentry" ], "autoload": { + "files": ["src/Sdk.php"], "psr-4" : { "Sentry\\" : "src/" } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 8fdbfd7fe..80c635453 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -131,7 +131,7 @@ public function __construct(array $options = []) /** * {@inheritdoc} */ - public static function create(array $options = []) + public static function create(array $options = []): self { return new static($options); } @@ -239,7 +239,7 @@ public function getMiddlewares() /** * {@inheritdoc} */ - public function getClient() + public function getClient(): ClientInterface { $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 5f9bf3473..54e1914cf 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -121,5 +121,5 @@ public function getMiddlewares(); * * @return ClientInterface */ - public function getClient(); + public function getClient(): ClientInterface; } diff --git a/src/Sdk.php b/src/Sdk.php new file mode 100644 index 000000000..b6cff6680 --- /dev/null +++ b/src/Sdk.php @@ -0,0 +1,87 @@ +getClient())); +} + +/** + * Captures a message event and sends it to Sentry. + * + * @param string $message The message + * @param Severity $level The severity level of the message + * + * @return null|string + */ +function captureMessage(string $message, ?Severity $level = null): ?string +{ + return Hub::getCurrent()->captureMessage($message, $level); +} + +/** + * Captures an exception event and sends it to Sentry. + * + * @param \Throwable $exception The exception + * + * @return null|string + */ +function captureException(\Throwable $exception): ?string +{ + return Hub::getCurrent()->captureException($exception); +} + +/** + * Captures a new event using the provided data. + * + * @param array $payload The data of the event being captured + * + * @return null|string + */ +function captureEvent(array $payload): ?string +{ + return Hub::getCurrent()->captureEvent($payload); +} + +/** + * Records a new breadcrumb which will be attached to future events. They + * will be added to subsequent events to provide more context on user's + * actions prior to an error or crash. + * + * @param Breadcrumb $breadcrumb The breadcrumb to record + */ +function addBreadcrumb(Breadcrumb $breadcrumb): void +{ + Hub::getCurrent()->addBreadcrumb($breadcrumb); +} + +/** + * Calls the given callback passing to it the current scope so that any + * operation can be run within its context. + * + * @param callable $callback The callback to be executed + */ +function configureScope(callable $callback): void +{ + Hub::getCurrent()->configureScope($callback); +} + +/** + * Creates a new scope with and executes the given operation within. The scope + * is automatically removed once the operation finishes or throws. + * + * @param callable $callback The callback to be executed + */ +function withScope(callable $callback): void +{ + Hub::getCurrent()->withScope($callback); +} diff --git a/src/State/Hub.php b/src/State/Hub.php index b594b2958..858ab3e43 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -34,6 +34,13 @@ final class Hub /** * Constructor. * + * @var Hub + */ + private static $currentHub; + + /** + * Hub constructor. + * * @param ClientInterface|null $client The client bound to the hub * @param Scope|null $scope The scope bound to the hub */ @@ -206,6 +213,24 @@ public function captureException(\Throwable $exception): ?string return null; } + /** + * Captures a new event using the provided data. + * + * @param array $payload The data of the event being captured + * + * @return null|string + */ + public function captureEvent(array $payload): ?string + { + $client = $this->getClient(); + + if (null !== $client) { + return $this->lastEventId = $client->capture($payload); + } + + return null; + } + /** * Records a new breadcrumb which will be attached to future events. They * will be added to subsequent events to provide more context on user's @@ -221,4 +246,32 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): void $client->addBreadcrumb($breadcrumb, $this->getScope()); } } + + /** + * Returns the current global Hub. + * + * @return Hub + */ + public static function getCurrent(): self + { + if (null === self::$currentHub) { + self::$currentHub = new self(); + } + + return self::$currentHub; + } + + /** + * Sets the Hub as the current. + * + * @param self $hub + * + * @return Hub + */ + public static function setCurrent(self $hub): self + { + self::$currentHub = $hub; + + return $hub; + } } diff --git a/tests/SdkTest.php b/tests/SdkTest.php new file mode 100644 index 000000000..964e771fd --- /dev/null +++ b/tests/SdkTest.php @@ -0,0 +1,111 @@ +assertNotNull(Hub::getCurrent()->getClient()); + } + + public function testCaptureMessage(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('captureMessage') + ->with('foo', [], ['level' => null]) + ->willReturn('92db40a886c0458288c7c83935a350ef'); + + Hub::getCurrent()->bindClient($client); + $this->assertEquals($client, Hub::getCurrent()->getClient()); + $this->assertEquals('92db40a886c0458288c7c83935a350ef', captureMessage('foo')); + } + + public function testCaptureException(): void + { + $exception = new \RuntimeException('foo'); + + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('captureException') + ->with($exception) + ->willReturn('2b867534eead412cbdb882fd5d441690'); + + Hub::getCurrent()->bindClient($client); + + $this->assertEquals('2b867534eead412cbdb882fd5d441690', captureException($exception)); + } + + public function testCaptureEvent(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('capture') + ->with(['message' => 'test']) + ->willReturn('2b867534eead412cbdb882fd5d441690'); + + Hub::getCurrent()->bindClient($client); + + $this->assertEquals('2b867534eead412cbdb882fd5d441690', captureEvent(['message' => 'test'])); + } + + public function testAddBreadcrumb(): void + { + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('addBreadcrumb') + ->with($breadcrumb, Hub::getCurrent()->getScope()); + + Hub::getCurrent()->bindClient($client); + addBreadcrumb($breadcrumb); + } + + public function testWithScope(): void + { + $callbackInvoked = false; + + withScope(function () use (&$callbackInvoked): void { + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); + } + + public function configureScope(): void + { + $callbackInvoked = false; + + configureScope(function () use (&$callbackInvoked): void { + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); + } +} diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index c85b9e114..47f6aa46f 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -262,4 +262,18 @@ public function testAddBreadcrumb(): void $hub = new Hub($client, $scope); $hub->addBreadcrumb($breadcrumb); } + + public function testCaptureEvent(): void + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('capture') + ->with(['message' => 'test']) + ->willReturn('2b867534eead412cbdb882fd5d441690'); + + $hub = new Hub($client); + + $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureEvent(['message' => 'test'])); + } } From 3c75bc018512f0ae8cb8d2a6ac35fb3c6c6b9f7c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 2 Nov 2018 11:01:42 +0100 Subject: [PATCH 0382/1161] Restrict conflict with php-http/client-common to version 1.8.0 only (#681) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index deb737c9a..1a243f298 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" }, "conflict": { - "php-http/client-common": "^1.8.0", + "php-http/client-common": "1.8.0", "raven/raven": "*" }, "bin": [ From fdb12a1ca1efdcbfb4f1b6742a0fd980f3c2f207 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 16 Nov 2018 10:31:55 +0100 Subject: [PATCH 0383/1161] Refactor the whole source code to support the Unified API (#682) --- Makefile | 2 +- phpstan.neon | 4 - src/AbstractErrorHandler.php | 32 +- src/BreadcrumbErrorHandler.php | 93 --- src/Breadcrumbs/Breadcrumb.php | 35 + src/Client.php | 415 ++++-------- src/ClientBuilder.php | 109 +-- src/ClientBuilderInterface.php | 29 - src/ClientInterface.php | 179 +---- src/Context/UserContext.php | 20 + src/ErrorHandler.php | 12 +- src/Event.php | 69 +- src/HttpClient/Authentication/SentryAuth.php | 8 +- .../Encoding/Base64EncodingStream.php | 67 -- src/Integration/ErrorHandlerIntegration.php | 60 ++ src/Integration/Handler.php | 45 ++ src/Integration/IntegrationInterface.php | 17 + src/Integration/ModulesIntegration.php | 84 +++ src/Integration/RequestIntegration.php | 77 +++ src/Middleware/ContextInterfaceMiddleware.php | 86 --- .../ExceptionInterfaceMiddleware.php | 91 --- src/Middleware/MessageInterfaceMiddleware.php | 48 -- src/Middleware/MiddlewareStack.php | 171 ----- src/Middleware/ModulesMiddleware.php | 71 -- .../ProcessorMiddlewareInterface.php | 26 - src/Middleware/RemoveHttpBodyMiddleware.php | 49 -- .../RemoveStacktraceContextMiddleware.php | 52 -- src/Middleware/RequestInterfaceMiddleware.php | 61 -- src/Middleware/SanitizeCookiesMiddleware.php | 107 --- src/Middleware/SanitizeDataMiddleware.php | 187 ------ .../SanitizeHttpHeadersMiddleware.php | 85 --- src/Middleware/SanitizerMiddleware.php | 69 -- src/Middleware/UserInterfaceMiddleware.php | 48 -- src/{Configuration.php => Options.php} | 87 ++- src/Sdk.php | 19 +- src/Severity.php | 34 + src/Stacktrace.php | 60 +- src/State/Hub.php | 40 +- src/State/Scope.php | 33 +- src/TransactionStack.php | 102 --- src/Transport/HttpTransport.php | 19 +- src/Transport/NullTransport.php | 4 +- src/Transport/SpoolTransport.php | 8 +- src/Transport/TransportInterface.php | 4 +- tests/AbstractErrorHandlerTest.php | 38 +- tests/AbstractSerializerTest.php | 21 +- .../BreadcrumbErrorHandlerTest.php | 325 --------- tests/Breadcrumbs/BreadcrumbTest.php | 31 +- tests/ClientBuilderTest.php | 38 +- tests/ClientTest.php | 627 +++++++++--------- tests/Context/UserContextTest.php | 5 + tests/ErrorHandlerTest.php | 42 +- tests/EventTest.php | 56 +- .../Authentication/SentryAuthTest.php | 6 +- .../Encoding/Base64EncodingStreamTest.php | 72 -- tests/Integration/ModulesIntegrationTest.php | 25 + .../RequestIntegrationTest.php} | 46 +- .../ContextInterfaceMiddlewareTest.php | 121 ---- .../ExceptionInterfaceMiddlewareTest.php | 232 ------- .../MessageInterfaceMiddlewareTest.php | 65 -- tests/Middleware/MiddlewareStackTest.php | 214 ------ tests/Middleware/MiddlewareTestCase.php | 53 -- tests/Middleware/ModulesMiddlewareTest.php | 31 - .../RemoveHttpBodyMiddlewareTest.php | 114 ---- .../RemoveStacktraceContextMiddlewareTest.php | 73 -- .../SanitizeCookiesMiddlewareTest.php | 108 --- .../Middleware/SanitizeDataMiddlewareTest.php | 251 ------- .../SanitizeHttpHeadersMiddlewareTest.php | 58 -- tests/Middleware/SanitizerMiddlewareTest.php | 54 -- .../UserInterfaceMiddlewareTest.php | 47 -- ...{ConfigurationTest.php => OptionsTest.php} | 49 +- tests/SdkTest.php | 18 +- tests/Spool/MemorySpoolTest.php | 7 +- tests/StacktraceTest.php | 53 +- tests/State/HubTest.php | 5 +- tests/State/ScopeTest.php | 15 +- tests/TransactionStackTest.php | 133 ---- tests/Transport/HttpTransportTest.php | 26 +- tests/Transport/NullTransportTest.php | 5 +- tests/Transport/SpoolTransportTest.php | 3 +- tests/phpt/error_handler_called.phpt | 33 + .../error_handler_respects_captureAt.phpt | 32 + tests/phpt/exception_rethrown.phpt | 7 +- tests/phpt/exception_thrown.phpt | 7 +- tests/phpt/fatal_error.phpt | 14 +- .../phpt/fatal_error_not_captured_twice.phpt | 10 +- tests/phpt/out_of_memory.phpt | 13 +- 87 files changed, 1438 insertions(+), 4663 deletions(-) delete mode 100644 src/BreadcrumbErrorHandler.php delete mode 100644 src/HttpClient/Encoding/Base64EncodingStream.php create mode 100644 src/Integration/ErrorHandlerIntegration.php create mode 100644 src/Integration/Handler.php create mode 100644 src/Integration/IntegrationInterface.php create mode 100644 src/Integration/ModulesIntegration.php create mode 100644 src/Integration/RequestIntegration.php delete mode 100644 src/Middleware/ContextInterfaceMiddleware.php delete mode 100644 src/Middleware/ExceptionInterfaceMiddleware.php delete mode 100644 src/Middleware/MessageInterfaceMiddleware.php delete mode 100644 src/Middleware/MiddlewareStack.php delete mode 100644 src/Middleware/ModulesMiddleware.php delete mode 100644 src/Middleware/ProcessorMiddlewareInterface.php delete mode 100644 src/Middleware/RemoveHttpBodyMiddleware.php delete mode 100644 src/Middleware/RemoveStacktraceContextMiddleware.php delete mode 100644 src/Middleware/RequestInterfaceMiddleware.php delete mode 100644 src/Middleware/SanitizeCookiesMiddleware.php delete mode 100644 src/Middleware/SanitizeDataMiddleware.php delete mode 100644 src/Middleware/SanitizeHttpHeadersMiddleware.php delete mode 100644 src/Middleware/SanitizerMiddleware.php delete mode 100644 src/Middleware/UserInterfaceMiddleware.php rename src/{Configuration.php => Options.php} (90%) delete mode 100644 src/TransactionStack.php delete mode 100644 tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php delete mode 100644 tests/HttpClient/Encoding/Base64EncodingStreamTest.php create mode 100644 tests/Integration/ModulesIntegrationTest.php rename tests/{Middleware/RequestInterfaceMiddlewareTest.php => Integration/RequestIntegrationTest.php} (74%) delete mode 100644 tests/Middleware/ContextInterfaceMiddlewareTest.php delete mode 100644 tests/Middleware/ExceptionInterfaceMiddlewareTest.php delete mode 100644 tests/Middleware/MessageInterfaceMiddlewareTest.php delete mode 100644 tests/Middleware/MiddlewareStackTest.php delete mode 100644 tests/Middleware/MiddlewareTestCase.php delete mode 100644 tests/Middleware/ModulesMiddlewareTest.php delete mode 100644 tests/Middleware/RemoveHttpBodyMiddlewareTest.php delete mode 100644 tests/Middleware/RemoveStacktraceContextMiddlewareTest.php delete mode 100644 tests/Middleware/SanitizeCookiesMiddlewareTest.php delete mode 100644 tests/Middleware/SanitizeDataMiddlewareTest.php delete mode 100644 tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php delete mode 100644 tests/Middleware/SanitizerMiddlewareTest.php delete mode 100644 tests/Middleware/UserInterfaceMiddlewareTest.php rename tests/{ConfigurationTest.php => OptionsTest.php} (85%) delete mode 100644 tests/TransactionStackTest.php create mode 100644 tests/phpt/error_handler_called.phpt create mode 100644 tests/phpt/error_handler_respects_captureAt.phpt diff --git a/Makefile b/Makefile index 1fbb6e9e4..fcdd77fbe 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: test +gc -am .PHONY: test develop: update-submodules composer install --dev diff --git a/phpstan.neon b/phpstan.neon index cdf0e5a67..5a2407dd1 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,14 +4,10 @@ parameters: - src - tests ignoreErrors: - - '/Constructor of class Sentry\\HttpClient\\Encoding\\Base64EncodingStream has an unused parameter \$(readFilterOptions|writeFilterOptions)/' - '/Call to an undefined method Sentry\\ClientBuilder::methodThatDoesNotExists\(\)/' - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' - '/Method Sentry\\ClientBuilder::\w+\(\) should return \$this\(Sentry\\ClientBuilderInterface\) but returns \$this\(Sentry\\ClientBuilder\)\./' - - '/Parameter #1 \$values of class Sentry\\TransactionStack constructor expects array, array given\./' - # to be removed with serializer refactoring - - '/Parameter #1 \$data of method Sentry\\Context\\\w*Context::replaceData\(\) expects array, array\|bool\|float\|int\|object\|string\|null given\./' excludes_analyse: - tests/resources includes: diff --git a/src/AbstractErrorHandler.php b/src/AbstractErrorHandler.php index b705a74ea..cf56b3993 100644 --- a/src/AbstractErrorHandler.php +++ b/src/AbstractErrorHandler.php @@ -19,9 +19,9 @@ abstract class AbstractErrorHandler { /** - * @var ClientInterface The Raven client + * @var callable Callback that will be invoked when an error is caught */ - protected $client; + protected $callback; /** * @var \ReflectionProperty A reflection cached instance that points to the @@ -80,16 +80,18 @@ abstract class AbstractErrorHandler /** * Constructor. * - * @param ClientInterface $client The Raven client - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * @param callable $callback The callback that will be invoked in case an error is caught + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * + * @throws \ReflectionException */ - protected function __construct(ClientInterface $client, $reservedMemorySize = 10240) + protected function __construct(callable $callback, int $reservedMemorySize = 10240) { if (!\is_int($reservedMemorySize) || $reservedMemorySize <= 0) { throw new \UnexpectedValueException('The value of the $reservedMemorySize argument must be an integer greater than 0.'); } - $this->client = $client; + $this->callback = $callback; $this->exceptionReflection = new \ReflectionProperty(\Exception::class, 'trace'); $this->exceptionReflection->setAccessible(true); @@ -104,7 +106,7 @@ protected function __construct(ClientInterface $client, $reservedMemorySize = 10 if (null === $this->previousErrorHandler) { restore_error_handler(); - // Specifying the error types catched by the error handler with the + // Specifying the error types caught by the error handler with the // first call to the set_error_handler method would cause the PHP // bug https://bugs.php.net/63206 if the handler is not the first // one @@ -152,6 +154,8 @@ public function captureAt($levels, $replace = false) * * @return bool If the function returns FALSE then the normal error handler continues * + * @throws \Throwable + * * @internal */ public function handleError($level, $message, $file, $line) @@ -169,8 +173,8 @@ public function handleError($level, $message, $file, $line) try { $this->handleException($errorAsException); - } catch (\Exception $exception) { - // Do nothing as this error handler should be as trasparent as possible + } catch (\Throwable $exception) { + // Do nothing as this error handler should be as transparent as possible } if (null !== $this->previousErrorHandler) { @@ -211,7 +215,7 @@ public function handleFatalError(array $error = null) if (null !== $errorAsException) { $this->handleException($errorAsException); } - } catch (\ErrorException $errorAsException) { + } catch (\Throwable $errorAsException) { // Ignore this re-throw } } @@ -220,9 +224,9 @@ public function handleFatalError(array $error = null) * Handles the given exception by capturing it through the Raven client and * then forwarding it to another handler. * - * @param \Exception|\Throwable $exception The exception to handle + * @param \Throwable $exception The exception to handle * - * @throws \Exception|\Throwable + * @throws \Throwable * * @internal */ @@ -320,9 +324,9 @@ protected function cleanBacktraceFromErrorHandlerFrames($backtrace, $file, $line * Handles the given exception. This method can be overridden to customize * the logging of an exception. * - * @param \Exception|\Throwable $exception The exception to handle + * @param \Throwable $exception The exception to handle * - * @throws \Exception|\Throwable + * @throws \Throwable */ abstract protected function doHandleException($exception); } diff --git a/src/BreadcrumbErrorHandler.php b/src/BreadcrumbErrorHandler.php deleted file mode 100644 index 8dcd25706..000000000 --- a/src/BreadcrumbErrorHandler.php +++ /dev/null @@ -1,93 +0,0 @@ - - */ -class BreadcrumbErrorHandler extends AbstractErrorHandler -{ - /** - * Registers this error handler by associating its instance with the given - * Raven client. - * - * @param ClientInterface $client The Raven client - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler - * - * @return BreadcrumbErrorHandler - */ - public static function register(ClientInterface $client, $reservedMemorySize = 10240) - { - return new self($client, $reservedMemorySize); - } - - /** - * {@inheritdoc} - */ - public function doHandleException($exception) - { - if (!$exception instanceof \ErrorException) { - return; - } - - $this->client->addBreadcrumb(new Breadcrumb( - $this->getSeverityFromErrorException($exception), - Breadcrumb::TYPE_ERROR, - 'error_reporting', - $exception->getMessage(), - [ - 'code' => $exception->getCode(), - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - ] - )); - } - - /** - * Maps the severity of the error to one of the levels supported by the - * breadcrumbs. - * - * @param \ErrorException $exception The exception - * - * @return string - */ - private function getSeverityFromErrorException(\ErrorException $exception): string - { - switch ($exception->getSeverity()) { - case E_DEPRECATED: - case E_USER_DEPRECATED: - case E_WARNING: - case E_USER_WARNING: - case E_RECOVERABLE_ERROR: - return Breadcrumb::LEVEL_WARNING; - case E_ERROR: - case E_PARSE: - case E_CORE_ERROR: - case E_CORE_WARNING: - case E_COMPILE_ERROR: - case E_COMPILE_WARNING: - return Breadcrumb::LEVEL_CRITICAL; - case E_USER_ERROR: - return Breadcrumb::LEVEL_ERROR; - case E_NOTICE: - case E_USER_NOTICE: - case E_STRICT: - return Breadcrumb::LEVEL_INFO; - default: - return Breadcrumb::LEVEL_ERROR; - } - } -} diff --git a/src/Breadcrumbs/Breadcrumb.php b/src/Breadcrumbs/Breadcrumb.php index 7ecbd87e6..9e1f68565 100644 --- a/src/Breadcrumbs/Breadcrumb.php +++ b/src/Breadcrumbs/Breadcrumb.php @@ -146,6 +146,41 @@ public static function create($level, $type, $category, $message = null, array $ return new static($level, $type, $category, $message, $metadata); } + /** + * Maps the severity of the error to one of the levels supported by the + * breadcrumbs. + * + * @param \ErrorException $exception The exception + * + * @return string + */ + public static function levelFromErrorException(\ErrorException $exception): string + { + switch ($exception->getSeverity()) { + case E_DEPRECATED: + case E_USER_DEPRECATED: + case E_WARNING: + case E_USER_WARNING: + case E_RECOVERABLE_ERROR: + return self::LEVEL_WARNING; + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + return self::LEVEL_CRITICAL; + case E_USER_ERROR: + return self::LEVEL_ERROR; + case E_NOTICE: + case E_USER_NOTICE: + case E_STRICT: + return self::LEVEL_INFO; + default: + return self::LEVEL_ERROR; + } + } + /** * Gets the breadcrumb type. * diff --git a/src/Client.php b/src/Client.php index 4e20412e8..c5ad3ae25 100644 --- a/src/Client.php +++ b/src/Client.php @@ -12,12 +12,8 @@ namespace Sentry; use Sentry\Breadcrumbs\Breadcrumb; -use Sentry\Context\Context; -use Sentry\Context\RuntimeContext; -use Sentry\Context\ServerOsContext; -use Sentry\Context\TagsContext; -use Sentry\Context\UserContext; -use Sentry\Middleware\MiddlewareStack; +use Sentry\Integration\Handler; +use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; use Sentry\Transport\TransportInterface; use Zend\Diactoros\ServerRequestFactory; @@ -32,57 +28,33 @@ class Client implements ClientInterface /** * The version of the library. */ - const VERSION = '2.0.x-dev'; + public const VERSION = '2.0.x-dev'; /** * The version of the protocol to communicate with the Sentry server. */ - const PROTOCOL_VERSION = '6'; + public const PROTOCOL_VERSION = '7'; /** - * This constant defines the client's user-agent string. - */ - const USER_AGENT = 'sentry-php/' . self::VERSION; - - /** - * This constant defines the maximum length of the message captured by the - * message SDK interface. - */ - const MESSAGE_MAX_LENGTH_LIMIT = 1024; - - /** - * Debug log levels. - */ - const LEVEL_DEBUG = 'debug'; - const LEVEL_INFO = 'info'; - const LEVEL_WARNING = 'warning'; - const LEVEL_ERROR = 'error'; - const LEVEL_FATAL = 'fatal'; - - /** - * @var string[]|null - */ - public $severityMap; - - /** - * @var Serializer The serializer + * The identifier of the SDK. */ - private $serializer; + public const SDK_IDENTIFIER = 'sentry.php'; /** - * @var ReprSerializer The representation serializer + * This constant defines the client's user-agent string. */ - private $representationSerializer; + public const USER_AGENT = self:: SDK_IDENTIFIER . '/' . self::VERSION; /** - * @var Configuration The client configuration + * This constant defines the maximum length of the message captured by the + * message SDK interface. */ - private $config; + public const MESSAGE_MAX_LENGTH_LIMIT = 1024; /** - * @var TransactionStack The transaction stack + * @var Options The client options */ - private $transactionStack; + private $options; /** * @var TransportInterface The transport @@ -90,87 +62,61 @@ class Client implements ClientInterface private $transport; /** - * @var TagsContext The tags context - */ - private $tagsContext; - - /** - * @var UserContext The user context - */ - private $userContext; - - /** - * @var Context The extra context - */ - private $extraContext; - - /** - * @var RuntimeContext The runtime context + * @var IntegrationInterface[] The stack of integrations */ - private $runtimeContext; + private $integrations; /** - * @var ServerOsContext The server OS context - */ - private $serverOsContext; - - /** - * @var MiddlewareStack The stack of middlewares to compose an event to send + * @var Serializer The serializer */ - private $middlewareStack; + private $serializer; /** - * @var Event The last event that was captured + * @var ReprSerializer The representation serializer */ - private $lastEvent; + private $representationSerializer; /** * Constructor. * - * @param Configuration $config The client configuration - * @param TransportInterface $transport The transport + * @param Options $options The client configuration + * @param TransportInterface $transport The transport + * @param IntegrationInterface[] $integrations The integrations used by the client */ - public function __construct(Configuration $config, TransportInterface $transport) + public function __construct(Options $options, TransportInterface $transport, array $integrations = []) { - $this->config = $config; + $this->options = $options; $this->transport = $transport; - $this->tagsContext = new TagsContext(); - $this->userContext = new UserContext(); - $this->extraContext = new Context(); - $this->runtimeContext = new RuntimeContext(); - $this->serverOsContext = new ServerOsContext(); - $this->transactionStack = new TransactionStack(); - $this->serializer = new Serializer($this->config->getMbDetectOrder()); - $this->representationSerializer = new ReprSerializer($this->config->getMbDetectOrder()); - $this->middlewareStack = new MiddlewareStack(function (Event $event) { - return $event; - }); - - $request = ServerRequestFactory::fromGlobals(); - $serverParams = $request->getServerParams(); - - if (isset($serverParams['PATH_INFO'])) { - $this->transactionStack->push($serverParams['PATH_INFO']); + $this->integrations = Handler::setupIntegrations($integrations); + $this->serializer = new Serializer($this->options->getMbDetectOrder()); + $this->representationSerializer = new ReprSerializer($this->options->getMbDetectOrder()); + if ($this->options->getSerializeAllObjects()) { + $this->serializer->setAllObjectSerialize($this->options->getSerializeAllObjects()); + $this->representationSerializer->setAllObjectSerialize($this->options->getSerializeAllObjects()); } + } - if ($this->config->getSerializeAllObjects()) { - $this->setAllObjectSerialize(true); - } + /** + * {@inheritdoc} + */ + public function getOptions(): Options + { + return $this->options; } /** * {@inheritdoc} */ - public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null) + public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null): void { - $beforeBreadcrumbCallback = $this->config->getBeforeBreadcrumbCallback(); - $maxBreadcrumbs = $this->config->getMaxBreadcrumbs(); + $beforeBreadcrumbCallback = $this->options->getBeforeBreadcrumbCallback(); + $maxBreadcrumbs = $this->options->getMaxBreadcrumbs(); if ($maxBreadcrumbs <= 0) { return; } - $breadcrumb = $beforeBreadcrumbCallback($breadcrumb); + $breadcrumb = \call_user_func($beforeBreadcrumbCallback, $breadcrumb); if (null !== $breadcrumb && null !== $scope) { $scope->addBreadcrumb($breadcrumb, $maxBreadcrumbs); @@ -180,101 +126,40 @@ public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null) /** * {@inheritdoc} */ - public function getConfig() - { - return $this->config; - } - - /** - * {@inheritdoc} - */ - public function getTransactionStack() - { - return $this->transactionStack; - } - - /** - * {@inheritdoc} - */ - public function addMiddleware(callable $middleware, $priority = 0) - { - $this->middlewareStack->addMiddleware($middleware, $priority); - } - - /** - * {@inheritdoc} - */ - public function removeMiddleware(callable $middleware) - { - $this->middlewareStack->removeMiddleware($middleware); - } - - /** - * {@inheritdoc} - */ - public function setAllObjectSerialize($value) - { - $this->serializer->setAllObjectSerialize($value); - $this->representationSerializer->setAllObjectSerialize($value); - } - - /** - * {@inheritdoc} - */ - public function getRepresentationSerializer() - { - return $this->representationSerializer; - } - - /** - * {@inheritdoc} - */ - public function setRepresentationSerializer(ReprSerializer $representationSerializer) + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string { - $this->representationSerializer = $representationSerializer; - } - - /** - * {@inheritdoc} - */ - public function getSerializer() - { - return $this->serializer; - } + $payload['message'] = $message; + $payload['level'] = $level; - /** - * {@inheritdoc} - */ - public function setSerializer(Serializer $serializer) - { - $this->serializer = $serializer; + return $this->captureEvent($payload, $scope); } /** * {@inheritdoc} */ - public function captureMessage(string $message, array $params = [], array $payload = []) + public function captureException(\Throwable $exception, ?Scope $scope = null): ?string { - $payload['message'] = $message; - $payload['message_params'] = $params; + $payload['exception'] = $exception; - return $this->capture($payload); + return $this->captureEvent($payload, $scope); } /** * {@inheritdoc} */ - public function captureException(\Throwable $exception, array $payload = []) + public function captureEvent(array $payload, ?Scope $scope = null): ?string { - $payload['exception'] = $exception; + if ($event = $this->prepareEvent($payload, $scope)) { + return $this->send($event); + } - return $this->capture($payload); + return null; } /** * {@inheritdoc} */ - public function captureLastError(array $payload = []) + public function captureLastError(?Scope $scope = null): ?string { $error = error_get_last(); @@ -284,158 +169,122 @@ public function captureLastError(array $payload = []) $exception = new \ErrorException(@$error['message'], 0, @$error['type'], @$error['file'], @$error['line']); - return $this->captureException($exception, $payload); + return $this->captureException($exception, $scope); } /** * {@inheritdoc} */ - public function getLastEvent() + public function getIntegration(string $className): ?IntegrationInterface { - return $this->lastEvent; + return $this->integrations[$className] ?? null; } /** - * {@inheritdoc} + * Sends the given event to the Sentry server. + * + * @param Event $event The event to send + * + * @return null|string */ - public function getLastEventId() + private function send(Event $event): ?string { - @trigger_error(sprintf('The %s() method is deprecated since version 2.0. Use getLastEvent() instead.', __METHOD__), E_USER_DEPRECATED); - - if (null === $this->lastEvent) { - return null; - } - - return str_replace('-', '', $this->lastEvent->getId()->toString()); + return $this->transport->send($event); } /** - * {@inheritdoc} + * Assembles an event and prepares it to be sent of to Sentry. + * + * @param array $payload the payload that will be converted to an Event + * @param null|Scope $scope optional scope which enriches the Event + * + * @return null|Event returns ready to send Event, however depending on options it can be discarded */ - public function capture(array $payload) + private function prepareEvent(array $payload, ?Scope $scope = null): ?Event { - $event = new Event($this->config); + $sampleRate = $this->getOptions()->getSampleRate(); + if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { + return null; + } + + $event = new Event(); + $event->setServerName($this->getOptions()->getServerName()); + $event->setRelease($this->getOptions()->getRelease()); + $event->setEnvironment($this->getOptions()->getCurrentEnvironment()); + $event->getTagsContext()->merge($this->getOptions()->getTags()); if (isset($payload['transaction'])) { $event->setTransaction($payload['transaction']); } else { - $event->setTransaction($this->transactionStack->peek()); + $request = ServerRequestFactory::fromGlobals(); + $serverParams = $request->getServerParams(); + + if (isset($serverParams['PATH_INFO'])) { + $event->setTransaction($serverParams['PATH_INFO']); + } } if (isset($payload['logger'])) { $event->setLogger($payload['logger']); } - $event = $this->middlewareStack->executeStack( - $event, - isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null, - isset($payload['exception']) ? $payload['exception'] : null, - $payload - ); + $message = $payload['message'] ?? null; + $messageParams = $payload['message_params'] ?? []; - $this->send($event); - - $this->lastEvent = $event; - - return str_replace('-', '', $event->getId()->toString()); - } - - /** - * {@inheritdoc} - */ - public function send(Event $event) - { - if (!$this->config->shouldCapture($event)) { - return; + if (null !== $message) { + $event->setMessage(substr($message, 0, self::MESSAGE_MAX_LENGTH_LIMIT), $messageParams); } - // should this event be sampled? - if (mt_rand(1, 100) / 100.0 > $this->config->getSampleRate()) { - return; + if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { + $this->addThrowableToEvent($event, $payload['exception']); } - $this->transport->send($event); - } - - /** - * {@inheritdoc} - */ - public function translateSeverity(int $severity) - { - if (\is_array($this->severityMap) && isset($this->severityMap[$severity])) { - return $this->severityMap[$severity]; - } - - switch ($severity) { - case E_DEPRECATED: - case E_USER_DEPRECATED: - case E_WARNING: - case E_USER_WARNING: - case E_RECOVERABLE_ERROR: - return self::LEVEL_WARNING; - case E_ERROR: - case E_PARSE: - case E_CORE_ERROR: - case E_CORE_WARNING: - case E_COMPILE_ERROR: - case E_COMPILE_WARNING: - return self::LEVEL_FATAL; - case E_USER_ERROR: - return self::LEVEL_ERROR; - case E_NOTICE: - case E_USER_NOTICE: - case E_STRICT: - return self::LEVEL_INFO; - default: - return self::LEVEL_ERROR; + if (null !== $scope) { + $event = $scope->applyToEvent($event, $payload); } - } - - /** - * {@inheritdoc} - */ - public function registerSeverityMap($map) - { - $this->severityMap = $map; - } - /** - * {@inheritdoc} - */ - public function getUserContext() - { - return $this->userContext; + return \call_user_func($this->options->getBeforeSendCallback(), $event); } /** - * {@inheritdoc} - */ - public function getTagsContext() - { - return $this->tagsContext; - } - - /** - * {@inheritdoc} - */ - public function getExtraContext() - { - return $this->extraContext; - } - - /** - * {@inheritdoc} + * Stores the given exception in the passed event. + * + * @param Event $event The event that will be enriched with the exception + * @param \Throwable $exception The exception that will be processed and added to the event */ - public function getRuntimeContext() + private function addThrowableToEvent(Event $event, \Throwable $exception): void { - return $this->runtimeContext; - } + if ($exception instanceof \ErrorException) { + $event->setLevel(Severity::fromError($exception->getSeverity())); + } - /** - * {@inheritdoc} - */ - public function getServerOsContext() - { - return $this->serverOsContext; + $exceptions = []; + $currentException = $exception; + + do { + if ($this->getOptions()->isExcludedException($currentException)) { + continue; + } + + $data = [ + 'type' => \get_class($currentException), + 'value' => $this->serializer->serialize($currentException->getMessage()), + ]; + + if ($this->getOptions()->getAutoLogStacks()) { + $data['stacktrace'] = Stacktrace::createFromBacktrace( + $this->getOptions(), + $this->serializer, + $this->representationSerializer, + $currentException->getTrace(), + $currentException->getFile(), + $currentException->getLine() + ); + } + + $exceptions[] = $data; + } while ($currentException = $currentException->getPrevious()); + + $event->setExceptions($exceptions); } } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 80c635453..9a321ee8a 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -24,18 +24,10 @@ use Http\Discovery\UriFactoryDiscovery; use Http\Message\MessageFactory; use Http\Message\UriFactory; -use Sentry\Context\Context; use Sentry\HttpClient\Authentication\SentryAuth; -use Sentry\Middleware\ContextInterfaceMiddleware; -use Sentry\Middleware\ExceptionInterfaceMiddleware; -use Sentry\Middleware\MessageInterfaceMiddleware; -use Sentry\Middleware\RemoveHttpBodyMiddleware; -use Sentry\Middleware\RequestInterfaceMiddleware; -use Sentry\Middleware\SanitizeCookiesMiddleware; -use Sentry\Middleware\SanitizeDataMiddleware; -use Sentry\Middleware\SanitizeHttpHeadersMiddleware; -use Sentry\Middleware\SanitizerMiddleware; -use Sentry\Middleware\UserInterfaceMiddleware; +use Sentry\Integration\ErrorHandlerIntegration; +use Sentry\Integration\IntegrationInterface; +use Sentry\Integration\RequestIntegration; use Sentry\Transport\HttpTransport; use Sentry\Transport\NullTransport; use Sentry\Transport\TransportInterface; @@ -84,9 +76,9 @@ final class ClientBuilder implements ClientBuilderInterface { /** - * @var Configuration The client configuration + * @var Options The client options */ - private $configuration; + private $options; /** * @var UriFactory|null The PSR-7 URI factory @@ -114,9 +106,9 @@ final class ClientBuilder implements ClientBuilderInterface private $httpClientPlugins = []; /** - * @var array List of middlewares and their priorities + * @var IntegrationInterface[] List of default integrations */ - private $middlewares = []; + private $integrations = []; /** * Class constructor. @@ -125,7 +117,16 @@ final class ClientBuilder implements ClientBuilderInterface */ public function __construct(array $options = []) { - $this->configuration = new Configuration($options); + $this->options = new Options($options); + + if (null === $this->options->getIntegrations()) { + $this->integrations = []; + } else { + $this->integrations = \array_merge([ + new ErrorHandlerIntegration(), + new RequestIntegration(), + ], $this->options->getIntegrations()); + } } /** @@ -202,40 +203,6 @@ public function removeHttpClientPlugin($className) return $this; } - /** - * {@inheritdoc} - */ - public function addMiddleware(callable $middleware, $priority = 0) - { - $this->middlewares[] = [$middleware, $priority]; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function removeMiddleware(callable $middleware) - { - foreach ($this->middlewares as $key => $value) { - if ($value[0] !== $middleware) { - continue; - } - - unset($this->middlewares[$key]); - } - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getMiddlewares() - { - return $this->middlewares; - } - /** * {@inheritdoc} */ @@ -244,33 +211,15 @@ public function getClient(): ClientInterface $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); - $this->transport = $this->createTransportInstance(); - - $client = new Client($this->configuration, $this->transport); - $client->addMiddleware(new SanitizerMiddleware($client->getSerializer()), -255); - $client->addMiddleware(new SanitizeDataMiddleware(), -200); - $client->addMiddleware(new SanitizeCookiesMiddleware(), -200); - $client->addMiddleware(new RemoveHttpBodyMiddleware(), -200); - $client->addMiddleware(new SanitizeHttpHeadersMiddleware(), -200); - $client->addMiddleware(new MessageInterfaceMiddleware()); - $client->addMiddleware(new RequestInterfaceMiddleware()); - $client->addMiddleware(new UserInterfaceMiddleware()); - $client->addMiddleware(new ContextInterfaceMiddleware($client->getTagsContext(), Context::CONTEXT_TAGS)); - $client->addMiddleware(new ContextInterfaceMiddleware($client->getUserContext(), Context::CONTEXT_USER)); - $client->addMiddleware(new ContextInterfaceMiddleware($client->getExtraContext(), Context::CONTEXT_EXTRA)); - $client->addMiddleware(new ContextInterfaceMiddleware($client->getRuntimeContext(), Context::CONTEXT_RUNTIME)); - $client->addMiddleware(new ContextInterfaceMiddleware($client->getServerOsContext(), Context::CONTEXT_SERVER_OS)); - $client->addMiddleware(new ExceptionInterfaceMiddleware($client)); - - foreach ($this->middlewares as $middleware) { - $client->addMiddleware($middleware[0], $middleware[1]); - } + $this->transport = $this->transport ?? $this->createTransportInstance(); + + $client = new Client($this->options, $this->transport, $this->integrations); return $client; } /** - * This method forwards all methods calls to the configuration object. + * This method forwards all methods calls to the options object. * * @param string $name The name of the method being called * @param array $arguments Parameters passed to the $name'ed method @@ -281,11 +230,11 @@ public function getClient(): ClientInterface */ public function __call($name, $arguments) { - if (!method_exists($this->configuration, $name)) { + if (!method_exists($this->options, $name)) { throw new \BadMethodCallException(sprintf('The method named "%s" does not exists.', $name)); } - $this->configuration->$name(...$arguments); + $this->options->$name(...$arguments); return $this; } @@ -305,13 +254,13 @@ private function createHttpClientInstance() throw new \RuntimeException('The PSR-18 HTTP client must be set.'); } - if (null !== $this->configuration->getDsn()) { - $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->configuration->getDsn()))); + if (null !== $this->options->getDsn()) { + $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->options->getDsn()))); } $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => Client::USER_AGENT])); - $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuth($this->configuration))); - $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->configuration->getSendAttempts()])); + $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuth($this->options))); + $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->options->getSendAttempts()])); $this->addHttpClientPlugin(new ErrorPlugin()); return new PluginClient($this->httpClient, $this->httpClientPlugins); @@ -328,7 +277,7 @@ private function createTransportInstance() return $this->transport; } - if (null === $this->configuration->getDsn()) { + if (null === $this->options->getDsn()) { return new NullTransport(); } @@ -336,6 +285,6 @@ private function createTransportInstance() throw new \RuntimeException('The PSR-7 message factory must be set.'); } - return new HttpTransport($this->configuration, $this->createHttpClientInstance(), $this->messageFactory); + return new HttpTransport($this->options, $this->createHttpClientInstance(), $this->messageFactory); } } diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 54e1914cf..71abface3 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -87,35 +87,6 @@ public function addHttpClientPlugin(Plugin $plugin); */ public function removeHttpClientPlugin($className); - /** - * Adds a new middleware with the given priority to the stack. - * - * @param callable $middleware The middleware instance - * @param int $priority The priority. The higher this value, the - * earlier a processor will be executed in - * the chain (defaults to 0) - * - * @return $this - */ - public function addMiddleware(callable $middleware, $priority = 0); - - /** - * Removes the given middleware from the stack. - * - * @param callable $middleware The middleware instance - * - * @return $this - */ - public function removeMiddleware(callable $middleware); - - /** - * Gets the list of middlewares that will be added to the client at the - * given priority. - * - * @return array - */ - public function getMiddlewares(); - /** * Gets the instance of the client built using the configured options. * diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 70bcd7ca9..9bb853923 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -12,10 +12,7 @@ namespace Sentry; use Sentry\Breadcrumbs\Breadcrumb; -use Sentry\Context\Context; -use Sentry\Context\RuntimeContext; -use Sentry\Context\ServerOsContext; -use Sentry\Context\TagsContext; +use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; /** @@ -26,192 +23,66 @@ interface ClientInterface { /** - * Gets the configuration of the client. + * Returns the options of the client. * - * @return Configuration + * @return Options */ - public function getConfig(); - - /** - * Gets the transaction stack. - * - * @return TransactionStack - */ - public function getTransactionStack(); - - /** - * Adds a new middleware with the given priority to the stack. - * - * @param callable $middleware The middleware instance - * @param int $priority The priority. The higher this value, the - * earlier a processor will be executed in - * the chain (defaults to 0) - */ - public function addMiddleware(callable $middleware, $priority = 0); - - /** - * Removes the given middleware from the stack. - * - * @param callable $middleware The middleware instance - */ - public function removeMiddleware(callable $middleware); + public function getOptions(): Options; /** * Records the given breadcrumb. * * @param Breadcrumb $breadcrumb The breadcrumb instance - * @param Scope|null $scope an optional scope to store this breadcrumb in + * @param Scope|null $scope An optional scope to store this breadcrumb in */ - public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null); + public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null): void; /** * Logs a message. * - * @param string $message The message (primary description) for the event - * @param array $params Params to use when formatting the message - * @param array $payload Additional attributes to pass with this event + * @param string $message The message (primary description) for the event + * @param Severity $level The level of the message to be sent + * @param Scope|null $scope An optional scope keeping the state * - * @return string + * @return null|string */ - public function captureMessage(string $message, array $params = [], array $payload = []); + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string; /** * Logs an exception. * * @param \Throwable $exception The exception object - * @param array $payload Additional attributes to pass with this event + * @param Scope|null $scope An optional scope keeping the state * - * @return string + * @return null|string */ - public function captureException(\Throwable $exception, array $payload = []); + public function captureException(\Throwable $exception, ?Scope $scope = null): ?string; /** * Logs the most recent error (obtained with {@link error_get_last}). * - * @param array $payload Additional attributes to pass with this event + * @param Scope|null $scope An optional scope keeping the state * - * @return string|null + * @return null|string */ - public function captureLastError(array $payload = []); - - /** - * Gets the last event that was captured by the client. However, it could - * have been sent or still sit in the queue of pending events. - * - * @return Event - */ - public function getLastEvent(); - - /** - * Return the last captured event's ID or null if none available. - * - * @return string|null - * - * @deprecated since version 2.0, to be removed in 3.0. Use getLastEvent() instead. - */ - public function getLastEventId(); + public function captureLastError(?Scope $scope = null): ?string; /** * Captures a new event using the provided data. * - * @param array $payload The data of the event being captured + * @param array $payload The data of the event being captured + * @param Scope|null $scope An optional scope keeping the state * - * @return string + * @return null|string */ - public function capture(array $payload); + public function captureEvent(array $payload, ?Scope $scope = null): ?string; /** - * Sends the given event to the Sentry server. + * Returns the integration instance if it is installed on the Client. * - * @param Event $event The event to send - */ - public function send(Event $event); - - /** - * Translate a PHP Error constant into a Sentry log level group. - * - * @param int $severity PHP E_$x error constant - * - * @return string Sentry log level group - */ - public function translateSeverity(int $severity); - - /** - * Provide a map of PHP Error constants to Sentry logging groups to use instead - * of the defaults in translateSeverity(). - * - * @param string[] $map - */ - public function registerSeverityMap($map); - - /** - * Gets the user context. - * - * @return Context - */ - public function getUserContext(); - - /** - * Gets the tags context. - * - * @return TagsContext - */ - public function getTagsContext(); - - /** - * Gets the extra context. - * - * @return Context - */ - public function getExtraContext(); - - /** - * Gets the runtime context. - * - * @return RuntimeContext - */ - public function getRuntimeContext(); - - /** - * Gets the server OS context. - * - * @return ServerOsContext - */ - public function getServerOsContext(); - - /** - * Sets whether all the objects should be serialized by the representation - * serializer. - * - * @param bool $value Whether the serialization of all objects is enabled or not - */ - public function setAllObjectSerialize($value); - - /** - * Gets the representation serialier. - * - * @return ReprSerializer - */ - public function getRepresentationSerializer(); - - /** - * Sets the representation serializer. - * - * @param ReprSerializer $representationSerializer The serializer instance - */ - public function setRepresentationSerializer(ReprSerializer $representationSerializer); - - /** - * Gets the serializer. - * - * @return Serializer - */ - public function getSerializer(); - - /** - * Sets the serializer. + * @param string $className the classname of the integration * - * @param Serializer $serializer The serializer instance + * @return null|IntegrationInterface */ - public function setSerializer(Serializer $serializer); + public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/src/Context/UserContext.php b/src/Context/UserContext.php index 9657d8b89..1b6d36de5 100644 --- a/src/Context/UserContext.php +++ b/src/Context/UserContext.php @@ -78,4 +78,24 @@ public function setEmail(?string $email): void { $this->data['email'] = $email; } + + /** + * Gets the ip address of the user. + * + * @return null|string + */ + public function getIpAddress(): ?string + { + return $this->data['ip_address'] ?? null; + } + + /** + * Sets the ip address of the user. + * + * @param null|string $ipAddress The ip address + */ + public function setIpAddress(?string $ipAddress): void + { + $this->data['ip_address'] = $ipAddress; + } } diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 61ded24bc..e6e7ac946 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -25,14 +25,16 @@ class ErrorHandler extends AbstractErrorHandler * Registers this error handler by associating its instance with the given * Raven client. * - * @param ClientInterface $client The Raven client - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * @param callable $callback callback that will be called when exception is caught + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * + * @throws \Exception|\Throwable|\ErrorException * * @return ErrorHandler */ - public static function register(ClientInterface $client, $reservedMemorySize = 10240) + public static function register(callable $callback, $reservedMemorySize = 10240) { - return new self($client, $reservedMemorySize); + return new self($callback, $reservedMemorySize); } /** @@ -40,6 +42,6 @@ public static function register(ClientInterface $client, $reservedMemorySize = 1 */ protected function doHandleException($exception) { - $this->client->captureException($exception); + \call_user_func($this->callback, $exception); } } diff --git a/src/Event.php b/src/Event.php index fbb6a2eae..09a678352 100644 --- a/src/Event.php +++ b/src/Event.php @@ -11,6 +11,7 @@ namespace Sentry; +use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Sentry\Breadcrumbs\Breadcrumb; @@ -18,6 +19,7 @@ use Sentry\Context\RuntimeContext; use Sentry\Context\ServerOsContext; use Sentry\Context\TagsContext; +use Sentry\Context\UserContext; /** * This is the base class for classes containing event data. @@ -97,7 +99,7 @@ final class Event implements \JsonSerializable private $runtimeContext; /** - * @var Context The user context data + * @var UserContext The user context data */ private $userContext; @@ -122,9 +124,9 @@ final class Event implements \JsonSerializable private $breadcrumbs = []; /** - * @var array The exception + * @var array The exceptions */ - private $exception; + private $exceptions; /** * @var Stacktrace|null The stacktrace that generated this event @@ -132,21 +134,20 @@ final class Event implements \JsonSerializable private $stacktrace; /** - * Class constructor. + * Event constructor. * - * @param Configuration $config The client configuration + * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present + * @throws \InvalidArgumentException + * @throws \Exception */ - public function __construct(Configuration $config) + public function __construct() { $this->id = Uuid::uuid4(); $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); $this->level = Severity::error(); - $this->serverName = $config->getServerName(); - $this->release = $config->getRelease(); - $this->environment = $config->getCurrentEnvironment(); $this->serverOsContext = new ServerOsContext(); $this->runtimeContext = new RuntimeContext(); - $this->userContext = new Context(); + $this->userContext = new UserContext(); $this->extraContext = new Context(); $this->tagsContext = new TagsContext(); } @@ -154,11 +155,11 @@ public function __construct(Configuration $config) /** * Gets the UUID of this event. * - * @return UuidInterface + * @return string */ - public function getId() + public function getId(): string { - return $this->id; + return str_replace('-', '', $this->id->toString()); } /** @@ -166,7 +167,7 @@ public function getId() * * @return string */ - public function getTimestamp() + public function getTimestamp(): string { return $this->timestamp; } @@ -186,7 +187,7 @@ public function getLevel(): Severity * * @param Severity $level The severity */ - public function setLevel(Severity $level) + public function setLevel(Severity $level): void { $this->level = $level; } @@ -196,7 +197,7 @@ public function setLevel(Severity $level) * * @return string */ - public function getLogger() + public function getLogger(): string { return $this->logger; } @@ -206,7 +207,7 @@ public function getLogger() * * @param string $logger The logger name */ - public function setLogger($logger) + public function setLogger($logger): void { $this->logger = $logger; } @@ -368,7 +369,7 @@ public function getTagsContext() /** * Gets the user context. * - * @return Context + * @return UserContext */ public function getUserContext() { @@ -448,13 +449,13 @@ public function getBreadcrumbs() } /** - * Adds a new breadcrumb to the event. + * Set new breadcrumbs to the event. * - * @param Breadcrumb $breadcrumb The breadcrumb + * @param Breadcrumb[] $breadcrumbs The breadcrumb array */ - public function setBreadcrumb(Breadcrumb $breadcrumb) + public function setBreadcrumb(array $breadcrumbs) { - $this->breadcrumbs[] = $breadcrumb; + $this->breadcrumbs = $breadcrumbs; } /** @@ -462,19 +463,19 @@ public function setBreadcrumb(Breadcrumb $breadcrumb) * * @return array */ - public function getException() + public function getExceptions() { - return $this->exception; + return $this->exceptions; } /** * Sets the exception. * - * @param array $exception The exception + * @param array $exceptions The exception */ - public function setException(array $exception) + public function setExceptions(array $exceptions) { - $this->exception = $exception; + $this->exceptions = $exceptions; } /** @@ -502,7 +503,7 @@ public function setStacktrace(Stacktrace $stacktrace) * * @return array */ - public function toArray() + public function toArray(): array { $data = [ 'event_id' => str_replace('-', '', $this->id->toString()), @@ -510,7 +511,7 @@ public function toArray() 'level' => (string) $this->level, 'platform' => 'php', 'sdk' => [ - 'name' => 'sentry-php', + 'name' => Client::SDK_IDENTIFIER, 'version' => Client::VERSION, ], ]; @@ -564,11 +565,13 @@ public function toArray() } if (!empty($this->breadcrumbs)) { - $data['breadcrumbs'] = $this->breadcrumbs; + $data['breadcrumbs']['values'] = $this->breadcrumbs; } - if (null !== $this->exception && isset($this->exception['values'])) { - foreach ($this->exception['values'] as $exception) { + if (null !== $this->exceptions) { + $reversedException = array_reverse($this->exceptions); + + foreach ($reversedException as $exception) { $exceptionData = [ 'type' => $exception['type'], 'value' => $exception['value'], @@ -612,7 +615,7 @@ public function toArray() /** * {@inheritdoc} */ - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } diff --git a/src/HttpClient/Authentication/SentryAuth.php b/src/HttpClient/Authentication/SentryAuth.php index 4d68ae9fb..53fdfeee0 100644 --- a/src/HttpClient/Authentication/SentryAuth.php +++ b/src/HttpClient/Authentication/SentryAuth.php @@ -14,7 +14,7 @@ use Http\Message\Authentication; use Psr\Http\Message\RequestInterface; use Sentry\Client; -use Sentry\Configuration; +use Sentry\Options; /** * This authentication method sends the requests along with a X-Sentry-Auth @@ -25,16 +25,16 @@ final class SentryAuth implements Authentication { /** - * @var Configuration The Raven client configuration + * @var Options The Raven client configuration */ private $configuration; /** * Constructor. * - * @param Configuration $configuration The Raven client configuration + * @param Options $configuration The Raven client configuration */ - public function __construct(Configuration $configuration) + public function __construct(Options $configuration) { $this->configuration = $configuration; } diff --git a/src/HttpClient/Encoding/Base64EncodingStream.php b/src/HttpClient/Encoding/Base64EncodingStream.php deleted file mode 100644 index 51bd7d778..000000000 --- a/src/HttpClient/Encoding/Base64EncodingStream.php +++ /dev/null @@ -1,67 +0,0 @@ - - */ -final class Base64EncodingStream extends FilteredStream -{ - /** - * {@inheritdoc} - */ - public function __construct(StreamInterface $stream, $readFilterOptions = null, $writeFilterOptions = null) - { - // $readFilterOptions and $writeFilterOptions arguments are overridden - // because otherwise an error stating that the filter parameter is - // invalid is thrown when appending the filter to the stream - parent::__construct($stream, [], []); - } - - /** - * {@inheritdoc} - */ - public function getSize() - { - $inputSize = $this->stream->getSize(); - - if (null === $inputSize) { - return null; - } - - // See https://stackoverflow.com/questions/1533113/calculate-the-size-to-a-base-64-encoded-message - $adjustment = (($inputSize % 3) ? (3 - ($inputSize % 3)) : 0); - - return (int) ((($inputSize + $adjustment) / 3) * 4); - } - - /** - * {@inheritdoc} - */ - protected function readFilter() - { - return 'convert.base64-encode'; - } - - /** - * {@inheritdoc} - */ - protected function writeFilter() - { - return 'convert.base64-decode'; - } -} diff --git a/src/Integration/ErrorHandlerIntegration.php b/src/Integration/ErrorHandlerIntegration.php new file mode 100644 index 000000000..fd2a073ab --- /dev/null +++ b/src/Integration/ErrorHandlerIntegration.php @@ -0,0 +1,60 @@ +getIntegration(self::class); + + if ($self instanceof self) { + $self->addBreadcrumb($exception); + $self->captureException($exception); + } + }); + } + + /** + * Captures the exception and sends it to Sentry. + * + * @param \Throwable $exception The exception that will be captured by the current client + */ + private function captureException(\Throwable $exception): void + { + Hub::getCurrent()->captureException($exception); + } + + /** + * Adds a breadcrumb of the error. + * + * @param \Throwable $exception The exception used to create a breadcrumb + */ + private function addBreadcrumb(\Throwable $exception): void + { + if ($exception instanceof \ErrorException) { + /* @var \ErrorException $exception */ + Hub::getCurrent()->addBreadcrumb(new Breadcrumb( + Breadcrumb::levelFromErrorException($exception), + Breadcrumb::TYPE_ERROR, + 'error_reporting', + $exception->getMessage(), + [ + 'code' => $exception->getCode(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + ] + )); + } + } +} diff --git a/src/Integration/Handler.php b/src/Integration/Handler.php new file mode 100644 index 000000000..560b7e6cd --- /dev/null +++ b/src/Integration/Handler.php @@ -0,0 +1,45 @@ + + */ + public static function setupIntegrations(array $integrations): array + { + $integrationIndex = []; + + foreach ($integrations as $integration) { + /* @var IntegrationInterface $integration */ + $class = \get_class($integration); + + if (!$integration instanceof IntegrationInterface) { + throw new \InvalidArgumentException(sprintf('Expecting integration implementing %s interface, got %s', IntegrationInterface::class, $class)); + } + + if (!isset(self::$integrations[$class])) { + self::$integrations[$class] = true; + + $integration->setupOnce(); + } + + $integrationIndex[$class] = $integration; + } + + return $integrationIndex; + } +} diff --git a/src/Integration/IntegrationInterface.php b/src/Integration/IntegrationInterface.php new file mode 100644 index 000000000..d0dc95ec4 --- /dev/null +++ b/src/Integration/IntegrationInterface.php @@ -0,0 +1,17 @@ + + */ +final class ModulesIntegration implements IntegrationInterface +{ + /** + * @var Options The client option + */ + private $options; + + /** + * @var array The list of installed vendors + */ + private static $loadedModules = []; + + /** + * Constructor. + * + * @param Options $options The Client options + */ + public function __construct(Options $options) + { + if (!class_exists(Composer::class)) { + throw new \LogicException('You need the "composer/composer" package in order to use this integration.'); + } + + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function setupOnce(): void + { + Scope::addGlobalEventProcessor(function (Event $event) { + $self = Hub::getCurrent()->getIntegration(self::class); + + if ($self instanceof self) { + self::applyToEvent($self, $event); + } + + return $event; + }); + } + + /** + * @param ModulesIntegration $self The instance of this integration + * @param Event $event The event that will be enriched with the modules + */ + public static function applyToEvent(self $self, Event $event): void + { + $composerFilePath = $self->options->getProjectRoot() . \DIRECTORY_SEPARATOR . 'composer.json'; + + if (file_exists($composerFilePath) && 0 == \count(self::$loadedModules)) { + $composer = Factory::create(new NullIO(), $composerFilePath, true); + $locker = $composer->getLocker(); + + if ($locker->isLocked()) { + foreach ($locker->getLockedRepository()->getPackages() as $package) { + self::$loadedModules[$package->getName()] = $package->getVersion(); + } + } + } + + $event->setModules(self::$loadedModules); + } +} diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php new file mode 100644 index 000000000..89a4225c8 --- /dev/null +++ b/src/Integration/RequestIntegration.php @@ -0,0 +1,77 @@ + + */ +final class RequestIntegration implements IntegrationInterface +{ + /** + * {@inheritdoc} + */ + public function setupOnce(): void + { + Scope::addGlobalEventProcessor(function (Event $event) { + $self = Hub::getCurrent()->getIntegration(self::class); + + if (!$self instanceof self) { + return $event; + } + + self::applyToEvent($event); + + return $event; + }); + } + + /** + * @param Event $event The event that will be enriched with a request + * @param null|ServerRequestInterface $request The Request that will be processed and added to the event + */ + public static function applyToEvent(Event $event, ?ServerRequestInterface $request = null): void + { + if (null === $request) { + /** @var null|ServerRequestInterface $request */ + $request = isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null; + } + + if (null === $request) { + return; + } + + $requestData = [ + 'url' => (string) $request->getUri(), + 'method' => $request->getMethod(), + 'headers' => $request->getHeaders(), + 'cookies' => $request->getCookieParams(), + ]; + + if ($request->getUri()->getQuery()) { + $requestData['query_string'] = $request->getUri()->getQuery(); + } + + if ($request->hasHeader('REMOTE_ADDR')) { + $requestData['env']['REMOTE_ADDR'] = $request->getHeaderLine('REMOTE_ADDR'); + } + + $event->setRequest($requestData); + + $userContext = $event->getUserContext(); + + if (null === $userContext->getIpAddress() && $request->hasHeader('REMOTE_ADDR')) { + $userContext->setIpAddress($request->getHeaderLine('REMOTE_ADDR')); + } + } +} diff --git a/src/Middleware/ContextInterfaceMiddleware.php b/src/Middleware/ContextInterfaceMiddleware.php deleted file mode 100644 index 0a22ad44b..000000000 --- a/src/Middleware/ContextInterfaceMiddleware.php +++ /dev/null @@ -1,86 +0,0 @@ - - */ -final class ContextInterfaceMiddleware -{ - /** - * @var Context The context - */ - private $context; - - /** - * @var string The alias name of the context - */ - private $contextName; - - /** - * Constructor. - * - * @param Context $context The context - * @param string $contextName The alias name of the context - */ - public function __construct(Context $context, $contextName) - { - $this->context = $context; - $this->contextName = $contextName; - } - - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $contextData = isset($payload[$this->contextName . '_context']) ? $payload[$this->contextName . '_context'] : []; - $contextData = array_merge($this->context->toArray(), $contextData); - - switch ($this->contextName) { - case Context::CONTEXT_USER: - $event->getUserContext()->setData($contextData); - break; - case Context::CONTEXT_RUNTIME: - $event->getRuntimeContext()->setData($contextData); - break; - case Context::CONTEXT_TAGS: - $event->getTagsContext()->setData($contextData); - break; - case Context::CONTEXT_EXTRA: - $event->getExtraContext()->setData($contextData); - break; - case Context::CONTEXT_SERVER_OS: - $event->getServerOsContext()->setData($contextData); - break; - default: - throw new \RuntimeException(sprintf('The "%s" context is not supported.', $this->contextName)); - } - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/ExceptionInterfaceMiddleware.php b/src/Middleware/ExceptionInterfaceMiddleware.php deleted file mode 100644 index bcbbf9413..000000000 --- a/src/Middleware/ExceptionInterfaceMiddleware.php +++ /dev/null @@ -1,91 +0,0 @@ - - */ -final class ExceptionInterfaceMiddleware -{ - /** - * @var ClientInterface The Raven client - */ - private $client; - - /** - * Constructor. - * - * @param ClientInterface $client The Raven client - */ - public function __construct(ClientInterface $client) - { - $this->client = $client; - } - - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - if (isset($payload['level'])) { - $event->setLevel($payload['level']); - } elseif ($exception instanceof \ErrorException) { - $event->setLevel(new Severity($this->client->translateSeverity($exception->getSeverity()))); - } - - if (null !== $exception) { - $exceptions = []; - $currentException = $exception; - - do { - if ($this->client->getConfig()->isExcludedException($currentException)) { - continue; - } - - $data = [ - 'type' => \get_class($currentException), - 'value' => $this->client->getSerializer()->serialize($currentException->getMessage()), - ]; - - if ($this->client->getConfig()->getAutoLogStacks()) { - $data['stacktrace'] = Stacktrace::createFromBacktrace($this->client, $currentException->getTrace(), $currentException->getFile(), $currentException->getLine()); - } - - $exceptions[] = $data; - } while ($currentException = $currentException->getPrevious()); - - $exceptions = [ - 'values' => array_reverse($exceptions), - ]; - - $event->setException($exceptions); - } - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/MessageInterfaceMiddleware.php b/src/Middleware/MessageInterfaceMiddleware.php deleted file mode 100644 index e64d90a31..000000000 --- a/src/Middleware/MessageInterfaceMiddleware.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ -final class MessageInterfaceMiddleware -{ - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $message = isset($payload['message']) ? $payload['message'] : null; - $messageParams = isset($payload['message_params']) ? $payload['message_params'] : []; - - if (null !== $message) { - $event->setMessage(substr($message, 0, Client::MESSAGE_MAX_LENGTH_LIMIT), $messageParams); - } - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/MiddlewareStack.php b/src/Middleware/MiddlewareStack.php deleted file mode 100644 index 582a2180d..000000000 --- a/src/Middleware/MiddlewareStack.php +++ /dev/null @@ -1,171 +0,0 @@ - - */ -class MiddlewareStack -{ - /** - * @var callable The handler that will end the middleware call stack - */ - private $handler; - - /** - * @var array The list of middlewares - */ - private $stack = []; - - /** - * @var callable|null The tip of the middleware call stack - */ - private $middlewareStackTip; - - /** - * @var bool Whether the stack of middleware callables is locked - */ - private $stackLocked = false; - - /** - * Constructor. - * - * @param callable $handler The handler that will end the middleware call stack - */ - public function __construct(callable $handler) - { - $this->handler = $handler; - } - - /** - * Invokes the handler stack as a composed handler. - * - * @param Event $event The event being processed - * @param ServerRequestInterface|null $request The request, if available - * @param \Throwable|\Exception|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function executeStack(Event $event, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $handler = $this->resolve(); - - $this->stackLocked = true; - - $event = $handler($event, $request, $exception, $payload); - - $this->stackLocked = false; - - if (!$event instanceof Event) { - throw new \UnexpectedValueException(sprintf('Middleware must return an instance of the "%s" class.', Event::class)); - } - - return $event; - } - - /** - * Adds a new middleware with the given priority to the stack. - * - * @param callable $middleware The middleware instance - * @param int $priority The priority. The higher this value, the - * earlier a middleware will be executed in - * the chain (defaults to 0) - * - * @throws \RuntimeException If the method is called while the stack is dequeuing - */ - public function addMiddleware(callable $middleware, $priority = 0) - { - if ($this->stackLocked) { - throw new \RuntimeException('Middleware can\'t be added once the stack is dequeuing.'); - } - - $this->middlewareStackTip = null; - - $this->stack[$priority][] = $middleware; - } - - /** - * Removes the given middleware from the stack. - * - * @param callable $middleware The middleware instance - * - * @return bool True if the middleware was removed, false otherwise - * - * @throws \RuntimeException If the method is called while the stack is dequeuing - */ - public function removeMiddleware(callable $middleware) - { - if ($this->stackLocked) { - throw new \RuntimeException('Middleware can\'t be removed once the stack is dequeuing.'); - } - - $this->middlewareStackTip = null; - - $result = false; - - foreach ($this->stack as $priority => &$middlewares) { - foreach ($middlewares as $index => $value) { - if ($middleware !== $value) { - continue; - } - - array_splice($middlewares, $index, 1); - - $result = true; - } - } - - // Free the memory by breaking the reference - unset($middlewares); - - return $result; - } - - /** - * Resolves the stack of middleware callables into a chain where each middleware - * will call the next one in order of priority. - * - * @return callable - */ - private function resolve() - { - if (null === $this->middlewareStackTip) { - $prev = $this->handler; - - ksort($this->stack); - - if (!empty($this->stack)) { - foreach (array_merge(...$this->stack) as $middleware) { - $prev = function (Event $event, ServerRequestInterface $request = null, $exception = null, array $payload = []) use ($middleware, $prev) { - $event = $middleware($event, $prev, $request, $exception, $payload); - - if (!$event instanceof Event) { - throw new \UnexpectedValueException(sprintf('Middleware must return an instance of the "%s" class.', Event::class)); - } - - return $event; - }; - } - } - - $this->middlewareStackTip = $prev; - } - - return $this->middlewareStackTip; - } -} diff --git a/src/Middleware/ModulesMiddleware.php b/src/Middleware/ModulesMiddleware.php deleted file mode 100644 index 45f8c4a50..000000000 --- a/src/Middleware/ModulesMiddleware.php +++ /dev/null @@ -1,71 +0,0 @@ - - */ -final class ModulesMiddleware -{ - /** - * @var Configuration The Raven client configuration - */ - private $config; - - /** - * Constructor. - * - * @param Configuration $config The Raven client configuration - */ - public function __construct(Configuration $config) - { - if (!class_exists(Composer::class)) { - throw new \LogicException('You need the "composer/composer" package in order to use this middleware.'); - } - - $this->config = $config; - } - - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $composerFilePath = $this->config->getProjectRoot() . \DIRECTORY_SEPARATOR . 'composer.json'; - - if (file_exists($composerFilePath)) { - $composer = Factory::create(new NullIO(), $composerFilePath, true); - $locker = $composer->getLocker(); - - if ($locker->isLocked()) { - $modules = []; - - foreach ($locker->getLockedRepository()->getPackages() as $package) { - $modules[$package->getName()] = $package->getVersion(); - } - - $event->setModules($modules); - } - } - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/ProcessorMiddlewareInterface.php b/src/Middleware/ProcessorMiddlewareInterface.php deleted file mode 100644 index 5487a34db..000000000 --- a/src/Middleware/ProcessorMiddlewareInterface.php +++ /dev/null @@ -1,26 +0,0 @@ - - */ -interface ProcessorMiddlewareInterface -{ - /** - * This constant defines the mask string used to strip sensitive information. - */ - const STRING_MASK = '********'; -} diff --git a/src/Middleware/RemoveHttpBodyMiddleware.php b/src/Middleware/RemoveHttpBodyMiddleware.php deleted file mode 100644 index bb1f3b95b..000000000 --- a/src/Middleware/RemoveHttpBodyMiddleware.php +++ /dev/null @@ -1,49 +0,0 @@ - - */ -final class RemoveHttpBodyMiddleware implements ProcessorMiddlewareInterface -{ - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $requestData = $event->getRequest(); - - if (isset($requestData['method']) && \in_array(strtoupper($requestData['method']), ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { - $requestData['data'] = self::STRING_MASK; - } - - $event->setRequest($requestData); - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/RemoveStacktraceContextMiddleware.php b/src/Middleware/RemoveStacktraceContextMiddleware.php deleted file mode 100644 index dd06cc26b..000000000 --- a/src/Middleware/RemoveStacktraceContextMiddleware.php +++ /dev/null @@ -1,52 +0,0 @@ - - */ -final class RemoveStacktraceContextMiddleware implements ProcessorMiddlewareInterface -{ - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $stacktrace = $event->getStacktrace(); - - if (null !== $stacktrace) { - foreach ($stacktrace->getFrames() as $frame) { - $frame->setPreContext([]); - $frame->setContextLine(null); - $frame->setPostContext([]); - } - - $event->setStacktrace($stacktrace); - } - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/RequestInterfaceMiddleware.php b/src/Middleware/RequestInterfaceMiddleware.php deleted file mode 100644 index e5f581d44..000000000 --- a/src/Middleware/RequestInterfaceMiddleware.php +++ /dev/null @@ -1,61 +0,0 @@ - - */ -final class RequestInterfaceMiddleware -{ - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - if (null === $request) { - return $next($event, $request, $exception, $payload); - } - - $requestData = [ - 'url' => (string) $request->getUri(), - 'method' => $request->getMethod(), - 'headers' => $request->getHeaders(), - 'cookies' => $request->getCookieParams(), - ]; - - if ('' !== $request->getUri()->getQuery()) { - $requestData['query_string'] = $request->getUri()->getQuery(); - } - - if ($request->hasHeader('REMOTE_ADDR')) { - $requestData['env']['REMOTE_ADDR'] = $request->getHeaderLine('REMOTE_ADDR'); - } - - $event->setRequest($requestData); - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/SanitizeCookiesMiddleware.php b/src/Middleware/SanitizeCookiesMiddleware.php deleted file mode 100644 index 979242021..000000000 --- a/src/Middleware/SanitizeCookiesMiddleware.php +++ /dev/null @@ -1,107 +0,0 @@ - - */ -final class SanitizeCookiesMiddleware implements ProcessorMiddlewareInterface -{ - /** - * @var array The configuration options - */ - private $options; - - /** - * Class constructor. - * - * @param array $options An optional array of configuration options - */ - public function __construct(array $options = []) - { - $resolver = new OptionsResolver(); - - $this->configureOptions($resolver); - - $this->options = $resolver->resolve($options); - - if (!empty($this->options['only']) && !empty($this->options['except'])) { - throw new InvalidOptionsException('You can configure only one of "only" and "except" options.'); - } - } - - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $requestData = $event->getRequest(); - - if (isset($requestData['cookies'])) { - $cookiesToSanitize = array_keys($requestData['cookies']); - - if (!empty($this->options['only'])) { - $cookiesToSanitize = $this->options['only']; - } - - if (!empty($this->options['except'])) { - $cookiesToSanitize = array_diff($cookiesToSanitize, $this->options['except']); - } - - foreach ($requestData['cookies'] as $name => $value) { - if (!\in_array($name, $cookiesToSanitize)) { - continue; - } - - $requestData['cookies'][$name] = self::STRING_MASK; - } - } - - unset($requestData['headers']['cookie']); - - $event->setRequest($requestData); - - return $next($event, $request, $exception, $payload); - } - - /** - * Configures the options for this processor. - * - * @param OptionsResolver $resolver The resolver for the options - */ - private function configureOptions(OptionsResolver $resolver) - { - $resolver->setDefaults([ - 'only' => [], - 'except' => [], - ]); - - $resolver->setAllowedTypes('only', 'array'); - $resolver->setAllowedTypes('except', 'array'); - } -} diff --git a/src/Middleware/SanitizeDataMiddleware.php b/src/Middleware/SanitizeDataMiddleware.php deleted file mode 100644 index f0d7d3f33..000000000 --- a/src/Middleware/SanitizeDataMiddleware.php +++ /dev/null @@ -1,187 +0,0 @@ - - * @author Stefano Arlandini - */ -final class SanitizeDataMiddleware implements ProcessorMiddlewareInterface -{ - /** - * @var array The configuration options - */ - private $options; - - /** - * Class constructor. - * - * @param array $options An optional array of configuration options - */ - public function __construct(array $options = []) - { - $resolver = new OptionsResolver(); - - $this->configureOptions($resolver); - - $this->options = $resolver->resolve($options); - } - - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $exceptionData = $event->getException(); - $stacktrace = $event->getStacktrace(); - $requestData = $event->getRequest(); - $extraContext = $event->getExtraContext()->toArray(); - - if (!empty($exceptionData)) { - $event->setException($this->sanitizeException($exceptionData)); - } - - if ($stacktrace) { - $event->setStacktrace($this->sanitizeStacktrace($stacktrace)); - } - - if (!empty($requestData)) { - $event->setRequest($this->sanitizeHttp($requestData)); - } - - if (!empty($extraContext)) { - $this->sanitize($extraContext); - - $event->getExtraContext()->replaceData($extraContext); - } - - return $next($event, $request, $exception, $payload); - } - - /** - * Replace any array values with our mask if the field name or the value matches a respective regex. - * - * @param array $data Associative array to be sanitized - */ - private function sanitize(&$data) - { - foreach ($data as $key => &$item) { - if (preg_match($this->options['fields_re'], $key)) { - if (\is_array($item)) { - array_walk_recursive($item, function (&$value) { - $value = self::STRING_MASK; - }); - - break; - } - - $item = self::STRING_MASK; - } - - if (\is_array($item)) { - $this->sanitize($item); - - break; - } - - if (preg_match($this->options['values_re'], $item)) { - $item = self::STRING_MASK; - } - } - } - - private function sanitizeException(&$data) - { - foreach ($data['values'] as &$value) { - if (!isset($value['stacktrace'])) { - continue; - } - - $this->sanitizeStacktrace($value['stacktrace']); - } - - return $data; - } - - private function sanitizeHttp(&$data) - { - if (!empty($data['cookies']) && \is_array($data['cookies'])) { - $cookies = &$data['cookies']; - if (!empty($cookies[$this->options['session_cookie_name']])) { - $cookies[$this->options['session_cookie_name']] = self::STRING_MASK; - } - } - - if (!empty($data['data']) && \is_array($data['data'])) { - $this->sanitize($data['data']); - } - - return $data; - } - - /** - * @param Stacktrace $data - * - * @return Stacktrace - */ - private function sanitizeStacktrace($data) - { - foreach ($data->getFrames() as &$frame) { - if (empty($frame->getVars())) { - continue; - } - - $vars = $frame->getVars(); - - $this->sanitize($vars); - $frame->setVars($vars); - } - - return $data; - } - - /** - * Configures the options for this processor. - * - * @param OptionsResolver $resolver The resolver for the options - */ - private function configureOptions(OptionsResolver $resolver) - { - $resolver->setDefaults([ - 'fields_re' => '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', - 'values_re' => '/^(?:\d[ -]*?){13,19}$/', - 'session_cookie_name' => ini_get('session.name'), - ]); - - $resolver->setAllowedTypes('fields_re', 'string'); - $resolver->setAllowedTypes('values_re', 'string'); - $resolver->setAllowedTypes('session_cookie_name', 'string'); - } -} diff --git a/src/Middleware/SanitizeHttpHeadersMiddleware.php b/src/Middleware/SanitizeHttpHeadersMiddleware.php deleted file mode 100644 index 61156fa4d..000000000 --- a/src/Middleware/SanitizeHttpHeadersMiddleware.php +++ /dev/null @@ -1,85 +0,0 @@ - - */ -final class SanitizeHttpHeadersMiddleware implements ProcessorMiddlewareInterface -{ - /** - * @var array The configuration options - */ - private $options; - - /** - * Class constructor. - * - * @param array $options An optional array of configuration options - */ - public function __construct(array $options = []) - { - $resolver = new OptionsResolver(); - - $this->configureOptions($resolver); - - $this->options = $resolver->resolve($options); - } - - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $requestData = $event->getRequest(); - - if (isset($requestData['headers'])) { - foreach ($requestData['headers'] as $header => &$value) { - if (\in_array($header, $this->options['sanitize_http_headers'], true)) { - $value = self::STRING_MASK; - } - } - - // Break the reference and free some memory - unset($value); - - $event->setRequest($requestData); - } - - return $next($event, $request, $exception, $payload); - } - - /** - * {@inheritdoc} - */ - private function configureOptions(OptionsResolver $resolver) - { - $resolver->setDefault('sanitize_http_headers', ['Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN']); - - $resolver->setAllowedTypes('sanitize_http_headers', 'array'); - } -} diff --git a/src/Middleware/SanitizerMiddleware.php b/src/Middleware/SanitizerMiddleware.php deleted file mode 100644 index c5663951f..000000000 --- a/src/Middleware/SanitizerMiddleware.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ -final class SanitizerMiddleware -{ - /** - * @var Serializer The serializer instance - */ - private $serializer; - - /** - * Constructor. - * - * @param Serializer $serializer The serializer instance - */ - public function __construct(Serializer $serializer) - { - $this->serializer = $serializer; - } - - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - if (!empty($requestData = $event->getRequest())) { - /** @var array $serializedRequest */ - $serializedRequest = $this->serializer->serialize($requestData, 5); - - $event->setRequest($serializedRequest); - } - - $event->getUserContext()->replaceData($this->serializer->serialize($event->getUserContext()->toArray())); - $event->getRuntimeContext()->replaceData($this->serializer->serialize($event->getRuntimeContext()->toArray())); - $event->getServerOsContext()->replaceData($this->serializer->serialize($event->getServerOsContext()->toArray())); - $event->getExtraContext()->replaceData($this->serializer->serialize($event->getExtraContext()->toArray())); - $event->getTagsContext()->replaceData($this->serializer->serialize($event->getTagsContext()->toArray())); - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Middleware/UserInterfaceMiddleware.php b/src/Middleware/UserInterfaceMiddleware.php deleted file mode 100644 index 458b5e37b..000000000 --- a/src/Middleware/UserInterfaceMiddleware.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ -final class UserInterfaceMiddleware -{ - /** - * Collects the needed data and sets it in the given event object. - * - * @param Event $event The event being processed - * @param callable $next The next middleware to call - * @param ServerRequestInterface|null $request The request, if available - * @param \Exception|\Throwable|null $exception The thrown exception, if available - * @param array $payload Additional data - * - * @return Event - */ - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - // following PHPDoc is incorrect, workaround for https://github.com/phpstan/phpstan/issues/1377 - /** @var array|Context $userContext */ - $userContext = $event->getUserContext(); - - if (!isset($userContext['ip_address']) && null !== $request && $request->hasHeader('REMOTE_ADDR')) { - $userContext['ip_address'] = $request->getHeaderLine('REMOTE_ADDR'); - } - - return $next($event, $request, $exception, $payload); - } -} diff --git a/src/Configuration.php b/src/Options.php similarity index 90% rename from src/Configuration.php rename to src/Options.php index 5b6770e44..76e46f9c6 100644 --- a/src/Configuration.php +++ b/src/Options.php @@ -14,7 +14,8 @@ namespace Sentry; use Sentry\Breadcrumbs\Breadcrumb; -use Symfony\Component\OptionsResolver\Options; +use Sentry\Integration\IntegrationInterface; +use Symfony\Component\OptionsResolver\Options as SymfonyOptions; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -22,7 +23,7 @@ * * @author Stefano Arlandini */ -class Configuration +class Options { /** * The default maximum number of breadcrumbs that will be sent with an event. @@ -515,37 +516,25 @@ public function setServerName($serverName) } /** - * Checks whether all events or a specific event (if provided) are allowed - * to be captured. + * Gets a callback that will be invoked before an event is sent to the server. + * If `null` is returned it won't be sent. * - * @param Event|null $event The event object - * - * @return bool + * @return callable */ - public function shouldCapture(Event $event = null) + public function getBeforeSendCallback(): callable { - $result = true; - - if (!empty($this->options['environments']) && !\in_array($this->options['current_environment'], $this->options['environments'])) { - $result = false; - } - - if (null !== $this->options['should_capture'] && null !== $event) { - $result = $result && $this->options['should_capture']($event); - } - - return $result; + return $this->options['before_send']; } /** - * Sets an optional callable to be called to decide whether an event should + * Sets a callable to be called to decide whether an event should * be captured or not. * - * @param callable|null $callable The callable + * @param callable $callback The callable */ - public function setShouldCapture(callable $callable = null) + public function setBeforeSendCallback(callable $callback): void { - $options = array_merge($this->options, ['should_capture' => $callable]); + $options = array_merge($this->options, ['before_send' => $callback]); $this->options = $this->resolver->resolve($options); } @@ -642,6 +631,28 @@ public function setBeforeBreadcrumbCallback(callable $callback): void $this->options = $this->resolver->resolve($options); } + /** + * Set integrations that will be used by the created client. + * + * @param null|IntegrationInterface[] $integrations The integrations + */ + public function setIntegrations(?array $integrations): void + { + $options = array_merge($this->options, ['integrations' => $integrations]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Returns all configured integrations that will be used by the Client. + * + * @return null|IntegrationInterface[] + */ + public function getIntegrations(): ?array + { + return $this->options['integrations']; + } + /** * Configures the options of the client. * @@ -653,6 +664,7 @@ public function setBeforeBreadcrumbCallback(callable $callback): void private function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ + 'integrations' => [], 'send_attempts' => 6, 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'serialize_all_object' => false, @@ -671,7 +683,9 @@ private function configureOptions(OptionsResolver $resolver) 'release' => null, 'dsn' => isset($_SERVER['SENTRY_DSN']) ? $_SERVER['SENTRY_DSN'] : null, 'server_name' => gethostname(), - 'should_capture' => null, + 'before_send' => function (Event $event): ?Event { + return $event; + }, 'tags' => [], 'error_types' => null, 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, @@ -698,11 +712,26 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'boolean', 'string']); $resolver->setAllowedTypes('server_name', 'string'); - $resolver->setAllowedTypes('should_capture', ['null', 'callable']); + $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('tags', 'array'); $resolver->setAllowedTypes('error_types', ['null', 'int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); $resolver->setAllowedTypes('before_breadcrumb', ['callable']); + $resolver->setAllowedTypes('integrations', ['null', 'array']); + + $resolver->setAllowedValues('integrations', function ($value) { + if (null === $value) { + return true; + } + + foreach ($value as $integration) { + if (!$integration instanceof IntegrationInterface) { + return false; + } + } + + return true; + }); $resolver->setAllowedValues('encoding', ['gzip', 'json']); $resolver->setAllowedValues('dsn', function ($value) { @@ -746,7 +775,7 @@ private function configureOptions(OptionsResolver $resolver) return $value <= self::DEFAULT_MAX_BREADCRUMBS; }); - $resolver->setNormalizer('dsn', function (Options $options, $value) { + $resolver->setNormalizer('dsn', function (SymfonyOptions $options, $value) { if (empty($value)) { $this->dsn = null; @@ -792,7 +821,7 @@ private function configureOptions(OptionsResolver $resolver) return $value; }); - $resolver->setNormalizer('project_root', function (Options $options, $value) { + $resolver->setNormalizer('project_root', function (SymfonyOptions $options, $value) { if (null === $value) { return null; } @@ -800,11 +829,11 @@ private function configureOptions(OptionsResolver $resolver) return $this->normalizeAbsolutePath($value); }); - $resolver->setNormalizer('prefixes', function (Options $options, $value) { + $resolver->setNormalizer('prefixes', function (SymfonyOptions $options, $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); - $resolver->setNormalizer('excluded_app_paths', function (Options $options, $value) { + $resolver->setNormalizer('excluded_app_paths', function (SymfonyOptions $options, $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); } diff --git a/src/Sdk.php b/src/Sdk.php index b6cff6680..afb0cd0f1 100644 --- a/src/Sdk.php +++ b/src/Sdk.php @@ -8,11 +8,14 @@ /** * Creates a new Client and Hub which will be set as current. * - * @param array $options + * @param array $options The client options + * @param null|ClientBuilder $clientBuilder An optional client builder instance */ -function init(array $options = []): void +function init(array $options = [], ?ClientBuilder $clientBuilder = null): void { - Hub::setCurrent(new Hub(ClientBuilder::create($options)->getClient())); + $builder = $clientBuilder ?? ClientBuilder::create($options); + + Hub::setCurrent(new Hub($builder->getClient())); } /** @@ -52,6 +55,16 @@ function captureEvent(array $payload): ?string return Hub::getCurrent()->captureEvent($payload); } +/** + * Logs the most recent error (obtained with {@link error_get_last}). + * + * @return null|string + */ +function captureLastError(): ?string +{ + return Hub::getCurrent()->captureLastError(); +} + /** * Records a new breadcrumb which will be attached to future events. They * will be added to subsequent events to provide more context on user's diff --git a/src/Severity.php b/src/Severity.php index 832e35b11..58c072933 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -76,6 +76,40 @@ public function __construct(string $value = self::INFO) $this->value = $value; } + /** + * Translate a PHP Error constant into a Sentry log level group. + * + * @param int $severity PHP E_* error constant + * + * @return Severity + */ + public static function fromError(int $severity): self + { + switch ($severity) { + case E_DEPRECATED: + case E_USER_DEPRECATED: + case E_WARNING: + case E_USER_WARNING: + case E_RECOVERABLE_ERROR: + return self::warning(); + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + return self::fatal(); + case E_USER_ERROR: + return self::error(); + case E_NOTICE: + case E_USER_NOTICE: + case E_STRICT: + return self::info(); + default: + return self::error(); + } + } + /** * Creates a new instance of this enum for the "debug" value. * diff --git a/src/Stacktrace.php b/src/Stacktrace.php index a7e38bece..a071ca7f9 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -24,9 +24,9 @@ class Stacktrace implements \JsonSerializable const CONTEXT_NUM_LINES = 5; /** - * @var ClientInterface The Raven client + * @var Options The client options */ - protected $client; + protected $options; /** * @var Serializer The serializer @@ -34,9 +34,9 @@ class Stacktrace implements \JsonSerializable protected $serializer; /** - * @var Serializer The representation serializer + * @var ReprSerializer The representation serializer */ - protected $reprSerializer; + protected $representationSerializer; /** * @var Frame[] The frames that compose the stacktrace @@ -54,44 +54,34 @@ class Stacktrace implements \JsonSerializable ]; /** - * Constructor. + * Stacktrace constructor. * - * @param ClientInterface $client The Raven client + * @param Options $options The client options + * @param Serializer $serializer The serializer + * @param ReprSerializer $representationSerializer The representation serializer */ - public function __construct(ClientInterface $client) + public function __construct(Options $options, Serializer $serializer, ReprSerializer $representationSerializer) { - $this->client = $client; - $this->serializer = $client->getSerializer(); - $this->reprSerializer = $client->getRepresentationSerializer(); - } - - /** - * Creates a new instance of this class using the current backtrace data. - * - * @param ClientInterface $client The Raven client - * - * @return static - */ - public static function create(ClientInterface $client) - { - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - - return static::createFromBacktrace($client, $backtrace, __FILE__, __LINE__); + $this->options = $options; + $this->serializer = $serializer; + $this->representationSerializer = $representationSerializer; } /** * Creates a new instance of this class from the given backtrace. * - * @param ClientInterface $client The Raven client - * @param array $backtrace The backtrace - * @param string $file The file that originated the backtrace - * @param int $line The line at which the backtrace originated + * @param Options $options The client options + * @param Serializer $serializer The serializer + * @param ReprSerializer $representationSerializer The representation serializer + * @param array $backtrace The backtrace + * @param string $file The file that originated the backtrace + * @param int $line The line at which the backtrace originated * * @return static */ - public static function createFromBacktrace(ClientInterface $client, array $backtrace, $file, $line) + public static function createFromBacktrace(Options $options, Serializer $serializer, ReprSerializer $representationSerializer, array $backtrace, $file, $line) { - $stacktrace = new static($client); + $stacktrace = new static($options, $serializer, $representationSerializer); foreach ($backtrace as $frame) { $stacktrace->addFrame($file, $line, $frame); @@ -157,10 +147,10 @@ public function addFrame($file, $line, array $backtraceFrame) $frame->setPostContext($sourceCodeExcerpt['post_context']); } - if (null !== $this->client->getConfig()->getProjectRoot()) { - $excludedAppPaths = $this->client->getConfig()->getExcludedProjectPaths(); + if (null !== $this->options->getProjectRoot()) { + $excludedAppPaths = $this->options->getExcludedProjectPaths(); $absoluteFilePath = @realpath($file) ?: $file; - $isApplicationFile = 0 === strpos($absoluteFilePath, $this->client->getConfig()->getProjectRoot()); + $isApplicationFile = 0 === strpos($absoluteFilePath, $this->options->getProjectRoot()); if ($isApplicationFile && !empty($excludedAppPaths)) { foreach ($excludedAppPaths as $path) { @@ -177,7 +167,7 @@ public function addFrame($file, $line, array $backtraceFrame) if (!empty($frameArguments)) { foreach ($frameArguments as $argumentName => $argumentValue) { - $argumentValue = $this->reprSerializer->serialize($argumentValue); + $argumentValue = $this->representationSerializer->serialize($argumentValue); if (\is_string($argumentValue) || is_numeric($argumentValue)) { $frameArguments[(string) $argumentName] = substr((string) $argumentValue, 0, Client::MESSAGE_MAX_LENGTH_LIMIT); @@ -297,7 +287,7 @@ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) */ protected function stripPrefixFromFilePath($filePath) { - foreach ($this->client->getConfig()->getPrefixes() as $prefix) { + foreach ($this->options->getPrefixes() as $prefix) { if (0 === strpos($filePath, $prefix)) { return substr($filePath, \strlen($prefix)); } diff --git a/src/State/Hub.php b/src/State/Hub.php index 858ab3e43..73e1ecd57 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -13,6 +13,7 @@ use Sentry\Breadcrumbs\Breadcrumb; use Sentry\ClientInterface; +use Sentry\Integration\IntegrationInterface; use Sentry\Severity; /** @@ -189,7 +190,7 @@ public function captureMessage(string $message, ?Severity $level = null): ?strin $client = $this->getClient(); if (null !== $client) { - return $this->lastEventId = $client->captureMessage($message, [], ['level' => $level]); + return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope()); } return null; @@ -207,7 +208,7 @@ public function captureException(\Throwable $exception): ?string $client = $this->getClient(); if (null !== $client) { - return $this->lastEventId = $client->captureException($exception); + return $this->lastEventId = $client->captureException($exception, $this->getScope()); } return null; @@ -225,7 +226,23 @@ public function captureEvent(array $payload): ?string $client = $this->getClient(); if (null !== $client) { - return $this->lastEventId = $client->capture($payload); + return $this->lastEventId = $client->captureEvent($payload, $this->getScope()); + } + + return null; + } + + /** + * Captures an event that logs the last occurred error. + * + * @return null|string + */ + public function captureLastError(): ?string + { + $client = $this->getClient(); + + if (null !== $client) { + return $this->lastEventId = $client->captureLastError($this->getScope()); } return null; @@ -274,4 +291,21 @@ public static function setCurrent(self $hub): self return $hub; } + + /** + * Gets the integration whose FQCN matches the given one if it's available on the current client. + * + * @param string $className The FQCN of the integration + * + * @return null|IntegrationInterface + */ + public function getIntegration(string $className): ?IntegrationInterface + { + $client = $this->getClient(); + if (null !== $client) { + return $client->getIntegration($className); + } + + return null; + } } diff --git a/src/State/Scope.php b/src/State/Scope.php index 3b24ae75a..7d0677010 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -63,6 +63,11 @@ final class Scope */ private $eventProcessors = []; + /** + * @var callable[] List of event processors + */ + private static $globalEventProcessors = []; + /** * Constructor. */ @@ -248,6 +253,17 @@ public function addEventProcessor(callable $eventProcessor): self return $this; } + /** + * Adds a new event processor that will be called after {@see Scope::applyToEvent} + * finished its work. + * + * @param callable $eventProcessor The event processor + */ + public static function addGlobalEventProcessor(callable $eventProcessor): void + { + self::$globalEventProcessors[] = $eventProcessor; + } + /** * Clears the scope and resets any data it contains. * @@ -270,24 +286,19 @@ public function clear(): self * Applies the current context and fingerprint to the event. If the event has * already some breadcrumbs on it, the ones from this scope won't get merged. * - * @param Event $event The event object - * @param int $maxBreadcrumbs The maximum number of breadcrumbs to add to - * the event + * @param Event $event The event object that will be enriched with scope data + * @param array $payload The raw payload of the event that will be propagated to the event processors * * @return Event|null */ - public function applyToEvent(Event $event, int $maxBreadcrumbs = 100): ?Event + public function applyToEvent(Event $event, array $payload): ?Event { if (empty($event->getFingerprint())) { $event->setFingerprint($this->fingerprint); } if (empty($event->getBreadcrumbs())) { - $breadcrumbs = \array_slice($this->breadcrumbs, -$maxBreadcrumbs); - - foreach ($breadcrumbs as $breadcrumb) { - $event->setBreadcrumb($breadcrumb); - } + $event->setBreadcrumb($this->breadcrumbs); } if (null !== $this->level) { @@ -298,8 +309,8 @@ public function applyToEvent(Event $event, int $maxBreadcrumbs = 100): ?Event $event->getExtraContext()->merge($this->extra->toArray()); $event->getUserContext()->merge($this->user->toArray()); - foreach ($this->eventProcessors as $processor) { - $event = $processor($event); + foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) { + $event = \call_user_func($processor, $event, $payload); if (null === $event) { return null; diff --git a/src/TransactionStack.php b/src/TransactionStack.php deleted file mode 100644 index d3a00c2f2..000000000 --- a/src/TransactionStack.php +++ /dev/null @@ -1,102 +0,0 @@ - - */ -final class TransactionStack implements \Countable -{ - /** - * @var string[] The transaction stack - */ - private $transactions = []; - - /** - * Class constructor. - * - * @param string[] $values An array of initial values - */ - public function __construct(array $values = []) - { - $this->push(...$values); - } - - /** - * Clears the stack by removing all values. - */ - public function clear() - { - $this->transactions = []; - } - - /** - * Checks whether the stack is empty. - * - * @return bool - */ - public function isEmpty() - { - return empty($this->transactions); - } - - /** - * Pushes the given values onto the stack. - * - * @param string ...$values The values to push - */ - public function push(string ...$values) - { - $this->transactions = array_merge($this->transactions, $values); - } - - /** - * Gets the value at the top of the stack without removing it. - * - * @return string|null - */ - public function peek() - { - if (empty($this->transactions)) { - return null; - } - - return $this->transactions[\count($this->transactions) - 1]; - } - - /** - * Removes and returns the value at the top of the stack. - * - * @return string|null - */ - public function pop() - { - if (empty($this->transactions)) { - return null; - } - - return array_pop($this->transactions); - } - - /** - * {@inheritdoc} - */ - public function count() - { - return \count($this->transactions); - } -} diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index c7df16661..48663c8a7 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -15,9 +15,8 @@ use Http\Message\Encoding\CompressStream; use Http\Message\RequestFactory; use Http\Promise\Promise; -use Sentry\Configuration; use Sentry\Event; -use Sentry\HttpClient\Encoding\Base64EncodingStream; +use Sentry\Options; use Sentry\Util\JSON; /** @@ -28,7 +27,7 @@ final class HttpTransport implements TransportInterface { /** - * @var Configuration The Raven client configuration + * @var Options The Raven client configuration */ private $config; @@ -50,11 +49,11 @@ final class HttpTransport implements TransportInterface /** * Constructor. * - * @param Configuration $config The Raven client configuration + * @param Options $config The Raven client configuration * @param HttpAsyncClient $httpClient The HTTP client * @param RequestFactory $requestFactory The PSR-7 request factory */ - public function __construct(Configuration $config, HttpAsyncClient $httpClient, RequestFactory $requestFactory) + public function __construct(Options $config, HttpAsyncClient $httpClient, RequestFactory $requestFactory) { $this->config = $config; $this->httpClient = $httpClient; @@ -82,7 +81,7 @@ public function __destruct() /** * {@inheritdoc} */ - public function send(Event $event) + public function send(Event $event): ?string { $request = $this->requestFactory->createRequest( 'POST', @@ -92,11 +91,7 @@ public function send(Event $event) ); if ($this->isEncodingCompressed()) { - $request = $request->withBody( - new Base64EncodingStream( - new CompressStream($request->getBody()) - ) - ); + $request = $request->withBody(new CompressStream($request->getBody())); } $promise = $this->httpClient->sendAsyncRequest($request); @@ -116,7 +111,7 @@ public function send(Event $event) $this->pendingRequests[] = $promise; - return true; + return $event->getId(); } /** diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index 6504de0de..74de6469d 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -23,8 +23,8 @@ class NullTransport implements TransportInterface /** * {@inheritdoc} */ - public function send(Event $event) + public function send(Event $event): ?string { - return true; + return $event->getId(); } } diff --git a/src/Transport/SpoolTransport.php b/src/Transport/SpoolTransport.php index 0636af50a..bc158928d 100644 --- a/src/Transport/SpoolTransport.php +++ b/src/Transport/SpoolTransport.php @@ -49,8 +49,12 @@ public function getSpool() /** * {@inheritdoc} */ - public function send(Event $event) + public function send(Event $event): ?string { - return $this->spool->queueEvent($event); + if ($this->spool->queueEvent($event)) { + return $event->getId(); + } + + return null; } } diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index 23c65fbbb..3bda35e63 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -26,7 +26,7 @@ interface TransportInterface * * @param Event $event The event * - * @return bool Whether the event was sent successfully or not + * @return null|string Returns the ID of the event or `null` if it failed to be sent */ - public function send(Event $event); + public function send(Event $event): ?string; } diff --git a/tests/AbstractErrorHandlerTest.php b/tests/AbstractErrorHandlerTest.php index f9eb6b96b..b12ce7efd 100644 --- a/tests/AbstractErrorHandlerTest.php +++ b/tests/AbstractErrorHandlerTest.php @@ -12,27 +12,23 @@ namespace Sentry\Tests; use PHPUnit\Framework\TestCase; -use Sentry\Client; abstract class AbstractErrorHandlerTest extends TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject|Client + * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $client; + protected $callbackMock; protected function setUp() { - $this->client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->setMethodsExcept(['translateSeverity']) - ->getMock(); + $this->callbackMock = $this->createPartialMock(\stdClass::class, ['__invoke']); } public function testConstructor() { try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $previousErrorHandler = set_error_handler('var_dump'); restore_error_handler(); @@ -52,7 +48,7 @@ public function testConstructor() */ public function testConstructorThrowsWhenReservedMemorySizeIsWrong($reservedMemorySize) { - $this->createErrorHandler($this->client, $reservedMemorySize); + $this->createErrorHandler($this->callbackMock, $reservedMemorySize); } public function constructorThrowsWhenReservedMemorySizeIsWrongDataProvider() @@ -60,39 +56,39 @@ public function constructorThrowsWhenReservedMemorySizeIsWrongDataProvider() return [ [-1], [0], - ['foo'], ]; } /** * @dataProvider handleErrorShouldNotCaptureDataProvider */ - public function testHandleErrorShouldNotCapture(bool $expectedToCapture, int $captureAt, int $errorReporting) + public function testHandleErrorShouldNotCapture(bool $expectedToCapture, int $captureAt) { if (!$expectedToCapture) { - $this->client->expects($this->never()) - ->method('capture'); + $this->callbackMock->expects($this->never()) + ->method('__invoke'); } - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $errorHandler->captureAt($captureAt, true); - $prevErrorReporting = error_reporting($errorReporting); + // to avoid making the test error bubble up and make the test fail + $prevErrorReporting = error_reporting(E_ERROR); try { $this->assertFalse($errorHandler->handleError(E_WARNING, 'Test', __FILE__, __LINE__)); } finally { error_reporting($prevErrorReporting); + restore_error_handler(); + restore_exception_handler(); } } - public function handleErrorShouldNotCaptureDataProvider() + public function handleErrorShouldNotCaptureDataProvider(): array { return [ - [false, E_ERROR, E_ERROR], - [false, E_ALL, E_ERROR], - [true, E_ERROR, E_ALL], - [true, E_ALL, E_ALL], + [false, E_ERROR], + [true, E_ALL], ]; } @@ -102,7 +98,7 @@ public function handleErrorShouldNotCaptureDataProvider() public function testCaptureAt($levels, $replace, $expectedCapturedErrors) { try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $previousCapturedErrors = $this->getObjectAttribute($errorHandler, 'capturedErrors'); $this->assertEquals($previousCapturedErrors, $errorHandler->captureAt($levels, $replace)); diff --git a/tests/AbstractSerializerTest.php b/tests/AbstractSerializerTest.php index 163a6957c..f18af1b53 100644 --- a/tests/AbstractSerializerTest.php +++ b/tests/AbstractSerializerTest.php @@ -50,8 +50,8 @@ public function testArraysAreArrays($serializeAllObjects) $result = $serializer->serialize($input); $this->assertEquals(['1', '2', '3'], $result); - $result = $serializer->serialize([Client::class, 'getConfig']); - $this->assertEquals([Client::class, 'getConfig'], $result); + $result = $serializer->serialize([Client::class, 'getOptions']); + $this->assertEquals([Client::class, 'getOptions'], $result); } /** @@ -447,9 +447,6 @@ public function serializableCallableProvider() ], [ 'callable' => [$this, 'serializableCallableProvider'], 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::serializableCallableProvider []', - ], [ - 'callable' => [Client::class, 'getConfig'], - 'expected' => 'Callable Sentry\Client::getConfig []', ], [ 'callable' => [TestCase::class, 'setUpBeforeClass'], 'expected' => 'Callable PHPUnit\\Framework\\TestCase::setUpBeforeClass []', @@ -459,6 +456,9 @@ public function serializableCallableProvider() ], [ 'callable' => [self::class, 'setUpBeforeClass'], 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', + ], [ + 'callable' => [SerializerTestObject::class, 'testy'], + 'expected' => 'Callable void Sentry\Tests\SerializerTestObject::testy []', ], ]; require_once 'resources/php70_serializing.inc'; @@ -495,9 +495,6 @@ public function serializableCallableProvider() ], [ 'callable' => [$this, 'serializableCallableProvider'], 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::serializableCallableProvider []', - ], [ - 'callable' => [Client::class, 'getConfig'], - 'expected' => 'Callable Sentry\Client::getConfig []', ], [ 'callable' => [TestCase::class, 'setUpBeforeClass'], 'expected' => 'Callable PHPUnit_Framework_TestCase::setUpBeforeClass []', @@ -507,6 +504,9 @@ public function serializableCallableProvider() ], [ 'callable' => [self::class, 'setUpBeforeClass'], 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', + ], [ + 'callable' => [SerializerTestObject::class, 'testy'], + 'expected' => 'Callable void Sentry\Tests\SerializerTestObject::testy []', ], ]; } @@ -545,4 +545,9 @@ class SerializerTestObject private $foo = 'bar'; public $key = 'value'; + + public static function testy(): void + { + throw new \Exception('We should not reach this'); + } } diff --git a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php b/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php deleted file mode 100644 index 8613a88f7..000000000 --- a/tests/Breadcrumbs/BreadcrumbErrorHandlerTest.php +++ /dev/null @@ -1,325 +0,0 @@ -client->expects($this->once()) - ->method('addBreadcrumb') - ->with($this->callback(function ($breadcrumb) { - /* @var Breadcrumb $breadcrumb */ - $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); - $this->assertEquals('User Notice: foo bar', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); - $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); - $this->assertEquals('error_reporting', $breadcrumb->getCategory()); - $this->assertArraySubset([ - 'code' => 0, - 'file' => __FILE__, - 'line' => __LINE__ + 20, - ], $breadcrumb->getMetadata()); - - return true; - })); - - try { - $errorHandler = $this->createErrorHandler($this->client); - $errorHandler->captureAt(0, true); - - $reflectionProperty = new \ReflectionProperty(BreadcrumbErrorHandler::class, 'previousErrorHandler'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($errorHandler, null); - $reflectionProperty->setAccessible(false); - - $this->assertFalse($errorHandler->handleError(0, 'foo bar', __FILE__, __LINE__)); - - $errorHandler->captureAt(E_USER_NOTICE, true); - - $this->assertFalse($errorHandler->handleError(E_USER_WARNING, 'foo bar', __FILE__, __LINE__)); - $this->assertFalse($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__)); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function testHandleErrorWithPreviousErrorHandler() - { - $this->client->expects($this->once()) - ->method('addBreadcrumb') - ->with($this->callback(function ($breadcrumb) { - /* @var Breadcrumb $breadcrumb */ - $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); - $this->assertEquals('User Notice: foo bar', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); - $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); - $this->assertEquals('error_reporting', $breadcrumb->getCategory()); - $this->assertArraySubset([ - 'code' => 0, - 'file' => __FILE__, - 'line' => __LINE__ + 20, - ], $breadcrumb->getMetadata()); - - return true; - })); - - $previousErrorHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); - $previousErrorHandler->expects($this->once()) - ->method('__invoke') - ->with(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__ + 11) - ->willReturn(false); - - try { - $errorHandler = $this->createErrorHandler($this->client); - - $reflectionProperty = new \ReflectionProperty(BreadcrumbErrorHandler::class, 'previousErrorHandler'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($errorHandler, $previousErrorHandler); - $reflectionProperty->setAccessible(false); - - $errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function testHandleFatalError() - { - $this->client->expects($this->once()) - ->method('addBreadcrumb') - ->with($this->callback(function ($breadcrumb) { - /* @var Breadcrumb $breadcrumb */ - $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); - $this->assertEquals('Parse Error: foo bar', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); - $this->assertEquals(Breadcrumb::LEVEL_CRITICAL, $breadcrumb->getLevel()); - $this->assertEquals('error_reporting', $breadcrumb->getCategory()); - $this->assertArraySubset([ - 'code' => 0, - 'file' => __FILE__, - 'line' => __LINE__ + 12, - ], $breadcrumb->getMetadata()); - - return true; - })); - - try { - $errorHandler = $this->createErrorHandler($this->client); - $errorHandler->handleFatalError([ - 'type' => E_PARSE, - 'message' => 'foo bar', - 'file' => __FILE__, - 'line' => __LINE__, - ]); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function testHandleFatalErrorWithNonFatalErrorDoesNothing() - { - $this->client->expects($this->never()) - ->method('addBreadcrumb'); - - try { - $errorHandler = $this->createErrorHandler($this->client); - $errorHandler->handleFatalError([ - 'type' => E_USER_NOTICE, - 'message' => 'foo bar', - 'file' => __FILE__, - 'line' => __LINE__, - ]); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function testHandleExceptionSkipsNotErrorExceptionException() - { - $exception = new \Exception('foo bar'); - - $this->client->expects($this->never()) - ->method('addBreadcrumb'); - - try { - $errorHandler = $this->createErrorHandler($this->client); - - try { - $errorHandler->handleException($exception); - - $this->fail('Exception expected'); - } catch (\Exception $catchedException) { - $this->assertSame($exception, $catchedException); - } - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function testHandleExceptionWithPreviousExceptionHandler() - { - $exception = new \ErrorException('foo bar', 0, E_USER_NOTICE); - - $this->client->expects($this->once()) - ->method('addBreadcrumb') - ->with($this->callback(function ($breadcrumb) { - /* @var Breadcrumb $breadcrumb */ - $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); - $this->assertEquals('foo bar', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); - $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); - $this->assertEquals('error_reporting', $breadcrumb->getCategory()); - $this->assertArraySubset([ - 'code' => 0, - 'file' => __FILE__, - 'line' => __LINE__ - 14, - ], $breadcrumb->getMetadata()); - - return true; - })); - - $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); - $previousExceptionHandler->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($exception)); - - try { - $errorHandler = $this->createErrorHandler($this->client); - - $reflectionProperty = new \ReflectionProperty(BreadcrumbErrorHandler::class, 'previousExceptionHandler'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); - $reflectionProperty->setAccessible(false); - - try { - $errorHandler->handleException($exception); - - $this->fail('Exception expected'); - } catch (\Exception $catchedException) { - $this->assertSame($exception, $catchedException); - } - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function testHandleExceptionWithThrowingPreviousExceptionHandler() - { - $exception1 = new \ErrorException('foo bar', 0, E_USER_NOTICE); - $exception2 = new \ErrorException('bar foo', 0, E_USER_NOTICE); - - $this->client->expects($this->exactly(2)) - ->method('addBreadcrumb') - ->withConsecutive($this->callback(function ($breadcrumb) { - /* @var Breadcrumb $breadcrumb */ - $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); - $this->assertEquals('foo bar', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); - $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); - $this->assertEquals('error_reporting', $breadcrumb->getCategory()); - $this->assertArraySubset([ - 'code' => 0, - 'file' => __FILE__, - 'line' => __LINE__ - 15, - ], $breadcrumb->getMetadata()); - - return true; - }), $this->callback(function ($breadcrumb) { - /* @var Breadcrumb $breadcrumb */ - $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); - $this->assertEquals('bar foo', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); - $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); - $this->assertEquals('error_reporting', $breadcrumb->getCategory()); - $this->assertArraySubset([ - 'code' => 0, - 'file' => __FILE__, - 'line' => __LINE__ - 29, - ], $breadcrumb->getMetadata()); - - return true; - })); - - $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); - $previousExceptionHandler->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($exception1)) - ->will($this->throwException($exception2)); - - try { - $errorHandler = $this->createErrorHandler($this->client); - - $reflectionProperty = new \ReflectionProperty(BreadcrumbErrorHandler::class, 'previousExceptionHandler'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); - $reflectionProperty->setAccessible(false); - - try { - $errorHandler->handleException($exception1); - - $this->fail('Exception expected'); - } catch (\Exception $catchedException) { - $this->assertSame($exception2, $catchedException); - } - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function testThrownErrorLeavesBreadcrumb() - { - $this->client->expects($this->once()) - ->method('addBreadcrumb') - ->with($this->callback(function ($breadcrumb) { - /* @var Breadcrumb $breadcrumb */ - $this->assertInstanceOf(Breadcrumb::class, $breadcrumb); - $this->assertEquals('User Warning: foo bar', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $breadcrumb->getType()); - $this->assertEquals(Client::LEVEL_WARNING, $breadcrumb->getLevel()); - $this->assertEquals('error_reporting', $breadcrumb->getCategory()); - $this->assertArraySubset([ - 'code' => 0, - 'file' => __FILE__, - 'line' => __LINE__ + 9, - ], $breadcrumb->getMetadata()); - - return true; - })); - - try { - $this->createErrorHandler($this->client); - - @trigger_error('foo bar', E_USER_WARNING); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - protected function createErrorHandler(...$arguments): BreadcrumbErrorHandler - { - return BreadcrumbErrorHandler::register(...$arguments); - } -} diff --git a/tests/Breadcrumbs/BreadcrumbTest.php b/tests/Breadcrumbs/BreadcrumbTest.php index c888844f6..df9f1769d 100644 --- a/tests/Breadcrumbs/BreadcrumbTest.php +++ b/tests/Breadcrumbs/BreadcrumbTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumbs\Breadcrumb; -use Sentry\Client; /** * @group time-sensitive @@ -35,16 +34,16 @@ public function testConstructorThrowsOnInvalidLevel() */ public function testSetLevelThrowsOnInvalidLevel() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $breadcrumb->withLevel('bar'); } public function testConstructor() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); $this->assertEquals('foo', $breadcrumb->getCategory()); - $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals(Breadcrumb::LEVEL_INFO, $breadcrumb->getLevel()); $this->assertEquals('foo bar', $breadcrumb->getMessage()); $this->assertEquals(Breadcrumb::TYPE_USER, $breadcrumb->getType()); $this->assertEquals(['baz'], $breadcrumb->getMetadata()); @@ -53,10 +52,10 @@ public function testConstructor() public function testCreate() { - $breadcrumb = Breadcrumb::create(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); + $breadcrumb = Breadcrumb::create(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); $this->assertEquals('foo', $breadcrumb->getCategory()); - $this->assertEquals(Client::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertEquals(Breadcrumb::LEVEL_INFO, $breadcrumb->getLevel()); $this->assertEquals('foo bar', $breadcrumb->getMessage()); $this->assertEquals(Breadcrumb::TYPE_USER, $breadcrumb->getType()); $this->assertEquals(['baz'], $breadcrumb->getMetadata()); @@ -65,7 +64,7 @@ public function testCreate() public function testWithCategory() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withCategory('bar'); $this->assertNotSame($breadcrumb, $newBreadcrumb); @@ -75,17 +74,17 @@ public function testWithCategory() public function testWithLevel() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); - $newBreadcrumb = $breadcrumb->withLevel(Client::LEVEL_WARNING); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $newBreadcrumb = $breadcrumb->withLevel(Breadcrumb::LEVEL_WARNING); $this->assertNotSame($breadcrumb, $newBreadcrumb); - $this->assertEquals(Client::LEVEL_WARNING, $newBreadcrumb->getLevel()); - $this->assertSame($newBreadcrumb, $newBreadcrumb->withLevel(Client::LEVEL_WARNING)); + $this->assertEquals(Breadcrumb::LEVEL_WARNING, $newBreadcrumb->getLevel()); + $this->assertSame($newBreadcrumb, $newBreadcrumb->withLevel(Breadcrumb::LEVEL_WARNING)); } public function testWithType() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withType(Breadcrumb::TYPE_ERROR); $this->assertNotSame($breadcrumb, $newBreadcrumb); @@ -95,7 +94,7 @@ public function testWithType() public function testWithMessage() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withMessage('foo bar'); $this->assertNotSame($breadcrumb, $newBreadcrumb); @@ -105,7 +104,7 @@ public function testWithMessage() public function testWithTimestamp() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withTimestamp(123); $this->assertNotSame($breadcrumb, $newBreadcrumb); @@ -115,7 +114,7 @@ public function testWithTimestamp() public function testWithMetadata() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withMetadata('foo', 'bar'); $this->assertNotSame($breadcrumb, $newBreadcrumb); @@ -125,7 +124,7 @@ public function testWithMetadata() public function testWithoutMetadata() { - $breadcrumb = new Breadcrumb(Client::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', null, ['foo' => 'bar']); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', null, ['foo' => 'bar']); $newBreadcrumb = $breadcrumb->withoutMetadata('foo'); $this->assertNotSame($breadcrumb, $newBreadcrumb); diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 73674e0ef..5617dfdaa 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -20,7 +20,7 @@ use Psr\Http\Message\RequestInterface; use Sentry\Client; use Sentry\ClientBuilder; -use Sentry\Configuration; +use Sentry\Options; use Sentry\Transport\HttpTransport; use Sentry\Transport\NullTransport; use Sentry\Transport\TransportInterface; @@ -139,32 +139,6 @@ public function testRemoveHttpClientPlugin() $this->assertSame($plugin2, reset($plugins)); } - public function testAddMiddlewares() - { - $middleware = function () {}; - - $clientBuilder = new ClientBuilder(); - $clientBuilder->addMiddleware($middleware, -10); - - $this->assertEquals([[$middleware, -10]], $clientBuilder->getMiddlewares()); - } - - public function testRemoveMiddleware() - { - $middleware1 = function () {}; - $middleware2 = function () {}; - - $clientBuilder = new ClientBuilder(); - $clientBuilder->addMiddleware($middleware1, -10); - $clientBuilder->addMiddleware($middleware2, 10); - - $this->assertEquals([[$middleware1, -10], [$middleware2, 10]], $clientBuilder->getMiddlewares()); - - $clientBuilder->removeMiddleware($middleware2); - - $this->assertEquals([[$middleware1, -10]], $clientBuilder->getMiddlewares()); - } - public function testGetClient() { $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -198,18 +172,16 @@ public function testCallInvalidMethodThrowsException() */ public function testCallExistingMethodForwardsCallToConfiguration($setterMethod, $value) { - $configuration = $this->getMockBuilder(Configuration::class) - ->getMock(); - - $configuration->expects($this->once()) + $options = $this->createMock(Options::class); + $options->expects($this->once()) ->method($setterMethod) ->with($this->equalTo($value)); $clientBuilder = new ClientBuilder(); - $reflectionProperty = new \ReflectionProperty(ClientBuilder::class, 'configuration'); + $reflectionProperty = new \ReflectionProperty(ClientBuilder::class, 'options'); $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($clientBuilder, $configuration); + $reflectionProperty->setValue($clientBuilder, $options); $reflectionProperty->setAccessible(false); $clientBuilder->$setterMethod($value); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index b7efb8724..51f848817 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -12,91 +12,58 @@ use Http\Mock\Client as MockClient; use PHPUnit\Framework\TestCase; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidFactory; use Sentry\Breadcrumbs\Breadcrumb; use Sentry\Client; use Sentry\ClientBuilder; -use Sentry\Context\Context; -use Sentry\Context\RuntimeContext; -use Sentry\Context\ServerOsContext; -use Sentry\Context\TagsContext; use Sentry\Event; -use Sentry\Middleware\MiddlewareStack; -use Sentry\ReprSerializer; -use Sentry\Serializer; +use Sentry\Options; use Sentry\Severity; +use Sentry\Stacktrace; +use Sentry\State\Hub; use Sentry\State\Scope; use Sentry\Tests\Fixtures\classes\CarelessException; -use Sentry\TransactionStack; use Sentry\Transport\TransportInterface; class ClientTest extends TestCase { - public function testConstructorInitializesTransactionStack() + public function testTransactionEventAttributeIsPopulated() { $_SERVER['PATH_INFO'] = '/foo'; $_SERVER['REQUEST_METHOD'] = 'GET'; - $client = ClientBuilder::create()->getClient(); - $transactionStack = $client->getTransactionStack(); - - $this->assertNotEmpty($transactionStack); - $this->assertEquals('/foo', $transactionStack->peek()); - } - - public function testConstructorInitializesTransactionStackInCli() - { - $client = ClientBuilder::create()->getClient(); - - $this->assertEmpty($client->getTransactionStack()); - } - - public function testGetTransactionStack() - { - $client = ClientBuilder::create()->getClient(); - - $this->assertInstanceOf(TransactionStack::class, $client->getTransactionStack()); - } - - public function testAddMiddleware() - { - $middleware = function () {}; + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); - /** @var MiddlewareStack|\PHPUnit_Framework_MockObject_MockObject $middlewareStack */ - $middlewareStack = $this->createMock(MiddlewareStack::class); - $middlewareStack->expects($this->once()) - ->method('addMiddleware') - ->with($middleware, -10); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertEquals('/foo', $event->getTransaction()); - $client = ClientBuilder::create()->getClient(); + return true; + })); - $reflectionProperty = new \ReflectionProperty($client, 'middlewareStack'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($client, $middlewareStack); - $reflectionProperty->setAccessible(false); + $client = new Client(new Options(), $transport); + $client->captureMessage('test'); - $client->addMiddleware($middleware, -10); + unset($_SERVER['PATH_INFO']); + unset($_SERVER['REQUEST_METHOD']); } - public function testRemoveMiddleware() + public function testTransactionEventAttributeIsNotPopulatedInCli() { - $middleware = function () {}; - - /** @var MiddlewareStack|\PHPUnit_Framework_MockObject_MockObject $middlewareStack */ - $middlewareStack = $this->createMock(MiddlewareStack::class); - $middlewareStack->expects($this->once()) - ->method('removeMiddleware') - ->with($middleware); + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); - $client = ClientBuilder::create()->getClient(); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertNull($event->getTransaction()); - $reflectionProperty = new \ReflectionProperty($client, 'middlewareStack'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($client, $middlewareStack); - $reflectionProperty->setAccessible(false); + return true; + })); - $client->removeMiddleware($middleware); + $client = new Client(new Options(), $transport); + $client->captureMessage('test'); } public function testCaptureMessage() @@ -108,14 +75,13 @@ public function testCaptureMessage() ->getMock(); $client->expects($this->once()) - ->method('capture') + ->method('captureEvent') ->with([ 'message' => 'foo', - 'message_params' => ['bar'], - 'foo' => 'bar', + 'level' => Severity::fatal(), ]); - $client->captureMessage('foo', ['bar'], ['foo' => 'bar']); + $client->captureMessage('foo', Severity::fatal()); } public function testCaptureException() @@ -129,58 +95,12 @@ public function testCaptureException() ->getMock(); $client->expects($this->once()) - ->method('capture') + ->method('captureEvent') ->with([ 'exception' => $exception, - 'foo' => 'bar', ]); - $client->captureException(new \Exception(), ['foo' => 'bar']); - } - - public function testCaptureLastError() - { - /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->setMethodsExcept(['captureLastError']) - ->getMock(); - - $client->expects($this->once()) - ->method('captureException') - ->with( - $this->logicalAnd( - $this->isInstanceOf(\ErrorException::class), - $this->attributeEqualTo('message', 'foo'), - $this->attributeEqualTo('code', 0), - $this->attributeEqualTo('severity', E_USER_NOTICE), - $this->attributeEqualTo('file', __FILE__), - $this->attributeEqualTo('line', __LINE__ + 5) - ), - ['foo' => 'bar'] - ); - - @trigger_error('foo', E_USER_NOTICE); - - $client->captureLastError(['foo' => 'bar']); - - $this->clearLastError(); - } - - public function testCaptureLastErrorDoesNothingWhenThereIsNoError() - { - /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ - $client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->setMethodsExcept(['captureLastError']) - ->getMock(); - - $client->expects($this->never()) - ->method('captureException'); - - $this->clearLastError(); - - $client->captureLastError(); + $client->captureException(new \Exception()); } public function testCapture() @@ -188,9 +108,10 @@ public function testCapture() /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send'); + ->method('send') + ->willReturn('500a339f3ab2450b96dee542adf36ba7'); - $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) + $client = ClientBuilder::create() ->setTransport($transport) ->getClient(); @@ -203,202 +124,90 @@ public function testCapture() 'user_context' => ['bar' => 'foo'], ]; - $eventId = $client->capture($inputData); - - $event = $client->getLastEvent(); - - $this->assertEquals(str_replace('-', '', $event->getId()->toString()), $eventId); - $this->assertEquals($inputData['transaction'], $event->getTransaction()); - $this->assertEquals($inputData['level'], $event->getLevel()); - $this->assertEquals($inputData['logger'], $event->getLogger()); - $this->assertEquals($inputData['tags_context'], $event->getTagsContext()->toArray()); - $this->assertEquals($inputData['extra_context'], $event->getExtraContext()->toArray()); - $this->assertEquals($inputData['user_context'], $event->getUserContext()->toArray()); + $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent($inputData)); } - public function testGetLastEvent() - { - $lastEvent = null; - - $client = ClientBuilder::create()->getClient(); - $client->addMiddleware(function (Event $event) use (&$lastEvent) { - $lastEvent = $event; - - return $event; - }); - - $client->capture(['message' => 'foo']); - - $this->assertSame($lastEvent, $client->getLastEvent()); - } - - /** - * @group legacy - * - * @expectedDeprecation The Sentry\Client::getLastEventId() method is deprecated since version 2.0. Use getLastEvent() instead. - */ - public function testGetLastEventId() + public function testCaptureLastError() { - /** @var UuidFactory|\PHPUnit_Framework_MockObject_MockObject $uuidFactory */ - $uuidFactory = $this->createMock(UuidFactory::class); - $uuidFactory->expects($this->once()) - ->method('uuid4') - ->willReturn(Uuid::fromString('ddbd643a-5190-4cce-a6ce-3098506f9d33')); - - Uuid::setFactory($uuidFactory); - - $client = ClientBuilder::create()->getClient(); - - $client->capture(['message' => 'test']); - - Uuid::setFactory(new UuidFactory()); - - $this->assertEquals('ddbd643a51904ccea6ce3098506f9d33', $client->getLastEventId()); - } + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $exception = $event->getExceptions()[0]; - public function testGetUserContext() - { - $client = ClientBuilder::create()->getClient(); + $this->assertEquals('ErrorException', $exception['type']); + $this->assertEquals('foo', $exception['value']); - $this->assertInstanceOf(Context::class, $client->getUserContext()); - } + return true; + })); - public function testGetTagsContext() - { - $client = ClientBuilder::create()->getClient(); + $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) + ->setTransport($transport) + ->getClient(); - $this->assertInstanceOf(TagsContext::class, $client->getTagsContext()); - } + @trigger_error('foo', E_USER_NOTICE); - public function testGetExtraContext() - { - $client = ClientBuilder::create()->getClient(); + $client->captureLastError(); - $this->assertInstanceOf(Context::class, $client->getExtraContext()); + $this->clearLastError(); } - public function testGetRuntimeContext() + public function testCaptureLastErrorDoesNothingWhenThereIsNoError() { - $client = ClientBuilder::create()->getClient(); + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->never()) + ->method('send'); - $this->assertInstanceOf(RuntimeContext::class, $client->getRuntimeContext()); - } + $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) + ->setTransport($transport) + ->getClient(); - public function testGetServerOsContext() - { - $client = ClientBuilder::create()->getClient(); + $this->clearLastError(); - $this->assertInstanceOf(ServerOsContext::class, $client->getServerOsContext()); + $client->captureLastError(); } public function testAppPathLinux() { $client = ClientBuilder::create(['project_root' => '/foo/bar'])->getClient(); - $this->assertEquals('/foo/bar/', $client->getConfig()->getProjectRoot()); + $this->assertEquals('/foo/bar/', $client->getOptions()->getProjectRoot()); - $client->getConfig()->setProjectRoot('/foo/baz/'); + $client->getOptions()->setProjectRoot('/foo/baz/'); - $this->assertEquals('/foo/baz/', $client->getConfig()->getProjectRoot()); + $this->assertEquals('/foo/baz/', $client->getOptions()->getProjectRoot()); } public function testAppPathWindows() { $client = ClientBuilder::create(['project_root' => 'C:\\foo\\bar\\'])->getClient(); - $this->assertEquals('C:\\foo\\bar\\', $client->getConfig()->getProjectRoot()); - } - - private function assertMixedValueAndArray($expected_value, $actual_value) - { - if (null === $expected_value) { - $this->assertNull($actual_value); - } elseif (true === $expected_value) { - $this->assertTrue($actual_value); - } elseif (false === $expected_value) { - $this->assertFalse($actual_value); - } elseif (\is_string($expected_value) || \is_numeric($expected_value)) { - $this->assertEquals($expected_value, $actual_value); - } elseif (\is_array($expected_value)) { - $this->assertInternalType('array', $actual_value); - $this->assertEquals(\count($expected_value), \count($actual_value)); - foreach ($expected_value as $key => $value) { - $this->assertArrayHasKey($key, $actual_value); - $this->assertMixedValueAndArray($value, $actual_value[$key]); - } - } elseif (\is_callable($expected_value)) { - $this->assertEquals($expected_value, $actual_value); - } elseif (\is_object($expected_value)) { - $this->assertEquals(spl_object_hash($expected_value), spl_object_hash($actual_value)); - } + $this->assertEquals('C:\\foo\\bar\\', $client->getOptions()->getProjectRoot()); } - /** - * @covers \Sentry\Client::translateSeverity - * @covers \Sentry\Client::registerSeverityMap - */ - public function testTranslateSeverity() + public function testSendChecksBeforeSendOption() { - $reflection = new \ReflectionProperty(Client::class, 'severityMap'); - $reflection->setAccessible(true); - $client = ClientBuilder::create()->getClient(); - - $predefined = [E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_CORE_ERROR, E_CORE_WARNING, - E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR, E_USER_WARNING, - E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR, ]; - $predefined[] = E_DEPRECATED; - $predefined[] = E_USER_DEPRECATED; - $predefined_values = ['debug', 'info', 'warning', 'warning', 'error', 'fatal']; - - // step 1 - foreach ($predefined as &$key) { - $this->assertContains($client->translateSeverity($key), $predefined_values); - } - $this->assertEquals('error', $client->translateSeverity(123456)); - // step 2 - $client->registerSeverityMap([]); - $this->assertMixedValueAndArray([], $reflection->getValue($client)); - foreach ($predefined as &$key) { - $this->assertContains($client->translateSeverity($key), $predefined_values); - } - $this->assertEquals('error', $client->translateSeverity(123456)); - $this->assertEquals('error', $client->translateSeverity(123456)); - // step 3 - $client->registerSeverityMap([123456 => 'foo']); - $this->assertMixedValueAndArray([123456 => 'foo'], $reflection->getValue($client)); - foreach ($predefined as &$key) { - $this->assertContains($client->translateSeverity($key), $predefined_values); - } - $this->assertEquals('foo', $client->translateSeverity(123456)); - $this->assertEquals('error', $client->translateSeverity(123457)); - // step 4 - $client->registerSeverityMap([E_USER_ERROR => 'bar']); - $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); - $this->assertEquals('error', $client->translateSeverity(123456)); - $this->assertEquals('error', $client->translateSeverity(123457)); - // step 5 - $client->registerSeverityMap([E_USER_ERROR => 'bar', 123456 => 'foo']); - $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); - $this->assertEquals('foo', $client->translateSeverity(123456)); - $this->assertEquals('error', $client->translateSeverity(123457)); - } + $beforeSendCalled = false; - public function testSendChecksShouldCaptureOption() - { - $shouldCaptureCalled = false; + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->never()) + ->method('send'); $client = ClientBuilder::create([ 'dsn' => 'http://public:secret@example.com/1', - 'should_capture' => function () use (&$shouldCaptureCalled) { - $shouldCaptureCalled = true; + 'before_send' => function () use (&$beforeSendCalled) { + $beforeSendCalled = true; - return false; + return null; }, - ])->getClient(); + ])->setTransport($transport)->getClient(); - $client->capture([]); + $client->captureEvent([]); - $this->assertTrue($shouldCaptureCalled); + $this->assertTrue($beforeSendCalled); } /** @@ -444,21 +253,6 @@ public function sampleRateAbsoluteDataProvider() ]; } - public function testSetAllObjectSerialize() - { - $client = ClientBuilder::create()->getClient(); - - $client->setAllObjectSerialize(true); - - $this->assertTrue($client->getSerializer()->getAllObjectSerialize()); - $this->assertTrue($client->getRepresentationSerializer()->getAllObjectSerialize()); - - $client->setAllObjectSerialize(false); - - $this->assertFalse($client->getSerializer()->getAllObjectSerialize()); - $this->assertFalse($client->getRepresentationSerializer()->getAllObjectSerialize()); - } - /** * @dataProvider addBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsTooLowDataProvider */ @@ -534,43 +328,251 @@ public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallb $this->assertSame([$breadcrumb2], $scope->getBreadcrumbs()); } - public function testSetSerializer() + public function testHandlingExceptionThrowingAnException() { - $client = ClientBuilder::create()->getClient(); - $serializer = $this->createMock(Serializer::class); + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function ($event) { + /* @var Event $event*/ + // Make sure the exception is of the careless exception and not the exception thrown inside + // the __set method of that exception caused by setting the event_id on the exception instance + $this->assertSame(CarelessException::class, $event->getExceptions()[0]['type']); - $client->setSerializer($serializer); + return true; + })); - $this->assertSame($serializer, $client->getSerializer()); + $client = new Client(new Options(), $transport, []); + Hub::getCurrent()->bindClient($client); + $client->captureException($this->createCarelessExceptionWithStacktrace(), Hub::getCurrent()->getScope()); } - public function testSetReprSerializer() + /** + * @dataProvider convertExceptionDataProvider + */ + public function testConvertException(\Exception $exception, array $clientConfig, array $expectedResult) { - $client = ClientBuilder::create()->getClient(); - $serializer = $this->createMock(ReprSerializer::class); + $options = new Options($clientConfig); + + $assertHasStacktrace = $options->getAutoLogStacks(); - $client->setRepresentationSerializer($serializer); + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event) use ($expectedResult, $assertHasStacktrace): bool { + $this->assertArraySubset($expectedResult, $event->toArray()); + $this->assertArrayNotHasKey('values', $event->getExceptions()); + $this->assertArrayHasKey('values', $event->toArray()['exception']); - $this->assertSame($serializer, $client->getRepresentationSerializer()); + foreach ($event->getExceptions() as $exceptionData) { + if ($assertHasStacktrace) { + $this->assertArrayHasKey('stacktrace', $exceptionData); + $this->assertInstanceOf(Stacktrace::class, $exceptionData['stacktrace']); + } else { + $this->assertArrayNotHasKey('stacktrace', $exceptionData); + } + } + + return true; + })); + + $client = new Client($options, $transport, []); + $client->captureException($exception); } - public function testHandlingExceptionThrowingAnException() + public function convertExceptionDataProvider() { - $client = ClientBuilder::create()->getClient(); - $client->captureException($this->createCarelessExceptionWithStacktrace()); - $event = $client->getLastEvent(); - // Make sure the exception is of the careless exception and not the exception thrown inside - // the __set method of that exception caused by setting the event_id on the exception instance - $this->assertSame(CarelessException::class, $event->getException()['values'][0]['type']); + return [ + [ + new \RuntimeException('foo'), + [], + [ + 'level' => Severity::ERROR, + 'exception' => [ + 'values' => [ + [ + 'type' => \RuntimeException::class, + 'value' => 'foo', + ], + ], + ], + ], + ], + [ + new \RuntimeException('foo'), + [ + 'auto_log_stacks' => false, + ], + [ + 'level' => Severity::ERROR, + 'exception' => [ + 'values' => [ + [ + 'type' => \RuntimeException::class, + 'value' => 'foo', + ], + ], + ], + ], + ], + [ + new \ErrorException('foo', 0, E_USER_WARNING), + [], + [ + 'level' => Severity::WARNING, + 'exception' => [ + 'values' => [ + [ + 'type' => \ErrorException::class, + 'value' => 'foo', + ], + ], + ], + ], + ], + [ + new \BadMethodCallException('baz', 0, new \BadFunctionCallException('bar', 0, new \LogicException('foo', 0))), + [ + 'excluded_exceptions' => [\BadMethodCallException::class], + ], + [ + 'level' => Severity::ERROR, + 'exception' => [ + 'values' => [ + [ + 'type' => \LogicException::class, + 'value' => 'foo', + ], + [ + 'type' => \BadFunctionCallException::class, + 'value' => 'bar', + ], + ], + ], + ], + ], + ]; } - private function createCarelessExceptionWithStacktrace() + public function testConvertExceptionContainingLatin1Characters() { - try { - throw new CarelessException('Foo bar'); - } catch (\Exception $ex) { - return $ex; - } + $options = new Options(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']]); + + $utf8String = 'äöü'; + $latin1String = utf8_decode($utf8String); + + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event) use ($utf8String): bool { + $expectedValue = [ + [ + 'type' => \Exception::class, + 'value' => $utf8String, + ], + ]; + + $this->assertArraySubset($expectedValue, $event->getExceptions()); + + return true; + })); + + $client = new Client($options, $transport, []); + $client->captureException(new \Exception($latin1String)); + } + + public function testConvertExceptionContainingInvalidUtf8Characters() + { + $malformedString = "\xC2\xA2\xC2"; // ill-formed 2-byte character U+00A2 (CENT SIGN) + + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $expectedValue = [ + [ + 'type' => \Exception::class, + 'value' => "\xC2\xA2\x3F", + ], + ]; + + $this->assertArraySubset($expectedValue, $event->getExceptions()); + + return true; + })); + + $client = new Client(new Options(), $transport, []); + $client->captureException(new \Exception($malformedString)); + } + + public function testConvertExceptionThrownInLatin1File() + { + $options = new Options([ + 'auto_log_stacks' => true, + 'mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8'], + ]); + + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $result = $event->getExceptions(); + $expectedValue = [ + [ + 'type' => \Exception::class, + 'value' => 'foo', + ], + ]; + + $this->assertArraySubset($expectedValue, $result); + + $latin1StringFound = false; + + /** @var \Sentry\Frame $frame */ + foreach ($result[0]['stacktrace']->getFrames() as $frame) { + if (null !== $frame->getPreContext() && \in_array('// äöü', $frame->getPreContext(), true)) { + $latin1StringFound = true; + + break; + } + } + + $this->assertTrue($latin1StringFound); + + return true; + })); + + $client = new Client($options, $transport, []); + $client->captureException(require_once __DIR__ . '/Fixtures/code/Latin1File.php'); + } + + public function testConvertExceptionWithAutoLogStacksDisabled() + { + $options = new Options(['auto_log_stacks' => false]); + + /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $result = $event->getExceptions(); + + $this->assertNotEmpty($result); + $this->assertInternalType('array', $result[0]); + $this->assertEquals(\Exception::class, $result[0]['type']); + $this->assertEquals('foo', $result[0]['value']); + $this->assertArrayNotHasKey('stacktrace', $result[0]); + + return true; + })); + + $client = new Client($options, $transport, []); + $client->captureException(new \Exception('foo')); } /** @@ -586,4 +588,13 @@ private function clearLastError() @trigger_error(''); restore_error_handler(); } + + private function createCarelessExceptionWithStacktrace() + { + try { + throw new CarelessException('Foo bar'); + } catch (\Exception $ex) { + return $ex; + } + } } diff --git a/tests/Context/UserContextTest.php b/tests/Context/UserContextTest.php index 800d2205a..efc12f329 100644 --- a/tests/Context/UserContextTest.php +++ b/tests/Context/UserContextTest.php @@ -84,6 +84,11 @@ public function gettersAndSettersDataProvider(): array 'setEmail', 'foo@bar.baz', ], + [ + 'getIpAddress', + 'setIpAddress', + '127.0.0.1', + ], ]; } diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 09d6e8e27..2ca87f753 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -18,8 +18,8 @@ class ErrorHandlerTest extends AbstractErrorHandlerTest { public function testHandleError() { - $this->client->expects($this->exactly(1)) - ->method('captureException') + $this->callbackMock->expects($this->exactly(1)) + ->method('__invoke') ->with($this->callback(function ($exception) { /* @var \ErrorException $exception */ $this->assertInstanceOf(\ErrorException::class, $exception); @@ -44,7 +44,7 @@ public function testHandleError() })); try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $errorHandler->captureAt(0, true); $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousErrorHandler'); @@ -66,8 +66,8 @@ public function testHandleError() public function testHandleErrorWithPreviousErrorHandler() { - $this->client->expects($this->once()) - ->method('captureException') + $this->callbackMock->expects($this->once()) + ->method('__invoke') ->with($this->callback(function ($exception) { /* @var \ErrorException $exception */ $this->assertInstanceOf(\ErrorException::class, $exception); @@ -98,7 +98,7 @@ public function testHandleErrorWithPreviousErrorHandler() ->willReturn(false); try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousErrorHandler'); $reflectionProperty->setAccessible(true); @@ -114,8 +114,8 @@ public function testHandleErrorWithPreviousErrorHandler() public function testHandleFatalError() { - $this->client->expects($this->exactly(1)) - ->method('captureException') + $this->callbackMock->expects($this->exactly(1)) + ->method('__invoke') ->with($this->callback(function ($exception) { /* @var \ErrorException $exception */ $this->assertInstanceOf(\ErrorException::class, $exception); @@ -128,7 +128,7 @@ public function testHandleFatalError() })); try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $errorHandler->handleFatalError([ 'type' => E_PARSE, 'message' => 'foo bar', @@ -143,11 +143,11 @@ public function testHandleFatalError() public function testHandleFatalErrorWithNonFatalErrorDoesNothing() { - $this->client->expects($this->never()) - ->method('captureException'); + $this->callbackMock->expects($this->never()) + ->method('__invoke'); try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $errorHandler->handleFatalError([ 'type' => E_USER_NOTICE, 'message' => 'foo bar', @@ -164,12 +164,12 @@ public function testHandleException() { $exception = new \Exception('foo bar'); - $this->client->expects($this->once()) - ->method('captureException') + $this->callbackMock->expects($this->once()) + ->method('__invoke') ->with($this->identicalTo($exception)); try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); try { $errorHandler->handleException($exception); @@ -188,8 +188,8 @@ public function testHandleExceptionWithPreviousExceptionHandler() { $exception = new \Exception('foo bar'); - $this->client->expects($this->once()) - ->method('captureException') + $this->callbackMock->expects($this->once()) + ->method('__invoke') ->with($this->identicalTo($exception)); $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); @@ -198,7 +198,7 @@ public function testHandleExceptionWithPreviousExceptionHandler() ->with($this->identicalTo($exception)); try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousExceptionHandler'); $reflectionProperty->setAccessible(true); @@ -223,8 +223,8 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler() $exception1 = new \Exception('foo bar'); $exception2 = new \Exception('bar foo'); - $this->client->expects($this->exactly(2)) - ->method('captureException') + $this->callbackMock->expects($this->exactly(2)) + ->method('__invoke') ->withConsecutive($this->identicalTo($exception1), $this->identicalTo($exception2)); $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); @@ -234,7 +234,7 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler() ->will($this->throwException($exception2)); try { - $errorHandler = $this->createErrorHandler($this->client); + $errorHandler = $this->createErrorHandler($this->callbackMock); $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousExceptionHandler'); $reflectionProperty->setAccessible(true); diff --git a/tests/EventTest.php b/tests/EventTest.php index 1687ff2c6..d5d183e91 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -10,12 +10,12 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\ClientInterface; -use Sentry\Configuration; use Sentry\Context\Context; use Sentry\Context\RuntimeContext; use Sentry\Context\ServerOsContext; use Sentry\Context\TagsContext; use Sentry\Event; +use Sentry\Options; use Sentry\Severity; use Sentry\Util\PHPVersion; @@ -25,8 +25,8 @@ class EventTest extends TestCase { const GENERATED_UUID = [ - '500a339f-3ab2-450b-96de-e542adf36ba7', - '4c981dd6-ad49-46be-9f16-c3b80fd25f05', + '4d310518-9e9d-463c-8161-bd46416f7817', + '431a2537-d1de-49da-80b6-b7861954c9cf', ]; protected $uuidGeneratorInvokationCount; @@ -37,9 +37,9 @@ class EventTest extends TestCase protected $originalUuidFactory; /** - * @var Configuration + * @var Options */ - protected $configuration; + protected $options; /** * @var ClientInterface @@ -51,7 +51,7 @@ protected function setUp() $this->uuidGeneratorInvokationCount = 0; $this->originalUuidFactory = new UuidFactory(); $this->client = ClientBuilder::create()->getClient(); - $this->configuration = $this->client->getConfig(); + $this->options = $this->client->getOptions(); /** @var UuidFactoryInterface|\PHPUnit_Framework_MockObject_MockObject $uuidFactory */ $uuidFactory = $this->getMockBuilder(UuidFactoryInterface::class) @@ -75,16 +75,16 @@ protected function tearDown() public function testEventIsGeneratedWithUniqueIdentifier() { - $event1 = new Event($this->configuration); - $event2 = new Event($this->configuration); + $event1 = new Event(); + $event2 = new Event(); - $this->assertEquals(static::GENERATED_UUID[0], $event1->getId()->toString()); - $this->assertEquals(static::GENERATED_UUID[1], $event2->getId()->toString()); + $this->assertEquals(str_replace('-', '', static::GENERATED_UUID[0]), $event1->getId()); + $this->assertEquals(str_replace('-', '', static::GENERATED_UUID[1]), $event2->getId()); } public function testToArray() { - $this->configuration->setRelease('1.2.3-dev'); + $this->options->setRelease('1.2.3-dev'); $expected = [ 'event_id' => str_replace('-', '', static::GENERATED_UUID[0]), @@ -92,12 +92,9 @@ public function testToArray() 'level' => 'error', 'platform' => 'php', 'sdk' => [ - 'name' => 'sentry-php', + 'name' => Client::SDK_IDENTIFIER, 'version' => Client::VERSION, ], - 'server_name' => $this->configuration->getServerName(), - 'release' => $this->configuration->getRelease(), - 'environment' => $this->configuration->getCurrentEnvironment(), 'contexts' => [ 'os' => [ 'name' => php_uname('s'), @@ -112,14 +109,14 @@ public function testToArray() ], ]; - $event = new Event($this->configuration); + $event = new Event(); $this->assertEquals($expected, $event->toArray()); } public function testToArrayWithMessage() { - $event = new Event($this->configuration); + $event = new Event(); $event->setMessage('foo bar'); $data = $event->toArray(); @@ -136,7 +133,7 @@ public function testToArrayWithMessageWithParams() 'formatted' => 'foo bar', ]; - $event = new Event($this->configuration); + $event = new Event(); $event->setMessage('foo %s', ['bar']); $data = $event->toArray(); @@ -152,51 +149,48 @@ public function testToArrayWithBreadcrumbs() new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'bar'), ]; - $event = new Event($this->configuration); - - foreach ($breadcrumbs as $breadcrumb) { - $event->setBreadcrumb($breadcrumb); - } + $event = new Event(); + $event->setBreadcrumb($breadcrumbs); $this->assertSame($breadcrumbs, $event->getBreadcrumbs()); $data = $event->toArray(); $this->assertArrayHasKey('breadcrumbs', $data); - $this->assertSame($breadcrumbs, $data['breadcrumbs']); + $this->assertSame($breadcrumbs, $data['breadcrumbs']['values']); } public function testGetServerOsContext() { - $event = new Event($this->configuration); + $event = new Event(); $this->assertInstanceOf(ServerOsContext::class, $event->getServerOsContext()); } public function testGetRuntimeContext() { - $event = new Event($this->configuration); + $event = new Event(); $this->assertInstanceOf(RuntimeContext::class, $event->getRuntimeContext()); } public function testGetUserContext() { - $event = new Event($this->configuration); + $event = new Event(); $this->assertInstanceOf(Context::class, $event->getUserContext()); } public function testGetExtraContext() { - $event = new Event($this->configuration); + $event = new Event(); $this->assertInstanceOf(Context::class, $event->getExtraContext()); } public function getTagsContext() { - $event = new Event($this->configuration); + $event = new Event(); $this->assertInstanceOf(TagsContext::class, $event->getTagsContext()); } @@ -209,7 +203,7 @@ public function testGettersAndSetters($propertyName, $propertyValue, $expectedVa $getterMethod = 'get' . ucfirst($propertyName); $setterMethod = 'set' . ucfirst($propertyName); - $event = new Event($this->configuration); + $event = new Event(); $event->$setterMethod($propertyValue); $this->assertEquals($event->$getterMethod(), $propertyValue); @@ -232,7 +226,7 @@ public function gettersAndSettersDataProvider() public function testEventJsonSerialization() { - $event = new Event($this->configuration); + $event = new Event(); $encodingOfToArray = json_encode($event->toArray()); $serializedEvent = json_encode($event); diff --git a/tests/HttpClient/Authentication/SentryAuthTest.php b/tests/HttpClient/Authentication/SentryAuthTest.php index 7840e2882..bdf4f9000 100644 --- a/tests/HttpClient/Authentication/SentryAuthTest.php +++ b/tests/HttpClient/Authentication/SentryAuthTest.php @@ -14,8 +14,8 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; use Sentry\Client; -use Sentry\Configuration; use Sentry\HttpClient\Authentication\SentryAuth; +use Sentry\Options; /** * @group time-sensitive @@ -24,7 +24,7 @@ class SentryAuthTest extends TestCase { public function testAuthenticate() { - $configuration = new Configuration(['dsn' => 'http://public:secret@example.com/']); + $configuration = new Options(['dsn' => 'http://public:secret@example.com/']); $authentication = new SentryAuth($configuration); /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $request */ @@ -52,7 +52,7 @@ public function testAuthenticate() public function testAuthenticateWithNoSecretKey() { - $configuration = new Configuration(['dsn' => 'http://public@example.com/']); + $configuration = new Options(['dsn' => 'http://public@example.com/']); $authentication = new SentryAuth($configuration); /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $request */ diff --git a/tests/HttpClient/Encoding/Base64EncodingStreamTest.php b/tests/HttpClient/Encoding/Base64EncodingStreamTest.php deleted file mode 100644 index 2d34fc61d..000000000 --- a/tests/HttpClient/Encoding/Base64EncodingStreamTest.php +++ /dev/null @@ -1,72 +0,0 @@ -getMockBuilder(StreamInterface::class) - ->getMock(); - - $stream->expects($this->once()) - ->method('getSize') - ->willReturn($decodedSize); - - $encodingStream = new Base64EncodingStream($stream); - - $stream->write($content); - - $this->assertSame($encodedSize, $encodingStream->getSize()); - } - - public function getSizeDataProvider() - { - return [ - ['', null, null], - ['foo', 3, 4], - ['foo bar', 7, 12], - ]; - } - - /** - * @dataProvider readDataProvider - */ - public function testRead($decoded, $encoded) - { - $stream = new Stream('php://memory', 'r+'); - $encodingStream = new Base64EncodingStream($stream); - - $stream->write($decoded); - $stream->rewind(); - - $this->assertSame($encoded, $encodingStream->getContents()); - } - - public function readDataProvider() - { - return [ - ['', ''], - ['foo', 'Zm9v'], - ['foo bar', 'Zm9vIGJhcg=='], - ]; - } -} diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php new file mode 100644 index 000000000..add4c3b12 --- /dev/null +++ b/tests/Integration/ModulesIntegrationTest.php @@ -0,0 +1,25 @@ + __DIR__ . '/../Fixtures']); + $event = new Event(); + + $integration = new ModulesIntegration($options); + + ModulesIntegration::applyToEvent($integration, $event); + + $this->assertEquals(['foo/bar' => '1.2.3.0', 'foo/baz' => '4.5.6.0'], $event->getModules()); + } +} diff --git a/tests/Middleware/RequestInterfaceMiddlewareTest.php b/tests/Integration/RequestIntegrationTest.php similarity index 74% rename from tests/Middleware/RequestInterfaceMiddlewareTest.php rename to tests/Integration/RequestIntegrationTest.php index 29f9f1b10..d240e978c 100644 --- a/tests/Middleware/RequestInterfaceMiddlewareTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -9,41 +9,22 @@ * file that was distributed with this source code. */ -namespace Sentry\Tests\Middleware; +namespace Sentry\Tests\Integration; -use Sentry\Configuration; +use PHPUnit\Framework\TestCase; use Sentry\Event; -use Sentry\Middleware\RequestInterfaceMiddleware; +use Sentry\Integration\RequestIntegration; use Zend\Diactoros\ServerRequest; use Zend\Diactoros\Uri; -class RequestInterfaceMiddlewareTest extends MiddlewareTestCase +class RequestIntegrationTest extends TestCase { - public function testInvokeWithNoRequest() - { - $configuration = new Configuration(); - $event = new Event($configuration); - - $callbackInvoked = false; - $callback = function (Event $eventArg) use ($event, &$callbackInvoked) { - $this->assertSame($event, $eventArg); - - $callbackInvoked = true; - }; - - $middleware = new RequestInterfaceMiddleware(); - $middleware($event, $callback); - - $this->assertTrue($callbackInvoked, 'Next middleware NOT invoked'); - } - /** * @dataProvider invokeDataProvider */ public function testInvoke(array $requestData, array $expectedValue) { - $configuration = new Configuration(); - $event = new Event($configuration); + $event = new Event(); $request = new ServerRequest(); $request = $request->withUri(new Uri($requestData['uri'])); @@ -54,11 +35,22 @@ public function testInvoke(array $requestData, array $expectedValue) $request = $request->withHeader($name, $value); } - $middleware = new RequestInterfaceMiddleware(); + RequestIntegration::applyToEvent($event, $request); + + $this->assertEquals($expectedValue, $event->getRequest()); + } + + public function testInvokeWithRequestHavingIpAddress() + { + $event = new Event(); + $event->getUserContext()->setData(['foo' => 'bar']); + + $request = new ServerRequest(); + $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, $request); + RequestIntegration::applyToEvent($event, $request); - $this->assertEquals($expectedValue, $returnedEvent->getRequest()); + $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $event->getUserContext()->toArray()); } public function invokeDataProvider() diff --git a/tests/Middleware/ContextInterfaceMiddlewareTest.php b/tests/Middleware/ContextInterfaceMiddlewareTest.php deleted file mode 100644 index 80898500c..000000000 --- a/tests/Middleware/ContextInterfaceMiddlewareTest.php +++ /dev/null @@ -1,121 +0,0 @@ -expectException(\RuntimeException::class); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $middleware = new ContextInterfaceMiddleware(new Context($initialData), $contextName); - $payload = [ - $contextName . '_context' => $payloadData, - ]; - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, null, null, null, $payload); - - $method = preg_replace_callback('/_[a-zA-Z]/', function ($matches) { - return strtoupper($matches[0][1]); - }, 'get_' . $contextName . '_context'); - - $this->assertEquals($expectedData, $returnedEvent->$method()->toArray()); - } - - public function invokeDataProvider() - { - return [ - [ - Context::CONTEXT_USER, - [ - 'foo' => 'bar', - 'foobaz' => 'bazfoo', - ], - [ - 'foobaz' => 'bazfoo', - ], - [ - 'foo' => 'bar', - 'foobaz' => 'bazfoo', - ], - null, - ], - [ - Context::CONTEXT_RUNTIME, - [ - 'name' => 'foo', - ], - [ - 'name' => 'foobar', - ], - [ - 'name' => 'foobar', - 'version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION, - ], - null, - ], - [ - Context::CONTEXT_TAGS, - ['foo', 'bar'], - ['foobar'], - ['foo', 'bar', 'foobar'], - null, - ], - [ - Context::CONTEXT_EXTRA, - [ - 'bar' => 'foo', - ], - [ - 'barbaz' => 'bazbar', - ], - [ - 'bar' => 'foo', - 'barbaz' => 'bazbar', - ], - null, - ], - [ - Context::CONTEXT_SERVER_OS, - [ - 'name' => 'baz', - ], - [ - 'name' => 'foobaz', - ], - [ - 'name' => 'foobaz', - 'version' => php_uname('r'), - 'build' => php_uname('v'), - 'kernel_version' => php_uname('a'), - ], - null, - ], - [ - 'foo', - [], - [], - [], - 'The "foo" context is not supported.', - ], - ]; - } -} diff --git a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php b/tests/Middleware/ExceptionInterfaceMiddlewareTest.php deleted file mode 100644 index c8bc613e8..000000000 --- a/tests/Middleware/ExceptionInterfaceMiddlewareTest.php +++ /dev/null @@ -1,232 +0,0 @@ -getClient(); - $assertHasStacktrace = $client->getConfig()->getAutoLogStacks(); - - $event = new Event($client->getConfig()); - $middleware = new ExceptionInterfaceMiddleware($client); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, $exception, $payload); - - $this->assertArraySubset($expectedResult, $returnedEvent->toArray()); - - foreach ($returnedEvent->getException()['values'] as $exceptionData) { - if ($assertHasStacktrace) { - $this->assertArrayHasKey('stacktrace', $exceptionData); - $this->assertInstanceOf(Stacktrace::class, $exceptionData['stacktrace']); - } else { - $this->assertArrayNotHasKey('stacktrace', $exceptionData); - } - } - } - - public function invokeDataProvider() - { - return [ - [ - new \RuntimeException('foo'), - [], - [], - [ - 'level' => Client::LEVEL_ERROR, - 'exception' => [ - 'values' => [ - [ - 'type' => \RuntimeException::class, - 'value' => 'foo', - ], - ], - ], - ], - ], - [ - new \RuntimeException('foo'), - [ - 'auto_log_stacks' => false, - ], - [], - [ - 'level' => Client::LEVEL_ERROR, - 'exception' => [ - 'values' => [ - [ - 'type' => \RuntimeException::class, - 'value' => 'foo', - ], - ], - ], - ], - ], - [ - new \ErrorException('foo', 0, E_USER_WARNING), - [], - [], - [ - 'level' => Client::LEVEL_WARNING, - 'exception' => [ - 'values' => [ - [ - 'type' => \ErrorException::class, - 'value' => 'foo', - ], - ], - ], - ], - ], - [ - new \BadMethodCallException('baz', 0, new \BadFunctionCallException('bar', 0, new \LogicException('foo', 0))), - [ - 'excluded_exceptions' => [\BadMethodCallException::class], - ], - [], - [ - 'level' => Client::LEVEL_ERROR, - 'exception' => [ - 'values' => [ - [ - 'type' => \LogicException::class, - 'value' => 'foo', - ], - [ - 'type' => \BadFunctionCallException::class, - 'value' => 'bar', - ], - ], - ], - ], - ], - ]; - } - - public function testInvokeWithExceptionContainingLatin1Characters() - { - $client = ClientBuilder::create(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']]) - ->getClient(); - - $event = new Event($client->getConfig()); - $utf8String = 'äöü'; - $latin1String = utf8_decode($utf8String); - - $middleware = new ExceptionInterfaceMiddleware($client); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, new \Exception($latin1String)); - - $expectedValue = [ - 'values' => [ - [ - 'type' => \Exception::class, - 'value' => $utf8String, - ], - ], - ]; - - $this->assertArraySubset($expectedValue, $returnedEvent->getException()); - } - - public function testInvokeWithExceptionContainingInvalidUtf8Characters() - { - $client = ClientBuilder::create()->getClient(); - $event = new Event($client->getConfig()); - - $middleware = new ExceptionInterfaceMiddleware($client); - - $malformedString = "\xC2\xA2\xC2"; // ill-formed 2-byte character U+00A2 (CENT SIGN) - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, new \Exception($malformedString)); - - $expectedValue = [ - 'values' => [ - [ - 'type' => \Exception::class, - 'value' => "\xC2\xA2\x3F", - ], - ], - ]; - - $this->assertArraySubset($expectedValue, $returnedEvent->getException()); - } - - public function testInvokeWithExceptionThrownInLatin1File() - { - $client = ClientBuilder::create([ - 'auto_log_stacks' => true, - 'mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8'], - ])->getClient(); - - $event = new Event($client->getConfig()); - - $middleware = new ExceptionInterfaceMiddleware($client); - - $returnedEvent = $this->assertMiddlewareInvokesNext( - $middleware, - $event, - null, - require_once __DIR__ . '/../Fixtures/code/Latin1File.php' - ); - - $result = $returnedEvent->getException(); - $expectedValue = [ - 'values' => [ - [ - 'type' => \Exception::class, - 'value' => 'foo', - ], - ], - ]; - - $this->assertArraySubset($expectedValue, $result); - - $latin1StringFound = false; - - /** @var \Sentry\Frame $frame */ - foreach ($result['values'][0]['stacktrace']->getFrames() as $frame) { - if (null !== $frame->getPreContext() && \in_array('// äöü', $frame->getPreContext(), true)) { - $latin1StringFound = true; - - break; - } - } - - $this->assertTrue($latin1StringFound); - } - - public function testInvokeWithAutoLogStacksDisabled() - { - $client = ClientBuilder::create(['auto_log_stacks' => false])->getClient(); - $event = new Event($client->getConfig()); - - $middleware = new ExceptionInterfaceMiddleware($client); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, new \Exception('foo')); - - $result = $returnedEvent->getException(); - $this->assertNotEmpty($result); - $this->assertInternalType('array', $result['values'][0]); - $this->assertEquals(\Exception::class, $result['values'][0]['type']); - $this->assertEquals('foo', $result['values'][0]['value']); - $this->assertArrayNotHasKey('stacktrace', $result['values'][0]); - } -} diff --git a/tests/Middleware/MessageInterfaceMiddlewareTest.php b/tests/Middleware/MessageInterfaceMiddlewareTest.php deleted file mode 100644 index 089c2b5a4..000000000 --- a/tests/Middleware/MessageInterfaceMiddlewareTest.php +++ /dev/null @@ -1,65 +0,0 @@ -assertMiddlewareInvokesNext($middleware, $event); - - $this->assertSame($event, $returnedEvent); - } - - /** - * @dataProvider invokeDataProvider - */ - public function testInvoke(array $payload) - { - $configuration = new Configuration(); - $event = new Event($configuration); - - $middleware = new MessageInterfaceMiddleware(); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, null, null, $payload); - - $this->assertEquals($payload['message'], $returnedEvent->getMessage()); - $this->assertEquals($payload['message_params'], $returnedEvent->getMessageParams()); - } - - public function invokeDataProvider() - { - return [ - [ - [ - 'message' => 'foo %s', - 'message_params' => [], - ], - ], - [ - [ - 'message' => 'foo %s', - 'message_params' => ['bar'], - ], - ], - ]; - } -} diff --git a/tests/Middleware/MiddlewareStackTest.php b/tests/Middleware/MiddlewareStackTest.php deleted file mode 100644 index 1f773cea9..000000000 --- a/tests/Middleware/MiddlewareStackTest.php +++ /dev/null @@ -1,214 +0,0 @@ -createMock(ServerRequestInterface::class); - - $capturedException = new \Exception(); - $capturedPayload = ['foo' => 'bar']; - - $handlerCalled = false; - $handler = function (Event $event, ServerRequestInterface $request = null, $exception = null, array $payload = []) use (&$handlerCalled, $capturedRequest, $capturedException, $capturedPayload) { - // These asserts verify that the arguments passed through all - // middlewares without getting lost - $this->assertSame($capturedRequest, $request); - $this->assertSame($capturedException, $exception); - $this->assertSame($capturedPayload, $payload); - - $handlerCalled = true; - - return $event; - }; - - $middlewareCalls = [false, false]; - - $middlewareStack = new MiddlewareStack($handler); - $middlewareStack->addMiddleware($this->createMiddlewareAssertingInvokation($middlewareCalls[0])); - $middlewareStack->addMiddleware($this->createMiddlewareAssertingInvokation($middlewareCalls[1])); - - $middlewareStack->executeStack($event, $capturedRequest, $capturedException, $capturedPayload); - - $this->assertTrue($handlerCalled); - $this->assertNotContains(false, $middlewareCalls); - } - - public function testAddMiddleware() - { - $middlewareCalls = []; - - $handler = function (Event $event) use (&$middlewareCalls) { - $middlewareCalls[] = 4; - - return $event; - }; - - $middleware1 = function (Event $event, callable $next) use (&$middlewareCalls) { - $middlewareCalls[] = 1; - - return $next($event); - }; - - $middleware2 = function (Event $event, callable $next) use (&$middlewareCalls) { - $middlewareCalls[] = 2; - - return $next($event); - }; - - $middleware3 = function (Event $event, callable $next) use (&$middlewareCalls) { - $middlewareCalls[] = 3; - - return $next($event); - }; - - $middlewareStack = new MiddlewareStack($handler); - $middlewareStack->addMiddleware($middleware1, -10); - $middlewareStack->addMiddleware($middleware2); - $middlewareStack->addMiddleware($middleware3, -10); - - $middlewareStack->executeStack(new Event(new Configuration())); - - $this->assertEquals([2, 3, 1, 4], $middlewareCalls); - } - - /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Middleware can't be added once the stack is dequeuing. - */ - public function testAddMiddlewareThrowsWhileStackIsRunning() - { - /** @var MiddlewareStack $middlewareStack */ - $middlewareStack = null; - - $middlewareStack = new MiddlewareStack(function () use (&$middlewareStack) { - $middlewareStack->addMiddleware(function () { - // Returning something is not important as the expected exception - // should be thrown before this point is ever reached - }); - }); - - $middlewareStack->executeStack(new Event(new Configuration())); - } - - public function testRemoveMiddleware() - { - $middlewareCalls = []; - - $middleware1 = function (Event $event, callable $next) use (&$middlewareCalls) { - $middlewareCalls[] = 1; - - return $next($event); - }; - - $middleware2 = function (Event $event, callable $next) use (&$middlewareCalls) { - $middlewareCalls[] = 2; - - return $next($event); - }; - - $middleware3 = function (Event $event, callable $next) use (&$middlewareCalls) { - $middlewareCalls[] = 3; - - return $next($event); - }; - - $middlewareStack = new MiddlewareStack(function (Event $event) use (&$middlewareCalls) { - $middlewareCalls[] = 4; - - return $event; - }); - - $this->assertFalse($middlewareStack->removeMiddleware($middleware1)); - - $middlewareStack->addMiddleware($middleware1, -10); - $middlewareStack->addMiddleware($middleware2); - $middlewareStack->addMiddleware($middleware3, -10); - - $this->assertTrue($middlewareStack->removeMiddleware($middleware3)); - - $middlewareStack->executeStack(new Event(new Configuration())); - - $this->assertEquals([2, 1, 4], $middlewareCalls); - } - - /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Middleware can't be removed once the stack is dequeuing. - */ - public function testRemoveMiddlewareThrowsWhileStackIsRunning() - { - /** @var MiddlewareStack $middlewareStack */ - $middlewareStack = null; - - $middlewareStack = new MiddlewareStack(function () use (&$middlewareStack) { - $middlewareStack->removeMiddleware(function () { - // Returning something is not important as the expected exception - // should be thrown before this point is ever reached - }); - }); - - $middlewareStack->executeStack(new Event(new Configuration())); - } - - /** - * @expectedException \UnexpectedValueException - * @expectedExceptionMessage Middleware must return an instance of the "Sentry\Event" class. - */ - public function testMiddlewareThrowsWhenBadValueIsReturned() - { - $event = new Event(new Configuration()); - $middlewareStack = new MiddlewareStack(function (Event $event) { - return $event; - }); - - $middlewareStack->addMiddleware(function () { - // Return nothing so that the expected exception is triggered - }); - - $middlewareStack->executeStack($event); - } - - /** - * @expectedException \UnexpectedValueException - * @expectedExceptionMessage Middleware must return an instance of the "Sentry\Event" class. - */ - public function testMiddlewareThrowsWhenBadValueIsReturnedFromHandler() - { - $event = new Event(new Configuration()); - $middlewareStack = new MiddlewareStack(function () { - // Return nothing so that the expected exception is triggered - }); - - $middlewareStack->executeStack($event); - } - - protected function createMiddlewareAssertingInvokation(&$wasCalled) - { - return function (Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) use (&$wasCalled) { - $wasCalled = true; - - return $next($event, $request, $exception, $payload); - }; - } -} diff --git a/tests/Middleware/MiddlewareTestCase.php b/tests/Middleware/MiddlewareTestCase.php deleted file mode 100644 index ad7938009..000000000 --- a/tests/Middleware/MiddlewareTestCase.php +++ /dev/null @@ -1,53 +0,0 @@ -assertInstanceOf(\Exception::class, $passedException); - } - - $callbackInvoked = true; - - return $passedEvent; - }; - - if (null === $event) { - $event = new Event($this->createMock(Configuration::class)); - } - - if (null === $request) { - $request = $this->createMock(ServerRequestInterface::class); - } - - if (null === $exception) { - $exception = new \Exception(); - } - - $returnedEvent = $middleware($event, $callback, $request, $exception, $payload); - - $this->assertTrue($callbackInvoked, 'Next middleware was not invoked'); - $this->assertSame($event, $returnedEvent); - - return $returnedEvent; - } -} diff --git a/tests/Middleware/ModulesMiddlewareTest.php b/tests/Middleware/ModulesMiddlewareTest.php deleted file mode 100644 index c538c8d50..000000000 --- a/tests/Middleware/ModulesMiddlewareTest.php +++ /dev/null @@ -1,31 +0,0 @@ - __DIR__ . '/../Fixtures']); - $event = new Event($configuration); - - $middleware = new ModulesMiddleware($configuration); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - $this->assertEquals(['foo/bar' => '1.2.3.0', 'foo/baz' => '4.5.6.0'], $returnedEvent->getModules()); - } -} diff --git a/tests/Middleware/RemoveHttpBodyMiddlewareTest.php b/tests/Middleware/RemoveHttpBodyMiddlewareTest.php deleted file mode 100644 index d8274d4c4..000000000 --- a/tests/Middleware/RemoveHttpBodyMiddlewareTest.php +++ /dev/null @@ -1,114 +0,0 @@ -client = ClientBuilder::create()->getClient(); - $this->middleware = new RemoveHttpBodyMiddleware(); - } - - /** - * @dataProvider invokeDataProvider - */ - public function testInvoke($inputData, $expectedData) - { - $event = new Event($this->client->getConfig()); - $event->setRequest($inputData); - - $middleware = new RemoveHttpBodyMiddleware(); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - $this->assertArraySubset($expectedData, $returnedEvent->getRequest()); - } - - public function invokeDataProvider() - { - return [ - [ - [ - 'method' => 'POST', - 'data' => [ - 'foo' => 'bar', - ], - ], - [ - 'data' => RemoveHttpBodyMiddleware::STRING_MASK, - ], - ], - [ - [ - 'method' => 'PUT', - 'data' => [ - 'foo' => 'bar', - ], - ], - [ - 'data' => RemoveHttpBodyMiddleware::STRING_MASK, - ], - ], - [ - [ - 'method' => 'PATCH', - 'data' => [ - 'foo' => 'bar', - ], - ], - [ - 'data' => RemoveHttpBodyMiddleware::STRING_MASK, - ], - ], - [ - [ - 'method' => 'DELETE', - 'data' => [ - 'foo' => 'bar', - ], - ], - [ - 'data' => RemoveHttpBodyMiddleware::STRING_MASK, - ], - ], - [ - [ - 'method' => 'GET', - 'data' => [ - 'foo' => 'bar', - ], - ], - [ - 'data' => [ - 'foo' => 'bar', - ], - ], - ], - ]; - } -} diff --git a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php b/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php deleted file mode 100644 index 625537903..000000000 --- a/tests/Middleware/RemoveStacktraceContextMiddlewareTest.php +++ /dev/null @@ -1,73 +0,0 @@ -client = ClientBuilder::create(['auto_log_stacks' => true]) - ->getClient(); - } - - public function testInvoke() - { - $exception = new \Exception(); - - $event = new Event($this->client->getConfig()); - $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception->getTrace(), $exception->getFile(), $exception->getLine())); - - $middleware = new RemoveStacktraceContextMiddleware(); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - $this->assertNotNull($returnedEvent->getStacktrace()); - - foreach ($returnedEvent->getStacktrace()->getFrames() as $frame) { - $this->assertEmpty($frame->getPreContext()); - $this->assertNull($frame->getContextLine()); - $this->assertEmpty($frame->getPostContext()); - } - } - - public function testInvokeWithPreviousException() - { - $exception1 = new \Exception(); - $exception2 = new \Exception('foo', 0, $exception1); - - $event = new Event($this->client->getConfig()); - $event->setStacktrace(Stacktrace::createFromBacktrace($this->client, $exception2->getTrace(), $exception2->getFile(), $exception2->getLine())); - - $middleware = new RemoveStacktraceContextMiddleware(); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - $this->assertNotNull($returnedEvent->getStacktrace()); - - foreach ($returnedEvent->getStacktrace()->getFrames() as $frame) { - $this->assertEmpty($frame->getPreContext()); - $this->assertNull($frame->getContextLine()); - $this->assertEmpty($frame->getPostContext()); - } - } -} diff --git a/tests/Middleware/SanitizeCookiesMiddlewareTest.php b/tests/Middleware/SanitizeCookiesMiddlewareTest.php deleted file mode 100644 index 9f658b6c7..000000000 --- a/tests/Middleware/SanitizeCookiesMiddlewareTest.php +++ /dev/null @@ -1,108 +0,0 @@ - ['foo'], - 'except' => ['bar'], - ]); - } - - /** - * @dataProvider invokeDataProvider - */ - public function testInvoke(array $options, array $expectedData) - { - $event = new Event(new Configuration()); - $event->setRequest([ - 'foo' => 'bar', - 'cookies' => [ - 'foo' => 'bar', - 'bar' => 'foo', - ], - 'headers' => [ - 'cookie' => 'bar', - 'another-header' => 'foo', - ], - ]); - - $middleware = new SanitizeCookiesMiddleware($options); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - $request = $returnedEvent->getRequest(); - - $this->assertArraySubset($expectedData, $request); - $this->assertArrayNotHasKey('cookie', $request['headers']); - } - - public function invokeDataProvider() - { - return [ - [ - [], - [ - 'foo' => 'bar', - 'cookies' => [ - 'foo' => SanitizeCookiesMiddleware::STRING_MASK, - 'bar' => SanitizeCookiesMiddleware::STRING_MASK, - ], - 'headers' => [ - 'another-header' => 'foo', - ], - ], - ], - [ - [ - 'only' => ['foo'], - ], - [ - 'foo' => 'bar', - 'cookies' => [ - 'foo' => SanitizeCookiesMiddleware::STRING_MASK, - 'bar' => 'foo', - ], - 'headers' => [ - 'another-header' => 'foo', - ], - ], - ], - [ - [ - 'except' => ['foo'], - ], - [ - 'foo' => 'bar', - 'cookies' => [ - 'foo' => 'bar', - 'bar' => SanitizeCookiesMiddleware::STRING_MASK, - ], - 'headers' => [ - 'another-header' => 'foo', - ], - ], - ], - ]; - } -} diff --git a/tests/Middleware/SanitizeDataMiddlewareTest.php b/tests/Middleware/SanitizeDataMiddlewareTest.php deleted file mode 100644 index 40440691e..000000000 --- a/tests/Middleware/SanitizeDataMiddlewareTest.php +++ /dev/null @@ -1,251 +0,0 @@ -client = ClientBuilder::create()->getClient(); - } - - /** - * @dataProvider invokeDataProvider - */ - public function testInvoke(array $inputData, array $expectedData) - { - $event = new Event($this->client->getConfig()); - - if (isset($inputData['request'])) { - $event->setRequest($inputData['request']); - } - - if (isset($inputData['extra_context'])) { - $event->getExtraContext()->replaceData($inputData['extra_context']); - } - - if (isset($inputData['exception'])) { - // We must convert the backtrace to a Stacktrace instance here because - // PHPUnit executes the data provider before the setUp method and so - // the client instance cannot be accessed from there - $event->setException($this->convertExceptionValuesToStacktrace($expectedData['exception'])); - } - - $middleware = new SanitizeDataMiddleware(); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - if (isset($expectedData['request'])) { - $this->assertArraySubset($expectedData['request'], $returnedEvent->getRequest()); - } - - if (isset($expectedData['extra_context'])) { - $this->assertArraySubset($expectedData['extra_context'], $returnedEvent->getExtraContext()); - } - - if (isset($expectedData['exception'])) { - // We must convert the backtrace to a Stacktrace instance here because - // PHPUnit executes the data provider before the setUp method and so - // the client instance cannot be accessed from there - $this->assertArraySubset($this->convertExceptionValuesToStacktrace($expectedData['exception']), $returnedEvent->getException()); - } - } - - public function invokeDataProvider() - { - return [ - [ - [ - 'request' => [ - 'data' => [ - 'foo' => 'bar', - 'password' => 'hello', - 'the_secret' => 'hello', - 'a_password_here' => 'hello', - 'mypasswd' => 'hello', - 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', - ], - ], - ], - [ - 'request' => [ - 'data' => [ - 'foo' => 'bar', - 'password' => SanitizeDataMiddleware::STRING_MASK, - 'the_secret' => SanitizeDataMiddleware::STRING_MASK, - 'a_password_here' => SanitizeDataMiddleware::STRING_MASK, - 'mypasswd' => SanitizeDataMiddleware::STRING_MASK, - 'authorization' => SanitizeDataMiddleware::STRING_MASK, - ], - ], - ], - ], - [ - [ - 'request' => [ - 'cookies' => [ - ini_get('session.name') => 'abc', - ], - ], - ], - [ - 'request' => [ - 'cookies' => [ - ini_get('session.name') => SanitizeDataMiddleware::STRING_MASK, - ], - ], - ], - ], - [ - [ - 'extra_context' => [ - 'ccnumba' => str_repeat('9', 13), - ], - ], - [ - 'extra_context' => [ - 'ccnumba' => SanitizeDataMiddleware::STRING_MASK, - ], - ], - ], - [ - [ - 'extra_context' => [ - 'ccnumba' => str_repeat('9', 19), - ], - ], - [ - 'extra_context' => [ - 'ccnumba' => SanitizeDataMiddleware::STRING_MASK, - ], - ], - ], - [ - [ - 'exception' => [ - 'values' => [ - [ - 'stacktrace' => [ - [ - 'args' => [ - [ - 'password' => 'foo', - ], - ], - ], - ], - ], - [ - 'stacktrace' => [ - [ - 'args' => [ - [ - 'password' => 'foo', - ], - ], - ], - ], - ], - ], - ], - ], - [ - 'exception' => [ - 'values' => [ - [ - 'stacktrace' => [ - [ - 'args' => [ - [ - 'password' => SanitizeDataMiddleware::STRING_MASK, - ], - ], - ], - ], - ], - [ - 'stacktrace' => [ - [ - 'args' => [ - [ - 'password' => SanitizeDataMiddleware::STRING_MASK, - ], - ], - ], - ], - ], - ], - ], - ], - ], - [ - [ - 'extra_context' => [ - 'foobar' => 'some-data', - 'authorization' => [ - 'foo' => 'secret1', - 'bar' => 'secret2', - 'baz' => [ - 'nested1' => 'nestedSecret1', - 'nested2' => 'nestedSecret2', - 'nested3' => [ - 'deep' => 'nestedSecret2', - ], - ], - ], - 'foobaz' => 'some-data', - ], - ], - [ - 'extra_context' => [ - 'foobar' => 'some-data', - 'authorization' => [ - 'foo' => SanitizeDataMiddleware::STRING_MASK, - 'bar' => SanitizeDataMiddleware::STRING_MASK, - 'baz' => [ - 'nested1' => SanitizeDataMiddleware::STRING_MASK, - 'nested2' => SanitizeDataMiddleware::STRING_MASK, - 'nested3' => [ - 'deep' => SanitizeDataMiddleware::STRING_MASK, - ], - ], - ], - 'foobaz' => 'some-data', - ], - ], - ], - ]; - } - - private function convertExceptionValuesToStacktrace($exceptionValues) - { - foreach ($exceptionValues['values'] as &$exceptionValue) { - $exceptionValue['stacktrace'] = Stacktrace::createFromBacktrace($this->client, $exceptionValue['stacktrace'], 'foo', 1); - } - - // Free the memory from the reference - unset($exceptionValue); - - return $exceptionValues; - } -} diff --git a/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php b/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php deleted file mode 100644 index 8a2efae8c..000000000 --- a/tests/Middleware/SanitizeHttpHeadersMiddlewareTest.php +++ /dev/null @@ -1,58 +0,0 @@ -setRequest($inputData); - - $middleware = new SanitizeHttpHeadersMiddleware([ - 'sanitize_http_headers' => ['User-Defined-Header'], - ]); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - $this->assertArraySubset($expectedData, $returnedEvent->getRequest()); - } - - public function invokeDataProvider() - { - return [ - [ - [ - 'headers' => [ - 'Authorization' => 'foo', - 'AnotherHeader' => 'bar', - 'User-Defined-Header' => 'baz', - ], - ], - [ - 'headers' => [ - 'Authorization' => 'foo', - 'AnotherHeader' => 'bar', - 'User-Defined-Header' => SanitizeHttpHeadersMiddleware::STRING_MASK, - ], - ], - ], - ]; - } -} diff --git a/tests/Middleware/SanitizerMiddlewareTest.php b/tests/Middleware/SanitizerMiddlewareTest.php deleted file mode 100644 index 515904e62..000000000 --- a/tests/Middleware/SanitizerMiddlewareTest.php +++ /dev/null @@ -1,54 +0,0 @@ -setRequest(['bar' => 'baz']); - $event->getUserContext()->replaceData(['foo' => 'bar']); - $event->getTagsContext()->replaceData(['foo', 'bar']); - $event->getServerOsContext()->replaceData(['name' => 'foo']); - $event->getRuntimeContext()->replaceData(['name' => 'baz']); - $event->getExtraContext()->replaceData(['baz' => 'foo']); - - /** @var Serializer|\PHPUnit_Framework_MockObject_MockObject $sanitizer */ - $sanitizer = $this->createMock(Serializer::class); - $sanitizer->expects($this->exactly(6)) - ->method('serialize') - ->willReturnCallback(function ($eventData) { - foreach ($eventData as $key => $value) { - $eventData[$key] = strrev($value); - } - - return $eventData; - }); - - $middleware = new SanitizerMiddleware($sanitizer); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - $this->assertArraySubset(['bar' => 'zab'], $returnedEvent->getRequest()); - $this->assertArraySubset(['foo' => 'rab'], $returnedEvent->getUserContext()); - $this->assertArraySubset(['name' => 'zab'], $returnedEvent->getRuntimeContext()); - $this->assertArraySubset(['name' => 'oof'], $returnedEvent->getServerOsContext()); - $this->assertArraySubset(['baz' => 'oof'], $returnedEvent->getExtraContext()); - $this->assertArraySubset(['oof', 'rab'], $returnedEvent->getTagsContext()); - } -} diff --git a/tests/Middleware/UserInterfaceMiddlewareTest.php b/tests/Middleware/UserInterfaceMiddlewareTest.php deleted file mode 100644 index f257116f4..000000000 --- a/tests/Middleware/UserInterfaceMiddlewareTest.php +++ /dev/null @@ -1,47 +0,0 @@ -getUserContext()->setData(['foo' => 'bar']); - - $middleware = new UserInterfaceMiddleware(); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event); - - $this->assertArrayNotHasKey('ip_address', $returnedEvent->getUserContext()); - } - - public function testInvokeWithRequest() - { - $event = new Event(new Configuration()); - $event->getUserContext()->setData(['foo' => 'bar']); - - $request = new ServerRequest(); - $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); - - $middleware = new UserInterfaceMiddleware(); - - $returnedEvent = $this->assertMiddlewareInvokesNext($middleware, $event, $request); - - $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $returnedEvent->getUserContext()->toArray()); - } -} diff --git a/tests/ConfigurationTest.php b/tests/OptionsTest.php similarity index 85% rename from tests/ConfigurationTest.php rename to tests/OptionsTest.php index 44a0ff56f..e156c7967 100644 --- a/tests/ConfigurationTest.php +++ b/tests/OptionsTest.php @@ -12,17 +12,16 @@ namespace Sentry\Tests; use PHPUnit\Framework\TestCase; -use Sentry\Configuration; -use Sentry\Event; +use Sentry\Options; -class ConfigurationTest extends TestCase +class OptionsTest extends TestCase { /** * @dataProvider optionsDataProvider */ public function testConstructor($option, $value, $getterMethod) { - $configuration = new Configuration([$option => $value]); + $configuration = new Options([$option => $value]); $this->assertEquals($value, $configuration->$getterMethod()); } @@ -32,7 +31,7 @@ public function testConstructor($option, $value, $getterMethod) */ public function testGettersAndSetters($option, $value, $getterMethod, $setterMethod = null) { - $configuration = new Configuration(); + $configuration = new Options(); if (null !== $setterMethod) { $configuration->$setterMethod($value); @@ -66,6 +65,7 @@ public function optionsDataProvider() ['tags', ['foo', 'bar'], 'getTags', 'setTags'], ['error_types', 0, 'getErrorTypes', 'setErrorTypes'], ['max_breadcrumbs', 50, 'getMaxBreadcrumbs', 'setMaxBreadcrumbs'], + ['before_send', function () {}, 'getBeforeSendCallback', 'setBeforeSendCallback'], ['before_breadcrumb', function () {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback'], ]; } @@ -75,7 +75,7 @@ public function optionsDataProvider() */ public function testServerOption($dsn, $options) { - $configuration = new Configuration(['dsn' => $dsn]); + $configuration = new Options(['dsn' => $dsn]); $this->assertEquals($options['project_id'], $configuration->getProjectId()); $this->assertEquals($options['public_key'], $configuration->getPublicKey()); @@ -160,7 +160,7 @@ public function serverOptionDataProvider() */ public function testServerOptionsWithInvalidServer($dsn) { - new Configuration(['dsn' => $dsn]); + new Options(['dsn' => $dsn]); } public function invalidServerOptionDataProvider() @@ -179,7 +179,7 @@ public function invalidServerOptionDataProvider() */ public function testParseDSNWithDisabledValue($dsn) { - $configuration = new Configuration(['dsn' => $dsn]); + $configuration = new Options(['dsn' => $dsn]); $this->assertNull($configuration->getProjectId()); $this->assertNull($configuration->getPublicKey()); @@ -199,41 +199,12 @@ public function disabledDsnProvider() ]; } - public function testShouldCapture() - { - $configuration = new Configuration(); - - $this->assertTrue($configuration->shouldCapture()); - - $configuration->setCurrentEnvironment('foo'); - $configuration->setEnvironments(['bar']); - - $this->assertFalse($configuration->shouldCapture()); - - $configuration->setCurrentEnvironment('foo'); - $configuration->setEnvironments(['foo']); - - $this->assertTrue($configuration->shouldCapture()); - - $configuration->setEnvironments([]); - - $this->assertTrue($configuration->shouldCapture()); - - $configuration->setShouldCapture(function ($value) { - return false; - }); - - $this->assertTrue($configuration->shouldCapture()); - - $this->assertFalse($configuration->shouldCapture(new Event($configuration))); - } - /** * @dataProvider excludedExceptionsDataProvider */ public function testIsExcludedException($excludedExceptions, $exception, $result) { - $configuration = new Configuration(['excluded_exceptions' => $excludedExceptions]); + $configuration = new Options(['excluded_exceptions' => $excludedExceptions]); $this->assertSame($result, $configuration->isExcludedException($exception)); } @@ -267,7 +238,7 @@ public function excludedExceptionsDataProvider() */ public function testExcludedAppPathsPathRegressionWithFileName($value, $expected) { - $configuration = new Configuration(['excluded_app_paths' => [$value]]); + $configuration = new Options(['excluded_app_paths' => [$value]]); $this->assertSame([$expected], $configuration->getExcludedProjectPaths()); } diff --git a/tests/SdkTest.php b/tests/SdkTest.php index 964e771fd..c5ea0aa2f 100644 --- a/tests/SdkTest.php +++ b/tests/SdkTest.php @@ -12,6 +12,7 @@ use function Sentry\addBreadcrumb; use function Sentry\captureEvent; use function Sentry\captureException; +use function Sentry\captureLastError; use function Sentry\captureMessage; use function Sentry\configureScope; use function Sentry\init; @@ -35,7 +36,6 @@ public function testCaptureMessage(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') - ->with('foo', [], ['level' => null]) ->willReturn('92db40a886c0458288c7c83935a350ef'); Hub::getCurrent()->bindClient($client); @@ -64,7 +64,7 @@ public function testCaptureEvent(): void /** @var ClientInterface|MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) - ->method('capture') + ->method('captureEvent') ->with(['message' => 'test']) ->willReturn('2b867534eead412cbdb882fd5d441690'); @@ -73,6 +73,20 @@ public function testCaptureEvent(): void $this->assertEquals('2b867534eead412cbdb882fd5d441690', captureEvent(['message' => 'test'])); } + public function testCaptureLastError() + { + /** @var ClientInterface|MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('captureLastError'); + + Hub::getCurrent()->bindClient($client); + + @trigger_error('foo', E_USER_NOTICE); + + captureLastError(); + } + public function testAddBreadcrumb(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); diff --git a/tests/Spool/MemorySpoolTest.php b/tests/Spool/MemorySpoolTest.php index 5a9b5df5b..336a88d4a 100644 --- a/tests/Spool/MemorySpoolTest.php +++ b/tests/Spool/MemorySpoolTest.php @@ -12,7 +12,6 @@ namespace Sentry\Tests\Spool; use PHPUnit\Framework\TestCase; -use Sentry\Configuration; use Sentry\Event; use Sentry\Spool\MemorySpool; use Sentry\Transport\TransportInterface; @@ -33,15 +32,15 @@ public function testQueueEvent() { $this->assertAttributeEmpty('events', $this->spool); - $this->spool->queueEvent(new Event(new Configuration())); + $this->spool->queueEvent(new Event()); $this->assertAttributeNotEmpty('events', $this->spool); } public function testFlushQueue() { - $event1 = new Event(new Configuration()); - $event2 = new Event(new Configuration()); + $event1 = new Event(); + $event2 = new Event(); /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ $transport = $this->createMock(TransportInterface::class); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 5b694993c..f17a6af85 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -12,26 +12,39 @@ namespace Sentry\Tests; use PHPUnit\Framework\TestCase; -use Sentry\ClientBuilder; -use Sentry\ClientInterface; use Sentry\Frame; +use Sentry\Options; +use Sentry\ReprSerializer; +use Sentry\Serializer; use Sentry\Stacktrace; class StacktraceTest extends TestCase { /** - * @var ClientInterface + * @var Options */ - protected $client; + protected $options; + + /** + * @var Serializer + */ + protected $serializer; + + /** + * @var ReprSerializer + */ + protected $representationSerializer; protected function setUp() { - $this->client = ClientBuilder::create()->getClient(); + $this->options = new Options(); + $this->serializer = new Serializer($this->options->getMbDetectOrder()); + $this->representationSerializer = new ReprSerializer($this->options->getMbDetectOrder()); } public function testGetFramesAndToArray() { - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); @@ -46,7 +59,7 @@ public function testGetFramesAndToArray() public function testStacktraceJsonSerialization() { - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); @@ -61,7 +74,7 @@ public function testStacktraceJsonSerialization() public function testAddFrame() { - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $frames = [ $this->getJsonFixture('frames/eval.json'), $this->getJsonFixture('frames/runtime_created.json'), @@ -82,7 +95,7 @@ public function testAddFrame() public function testAddFrameSerializesMethodArguments() { - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame('path/to/file', 12, [ 'file' => 'path/to/file', 'line' => 12, @@ -99,9 +112,9 @@ public function testAddFrameSerializesMethodArguments() public function testAddFrameStripsPath() { - $this->client->getConfig()->setPrefixes(['path/to/', 'path/to/app']); + $this->options->setPrefixes(['path/to/', 'path/to/app']); - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame('path/to/app/file', 12, ['function' => 'test_function_parent_parent_parent']); $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function_parent_parent']); @@ -118,10 +131,10 @@ public function testAddFrameStripsPath() public function testAddFrameMarksAsInApp() { - $this->client->getConfig()->setProjectRoot('path/to'); - $this->client->getConfig()->setExcludedProjectPaths(['path/to/excluded/path']); + $this->options->setProjectRoot('path/to'); + $this->options->setExcludedProjectPaths(['path/to/excluded/path']); - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function']); $stacktrace->addFrame('path/to/excluded/path/to/file', 12, ['function' => 'test_function']); @@ -135,7 +148,7 @@ public function testAddFrameMarksAsInApp() public function testAddFrameReadsCodeFromShortFile() { $fileContent = explode("\n", $this->getFixture('code/ShortFile.php')); - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame($this->getFixturePath('code/ShortFile.php'), 3, ['function' => '[unknown]']); @@ -159,7 +172,7 @@ public function testAddFrameReadsCodeFromShortFile() public function testAddFrameReadsCodeFromLongFile() { $fileContent = explode("\n", $this->getFixture('code/LongFile.php')); - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame($this->getFixturePath('code/LongFile.php'), 8, [ 'function' => '[unknown]', @@ -192,7 +205,7 @@ public function testRemoveFrame($index, $throwException) $this->expectExceptionMessage('Invalid frame index to remove.'); } - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame('path/to/file', 12, [ 'function' => 'test_function_parent', @@ -224,7 +237,7 @@ public function removeFrameDataProvider() public function testFromBacktrace() { $fixture = $this->getJsonFixture('backtraces/exception.json'); - $frames = Stacktrace::createFromBacktrace($this->client, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); $this->assertFrameEquals($frames[0], null, 'path/to/file', 16); $this->assertFrameEquals($frames[1], 'TestClass::crashyFunction', 'path/to/file', 7); @@ -234,7 +247,7 @@ public function testFromBacktrace() public function testFromBacktraceWithAnonymousFrame() { $fixture = $this->getJsonFixture('backtraces/anonymous_frame.json'); - $frames = Stacktrace::createFromBacktrace($this->client, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); $this->assertFrameEquals($frames[0], null, 'path/to/file', 7); $this->assertFrameEquals($frames[1], 'call_user_func', '[internal]', 0); @@ -250,7 +263,7 @@ public function testInAppWithEmptyFrame() null, ]; - $stacktrace = new Stacktrace($this->client); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame('/some/file', 123, $stack); $frames = $stacktrace->getFrames(); diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 47f6aa46f..7c83741a0 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -87,7 +87,6 @@ public function testGetLastEventId(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') - ->with('foo', [], ['level' => null]) ->willReturn('92db40a886c0458288c7c83935a350ef'); $hub = new Hub($client); @@ -206,7 +205,7 @@ public function testCaptureMessage(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') - ->with('foo', [], ['level' => Severity::debug()]) + ->with('foo', Severity::debug()) ->willReturn('2b867534eead412cbdb882fd5d441690'); $hub = new Hub($client); @@ -268,7 +267,7 @@ public function testCaptureEvent(): void /** @var ClientInterface|MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) - ->method('capture') + ->method('captureEvent') ->with(['message' => 'test']) ->willReturn('2b867534eead412cbdb882fd5d441690'); diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 420ffd80c..5e05b30fc 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -15,7 +15,6 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumbs\Breadcrumb; -use Sentry\Configuration; use Sentry\Event; use Sentry\Severity; use Sentry\State\Scope; @@ -108,7 +107,7 @@ public function testAddEventProcessor(): void $callback2Called = false; $callback3Called = false; - $event = new Event(new Configuration()); + $event = new Event(); $scope = new Scope(); $scope->addEventProcessor(function (Event $eventArg) use (&$callback1Called, $callback2Called, $callback3Called): ?Event { @@ -120,7 +119,7 @@ public function testAddEventProcessor(): void return $eventArg; }); - $this->assertSame($event, $scope->applyToEvent($event)); + $this->assertSame($event, $scope->applyToEvent($event, [])); $this->assertTrue($callback1Called); $scope->addEventProcessor(function () use ($callback1Called, &$callback2Called, $callback3Called): ?Event { @@ -138,7 +137,7 @@ public function testAddEventProcessor(): void return null; }); - $this->assertNull($scope->applyToEvent($event)); + $this->assertNull($scope->applyToEvent($event, [])); $this->assertTrue($callback2Called); $this->assertFalse($callback3Called); } @@ -163,7 +162,7 @@ public function testClear(): void public function testApplyToEvent(): void { - $event = new Event(new Configuration()); + $event = new Event(); $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $scope = new Scope(); @@ -174,7 +173,7 @@ public function testApplyToEvent(): void $scope->setExtra('bar', 'foo'); $scope->setUser(['foo' => 'baz']); - $event = $scope->applyToEvent($event); + $event = $scope->applyToEvent($event, []); $this->assertNotNull($event); $this->assertTrue($event->getLevel()->isEqualTo(Severity::warning())); @@ -191,7 +190,7 @@ public function testApplyToEvent(): void $scope->setExtra('foo', 'bar'); $scope->setUser(['baz' => 'foo']); - $event = $scope->applyToEvent($event); + $event = $scope->applyToEvent($event, []); $this->assertNotNull($event); $this->assertTrue($event->getLevel()->isEqualTo(Severity::fatal())); @@ -201,6 +200,6 @@ public function testApplyToEvent(): void $this->assertEquals(['bar' => 'foo', 'foo' => 'bar'], $event->getExtraContext()->toArray()); $this->assertEquals(['foo' => 'baz', 'baz' => 'foo'], $event->getUserContext()->toArray()); - $this->assertSame($event, $scope->applyToEvent($event)); + $this->assertSame($event, $scope->applyToEvent($event, [])); } } diff --git a/tests/TransactionStackTest.php b/tests/TransactionStackTest.php deleted file mode 100644 index dcc98035d..000000000 --- a/tests/TransactionStackTest.php +++ /dev/null @@ -1,133 +0,0 @@ -assertAttributeEquals(['a', 'b'], 'transactions', $stack); - } - - public function testConstructorThrowsIfValuesAreNotAllStrings() - { - $this->expectException(\TypeError::class); - - new TransactionStack(['a', new \stdClass()]); - } - - public function testClear() - { - $stack = new TransactionStack(['a', 'b']); - - $this->assertAttributeEquals(['a', 'b'], 'transactions', $stack); - - $stack->clear(); - - $this->assertEmpty($stack); - } - - public function testIsEmpty() - { - $stack = new TransactionStack(); - - $this->assertEmpty($stack); - $this->assertTrue($stack->isEmpty()); - - $stack->push('a'); - - $this->assertNotEmpty($stack); - $this->assertFalse($stack->isEmpty()); - } - - public function testPush() - { - $stack = new TransactionStack(); - $stack->push('a'); - - $this->assertAttributeEquals(['a'], 'transactions', $stack); - - $stack->push('b', 'c'); - - $this->assertAttributeEquals(['a', 'b', 'c'], 'transactions', $stack); - } - - /** - * @dataProvider peekDataProvider - */ - public function testPeek($initialData, $expectedData, $expectedRemainingData) - { - $stack = new TransactionStack($initialData); - - $this->assertEquals($expectedData, $stack->peek()); - $this->assertAttributeEquals($expectedRemainingData, 'transactions', $stack); - } - - public function peekDataProvider() - { - return [ - [ - ['a', 'b'], - 'b', - ['a', 'b'], - ], - [ - [], - null, - [], - ], - ]; - } - - /** - * @dataProvider popDataProvider - */ - public function testPop($initialData, $expectedData, $expectedRemainingData) - { - $stack = new TransactionStack($initialData); - - $this->assertEquals($expectedData, $stack->pop()); - $this->assertAttributeEquals($expectedRemainingData, 'transactions', $stack); - } - - public function popDataProvider() - { - return [ - [ - ['a', 'b'], - 'b', - ['a'], - ], - [ - [], - null, - [], - ], - ]; - } - - public function testCount() - { - $stack = new TransactionStack(); - - $this->assertCount(0, $stack); - - $stack->push('a'); - - $this->assertCount(1, $stack); - } -} diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 7f67f89ba..5bcb2b772 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -17,8 +17,8 @@ use Http\Promise\Promise; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; -use Sentry\Configuration; use Sentry\Event; +use Sentry\Options; use Sentry\Transport\HttpTransport; use Sentry\Util\JSON; @@ -37,10 +37,10 @@ public function testDestructor() ->method('sendAsyncRequest') ->willReturn($promise); - $config = new Configuration(); + $config = new Options(); $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); - $transport->send(new Event($config)); + $transport->send(new Event()); // In PHP calling the destructor manually does not destroy the object, // but for testing we will do it anyway because otherwise we could not @@ -68,13 +68,13 @@ public function testCleanupPendingRequests() ->method('sendAsyncRequest') ->willReturnOnConsecutiveCalls($promise1, $promise2); - $config = new Configuration(); + $config = new Options(); $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); $this->assertAttributeEmpty('pendingRequests', $transport); - $transport->send(new Event($config)); - $transport->send(new Event($config)); + $transport->send(new Event()); + $transport->send(new Event()); $this->assertAttributeNotEmpty('pendingRequests', $transport); @@ -86,8 +86,8 @@ public function testCleanupPendingRequests() public function testSendWithoutCompressedEncoding() { - $config = new Configuration(['encoding' => 'json']); - $event = new Event($config); + $config = new Options(['encoding' => 'json']); + $event = new Event(); $promise = $this->createMock(Promise::class); $promise->expects($this->once()) @@ -116,8 +116,8 @@ public function testSendWithoutCompressedEncoding() public function testSendWithCompressedEncoding() { - $config = new Configuration(['encoding' => 'gzip']); - $event = new Event($config); + $config = new Options(['encoding' => 'gzip']); + $event = new Event(); $promise = $this->createMock(Promise::class); $promise->expects($this->once()) @@ -135,7 +135,7 @@ public function testSendWithCompressedEncoding() $this->assertNotFalse($compressedPayload); return 'application/octet-stream' === $request->getHeaderLine('Content-Type') - && base64_encode($compressedPayload) === $request->getBody()->getContents(); + && $compressedPayload === $request->getBody()->getContents(); })) ->willReturn($promise); @@ -161,10 +161,10 @@ public function testSendFailureCleanupPendingRequests() ->method('sendAsyncRequest') ->willReturn($promise); - $config = new Configuration(); + $config = new Options(); $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); - $transport->send(new Event($config)); + $transport->send(new Event()); $this->assertAttributeNotEmpty('pendingRequests', $transport); $this->assertSame($exception, $promise->wait(true)); diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php index 5a2bdb45d..c18d89a0a 100644 --- a/tests/Transport/NullTransportTest.php +++ b/tests/Transport/NullTransportTest.php @@ -12,7 +12,6 @@ namespace Sentry\Tests\Transport; use PHPUnit\Framework\TestCase; -use Sentry\Configuration; use Sentry\Event; use Sentry\Transport\NullTransport; @@ -21,8 +20,8 @@ class NullTransportTest extends TestCase public function testSend() { $transport = new NullTransport(); - $event = new Event(new Configuration()); + $event = new Event(); - $this->assertTrue($transport->send($event)); + $this->assertEquals($event->getId(), $transport->send($event)); } } diff --git a/tests/Transport/SpoolTransportTest.php b/tests/Transport/SpoolTransportTest.php index efaca965f..211a901cb 100644 --- a/tests/Transport/SpoolTransportTest.php +++ b/tests/Transport/SpoolTransportTest.php @@ -12,7 +12,6 @@ namespace Sentry\Tests\Transport; use PHPUnit\Framework\TestCase; -use Sentry\Configuration; use Sentry\Event; use Sentry\Spool\SpoolInterface; use Sentry\Transport\SpoolTransport; @@ -42,7 +41,7 @@ public function testGetSpool() public function testSend() { - $event = new Event(new Configuration()); + $event = new Event(); $this->spool->expects($this->once()) ->method('queueEvent') diff --git a/tests/phpt/error_handler_called.phpt b/tests/phpt/error_handler_called.phpt new file mode 100644 index 000000000..6489abaac --- /dev/null +++ b/tests/phpt/error_handler_called.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test that the error handler is not called regardless of the current +`error_reporting` setting if its own `captureAt` configuration doesn't match +the level of the thrown error. +--FILE-- + +--EXPECTF-- +Triggering error +Callback invoked +End diff --git a/tests/phpt/error_handler_respects_captureAt.phpt b/tests/phpt/error_handler_respects_captureAt.phpt new file mode 100644 index 000000000..e4de50245 --- /dev/null +++ b/tests/phpt/error_handler_respects_captureAt.phpt @@ -0,0 +1,32 @@ +--TEST-- +Even if the error is catched by the current error_reporting setting, Sentry's error handler respects its own capture +level, and it should NOT be invoked in this case +--FILE-- +captureAt(E_ERROR, true); + +echo 'Triggering error'; +trigger_error('Triggered error which will be captured by PHP error handler'); +echo 'End'; +?> +--EXPECTREGEX-- +Triggering error +Notice: Triggered error which will be captured by PHP error handler .+ +End diff --git a/tests/phpt/exception_rethrown.phpt b/tests/phpt/exception_rethrown.phpt index 893343255..8cbf8f466 100644 --- a/tests/phpt/exception_rethrown.phpt +++ b/tests/phpt/exception_rethrown.phpt @@ -5,8 +5,7 @@ Test rethrowing in custom exception handler namespace Sentry\Tests; -use Sentry\ClientBuilder; -use Sentry\ErrorHandler; +use function Sentry\init; $vendor = __DIR__; @@ -22,9 +21,7 @@ set_exception_handler(function ($exception) { throw $exception; }); -$client = ClientBuilder::create()->getClient(); - -ErrorHandler::register($client); +init(); throw new \Exception('foo bar'); ?> diff --git a/tests/phpt/exception_thrown.phpt b/tests/phpt/exception_thrown.phpt index 35706878c..06256a385 100644 --- a/tests/phpt/exception_thrown.phpt +++ b/tests/phpt/exception_thrown.phpt @@ -5,8 +5,7 @@ Test throwing exception in custom exception handler namespace Sentry\Tests; -use Sentry\ClientBuilder; -use Sentry\ErrorHandler; +use function Sentry\init; $vendor = __DIR__; @@ -22,9 +21,7 @@ set_exception_handler(function ($exception) { throw new \Exception('bar foo'); }); -$client = ClientBuilder::create()->getClient(); - -ErrorHandler::register($client); +init(); throw new \Exception('foo bar'); ?> diff --git a/tests/phpt/fatal_error.phpt b/tests/phpt/fatal_error.phpt index 239675c7b..c6d25f495 100644 --- a/tests/phpt/fatal_error.phpt +++ b/tests/phpt/fatal_error.phpt @@ -6,8 +6,8 @@ Test catching fatal errors namespace Sentry\Tests; use PHPUnit\Framework\Assert; -use Sentry\ClientBuilder; -use Sentry\ErrorHandler; +use function Sentry\init; +use Sentry\State\Hub; $vendor = __DIR__; @@ -17,19 +17,19 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$client = ClientBuilder::create([ +init([ 'dsn' => 'http://public:secret@local.host/1', 'send_attempts' => 1, -])->getClient(); +]); -ErrorHandler::register($client); +register_shutdown_function('register_shutdown_function', function () { + $client = Hub::getCurrent()->getClient(); -register_shutdown_function('register_shutdown_function', function () use ($client) { /** @var \Sentry\Transport\HttpTransport $transport */ $transport = Assert::getObjectAttribute($client, 'transport'); - Assert::assertNotNull($client->getLastEvent()); Assert::assertAttributeEmpty('pendingRequests', $transport); + Assert::assertNotNull(Hub::getCurrent()->getLastEventId()); echo 'Shutdown function called'; }); diff --git a/tests/phpt/fatal_error_not_captured_twice.phpt b/tests/phpt/fatal_error_not_captured_twice.phpt index 34fd03bef..c4afbc4c3 100644 --- a/tests/phpt/fatal_error_not_captured_twice.phpt +++ b/tests/phpt/fatal_error_not_captured_twice.phpt @@ -6,10 +6,10 @@ Test catching fatal errors does not capture twice namespace Sentry\Tests; use PHPUnit\Framework\Assert; -use Sentry\ClientBuilder; -use Sentry\ErrorHandler; use Sentry\Spool\MemorySpool; use Sentry\Transport\SpoolTransport; +use Sentry\ClientBuilder; +use function Sentry\init; $vendor = __DIR__; @@ -22,11 +22,7 @@ require $vendor . '/vendor/autoload.php'; $spool = new MemorySpool(); $transport = new SpoolTransport($spool); -$client = ClientBuilder::create() - ->setTransport($transport) - ->getClient(); - -ErrorHandler::register($client); +init([], ClientBuilder::create()->setTransport($transport)); register_shutdown_function('register_shutdown_function', function () use ($spool) { Assert::assertAttributeCount(1, 'events', $spool); diff --git a/tests/phpt/out_of_memory.phpt b/tests/phpt/out_of_memory.phpt index b6c5dfd58..151cddbfb 100644 --- a/tests/phpt/out_of_memory.phpt +++ b/tests/phpt/out_of_memory.phpt @@ -6,8 +6,8 @@ Test catching out of memory fatal error namespace Sentry\Tests; use PHPUnit\Framework\Assert; -use Sentry\ClientBuilder; -use Sentry\ErrorHandler; +use function Sentry\init; +use Sentry\State\Hub; ini_set('memory_limit', '20M'); @@ -19,18 +19,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$client = ClientBuilder::create([ +init([ 'dsn' => 'http://public:secret@local.host/1', 'send_attempts' => 1, -])->getClient(); +]); -ErrorHandler::register($client); +register_shutdown_function('register_shutdown_function', function () { + $client = Hub::getCurrent()->getClient(); -register_shutdown_function('register_shutdown_function', function () use ($client) { /** @var \Sentry\Transport\HttpTransport $transport */ $transport = Assert::getObjectAttribute($client, 'transport'); - Assert::assertNotNull($client->getLastEvent()); Assert::assertAttributeEmpty('pendingRequests', $transport); echo 'Shutdown function called'; From 6b6311753a0a066e37f3d002d56ccd495e747474 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 16 Nov 2018 15:47:32 +0100 Subject: [PATCH 0384/1161] Use jean85/pretty-package-versions in the ModulesIntegration class (#694) --- composer.json | 3 +- src/Integration/ModulesIntegration.php | 44 +++++--------------- tests/Fixtures/composer.json | 8 ---- tests/Fixtures/composer.lock | 18 -------- tests/Integration/ModulesIntegrationTest.php | 12 +++--- 5 files changed, 20 insertions(+), 65 deletions(-) delete mode 100644 tests/Fixtures/composer.json delete mode 100644 tests/Fixtures/composer.lock diff --git a/composer.json b/composer.json index 1a243f298..639e9d080 100644 --- a/composer.json +++ b/composer.json @@ -25,8 +25,8 @@ "zendframework/zend-diactoros": "~1.4" }, "require-dev": { - "composer/composer": "^1.6", "friendsofphp/php-cs-fixer": "^2.13", + "jean85/pretty-package-versions": "^1.2", "monolog/monolog": "^1.3", "php-http/curl-client": "^1.7.1", "php-http/message": "^1.5", @@ -37,6 +37,7 @@ "symfony/phpunit-bridge": "^4.1.6" }, "suggest": { + "jean85/pretty-package-versions": "To use the ModulesIntegration, that lists all installed dependencies in every sent event", "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" }, "conflict": { diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index 85cd34abd..5e7c66993 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -5,43 +5,28 @@ namespace Sentry\Integration; use Composer\Composer; -use Composer\Factory; -use Composer\IO\NullIO; +use Jean85\PrettyVersions; +use PackageVersions\Versions; use Sentry\Event; -use Sentry\Options; use Sentry\State\Hub; use Sentry\State\Scope; /** * This integration logs with the event details all the versions of the packages - * installed with Composer, if any. - * - * @author Stefano Arlandini + * installed with Composer; the root project is included too. */ final class ModulesIntegration implements IntegrationInterface { - /** - * @var Options The client option - */ - private $options; - /** * @var array The list of installed vendors */ private static $loadedModules = []; - /** - * Constructor. - * - * @param Options $options The Client options - */ - public function __construct(Options $options) + public function __construct() { - if (!class_exists(Composer::class)) { - throw new \LogicException('You need the "composer/composer" package in order to use this integration.'); + if (!class_exists(PrettyVersions::class)) { + throw new \LogicException('You need the "jean85/pretty-package-versions" package in order to use this integration.'); } - - $this->options = $options; } /** @@ -61,21 +46,14 @@ public function setupOnce(): void } /** - * @param ModulesIntegration $self The instance of this integration - * @param Event $event The event that will be enriched with the modules + * @param self $self The instance of this integration + * @param Event $event The event that will be enriched with the modules */ public static function applyToEvent(self $self, Event $event): void { - $composerFilePath = $self->options->getProjectRoot() . \DIRECTORY_SEPARATOR . 'composer.json'; - - if (file_exists($composerFilePath) && 0 == \count(self::$loadedModules)) { - $composer = Factory::create(new NullIO(), $composerFilePath, true); - $locker = $composer->getLocker(); - - if ($locker->isLocked()) { - foreach ($locker->getLockedRepository()->getPackages() as $package) { - self::$loadedModules[$package->getName()] = $package->getVersion(); - } + if (empty(self::$loadedModules)) { + foreach (Versions::VERSIONS as $package => $rawVersion) { + self::$loadedModules[$package] = PrettyVersions::getVersion($package)->getPrettyVersion(); } } diff --git a/tests/Fixtures/composer.json b/tests/Fixtures/composer.json deleted file mode 100644 index 1736850eb..000000000 --- a/tests/Fixtures/composer.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "vendor-name/project-name", - "type": "library", - "require": { - "foo/bar": "1.2.3", - "foo/baz": "4.5.6" - } -} diff --git a/tests/Fixtures/composer.lock b/tests/Fixtures/composer.lock deleted file mode 100644 index f38dcd18e..000000000 --- a/tests/Fixtures/composer.lock +++ /dev/null @@ -1,18 +0,0 @@ -{ - "packages": [ - { - "name": "foo/bar", - "version": "1.2.3" - }, - { - "name": "foo/baz", - "version": "4.5.6" - } - ], - "packages-dev": null, - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false -} diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index add4c3b12..e22d968c4 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -4,22 +4,24 @@ namespace Sentry\Tests\Integration; +use Jean85\PrettyVersions; use PHPUnit\Framework\TestCase; use Sentry\Event; use Sentry\Integration\ModulesIntegration; -use Sentry\Options; class ModulesIntegrationTest extends TestCase { public function testInvoke() { - $options = new Options(['project_root' => __DIR__ . '/../Fixtures']); $event = new Event(); - - $integration = new ModulesIntegration($options); + $integration = new ModulesIntegration(); ModulesIntegration::applyToEvent($integration, $event); - $this->assertEquals(['foo/bar' => '1.2.3.0', 'foo/baz' => '4.5.6.0'], $event->getModules()); + $modules = $event->getModules(); + + $this->assertArrayHasKey('sentry/sentry', $modules, 'Root project missing'); + $this->assertArrayHasKey('ocramius/package-versions', $modules, 'Indirect dependency missing'); + $this->assertEquals(PrettyVersions::getVersion('sentry/sentry'), $modules['sentry/sentry']); } } From e72a40cba9cb2574995db05392376e139805497e Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Sat, 17 Nov 2018 00:53:24 +0100 Subject: [PATCH 0385/1161] Update the composer.json dependencies and constraints (#698) --- composer.json | 18 +++++++++--------- tests/Integration/RequestIntegrationTest.php | 6 +++++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 639e9d080..76fbef417 100644 --- a/composer.json +++ b/composer.json @@ -15,14 +15,14 @@ "php": "^7.1", "ext-json": "*", "ext-mbstring": "*", - "php-http/async-client-implementation": "~1.0", - "php-http/client-common": "~1.5", - "php-http/discovery": "~1.2", - "php-http/httplug": "~1.1", - "psr/http-message-implementation": "~1.0", - "ramsey/uuid": "~3.3", - "symfony/options-resolver": "~2.7|~3.0", - "zendframework/zend-diactoros": "~1.4" + "php-http/async-client-implementation": "^1.0", + "php-http/client-common": "^1.5", + "php-http/discovery": "^1.2", + "php-http/httplug": "^1.1", + "psr/http-message-implementation": "^1.0", + "ramsey/uuid": "^3.3", + "symfony/options-resolver": "^2.7|^3.0|^4.0", + "zendframework/zend-diactoros": "^1.4|^2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", @@ -30,7 +30,7 @@ "monolog/monolog": "^1.3", "php-http/curl-client": "^1.7.1", "php-http/message": "^1.5", - "php-http/mock-client": "~1.0", + "php-http/mock-client": "^1.0", "phpstan/phpstan-phpunit": "^0.10", "phpstan/phpstan": "^0.10.3", "phpunit/phpunit": "^7.0", diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index d240e978c..b6ccc628e 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -12,6 +12,7 @@ namespace Sentry\Tests\Integration; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ServerRequestInterface; use Sentry\Event; use Sentry\Integration\RequestIntegration; use Zend\Diactoros\ServerRequest; @@ -27,14 +28,16 @@ public function testInvoke(array $requestData, array $expectedValue) $event = new Event(); $request = new ServerRequest(); + $request = $request->withCookieParams($requestData['cookies']); $request = $request->withUri(new Uri($requestData['uri'])); $request = $request->withMethod($requestData['method']); - $request = $request->withCookieParams($requestData['cookies']); foreach ($requestData['headers'] as $name => $value) { $request = $request->withHeader($name, $value); } + $this->assertInstanceOf(ServerRequestInterface::class, $request); + RequestIntegration::applyToEvent($event, $request); $this->assertEquals($expectedValue, $event->getRequest()); @@ -47,6 +50,7 @@ public function testInvokeWithRequestHavingIpAddress() $request = new ServerRequest(); $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); + $this->assertInstanceOf(ServerRequestInterface::class, $request); RequestIntegration::applyToEvent($event, $request); From c3743df70fbb2b53fe740eab651938b2e6a8c8bc Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 22 Nov 2018 09:59:01 +0100 Subject: [PATCH 0386/1161] Fix compression of HTTP requests using Gzip (#702) --- src/ClientBuilder.php | 5 ++++ src/Options.php | 21 ++++++++-------- src/Transport/HttpTransport.php | 19 ++------------ tests/ClientBuilderTest.php | 31 ++++++++++++++++++++++- tests/OptionsTest.php | 2 +- tests/Transport/HttpTransportTest.php | 36 +-------------------------- 6 files changed, 49 insertions(+), 65 deletions(-) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 9a321ee8a..7311ddc36 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -14,6 +14,7 @@ use Http\Client\Common\Plugin; use Http\Client\Common\Plugin\AuthenticationPlugin; use Http\Client\Common\Plugin\BaseUriPlugin; +use Http\Client\Common\Plugin\DecoderPlugin; use Http\Client\Common\Plugin\ErrorPlugin; use Http\Client\Common\Plugin\HeaderSetPlugin; use Http\Client\Common\Plugin\RetryPlugin; @@ -263,6 +264,10 @@ private function createHttpClientInstance() $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->options->getSendAttempts()])); $this->addHttpClientPlugin(new ErrorPlugin()); + if ($this->options->isCompressionEnabled()) { + $this->addHttpClientPlugin(new DecoderPlugin()); + } + return new PluginClient($this->httpClient, $this->httpClientPlugins); } diff --git a/src/Options.php b/src/Options.php index 76e46f9c6..bc214463e 100644 --- a/src/Options.php +++ b/src/Options.php @@ -233,23 +233,23 @@ public function setContextLines($contextLines) } /** - * Gets the encoding type for event bodies (GZIP or JSON). + * Returns whether the requests should be compressed using GZIP or not. * - * @return string + * @return bool */ - public function getEncoding() + public function isCompressionEnabled(): bool { - return $this->options['encoding']; + return $this->options['enable_compression']; } /** - * Sets the encoding type for event bodies (GZIP or JSON). + * Sets whether the request should be compressed using JSON or not. * - * @param string $encoding The encoding type + * @param bool $enabled Flag indicating whether the request should be compressed */ - public function setEncoding($encoding) + public function setEnableCompression(bool $enabled): void { - $options = array_merge($this->options, ['encoding' => $encoding]); + $options = array_merge($this->options, ['enable_compression' => $enabled]); $this->options = $this->resolver->resolve($options); } @@ -672,7 +672,7 @@ private function configureOptions(OptionsResolver $resolver) 'mb_detect_order' => null, 'auto_log_stacks' => true, 'context_lines' => 3, - 'encoding' => 'gzip', + 'enable_compression' => true, 'current_environment' => 'default', 'environments' => [], 'excluded_loggers' => [], @@ -701,7 +701,7 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('mb_detect_order', ['null', 'array', 'string']); $resolver->setAllowedTypes('auto_log_stacks', 'bool'); $resolver->setAllowedTypes('context_lines', 'int'); - $resolver->setAllowedTypes('encoding', 'string'); + $resolver->setAllowedTypes('enable_compression', 'bool'); $resolver->setAllowedTypes('current_environment', 'string'); $resolver->setAllowedTypes('environments', 'array'); $resolver->setAllowedTypes('excluded_loggers', 'array'); @@ -733,7 +733,6 @@ private function configureOptions(OptionsResolver $resolver) return true; }); - $resolver->setAllowedValues('encoding', ['gzip', 'json']); $resolver->setAllowedValues('dsn', function ($value) { if (empty($value)) { return true; diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 48663c8a7..191094d92 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -12,7 +12,6 @@ namespace Sentry\Transport; use Http\Client\HttpAsyncClient; -use Http\Message\Encoding\CompressStream; use Http\Message\RequestFactory; use Http\Promise\Promise; use Sentry\Event; @@ -85,15 +84,11 @@ public function send(Event $event): ?string { $request = $this->requestFactory->createRequest( 'POST', - sprintf('api/%d/store/', $this->config->getProjectId()), - ['Content-Type' => $this->isEncodingCompressed() ? 'application/octet-stream' : 'application/json'], + sprintf('/api/%d/store/', $this->config->getProjectId()), + ['Content-Type' => 'application/json'], JSON::encode($event) ); - if ($this->isEncodingCompressed()) { - $request = $request->withBody(new CompressStream($request->getBody())); - } - $promise = $this->httpClient->sendAsyncRequest($request); // This function is defined in-line so it doesn't show up for type-hinting @@ -114,16 +109,6 @@ public function send(Event $event): ?string return $event->getId(); } - /** - * Checks whether the encoding is compressed. - * - * @return bool - */ - private function isEncodingCompressed() - { - return 'gzip' === $this->config->getEncoding(); - } - /** * Cleanups the pending requests by forcing them to be sent. Any error that * occurs will be ignored. diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 5617dfdaa..5a9cd43fc 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -196,7 +196,7 @@ public function optionsDataProvider() ['setMbDetectOrder', ['foo', 'bar']], ['setAutoLogStacks', false], ['setContextLines', 0], - ['setEncoding', 'gzip'], + ['setEnableCompression', false], ['setCurrentEnvironment', 'test'], ['setEnvironments', ['default']], ['setExcludedLoggers', ['foo', 'bar']], @@ -210,6 +210,35 @@ public function optionsDataProvider() ['setErrorTypes', 0], ]; } + + /** + * @dataProvider getClientTogglesCompressionPluginInHttpClientDataProvider + */ + public function testGetClientTogglesCompressionPluginInHttpClient(bool $enabled): void + { + $builder = ClientBuilder::create(['enable_compression' => $enabled, 'dsn' => 'http://public:secret@example.com/sentry/1']); + $builder->getClient(); + + $decoderPluginFound = false; + + foreach ($this->getObjectAttribute($builder, 'httpClientPlugins') as $plugin) { + if ($plugin instanceof Plugin\DecoderPlugin) { + $decoderPluginFound = true; + + break; + } + } + + $this->assertEquals($enabled, $decoderPluginFound); + } + + public function getClientTogglesCompressionPluginInHttpClientDataProvider(): array + { + return [ + [true], + [false], + ]; + } } class PluginStub1 implements Plugin diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index e156c7967..ba1bc7119 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -52,7 +52,7 @@ public function optionsDataProvider() ['mb_detect_order', ['UTF-8'], 'getMbDetectOrder', 'setMbDetectOrder'], ['auto_log_stacks', false, 'getAutoLogStacks', 'setAutoLogStacks'], ['context_lines', 3, 'getContextLines', 'setContextLines'], - ['encoding', 'json', 'getEncoding', 'setEncoding'], + ['enable_compression', false, 'isCompressionEnabled', 'setEnableCompression'], ['current_environment', 'foo', 'getCurrentEnvironment', 'setCurrentEnvironment'], ['environments', ['foo', 'bar'], 'getEnvironments', 'setEnvironments'], ['excluded_loggers', ['bar', 'foo'], 'getExcludedLoggers', 'setExcludedLoggers'], diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 5bcb2b772..925d32ea6 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -86,7 +86,7 @@ public function testCleanupPendingRequests() public function testSendWithoutCompressedEncoding() { - $config = new Options(['encoding' => 'json']); + $config = new Options(['enable_compression' => false]); $event = new Event(); $promise = $this->createMock(Promise::class); @@ -114,40 +114,6 @@ public function testSendWithoutCompressedEncoding() $reflectionMethod->setAccessible(false); } - public function testSendWithCompressedEncoding() - { - $config = new Options(['encoding' => 'gzip']); - $event = new Event(); - - $promise = $this->createMock(Promise::class); - $promise->expects($this->once()) - ->method('wait'); - - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); - $httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->with($this->callback(function (RequestInterface $request) use ($event) { - $request->getBody()->rewind(); - - $compressedPayload = gzcompress(JSON::encode($event)); - - $this->assertNotFalse($compressedPayload); - - return 'application/octet-stream' === $request->getHeaderLine('Content-Type') - && $compressedPayload === $request->getBody()->getContents(); - })) - ->willReturn($promise); - - $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); - $transport->send($event); - - $reflectionMethod = new \ReflectionMethod(HttpTransport::class, 'cleanupPendingRequests'); - $reflectionMethod->setAccessible(true); - $reflectionMethod->invoke($transport); - $reflectionMethod->setAccessible(false); - } - public function testSendFailureCleanupPendingRequests() { /** @var HttpException|\PHPUnit_Framework_MockObject_MockObject $exception */ From f01a5fe22034cf75d1ba7d7dcd23f79856e104ef Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 22 Nov 2018 10:37:53 +0100 Subject: [PATCH 0387/1161] Add PHP 7 typehints to the codebase (#699) --- .php_cs | 4 +- composer.json | 6 +- phpstan.neon | 1 - phpunit.xml.dist | 2 +- src/AbstractErrorHandler.php | 332 ------------------ src/{Breadcrumbs => }/Breadcrumb.php | 76 +--- src/Client.php | 11 +- src/ClientBuilder.php | 35 +- src/ClientBuilderInterface.php | 11 +- src/ClientInterface.php | 10 +- src/Context/Context.php | 43 +-- src/Context/RuntimeContext.php | 27 +- src/Context/ServerOsContext.php | 35 +- src/Context/TagsContext.php | 17 +- src/Context/UserContext.php | 9 - src/ErrorHandler.php | 325 ++++++++++++++++- src/Event.php | 84 ++--- src/Exception.php | 7 - src/Exception/ExceptionInterface.php | 9 +- src/Exception/InvalidArgumentException.php | 9 +- src/Frame.php | 51 ++- src/HttpClient/Authentication/SentryAuth.php | 11 +- src/Integration/ErrorHandlerIntegration.php | 7 +- src/Integration/Handler.php | 12 +- src/Integration/ModulesIntegration.php | 3 +- src/Integration/RequestIntegration.php | 5 +- src/Options.php | 317 +++++++++-------- src/ReprSerializer.php | 9 +- src/Sdk.php | 3 +- src/Serializer.php | 2 + src/Severity.php | 9 - src/Spool/MemorySpool.php | 15 +- src/Spool/SpoolInterface.php | 13 +- src/Stacktrace.php | 55 ++- src/State/Hub.php | 11 +- src/State/Layer.php | 9 - src/State/Scope.php | 11 +- src/Transport/HttpTransport.php | 19 +- src/Transport/NullTransport.php | 9 +- src/Transport/SpoolTransport.php | 11 +- src/Transport/TransportInterface.php | 9 +- src/Util/JSON.php | 13 +- src/Util/PHPVersion.php | 14 +- tests/AbstractErrorHandlerTest.php | 121 ------- tests/AbstractSerializerTest.php | 9 +- tests/{Breadcrumbs => }/BreadcrumbTest.php | 55 +-- tests/ClientBuilderTest.php | 52 ++- tests/ClientTest.php | 41 +-- tests/Context/AbstractContextTest.php | 9 - tests/Context/ContextTest.php | 9 +- tests/Context/RuntimeContextTest.php | 9 +- tests/Context/ServerOsContextTest.php | 9 +- tests/Context/TagsContextTest.php | 9 +- tests/Context/UserContextTest.php | 9 +- tests/ErrorHandlerTest.php | 176 ++++++++-- tests/EventTest.php | 44 ++- tests/FrameTest.php | 21 +- .../Authentication/SentryAuthTest.php | 24 +- tests/Integration/ModulesIntegrationTest.php | 4 +- tests/Integration/RequestIntegrationTest.php | 17 +- tests/OptionsTest.php | 25 +- tests/ReprSerializerTest.php | 9 +- tests/SdkTest.php | 4 +- tests/SerializerTest.php | 4 +- tests/SeverityTest.php | 9 - tests/Spool/MemorySpoolTest.php | 28 +- tests/StacktraceTest.php | 79 +++-- tests/State/HubTest.php | 11 +- tests/State/LayerTest.php | 9 - tests/State/ScopeTest.php | 15 +- tests/Transport/HttpTransportTest.php | 66 +--- tests/Transport/NullTransportTest.php | 13 +- tests/Transport/SpoolTransportTest.php | 20 +- tests/Util/Fixtures/JsonSerializableClass.php | 9 +- tests/Util/Fixtures/SimpleClass.php | 9 +- tests/Util/JSONTest.php | 59 ++-- tests/Util/PHPVersionTest.php | 43 ++- tests/bootstrap.php | 9 +- 78 files changed, 1110 insertions(+), 1600 deletions(-) delete mode 100644 src/AbstractErrorHandler.php rename src/{Breadcrumbs => }/Breadcrumb.php (80%) delete mode 100644 src/Exception.php delete mode 100644 tests/AbstractErrorHandlerTest.php rename tests/{Breadcrumbs => }/BreadcrumbTest.php (68%) diff --git a/.php_cs b/.php_cs index 23f13efb4..2ed598fb7 100644 --- a/.php_cs +++ b/.php_cs @@ -8,6 +8,7 @@ return PhpCsFixer\Config::create() 'array_syntax' => ['syntax' => 'short'], 'concat_space' => ['spacing' => 'one'], 'ordered_imports' => true, + 'declare_strict_types' => true, 'psr0' => true, 'psr4' => true, 'random_api_migration' => true, @@ -23,7 +24,8 @@ return PhpCsFixer\Config::create() ->in(__DIR__) ->exclude([ 'tests/resources', - 'tests/phpt' + 'tests/phpt', + 'tests/Fixtures', ]) ) ; diff --git a/composer.json b/composer.json index 76fbef417..9571d4172 100644 --- a/composer.json +++ b/composer.json @@ -37,16 +37,12 @@ "symfony/phpunit-bridge": "^4.1.6" }, "suggest": { - "jean85/pretty-package-versions": "To use the ModulesIntegration, that lists all installed dependencies in every sent event", - "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" + "jean85/pretty-package-versions": "To use the ModulesIntegration, that lists all installed dependencies in every sent event" }, "conflict": { "php-http/client-common": "1.8.0", "raven/raven": "*" }, - "bin": [ - "bin/sentry" - ], "autoload": { "files": ["src/Sdk.php"], "psr-4" : { diff --git a/phpstan.neon b/phpstan.neon index 5a2407dd1..3c9523dd1 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,7 +7,6 @@ parameters: - '/Call to an undefined method Sentry\\ClientBuilder::methodThatDoesNotExists\(\)/' - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' - - '/Method Sentry\\ClientBuilder::\w+\(\) should return \$this\(Sentry\\ClientBuilderInterface\) but returns \$this\(Sentry\\ClientBuilder\)\./' excludes_analyse: - tests/resources includes: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 058f0a8d1..bcc751a0c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,7 +11,7 @@ - + tests tests/phpt diff --git a/src/AbstractErrorHandler.php b/src/AbstractErrorHandler.php deleted file mode 100644 index cf56b3993..000000000 --- a/src/AbstractErrorHandler.php +++ /dev/null @@ -1,332 +0,0 @@ - - */ -abstract class AbstractErrorHandler -{ - /** - * @var callable Callback that will be invoked when an error is caught - */ - protected $callback; - - /** - * @var \ReflectionProperty A reflection cached instance that points to the - * trace property of the exception objects - */ - protected $exceptionReflection; - - /** - * @var callable|null The previous error handler, if any - */ - protected $previousErrorHandler; - - /** - * @var callable|null The previous exception handler, if any - */ - protected $previousExceptionHandler; - - /** - * @var int The errors that will be catched by the error handler - */ - protected $capturedErrors = E_ALL; - - /** - * @var bool Flag indicating whether this error handler is the first in the - * chain of registered error handlers - */ - protected $isRoot = false; - - /** - * @var string|null A portion of pre-allocated memory data that will be reclaimed - * in case a fatal error occurs to handle it - */ - protected static $reservedMemory; - - /** - * @var array List of error levels and their description - */ - const ERROR_LEVELS_DESCRIPTION = [ - E_DEPRECATED => 'Deprecated', - E_USER_DEPRECATED => 'User Deprecated', - E_NOTICE => 'Notice', - E_USER_NOTICE => 'User Notice', - E_STRICT => 'Runtime Notice', - E_WARNING => 'Warning', - E_USER_WARNING => 'User Warning', - E_COMPILE_WARNING => 'Compile Warning', - E_CORE_WARNING => 'Core Warning', - E_USER_ERROR => 'User Error', - E_RECOVERABLE_ERROR => 'Catchable Fatal Error', - E_COMPILE_ERROR => 'Compile Error', - E_PARSE => 'Parse Error', - E_ERROR => 'Error', - E_CORE_ERROR => 'Core Error', - ]; - - /** - * Constructor. - * - * @param callable $callback The callback that will be invoked in case an error is caught - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler - * - * @throws \ReflectionException - */ - protected function __construct(callable $callback, int $reservedMemorySize = 10240) - { - if (!\is_int($reservedMemorySize) || $reservedMemorySize <= 0) { - throw new \UnexpectedValueException('The value of the $reservedMemorySize argument must be an integer greater than 0.'); - } - - $this->callback = $callback; - $this->exceptionReflection = new \ReflectionProperty(\Exception::class, 'trace'); - $this->exceptionReflection->setAccessible(true); - - if (null === self::$reservedMemory) { - self::$reservedMemory = str_repeat('x', $reservedMemorySize); - - register_shutdown_function([$this, 'handleFatalError']); - } - - $this->previousErrorHandler = set_error_handler([$this, 'handleError']); - - if (null === $this->previousErrorHandler) { - restore_error_handler(); - - // Specifying the error types caught by the error handler with the - // first call to the set_error_handler method would cause the PHP - // bug https://bugs.php.net/63206 if the handler is not the first - // one - set_error_handler([$this, 'handleError'], $this->capturedErrors); - - $this->isRoot = true; - } - - $this->previousExceptionHandler = set_exception_handler([$this, 'handleException']); - } - - /** - * Sets the PHP error levels that will be captured by the Raven client when - * a PHP error occurs. - * - * @param int $levels A bit field of E_* constants for captured errors - * @param bool $replace Whether to replace or amend the previous value - * - * @return int The previous value - */ - public function captureAt($levels, $replace = false) - { - $prev = $this->capturedErrors; - - $this->capturedErrors = $levels; - - if (!$replace) { - $this->capturedErrors |= $prev; - } - - $this->reRegister($prev); - - return $prev; - } - - /** - * Handles errors by capturing them through the Raven client according to - * the configured bit field. - * - * @param int $level The level of the error raised, represented by one - * of the E_* constants - * @param string $message The error message - * @param string $file The filename the error was raised in - * @param int $line The line number the error was raised at - * - * @return bool If the function returns FALSE then the normal error handler continues - * - * @throws \Throwable - * - * @internal - */ - public function handleError($level, $message, $file, $line) - { - $shouldCaptureError = (bool) ($this->capturedErrors & $level); - - if (!$shouldCaptureError) { - return false; - } - - $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); - $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $file, $line); - - $this->exceptionReflection->setValue($errorAsException, $backtrace); - - try { - $this->handleException($errorAsException); - } catch (\Throwable $exception) { - // Do nothing as this error handler should be as transparent as possible - } - - if (null !== $this->previousErrorHandler) { - return \call_user_func($this->previousErrorHandler, $level, $message, $file, $line); - } - - return false; - } - - /** - * Handles fatal errors by capturing them through the Raven client. This - * method is used as callback of a shutdown function. - * - * @param array|null $error The error details as returned by error_get_last() - * - * @internal - */ - public function handleFatalError(array $error = null) - { - // If there is not enough memory that can be used to handle the error - // do nothing - if (null === self::$reservedMemory) { - return; - } - - self::$reservedMemory = null; - $errorAsException = null; - - if (null === $error) { - $error = error_get_last(); - } - - if (!empty($error) && $error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)) { - $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); - } - - try { - if (null !== $errorAsException) { - $this->handleException($errorAsException); - } - } catch (\Throwable $errorAsException) { - // Ignore this re-throw - } - } - - /** - * Handles the given exception by capturing it through the Raven client and - * then forwarding it to another handler. - * - * @param \Throwable $exception The exception to handle - * - * @throws \Throwable - * - * @internal - */ - public function handleException($exception) - { - $this->doHandleException($exception); - - $previousExceptionHandlerException = $exception; - - // Unset the previous exception handler to prevent infinite loop in case - // we need to handle an exception thrown from it - $previousExceptionHandler = $this->previousExceptionHandler; - $this->previousExceptionHandler = null; - - try { - if (null !== $previousExceptionHandler) { - $previousExceptionHandler($exception); - } - } catch (\Exception $previousExceptionHandlerException) { - // Do nothing, we just need to set the $previousExceptionHandlerException - // variable to the exception we just catched to compare it later - // with the original object instance - } - - // If the exception object instance is the same as the one catched from - // the previous exception handler, if any, give it back to the native - // PHP handler to prevent infinite circular loop - if ($exception === $previousExceptionHandlerException) { - // Disable the fatal error handler or the error will be reported twice - self::$reservedMemory = null; - - throw $exception; - } - - $this->handleException($previousExceptionHandlerException); - } - - /** - * Re-registers the error handler if the mask that configures the intercepted - * error types changed. - * - * @param int $previousThrownErrors The previous error mask - */ - protected function reRegister($previousThrownErrors) - { - if ($this->capturedErrors === $previousThrownErrors) { - return; - } - - $handler = set_error_handler('var_dump'); - $handler = \is_array($handler) ? $handler[0] : null; - - restore_error_handler(); - - if ($handler === $this) { - restore_error_handler(); - - if ($this->isRoot) { - set_error_handler([$this, 'handleError'], $this->capturedErrors); - } else { - set_error_handler([$this, 'handleError']); - } - } - } - - /** - * Cleans and returns the backtrace without the first frames that belong to - * this error handler. - * - * @param array $backtrace The backtrace to clear - * @param string $file The filename the backtrace was raised in - * @param int $line The line number the backtrace was raised at - * - * @return array - */ - protected function cleanBacktraceFromErrorHandlerFrames($backtrace, $file, $line) - { - $cleanedBacktrace = $backtrace; - $index = 0; - - while ($index < \count($backtrace)) { - if (isset($backtrace[$index]['file'], $backtrace[$index]['line']) && $backtrace[$index]['line'] === $line && $backtrace[$index]['file'] === $file) { - $cleanedBacktrace = \array_slice($cleanedBacktrace, 1 + $index); - - break; - } - - ++$index; - } - - return $cleanedBacktrace; - } - - /** - * Handles the given exception. This method can be overridden to customize - * the logging of an exception. - * - * @param \Throwable $exception The exception to handle - * - * @throws \Throwable - */ - abstract protected function doHandleException($exception); -} diff --git a/src/Breadcrumbs/Breadcrumb.php b/src/Breadcrumb.php similarity index 80% rename from src/Breadcrumbs/Breadcrumb.php rename to src/Breadcrumb.php index 9e1f68565..e8d13889e 100644 --- a/src/Breadcrumbs/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -1,15 +1,8 @@ timestamp = microtime(true); } - /** - * Creates a new instance of this class configured with the given params. - * - * @param string $level The error level of the breadcrumb - * @param string $type The type of the breadcrumb - * @param string $category The category of the breadcrumb - * @param string|null $message Optional text message - * @param array $metadata Additional information about the breadcrumb - * - * @return static - */ - public static function create($level, $type, $category, $message = null, array $metadata = []) - { - return new static($level, $type, $category, $message, $metadata); - } - /** * Maps the severity of the error to one of the levels supported by the * breadcrumbs. @@ -186,7 +163,7 @@ public static function levelFromErrorException(\ErrorException $exception): stri * * @return string */ - public function getType() + public function getType(): string { return $this->type; } @@ -198,7 +175,7 @@ public function getType() * * @return static */ - public function withType($type) + public function withType(string $type): self { if ($type === $this->type) { return $this; @@ -215,7 +192,7 @@ public function withType($type) * * @return string */ - public function getLevel() + public function getLevel(): string { return $this->level; } @@ -227,7 +204,7 @@ public function getLevel() * * @return static */ - public function withLevel($level) + public function withLevel(string $level): self { if (!\in_array($level, self::ALLOWED_LEVELS, true)) { throw new InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); @@ -248,7 +225,7 @@ public function withLevel($level) * * @return string */ - public function getCategory() + public function getCategory(): string { return $this->category; } @@ -260,7 +237,7 @@ public function getCategory() * * @return static */ - public function withCategory($category) + public function withCategory(string $category): self { if ($category === $this->category) { return $this; @@ -277,7 +254,7 @@ public function withCategory($category) * * @return string|null */ - public function getMessage() + public function getMessage(): ?string { return $this->message; } @@ -289,7 +266,7 @@ public function getMessage() * * @return static */ - public function withMessage($message) + public function withMessage(string $message): self { if ($message === $this->message) { return $this; @@ -306,7 +283,7 @@ public function withMessage($message) * * @return array */ - public function getMetadata() + public function getMetadata(): array { return $this->metadata; } @@ -320,7 +297,7 @@ public function getMetadata() * * @return static */ - public function withMetadata($name, $value) + public function withMetadata(string $name, $value): self { if (isset($this->metadata[$name]) && $value === $this->metadata[$name]) { return $this; @@ -340,7 +317,7 @@ public function withMetadata($name, $value) * * @return static|Breadcrumb */ - public function withoutMetadata($name) + public function withoutMetadata(string $name): self { if (!isset($this->metadata[$name])) { return $this; @@ -358,36 +335,17 @@ public function withoutMetadata($name) * * @return float */ - public function getTimestamp() + public function getTimestamp(): float { return $this->timestamp; } - /** - * Sets the breadcrumb timestamp. - * - * @param float $timestamp the timestamp - * - * @return static - */ - public function withTimestamp($timestamp) - { - if ($timestamp === $this->timestamp) { - return $this; - } - - $new = clone $this; - $new->timestamp = $timestamp; - - return $new; - } - /** * Gets the breadcrumb as an array. * * @return array */ - public function toArray() + public function toArray(): array { return [ 'type' => $this->type, @@ -402,7 +360,7 @@ public function toArray() /** * {@inheritdoc} */ - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } diff --git a/src/Client.php b/src/Client.php index c5ad3ae25..ff589a872 100644 --- a/src/Client.php +++ b/src/Client.php @@ -1,17 +1,9 @@ integrations = Handler::setupIntegrations($integrations); $this->serializer = new Serializer($this->options->getMbDetectOrder()); $this->representationSerializer = new ReprSerializer($this->options->getMbDetectOrder()); + if ($this->options->getSerializeAllObjects()) { $this->serializer->setAllObjectSerialize($this->options->getSerializeAllObjects()); $this->representationSerializer->setAllObjectSerialize($this->options->getSerializeAllObjects()); diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 7311ddc36..a977e710c 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -1,13 +1,6 @@ options = new Options($options); - if (null === $this->options->getIntegrations()) { - $this->integrations = []; - } else { + if (null !== $this->options->getIntegrations()) { $this->integrations = \array_merge([ new ErrorHandlerIntegration(), new RequestIntegration(), @@ -141,7 +132,7 @@ public static function create(array $options = []): self /** * {@inheritdoc} */ - public function setUriFactory(UriFactory $uriFactory) + public function setUriFactory(UriFactory $uriFactory): self { $this->uriFactory = $uriFactory; @@ -151,7 +142,7 @@ public function setUriFactory(UriFactory $uriFactory) /** * {@inheritdoc} */ - public function setMessageFactory(MessageFactory $messageFactory) + public function setMessageFactory(MessageFactory $messageFactory): self { $this->messageFactory = $messageFactory; @@ -161,7 +152,7 @@ public function setMessageFactory(MessageFactory $messageFactory) /** * {@inheritdoc} */ - public function setTransport(TransportInterface $transport) + public function setTransport(TransportInterface $transport): self { $this->transport = $transport; @@ -171,7 +162,7 @@ public function setTransport(TransportInterface $transport) /** * {@inheritdoc} */ - public function setHttpClient(HttpAsyncClient $httpClient) + public function setHttpClient(HttpAsyncClient $httpClient): self { $this->httpClient = $httpClient; @@ -181,7 +172,7 @@ public function setHttpClient(HttpAsyncClient $httpClient) /** * {@inheritdoc} */ - public function addHttpClientPlugin(Plugin $plugin) + public function addHttpClientPlugin(Plugin $plugin): self { $this->httpClientPlugins[] = $plugin; @@ -191,7 +182,7 @@ public function addHttpClientPlugin(Plugin $plugin) /** * {@inheritdoc} */ - public function removeHttpClientPlugin($className) + public function removeHttpClientPlugin(string $className): self { foreach ($this->httpClientPlugins as $index => $httpClientPlugin) { if (!$httpClientPlugin instanceof $className) { @@ -235,17 +226,15 @@ public function __call($name, $arguments) throw new \BadMethodCallException(sprintf('The method named "%s" does not exists.', $name)); } - $this->options->$name(...$arguments); - - return $this; + return $this->options->$name(...$arguments); } /** * Creates a new instance of the HTTP client. * - * @return HttpAsyncClient + * @return PluginClient */ - private function createHttpClientInstance() + private function createHttpClientInstance(): PluginClient { if (null === $this->uriFactory) { throw new \RuntimeException('The PSR-7 URI factory must be set.'); @@ -276,7 +265,7 @@ private function createHttpClientInstance() * * @return TransportInterface */ - private function createTransportInstance() + private function createTransportInstance(): TransportInterface { if (null !== $this->transport) { return $this->transport; diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 71abface3..dbcc37619 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -1,13 +1,6 @@ data = $recursive ? array_merge_recursive($this->data, $data) : array_merge($this->data, $data); } /** + * Sets the given data into this object. + * * @param array $data */ - public function setData(array $data) + public function setData(array $data): void { foreach ($data as $index => $value) { $this->data[$index] = $value; @@ -86,7 +81,7 @@ public function setData(array $data) * * @param array $data The data to set */ - public function replaceData(array $data) + public function replaceData(array $data): void { $this->data = $data; } @@ -94,7 +89,7 @@ public function replaceData(array $data) /** * Clears the entire data contained in this object. */ - public function clear() + public function clear(): void { $this->data = []; } @@ -104,7 +99,7 @@ public function clear() * * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return empty($this->data); } @@ -114,7 +109,7 @@ public function isEmpty() * * @return array */ - public function toArray() + public function toArray(): array { return $this->data; } @@ -122,7 +117,7 @@ public function toArray() /** * {@inheritdoc} */ - public function offsetExists($offset) + public function offsetExists($offset): bool { return isset($this->data[$offset]) || array_key_exists($offset, $this->data); } @@ -138,7 +133,7 @@ public function offsetGet($offset) /** * {@inheritdoc} */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } @@ -146,7 +141,7 @@ public function offsetSet($offset, $value) /** * {@inheritdoc} */ - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->data[$offset]); } @@ -154,7 +149,7 @@ public function offsetUnset($offset) /** * {@inheritdoc} */ - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } @@ -162,7 +157,7 @@ public function jsonSerialize() /** * {@inheritdoc} */ - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator($this->data); } diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 105578011..1bddb653a 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -1,13 +1,6 @@ resolver->resolve($data); @@ -69,7 +62,7 @@ public function merge(array $data, $recursive = false) * @throws InvalidOptionsException If any of the options don't fulfill the * specified validation rules */ - public function setData(array $data) + public function setData(array $data): void { $data = $this->resolver->resolve($data); @@ -84,7 +77,7 @@ public function setData(array $data) * @throws InvalidOptionsException If any of the options don't fulfill the * specified validation rules */ - public function replaceData(array $data) + public function replaceData(array $data): void { $data = $this->resolver->resolve($data); @@ -99,7 +92,7 @@ public function replaceData(array $data) * @throws InvalidOptionsException If any of the options don't fulfill the * specified validation rules */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { $data = $this->resolver->resolve([$offset => $value]); @@ -111,7 +104,7 @@ public function offsetSet($offset, $value) * * @return string */ - public function getName() + public function getName(): string { return $this->data['name']; } @@ -121,7 +114,7 @@ public function getName() * * @param string $name The name */ - public function setName($name) + public function setName(string $name): void { $this->offsetSet('name', $name); } @@ -131,7 +124,7 @@ public function setName($name) * * @return string */ - public function getVersion() + public function getVersion(): string { return $this->data['version']; } @@ -141,7 +134,7 @@ public function getVersion() * * @param string $version The version */ - public function setVersion($version) + public function setVersion(string $version): void { $this->offsetSet('version', $version); } @@ -151,7 +144,7 @@ public function setVersion($version) * * @param OptionsResolver $resolver The resolver for the options */ - private function configureOptions(OptionsResolver $resolver) + private function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'name' => 'php', diff --git a/src/Context/ServerOsContext.php b/src/Context/ServerOsContext.php index 6d3540b15..1639d5fb4 100644 --- a/src/Context/ServerOsContext.php +++ b/src/Context/ServerOsContext.php @@ -1,13 +1,6 @@ resolver->resolve($data); @@ -68,7 +61,7 @@ public function merge(array $data, $recursive = false) * @throws InvalidOptionsException If any of the options don't fulfill the * specified validation rules */ - public function setData(array $data) + public function setData(array $data): void { $data = $this->resolver->resolve($data); @@ -83,7 +76,7 @@ public function setData(array $data) * @throws InvalidOptionsException If any of the options don't fulfill the * specified validation rules */ - public function replaceData(array $data) + public function replaceData(array $data): void { $data = $this->resolver->resolve($data); @@ -98,7 +91,7 @@ public function replaceData(array $data) * @throws InvalidOptionsException If any of the options don't fulfill the * specified validation rules */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { $data = $this->resolver->resolve([$offset => $value]); @@ -110,7 +103,7 @@ public function offsetSet($offset, $value) * * @return string */ - public function getName() + public function getName(): string { return $this->data['name']; } @@ -120,7 +113,7 @@ public function getName() * * @param string $name The name */ - public function setName($name) + public function setName(string $name): void { $this->offsetSet('name', $name); } @@ -130,7 +123,7 @@ public function setName($name) * * @return string */ - public function getVersion() + public function getVersion(): string { return $this->data['version']; } @@ -140,7 +133,7 @@ public function getVersion() * * @param string $version The version */ - public function setVersion($version) + public function setVersion(string $version): void { $this->offsetSet('version', $version); } @@ -150,7 +143,7 @@ public function setVersion($version) * * @return string */ - public function getBuild() + public function getBuild(): string { return $this->data['build']; } @@ -160,7 +153,7 @@ public function getBuild() * * @param string $build The build */ - public function setBuild($build) + public function setBuild(string $build): void { $this->offsetSet('build', $build); } @@ -170,7 +163,7 @@ public function setBuild($build) * * @return string */ - public function getKernelVersion() + public function getKernelVersion(): string { return $this->data['kernel_version']; } @@ -180,7 +173,7 @@ public function getKernelVersion() * * @param string $kernelVersion The kernel version */ - public function setKernelVersion($kernelVersion) + public function setKernelVersion(string $kernelVersion): void { $this->offsetSet('kernel_version', $kernelVersion); } @@ -190,7 +183,7 @@ public function setKernelVersion($kernelVersion) * * @param OptionsResolver $resolver The resolver for the options */ - private function configureOptions(OptionsResolver $resolver) + private function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'name' => php_uname('s'), diff --git a/src/Context/TagsContext.php b/src/Context/TagsContext.php index 6e06d4527..d36d3103c 100644 --- a/src/Context/TagsContext.php +++ b/src/Context/TagsContext.php @@ -1,13 +1,6 @@ */ -class ErrorHandler extends AbstractErrorHandler +final class ErrorHandler { /** - * Registers this error handler by associating its instance with the given - * Raven client. + * The default amount of bytes of memory to reserve for the fatal error handler. + */ + private const DEFAULT_RESERVED_MEMORY_SIZE = 10240; + + /** + * @var callable Callback that will be invoked when an error is caught + */ + private $callback; + + /** + * @var \ReflectionProperty A reflection cached instance that points to the + * trace property of the exception objects + */ + private $exceptionReflection; + + /** + * @var callable|null The previous error handler, if any + */ + private $previousErrorHandler; + + /** + * @var callable|null The previous exception handler, if any + */ + private $previousExceptionHandler; + + /** + * @var int The errors that will be catched by the error handler + */ + private $capturedErrors = E_ALL; + + /** + * @var bool Flag indicating whether this error handler is the first in the + * chain of registered error handlers + */ + private $isRoot = false; + + /** + * @var string|null A portion of pre-allocated memory data that will be reclaimed + * in case a fatal error occurs to handle it + */ + private static $reservedMemory; + + /** + * @var array List of error levels and their description + */ + const ERROR_LEVELS_DESCRIPTION = [ + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + E_NOTICE => 'Notice', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_WARNING => 'Warning', + E_USER_WARNING => 'User Warning', + E_COMPILE_WARNING => 'Compile Warning', + E_CORE_WARNING => 'Core Warning', + E_USER_ERROR => 'User Error', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + E_COMPILE_ERROR => 'Compile Error', + E_PARSE => 'Parse Error', + E_ERROR => 'Error', + E_CORE_ERROR => 'Core Error', + ]; + + /** + * Constructor. + * + * @param callable $callback The callback that will be invoked in case an error is caught + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * + * @throws \ReflectionException + */ + private function __construct(callable $callback, int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE) + { + if ($reservedMemorySize <= 0) { + throw new \UnexpectedValueException('The $reservedMemorySize argument must be greater than 0.'); + } + + $this->callback = $callback; + $this->exceptionReflection = new \ReflectionProperty(\Exception::class, 'trace'); + $this->exceptionReflection->setAccessible(true); + + if (null === self::$reservedMemory) { + self::$reservedMemory = str_repeat('x', $reservedMemorySize); + + register_shutdown_function([$this, 'handleFatalError']); + } + + $this->previousErrorHandler = set_error_handler([$this, 'handleError']); + + if (null === $this->previousErrorHandler) { + restore_error_handler(); + + // Specifying the error types caught by the error handler with the + // first call to the set_error_handler method would cause the PHP + // bug https://bugs.php.net/63206 if the handler is not the first + // one in the chain of handlers + set_error_handler([$this, 'handleError'], $this->capturedErrors); + + $this->isRoot = true; + } + + $this->previousExceptionHandler = set_exception_handler([$this, 'handleException']); + } + + /** + * Registers this error handler and associates the given callback to the + * instance. * * @param callable $callback callback that will be called when exception is caught * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler * - * @throws \Exception|\Throwable|\ErrorException + * @return self * - * @return ErrorHandler + * @throws \ReflectionException */ - public static function register(callable $callback, $reservedMemorySize = 10240) + public static function register(callable $callback, int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE): self { return new self($callback, $reservedMemorySize); } /** - * {@inheritdoc} + * Sets the PHP error levels that will be captured by the Raven client when + * a PHP error occurs. + * + * @param int $levels A bit field of E_* constants for captured errors + * @param bool $replace Whether to replace or amend the previous value + * + * @return int The previous value */ - protected function doHandleException($exception) + public function captureAt(int $levels, bool $replace = false): int + { + $prev = $this->capturedErrors; + + $this->capturedErrors = $levels; + + if (!$replace) { + $this->capturedErrors |= $prev; + } + + $this->reRegister($prev); + + return $prev; + } + + /** + * Handles errors by capturing them through the Raven client according to + * the configured bit field. + * + * @param int $level The level of the error raised, represented by one + * of the E_* constants + * @param string $message The error message + * @param string $file The filename the error was raised in + * @param int $line The line number the error was raised at + * + * @return bool If the function returns `false` then the PHP native error + * handler will be called + * + * @throws \Throwable + * + * @internal + */ + public function handleError(int $level, string $message, string $file, int $line): bool + { + $shouldCaptureError = (bool) ($this->capturedErrors & $level); + + if (!$shouldCaptureError) { + return false; + } + + $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); + $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $file, $line); + + $this->exceptionReflection->setValue($errorAsException, $backtrace); + + try { + $this->handleException($errorAsException); + } catch (\Throwable $exception) { + // Do nothing as this error handler should be as transparent as possible + } + + if (null !== $this->previousErrorHandler) { + return false !== \call_user_func($this->previousErrorHandler, $level, $message, $file, $line); + } + + return false; + } + + /** + * Handles fatal errors by capturing them through the Raven client. This + * method is used as callback of a shutdown function. + * + * @param array|null $error The error details as returned by error_get_last() + * + * @internal + */ + public function handleFatalError(array $error = null): void + { + // If there is not enough memory that can be used to handle the error + // do nothing + if (null === self::$reservedMemory) { + return; + } + + self::$reservedMemory = null; + $errorAsException = null; + + if (null === $error) { + $error = error_get_last(); + } + + if (!empty($error) && $error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)) { + $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); + } + + try { + if (null !== $errorAsException) { + $this->handleException($errorAsException); + } + } catch (\Throwable $errorAsException) { + // Ignore this re-throw + } + } + + /** + * Handles the given exception by capturing it through the Raven client and + * then forwarding it to another handler. + * + * @param \Throwable $exception The exception to handle + * + * @throws \Throwable + * + * @internal + */ + public function handleException(\Throwable $exception): void { \call_user_func($this->callback, $exception); + + $previousExceptionHandlerException = $exception; + + // Unset the previous exception handler to prevent infinite loop in case + // we need to handle an exception thrown from it + $previousExceptionHandler = $this->previousExceptionHandler; + $this->previousExceptionHandler = null; + + try { + if (null !== $previousExceptionHandler) { + $previousExceptionHandler($exception); + } + } catch (\Throwable $previousExceptionHandlerException) { + // Do nothing, we just need to set the $previousExceptionHandlerException + // variable to the exception we just catched to compare it later + // with the original object instance + } + + // If the exception object instance is the same as the one catched from + // the previous exception handler, if any, give it back to the native + // PHP handler to prevent infinite circular loop + if ($exception === $previousExceptionHandlerException) { + // Disable the fatal error handler or the error will be reported twice + self::$reservedMemory = null; + + throw $exception; + } + + $this->handleException($previousExceptionHandlerException); + } + + /** + * Re-registers the error handler if the mask that configures the intercepted + * error types changed. + * + * @param int $previousThrownErrors The previous error mask + */ + private function reRegister(int $previousThrownErrors): void + { + if ($this->capturedErrors === $previousThrownErrors) { + return; + } + + $handler = set_error_handler('var_dump'); + $handler = \is_array($handler) ? $handler[0] : null; + + restore_error_handler(); + + if ($handler === $this) { + restore_error_handler(); + + if ($this->isRoot) { + set_error_handler([$this, 'handleError'], $this->capturedErrors); + } else { + set_error_handler([$this, 'handleError']); + } + } + } + + /** + * Cleans and returns the backtrace without the first frames that belong to + * this error handler. + * + * @param array $backtrace The backtrace to clear + * @param string $file The filename the backtrace was raised in + * @param int $line The line number the backtrace was raised at + * + * @return array + */ + private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array + { + $cleanedBacktrace = $backtrace; + $index = 0; + + while ($index < \count($backtrace)) { + if (isset($backtrace[$index]['file'], $backtrace[$index]['line']) && $backtrace[$index]['line'] === $line && $backtrace[$index]['file'] === $file) { + $cleanedBacktrace = \array_slice($cleanedBacktrace, 1 + $index); + + break; + } + + ++$index; + } + + return $cleanedBacktrace; } } diff --git a/src/Event.php b/src/Event.php index 09a678352..dec6c415c 100644 --- a/src/Event.php +++ b/src/Event.php @@ -1,20 +1,12 @@ logger = $logger; } @@ -218,7 +210,7 @@ public function setLogger($logger): void * * @return string|null */ - public function getTransaction() + public function getTransaction(): ?string { return $this->transaction; } @@ -229,7 +221,7 @@ public function getTransaction() * * @param string|null $transaction The transaction name */ - public function setTransaction($transaction) + public function setTransaction(?string $transaction): void { $this->transaction = $transaction; } @@ -239,7 +231,7 @@ public function setTransaction($transaction) * * @return string */ - public function getServerName() + public function getServerName(): string { return $this->serverName; } @@ -249,7 +241,7 @@ public function getServerName() * * @param string $serverName The server name */ - public function setServerName($serverName) + public function setServerName(string $serverName): void { $this->serverName = $serverName; } @@ -257,9 +249,9 @@ public function setServerName($serverName) /** * Gets the release of the program. * - * @return string + * @return string|null */ - public function getRelease() + public function getRelease(): ?string { return $this->release; } @@ -267,9 +259,9 @@ public function getRelease() /** * Sets the release of the program. * - * @param string $release The release + * @param string|null $release The release */ - public function setRelease($release) + public function setRelease(?string $release): void { $this->release = $release; } @@ -277,9 +269,9 @@ public function setRelease($release) /** * Gets the error message. * - * @return string + * @return string|null */ - public function getMessage() + public function getMessage(): ?string { return $this->message; } @@ -289,7 +281,7 @@ public function getMessage() * * @return string[] */ - public function getMessageParams() + public function getMessageParams(): array { return $this->messageParams; } @@ -300,7 +292,7 @@ public function getMessageParams() * @param string $message The message * @param array $params The parameters to use to format the message */ - public function setMessage($message, array $params = []) + public function setMessage(string $message, array $params = []): void { $this->message = $message; $this->messageParams = $params; @@ -311,7 +303,7 @@ public function setMessage($message, array $params = []) * * @return array */ - public function getModules() + public function getModules(): array { return $this->modules; } @@ -321,7 +313,7 @@ public function getModules() * * @param array $modules */ - public function setModules(array $modules) + public function setModules(array $modules): void { $this->modules = $modules; } @@ -331,7 +323,7 @@ public function setModules(array $modules) * * @return array */ - public function getRequest() + public function getRequest(): array { return $this->request; } @@ -341,7 +333,7 @@ public function getRequest() * * @param array $request The request data */ - public function setRequest(array $request) + public function setRequest(array $request): void { $this->request = $request; } @@ -351,7 +343,7 @@ public function setRequest(array $request) * * @return Context */ - public function getExtraContext() + public function getExtraContext(): Context { return $this->extraContext; } @@ -361,7 +353,7 @@ public function getExtraContext() * * @return TagsContext */ - public function getTagsContext() + public function getTagsContext(): TagsContext { return $this->tagsContext; } @@ -371,7 +363,7 @@ public function getTagsContext() * * @return UserContext */ - public function getUserContext() + public function getUserContext(): UserContext { return $this->userContext; } @@ -381,7 +373,7 @@ public function getUserContext() * * @return ServerOsContext */ - public function getServerOsContext() + public function getServerOsContext(): ServerOsContext { return $this->serverOsContext; } @@ -391,7 +383,7 @@ public function getServerOsContext() * * @return RuntimeContext */ - public function getRuntimeContext() + public function getRuntimeContext(): RuntimeContext { return $this->runtimeContext; } @@ -402,7 +394,7 @@ public function getRuntimeContext() * * @return string[] */ - public function getFingerprint() + public function getFingerprint(): array { return $this->fingerprint; } @@ -413,7 +405,7 @@ public function getFingerprint() * * @param string[] $fingerprint The strings */ - public function setFingerprint(array $fingerprint) + public function setFingerprint(array $fingerprint): void { $this->fingerprint = $fingerprint; } @@ -421,9 +413,9 @@ public function setFingerprint(array $fingerprint) /** * Gets the environment in which this event was generated. * - * @return string + * @return string|null */ - public function getEnvironment() + public function getEnvironment(): ?string { return $this->environment; } @@ -431,9 +423,9 @@ public function getEnvironment() /** * Sets the environment in which this event was generated. * - * @param string $environment The name of the environment + * @param string|null $environment The name of the environment */ - public function setEnvironment($environment) + public function setEnvironment(?string $environment): void { $this->environment = $environment; } @@ -443,7 +435,7 @@ public function setEnvironment($environment) * * @return Breadcrumb[] */ - public function getBreadcrumbs() + public function getBreadcrumbs(): array { return $this->breadcrumbs; } @@ -453,7 +445,7 @@ public function getBreadcrumbs() * * @param Breadcrumb[] $breadcrumbs The breadcrumb array */ - public function setBreadcrumb(array $breadcrumbs) + public function setBreadcrumb(array $breadcrumbs): void { $this->breadcrumbs = $breadcrumbs; } @@ -463,7 +455,7 @@ public function setBreadcrumb(array $breadcrumbs) * * @return array */ - public function getExceptions() + public function getExceptions(): array { return $this->exceptions; } @@ -473,7 +465,7 @@ public function getExceptions() * * @param array $exceptions The exception */ - public function setExceptions(array $exceptions) + public function setExceptions(array $exceptions): void { $this->exceptions = $exceptions; } @@ -483,7 +475,7 @@ public function setExceptions(array $exceptions) * * @return Stacktrace|null */ - public function getStacktrace() + public function getStacktrace(): ?Stacktrace { return $this->stacktrace; } @@ -493,7 +485,7 @@ public function getStacktrace() * * @param Stacktrace $stacktrace The stacktrace instance */ - public function setStacktrace(Stacktrace $stacktrace) + public function setStacktrace(Stacktrace $stacktrace): void { $this->stacktrace = $stacktrace; } diff --git a/src/Exception.php b/src/Exception.php deleted file mode 100644 index adf0603d8..000000000 --- a/src/Exception.php +++ /dev/null @@ -1,7 +0,0 @@ -functionName = $functionName; $this->file = $file; @@ -80,9 +73,9 @@ public function __construct($functionName, $file, $line) /** * Gets the name of the function being called. * - * @return string + * @return string|null */ - public function getFunctionName() + public function getFunctionName(): ?string { return $this->functionName; } @@ -92,7 +85,7 @@ public function getFunctionName() * * @return string */ - public function getFile() + public function getFile(): string { return $this->file; } @@ -102,7 +95,7 @@ public function getFile() * * @return int */ - public function getLine() + public function getLine(): int { return $this->line; } @@ -112,7 +105,7 @@ public function getLine() * * @return string[] */ - public function getPreContext() + public function getPreContext(): array { return $this->preContext; } @@ -122,7 +115,7 @@ public function getPreContext() * * @param string[] $preContext The source code lines */ - public function setPreContext(array $preContext) + public function setPreContext(array $preContext): void { $this->preContext = $preContext; } @@ -133,7 +126,7 @@ public function setPreContext(array $preContext) * * @return string|null */ - public function getContextLine() + public function getContextLine(): ?string { return $this->contextLine; } @@ -144,7 +137,7 @@ public function getContextLine() * * @param string|null $contextLine The source code line */ - public function setContextLine($contextLine) + public function setContextLine(?string $contextLine): void { $this->contextLine = $contextLine; } @@ -154,7 +147,7 @@ public function setContextLine($contextLine) * * @return string[] */ - public function getPostContext() + public function getPostContext(): array { return $this->postContext; } @@ -164,7 +157,7 @@ public function getPostContext() * * @param string[] $postContext The source code lines */ - public function setPostContext(array $postContext) + public function setPostContext(array $postContext): void { $this->postContext = $postContext; } @@ -175,7 +168,7 @@ public function setPostContext(array $postContext) * * @return bool */ - public function isInApp() + public function isInApp(): bool { return $this->inApp; } @@ -186,7 +179,7 @@ public function isInApp() * * @param bool $inApp flag indicating whether the frame is application-related */ - public function setIsInApp(bool $inApp) + public function setIsInApp(bool $inApp): void { $this->inApp = $inApp; } @@ -197,7 +190,7 @@ public function setIsInApp(bool $inApp) * * @return array */ - public function getVars() + public function getVars(): array { return $this->vars; } @@ -208,7 +201,7 @@ public function getVars() * * @param array $vars The variables */ - public function setVars(array $vars) + public function setVars(array $vars): void { $this->vars = $vars; } @@ -219,7 +212,7 @@ public function setVars(array $vars) * * @return array */ - public function toArray() + public function toArray(): array { $result = [ 'function' => $this->functionName, @@ -250,7 +243,7 @@ public function toArray() /** * {@inheritdoc} */ - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } diff --git a/src/HttpClient/Authentication/SentryAuth.php b/src/HttpClient/Authentication/SentryAuth.php index 53fdfeee0..e564327a1 100644 --- a/src/HttpClient/Authentication/SentryAuth.php +++ b/src/HttpClient/Authentication/SentryAuth.php @@ -1,13 +1,6 @@ Client::PROTOCOL_VERSION, diff --git a/src/Integration/ErrorHandlerIntegration.php b/src/Integration/ErrorHandlerIntegration.php index fd2a073ab..27b57df2f 100644 --- a/src/Integration/ErrorHandlerIntegration.php +++ b/src/Integration/ErrorHandlerIntegration.php @@ -4,18 +4,19 @@ namespace Sentry\Integration; -use Sentry\Breadcrumbs\Breadcrumb; +use Sentry\Breadcrumb; use Sentry\ErrorHandler; use Sentry\State\Hub; /** - * This integration hooks into the global error handlers and emits events to Sentry. + * This integration hooks into the global error handlers and emits events to + * Sentry. */ final class ErrorHandlerIntegration implements IntegrationInterface { public function setupOnce(): void { - ErrorHandler::register(function ($exception) { + ErrorHandler::register(function (\Throwable $exception): void { $self = Hub::getCurrent()->getIntegration(self::class); if ($self instanceof self) { diff --git a/src/Integration/Handler.php b/src/Integration/Handler.php index 560b7e6cd..5dfb481d6 100644 --- a/src/Integration/Handler.php +++ b/src/Integration/Handler.php @@ -23,21 +23,21 @@ public static function setupIntegrations(array $integrations): array { $integrationIndex = []; + /* @var IntegrationInterface $integration */ foreach ($integrations as $integration) { - /* @var IntegrationInterface $integration */ - $class = \get_class($integration); + $className = \get_class($integration); if (!$integration instanceof IntegrationInterface) { - throw new \InvalidArgumentException(sprintf('Expecting integration implementing %s interface, got %s', IntegrationInterface::class, $class)); + throw new \InvalidArgumentException(sprintf('Expecting integration implementing %s interface, got %s', IntegrationInterface::class, $className)); } - if (!isset(self::$integrations[$class])) { - self::$integrations[$class] = true; + if (!isset(self::$integrations[$className])) { + self::$integrations[$className] = true; $integration->setupOnce(); } - $integrationIndex[$class] = $integration; + $integrationIndex[$className] = $integration; } return $integrationIndex; diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index 5e7c66993..3e5d64891 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -4,7 +4,6 @@ namespace Sentry\Integration; -use Composer\Composer; use Jean85\PrettyVersions; use PackageVersions\Versions; use Sentry\Event; @@ -46,6 +45,8 @@ public function setupOnce(): void } /** + * Applies the information gathered by this integration to the event. + * * @param self $self The instance of this integration * @param Event $event The event that will be enriched with the modules */ diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 89a4225c8..077e10a83 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -23,7 +23,7 @@ final class RequestIntegration implements IntegrationInterface */ public function setupOnce(): void { - Scope::addGlobalEventProcessor(function (Event $event) { + Scope::addGlobalEventProcessor(function (Event $event): Event { $self = Hub::getCurrent()->getIntegration(self::class); if (!$self instanceof self) { @@ -37,13 +37,14 @@ public function setupOnce(): void } /** + * Applies the information gathered by the this integration to the event. + * * @param Event $event The event that will be enriched with a request * @param null|ServerRequestInterface $request The Request that will be processed and added to the event */ public static function applyToEvent(Event $event, ?ServerRequestInterface $request = null): void { if (null === $request) { - /** @var null|ServerRequestInterface $request */ $request = isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null; } diff --git a/src/Options.php b/src/Options.php index bc214463e..d31dcf66f 100644 --- a/src/Options.php +++ b/src/Options.php @@ -1,19 +1,9 @@ options['send_attempts']; } @@ -89,7 +79,7 @@ public function getSendAttempts() * * @param int $attemptsCount The number of attempts */ - public function setSendAttempts($attemptsCount) + public function setSendAttempts(int $attemptsCount): void { $options = array_merge($this->options, ['send_attempts' => $attemptsCount]); @@ -102,7 +92,7 @@ public function setSendAttempts($attemptsCount) * * @return string[] */ - public function getPrefixes() + public function getPrefixes(): array { return $this->options['prefixes']; } @@ -113,7 +103,7 @@ public function getPrefixes() * * @param array $prefixes The prefixes */ - public function setPrefixes(array $prefixes) + public function setPrefixes(array $prefixes): void { $options = array_merge($this->options, ['prefixes' => $prefixes]); @@ -125,7 +115,7 @@ public function setPrefixes(array $prefixes) * * @return bool */ - public function getSerializeAllObjects() + public function getSerializeAllObjects(): bool { return $this->options['serialize_all_object']; } @@ -135,7 +125,7 @@ public function getSerializeAllObjects() * * @param bool $serializeAllObjects Flag indicating if all objects should be serialized */ - public function setSerializeAllObjects($serializeAllObjects) + public function setSerializeAllObjects(bool $serializeAllObjects): void { $options = array_merge($this->options, ['serialize_all_object' => $serializeAllObjects]); @@ -148,7 +138,7 @@ public function setSerializeAllObjects($serializeAllObjects) * * @return float */ - public function getSampleRate() + public function getSampleRate(): float { return $this->options['sample_rate']; } @@ -159,7 +149,7 @@ public function getSampleRate() * * @param float $sampleRate The sampling factor */ - public function setSampleRate($sampleRate) + public function setSampleRate(float $sampleRate): void { $options = array_merge($this->options, ['sample_rate' => $sampleRate]); @@ -181,7 +171,7 @@ public function getMbDetectOrder() * * @param string|string[]|null $detectOrder The detection order */ - public function setMbDetectOrder($detectOrder) + public function setMbDetectOrder($detectOrder): void { $options = array_merge($this->options, ['mb_detect_order' => $detectOrder]); @@ -193,7 +183,7 @@ public function setMbDetectOrder($detectOrder) * * @return bool */ - public function getAutoLogStacks() + public function getAutoLogStacks(): bool { return $this->options['auto_log_stacks']; } @@ -203,7 +193,7 @@ public function getAutoLogStacks() * * @param bool $enable Flag indicating if the stacktrace must be auto-filled */ - public function setAutoLogStacks($enable) + public function setAutoLogStacks(bool $enable): void { $options = array_merge($this->options, ['auto_log_stacks' => $enable]); @@ -215,7 +205,7 @@ public function setAutoLogStacks($enable) * * @return int|null */ - public function getContextLines() + public function getContextLines(): ?int { return $this->options['context_lines']; } @@ -225,7 +215,7 @@ public function getContextLines() * * @param int|null $contextLines The number of lines of code */ - public function setContextLines($contextLines) + public function setContextLines(?int $contextLines): void { $options = array_merge($this->options, ['context_lines' => $contextLines]); @@ -259,7 +249,7 @@ public function setEnableCompression(bool $enabled): void * * @return string */ - public function getCurrentEnvironment() + public function getCurrentEnvironment(): string { return $this->options['current_environment']; } @@ -269,7 +259,7 @@ public function getCurrentEnvironment() * * @param string $environment The environment */ - public function setCurrentEnvironment($environment) + public function setCurrentEnvironment(string $environment): void { $options = array_merge($this->options, ['current_environment' => $environment]); @@ -282,7 +272,7 @@ public function setCurrentEnvironment($environment) * * @return string[] */ - public function getEnvironments() + public function getEnvironments(): array { return $this->options['environments']; } @@ -293,7 +283,7 @@ public function getEnvironments() * * @param string[] $environments The environments */ - public function setEnvironments(array $environments) + public function setEnvironments(array $environments): void { $options = array_merge($this->options, ['environments' => $environments]); @@ -305,7 +295,7 @@ public function setEnvironments(array $environments) * * @return string[] */ - public function getExcludedLoggers() + public function getExcludedLoggers(): array { return $this->options['excluded_loggers']; } @@ -315,7 +305,7 @@ public function getExcludedLoggers() * * @param string[] $loggers The list of logger 'progname's */ - public function setExcludedLoggers(array $loggers) + public function setExcludedLoggers(array $loggers): void { $options = array_merge($this->options, ['excluded_loggers' => $loggers]); @@ -328,7 +318,7 @@ public function setExcludedLoggers(array $loggers) * * @return string[] */ - public function getExcludedExceptions() + public function getExcludedExceptions(): array { return $this->options['excluded_exceptions']; } @@ -339,7 +329,7 @@ public function getExcludedExceptions() * * @param string[] $exceptions The list of exception classes */ - public function setExcludedExceptions(array $exceptions) + public function setExcludedExceptions(array $exceptions): void { $options = array_merge($this->options, ['excluded_exceptions' => $exceptions]); @@ -350,11 +340,11 @@ public function setExcludedExceptions(array $exceptions) * Checks whether the given exception should be ignored when sending events * to Sentry. * - * @param \Throwable|\Exception $exception The exception + * @param \Throwable $exception The exception * * @return bool */ - public function isExcludedException($exception) + public function isExcludedException(\Throwable $exception): bool { foreach ($this->options['excluded_exceptions'] as $exceptionClass) { if ($exception instanceof $exceptionClass) { @@ -370,7 +360,7 @@ public function isExcludedException($exception) * * @return string[] */ - public function getExcludedProjectPaths() + public function getExcludedProjectPaths(): array { return $this->options['excluded_app_paths']; } @@ -380,7 +370,7 @@ public function getExcludedProjectPaths() * * @param array $paths The list of paths */ - public function setExcludedProjectPaths(array $paths) + public function setExcludedProjectPaths(array $paths): void { $options = array_merge($this->options, ['excluded_app_paths' => $paths]); @@ -392,7 +382,7 @@ public function setExcludedProjectPaths(array $paths) * * @return string|null */ - public function getProjectId() + public function getProjectId(): ?string { return $this->projectId; } @@ -402,7 +392,7 @@ public function getProjectId() * * @return string|null */ - public function getProjectRoot() + public function getProjectRoot(): ?string { return $this->options['project_root']; } @@ -412,7 +402,7 @@ public function getProjectRoot() * * @param string|null $path The path to the project root */ - public function setProjectRoot($path) + public function setProjectRoot(?string $path): void { $options = array_merge($this->options, ['project_root' => $path]); @@ -424,7 +414,7 @@ public function setProjectRoot($path) * * @return string|null */ - public function getPublicKey() + public function getPublicKey(): ?string { return $this->publicKey; } @@ -434,7 +424,7 @@ public function getPublicKey() * * @return string|null */ - public function getSecretKey() + public function getSecretKey(): ?string { return $this->secretKey; } @@ -444,7 +434,7 @@ public function getSecretKey() * * @return string */ - public function getLogger() + public function getLogger(): string { return $this->options['logger']; } @@ -454,7 +444,7 @@ public function getLogger() * * @param string $logger The logger */ - public function setLogger($logger) + public function setLogger(string $logger): void { $options = array_merge($this->options, ['logger' => $logger]); @@ -466,7 +456,7 @@ public function setLogger($logger) * * @return string */ - public function getRelease() + public function getRelease(): ?string { return $this->options['release']; } @@ -476,7 +466,7 @@ public function getRelease() * * @param string $release The release */ - public function setRelease($release) + public function setRelease(?string $release): void { $options = array_merge($this->options, ['release' => $release]); @@ -488,7 +478,7 @@ public function setRelease($release) * * @return string|null */ - public function getDsn() + public function getDsn(): ?string { return $this->dsn; } @@ -498,7 +488,7 @@ public function getDsn() * * @return string */ - public function getServerName() + public function getServerName(): string { return $this->options['server_name']; } @@ -508,7 +498,7 @@ public function getServerName() * * @param string $serverName The server name */ - public function setServerName($serverName) + public function setServerName(string $serverName): void { $options = array_merge($this->options, ['server_name' => $serverName]); @@ -544,7 +534,7 @@ public function setBeforeSendCallback(callable $callback): void * * @return string[] */ - public function getTags() + public function getTags(): array { return $this->options['tags']; } @@ -554,7 +544,7 @@ public function getTags() * * @param string[] $tags A list of tags */ - public function setTags(array $tags) + public function setTags(array $tags): void { $options = array_merge($this->options, ['tags' => $tags]); @@ -566,7 +556,7 @@ public function setTags(array $tags) * * @return int */ - public function getErrorTypes() + public function getErrorTypes(): int { return $this->options['error_types']; } @@ -576,7 +566,7 @@ public function getErrorTypes() * * @param int $errorTypes The bit mask */ - public function setErrorTypes($errorTypes) + public function setErrorTypes(int $errorTypes): void { $options = array_merge($this->options, ['error_types' => $errorTypes]); @@ -687,7 +677,7 @@ private function configureOptions(OptionsResolver $resolver) return $event; }, 'tags' => [], - 'error_types' => null, + 'error_types' => E_ALL, 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, 'before_breadcrumb' => function (Breadcrumb $breadcrumb): ?Breadcrumb { return $breadcrumb; @@ -714,112 +704,18 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('tags', 'array'); - $resolver->setAllowedTypes('error_types', ['null', 'int']); + $resolver->setAllowedTypes('error_types', ['int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); $resolver->setAllowedTypes('before_breadcrumb', ['callable']); $resolver->setAllowedTypes('integrations', ['null', 'array']); - $resolver->setAllowedValues('integrations', function ($value) { - if (null === $value) { - return true; - } - - foreach ($value as $integration) { - if (!$integration instanceof IntegrationInterface) { - return false; - } - } - - return true; - }); - - $resolver->setAllowedValues('dsn', function ($value) { - if (empty($value)) { - return true; - } - - switch (strtolower($value)) { - case '': - case 'false': - case '(false)': - case 'empty': - case '(empty)': - case 'null': - case '(null)': - return true; - } - - $parsed = @parse_url($value); - - if (false === $parsed) { - return false; - } - - if (!isset($parsed['scheme'], $parsed['user'], $parsed['host'], $parsed['path'])) { - return false; - } - - if (empty($parsed['user']) || (isset($parsed['pass']) && empty($parsed['pass']))) { - return false; - } - - if (!\in_array(strtolower($parsed['scheme']), ['http', 'https'])) { - return false; - } - - return true; - }); - + $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); + $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); $resolver->setAllowedValues('max_breadcrumbs', function ($value) { return $value <= self::DEFAULT_MAX_BREADCRUMBS; }); - $resolver->setNormalizer('dsn', function (SymfonyOptions $options, $value) { - if (empty($value)) { - $this->dsn = null; - - return null; - } - - switch (strtolower($value)) { - case '': - case 'false': - case '(false)': - case 'empty': - case '(empty)': - case 'null': - case '(null)': - $this->dsn = null; - - return null; - } - - $parsed = @parse_url($value); - - $this->dsn = $parsed['scheme'] . '://' . $parsed['host']; - - if (isset($parsed['port']) && ((80 !== $parsed['port'] && 'http' === $parsed['scheme']) || (443 !== $parsed['port'] && 'https' === $parsed['scheme']))) { - $this->dsn .= ':' . $parsed['port']; - } - - $lastSlashPosition = strrpos($parsed['path'], '/'); - - if (false !== $lastSlashPosition) { - $this->dsn .= substr($parsed['path'], 0, $lastSlashPosition); - } else { - $this->dsn .= $parsed['path']; - } - - $this->publicKey = $parsed['user']; - $this->secretKey = $parsed['pass'] ?? null; - - $parts = explode('/', $parsed['path']); - - $this->projectId = array_pop($parts); - - return $value; - }); - + $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); $resolver->setNormalizer('project_root', function (SymfonyOptions $options, $value) { if (null === $value) { return null; @@ -862,4 +758,125 @@ private function normalizeAbsolutePath($value) return $path; } + + /** + * Normalizes the DSN option by parsing the host, public and secret keys and + * an optional path. + * + * @param SymfonyOptions $options The configuration options + * @param mixed $dsn The actual value of the option to normalize + * + * @return null|string + */ + private function normalizeDsnOption(SymfonyOptions $options, $dsn): ?string + { + if (empty($dsn)) { + return null; + } + + switch (strtolower($dsn)) { + case '': + case 'false': + case '(false)': + case 'empty': + case '(empty)': + case 'null': + case '(null)': + return null; + } + + $parsed = @parse_url($dsn); + + $this->dsn = $parsed['scheme'] . '://' . $parsed['host']; + + if (isset($parsed['port']) && ((80 !== $parsed['port'] && 'http' === $parsed['scheme']) || (443 !== $parsed['port'] && 'https' === $parsed['scheme']))) { + $this->dsn .= ':' . $parsed['port']; + } + + $lastSlashPosition = strrpos($parsed['path'], '/'); + + if (false !== $lastSlashPosition) { + $this->dsn .= substr($parsed['path'], 0, $lastSlashPosition); + } else { + $this->dsn .= $parsed['path']; + } + + $this->publicKey = $parsed['user']; + $this->secretKey = $parsed['pass'] ?? null; + + $parts = explode('/', $parsed['path']); + + $this->projectId = array_pop($parts); + + return $dsn; + } + + /** + * Validates the DSN option ensuring that all required pieces are set and + * that the URL is valid. + * + * @param string|null $dsn The value of the option + * + * @return bool + */ + private function validateDsnOption(?string $dsn): bool + { + if (null === $dsn) { + return true; + } + + switch (strtolower($dsn)) { + case '': + case 'false': + case '(false)': + case 'empty': + case '(empty)': + case 'null': + case '(null)': + return true; + } + + $parsed = @parse_url($dsn); + + if (false === $parsed) { + return false; + } + + if (!isset($parsed['scheme'], $parsed['user'], $parsed['host'], $parsed['path'])) { + return false; + } + + if (empty($parsed['user']) || (isset($parsed['pass']) && empty($parsed['pass']))) { + return false; + } + + if (!\in_array(strtolower($parsed['scheme']), ['http', 'https'])) { + return false; + } + + return true; + } + + /** + * Validates that the elements of this option are all class instances that + * implements the {@see IntegrationInterface} interface. + * + * @param array|null $integrations The value to validate + * + * @return bool + */ + private function validateIntegrationsOption(?array $integrations): bool + { + if (null === $integrations) { + return true; + } + + foreach ($integrations as $integration) { + if (!$integration instanceof IntegrationInterface) { + return false; + } + } + + return true; + } } diff --git a/src/ReprSerializer.php b/src/ReprSerializer.php index c192426d4..d79ae85ef 100644 --- a/src/ReprSerializer.php +++ b/src/ReprSerializer.php @@ -1,13 +1,6 @@ events[] = $event; + + return true; } /** * {@inheritdoc} */ - public function flushQueue(TransportInterface $transport) + public function flushQueue(TransportInterface $transport): void { if (empty($this->events)) { return; diff --git a/src/Spool/SpoolInterface.php b/src/Spool/SpoolInterface.php index f9d16b596..bacb31eb2 100644 --- a/src/Spool/SpoolInterface.php +++ b/src/Spool/SpoolInterface.php @@ -1,13 +1,6 @@ frames; } @@ -113,7 +106,7 @@ public function getFrames() * @param int $line The line at which the frame originated * @param array $backtraceFrame The data of the frame to add */ - public function addFrame($file, $line, array $backtraceFrame) + public function addFrame(string $file, int $line, array $backtraceFrame): void { // The $file argument can be any of these formats: // @@ -121,7 +114,7 @@ public function addFrame($file, $line, array $backtraceFrame) // () : runtime-created function if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d code|runtime-created function)$/', $file, $matches)) { $file = $matches[1]; - $line = $matches[2]; + $line = (int) $matches[2]; } if (isset($backtraceFrame['class'])) { @@ -132,7 +125,7 @@ public function addFrame($file, $line, array $backtraceFrame) $functionName = null; } - $frame = new Frame($functionName, $this->stripPrefixFromFilePath($file), (int) $line); + $frame = new Frame($functionName, $this->stripPrefixFromFilePath($file), $line); $sourceCodeExcerpt = self::getSourceCodeExcerpt($file, $line, self::CONTEXT_NUM_LINES); if (isset($sourceCodeExcerpt['pre_context'])) { @@ -189,7 +182,7 @@ public function addFrame($file, $line, array $backtraceFrame) * * @throws \OutOfBoundsException If the index is out of range */ - public function removeFrame($index) + public function removeFrame(int $index): void { if (!isset($this->frames[$index])) { throw new \OutOfBoundsException('Invalid frame index to remove.'); @@ -204,7 +197,7 @@ public function removeFrame($index) * * @return Frame[] */ - public function toArray() + public function toArray(): array { return $this->frames; } @@ -220,13 +213,13 @@ public function jsonSerialize() /** * Gets an excerpt of the source code around a given line. * - * @param string $path The file path - * @param int $lineNumber The line to centre about - * @param int $linesNum The number of lines to fetch + * @param string $path The file path + * @param int $lineNumber The line to centre about + * @param int $maxLinesToFetch The maximum number of lines to fetch * * @return array */ - protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) + protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array { if (!is_file($path) || !is_readable($path)) { return []; @@ -238,7 +231,7 @@ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) 'post_context' => [], ]; - $target = max(0, ($lineNumber - ($linesNum + 1))); + $target = max(0, ($lineNumber - ($maxLinesToFetch + 1))); $currentLineNumber = $target + 1; try { @@ -246,9 +239,9 @@ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) $file->seek($target); while (!$file->eof()) { - /** @var string $row */ - $row = $file->current(); - $line = rtrim($row, "\r\n"); + /** @var string $line */ + $line = $file->current(); + $line = rtrim($line, "\r\n"); if ($currentLineNumber == $lineNumber) { $frame['context_line'] = $line; @@ -260,16 +253,16 @@ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) ++$currentLineNumber; - if ($currentLineNumber > $lineNumber + $linesNum) { + if ($currentLineNumber > $lineNumber + $maxLinesToFetch) { break; } $file->next(); } - // @codeCoverageIgnoreStart - } catch (\Exception $ex) { + } catch (\Exception $exception) { + // Do nothing, if any error occurs while trying to get the excerpts + // it's not a drama } - // @codeCoverageIgnoreEnd $frame['pre_context'] = $this->serializer->serialize($frame['pre_context']); $frame['context_line'] = $this->serializer->serialize($frame['context_line']); @@ -285,7 +278,7 @@ protected function getSourceCodeExcerpt($path, $lineNumber, $linesNum) * * @return string */ - protected function stripPrefixFromFilePath($filePath) + protected function stripPrefixFromFilePath(string $filePath): string { foreach ($this->options->getPrefixes() as $prefix) { if (0 === strpos($filePath, $prefix)) { @@ -304,7 +297,7 @@ protected function stripPrefixFromFilePath($filePath) * * @return array */ - protected static function getFrameArgumentsValues($frame, $maxValueLength = Client::MESSAGE_MAX_LENGTH_LIMIT) + protected static function getFrameArgumentsValues(array $frame, int $maxValueLength = Client::MESSAGE_MAX_LENGTH_LIMIT): array { if (!isset($frame['args'])) { return []; @@ -327,7 +320,7 @@ protected static function getFrameArgumentsValues($frame, $maxValueLength = Clie * * @return array */ - public static function getFrameArguments($frame, $maxValueLength = Client::MESSAGE_MAX_LENGTH_LIMIT) + public static function getFrameArguments(array $frame, int $maxValueLength = Client::MESSAGE_MAX_LENGTH_LIMIT) { if (!isset($frame['args'])) { return []; @@ -396,7 +389,7 @@ public static function getFrameArguments($frame, $maxValueLength = Client::MESSA return $args; } - protected static function serializeArgument($arg, $maxValueLength) + protected static function serializeArgument($arg, int $maxValueLength) { if (\is_array($arg)) { $result = []; diff --git a/src/State/Hub.php b/src/State/Hub.php index 73e1ecd57..feab6985f 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -1,17 +1,10 @@ cleanupPendingRequests(); - }); + register_shutdown_function('register_shutdown_function', \Closure::fromCallable([$this, 'cleanupPendingRequests'])); } /** @@ -113,12 +102,12 @@ public function send(Event $event): ?string * Cleanups the pending requests by forcing them to be sent. Any error that * occurs will be ignored. */ - private function cleanupPendingRequests() + private function cleanupPendingRequests(): void { foreach ($this->pendingRequests as $pendingRequest) { try { $pendingRequest->wait(); - } catch (\Exception $exception) { + } catch (\Throwable $exception) { // Do nothing because an exception thrown from a destructor // can't be catched in PHP (see http://php.net/manual/en/language.oop5.decon.php#language.oop5.decon.destructor) } diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index 74de6469d..ea1a895d4 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -1,13 +1,6 @@ spool; } diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index 3bda35e63..a1b3d54f9 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -1,13 +1,6 @@ \d\.\d\.\d{1,2})(?-(beta|rc)-?(\d+)?(-dev)?)?/i'; + private const VERSION_PARSING_REGEX = '/^(?\d\.\d\.\d{1,2})(?-(beta|rc)-?(\d+)?(-dev)?)?/i'; /** - * @param string $version + * Parses the given string representing a PHP version and returns it in a + * normalized form. + * + * @param string $version The string to parse * * @return string */ - public static function parseVersion($version = PHP_VERSION) + public static function parseVersion(string $version = PHP_VERSION): string { if (!preg_match(self::VERSION_PARSING_REGEX, $version, $matches)) { return $version; } $version = $matches['base']; + if (isset($matches['extra'])) { $version .= $matches['extra']; } diff --git a/tests/AbstractErrorHandlerTest.php b/tests/AbstractErrorHandlerTest.php deleted file mode 100644 index b12ce7efd..000000000 --- a/tests/AbstractErrorHandlerTest.php +++ /dev/null @@ -1,121 +0,0 @@ -callbackMock = $this->createPartialMock(\stdClass::class, ['__invoke']); - } - - public function testConstructor() - { - try { - $errorHandler = $this->createErrorHandler($this->callbackMock); - $previousErrorHandler = set_error_handler('var_dump'); - - restore_error_handler(); - - $this->assertSame([$errorHandler, 'handleError'], $previousErrorHandler); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - /** - * @dataProvider constructorThrowsWhenReservedMemorySizeIsWrongDataProvider - * - * @expectedException \UnexpectedValueException - * @expectedExceptionMessage The value of the $reservedMemorySize argument must be an integer greater than 0. - */ - public function testConstructorThrowsWhenReservedMemorySizeIsWrong($reservedMemorySize) - { - $this->createErrorHandler($this->callbackMock, $reservedMemorySize); - } - - public function constructorThrowsWhenReservedMemorySizeIsWrongDataProvider() - { - return [ - [-1], - [0], - ]; - } - - /** - * @dataProvider handleErrorShouldNotCaptureDataProvider - */ - public function testHandleErrorShouldNotCapture(bool $expectedToCapture, int $captureAt) - { - if (!$expectedToCapture) { - $this->callbackMock->expects($this->never()) - ->method('__invoke'); - } - - $errorHandler = $this->createErrorHandler($this->callbackMock); - $errorHandler->captureAt($captureAt, true); - - // to avoid making the test error bubble up and make the test fail - $prevErrorReporting = error_reporting(E_ERROR); - - try { - $this->assertFalse($errorHandler->handleError(E_WARNING, 'Test', __FILE__, __LINE__)); - } finally { - error_reporting($prevErrorReporting); - restore_error_handler(); - restore_exception_handler(); - } - } - - public function handleErrorShouldNotCaptureDataProvider(): array - { - return [ - [false, E_ERROR], - [true, E_ALL], - ]; - } - - /** - * @dataProvider captureAtDataProvider - */ - public function testCaptureAt($levels, $replace, $expectedCapturedErrors) - { - try { - $errorHandler = $this->createErrorHandler($this->callbackMock); - $previousCapturedErrors = $this->getObjectAttribute($errorHandler, 'capturedErrors'); - - $this->assertEquals($previousCapturedErrors, $errorHandler->captureAt($levels, $replace)); - $this->assertAttributeEquals($expectedCapturedErrors, 'capturedErrors', $errorHandler); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function captureAtDataProvider() - { - return [ - [E_USER_NOTICE, false, E_ALL], - [E_USER_NOTICE, true, E_USER_NOTICE], - ]; - } - - abstract protected function createErrorHandler(...$arguments); -} diff --git a/tests/AbstractSerializerTest.php b/tests/AbstractSerializerTest.php index f18af1b53..d5e155aac 100644 --- a/tests/AbstractSerializerTest.php +++ b/tests/AbstractSerializerTest.php @@ -1,13 +1,6 @@ withLevel('bar'); } - public function testConstructor() + public function testConstructor(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); @@ -50,19 +43,7 @@ public function testConstructor() $this->assertEquals(microtime(true), $breadcrumb->getTimestamp()); } - public function testCreate() - { - $breadcrumb = Breadcrumb::create(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); - - $this->assertEquals('foo', $breadcrumb->getCategory()); - $this->assertEquals(Breadcrumb::LEVEL_INFO, $breadcrumb->getLevel()); - $this->assertEquals('foo bar', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_USER, $breadcrumb->getType()); - $this->assertEquals(['baz'], $breadcrumb->getMetadata()); - $this->assertEquals(microtime(true), $breadcrumb->getTimestamp()); - } - - public function testWithCategory() + public function testWithCategory(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withCategory('bar'); @@ -72,7 +53,7 @@ public function testWithCategory() $this->assertSame($newBreadcrumb, $newBreadcrumb->withCategory('bar')); } - public function testWithLevel() + public function testWithLevel(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withLevel(Breadcrumb::LEVEL_WARNING); @@ -82,7 +63,7 @@ public function testWithLevel() $this->assertSame($newBreadcrumb, $newBreadcrumb->withLevel(Breadcrumb::LEVEL_WARNING)); } - public function testWithType() + public function testWithType(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withType(Breadcrumb::TYPE_ERROR); @@ -92,7 +73,7 @@ public function testWithType() $this->assertSame($newBreadcrumb, $newBreadcrumb->withType(Breadcrumb::TYPE_ERROR)); } - public function testWithMessage() + public function testWithMessage(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withMessage('foo bar'); @@ -102,17 +83,7 @@ public function testWithMessage() $this->assertSame($newBreadcrumb, $newBreadcrumb->withMessage('foo bar')); } - public function testWithTimestamp() - { - $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); - $newBreadcrumb = $breadcrumb->withTimestamp(123); - - $this->assertNotSame($breadcrumb, $newBreadcrumb); - $this->assertEquals(123, $newBreadcrumb->getTimestamp()); - $this->assertSame($newBreadcrumb, $newBreadcrumb->withTimestamp(123)); - } - - public function testWithMetadata() + public function testWithMetadata(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $newBreadcrumb = $breadcrumb->withMetadata('foo', 'bar'); @@ -122,7 +93,7 @@ public function testWithMetadata() $this->assertSame(['foo' => 'bar'], $newBreadcrumb->getMetadata()); } - public function testWithoutMetadata() + public function testWithoutMetadata(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', null, ['foo' => 'bar']); $newBreadcrumb = $breadcrumb->withoutMetadata('foo'); diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 5a9cd43fc..3b9208b32 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -1,13 +1,6 @@ assertInstanceOf(ClientBuilder::class, $clientBuilder); } - public function testHttpTransportIsUsedWhenServeIsConfigured() + public function testHttpTransportIsUsedWhenServeIsConfigured(): void { $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -43,7 +37,7 @@ public function testHttpTransportIsUsedWhenServeIsConfigured() $this->assertInstanceOf(HttpTransport::class, $transport); } - public function testNullTransportIsUsedWhenNoServerIsConfigured() + public function testNullTransportIsUsedWhenNoServerIsConfigured(): void { $clientBuilder = new ClientBuilder(); @@ -52,9 +46,9 @@ public function testNullTransportIsUsedWhenNoServerIsConfigured() $this->assertInstanceOf(NullTransport::class, $transport); } - public function testSetUriFactory() + public function testSetUriFactory(): void { - /** @var UriFactory|\PHPUnit_Framework_MockObject_MockObject $uriFactory */ + /** @var UriFactory|MockObject $uriFactory */ $uriFactory = $this->createMock(UriFactory::class); $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -63,9 +57,9 @@ public function testSetUriFactory() $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); } - public function testSetMessageFactory() + public function testSetMessageFactory(): void { - /** @var MessageFactory|\PHPUnit_Framework_MockObject_MockObject $messageFactory */ + /** @var MessageFactory|MockObject $messageFactory */ $messageFactory = $this->createMock(MessageFactory::class); $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -78,9 +72,9 @@ public function testSetMessageFactory() $this->assertAttributeSame($messageFactory, 'requestFactory', $transport); } - public function testSetTransport() + public function testSetTransport(): void { - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -90,9 +84,9 @@ public function testSetTransport() $this->assertAttributeSame($transport, 'transport', $clientBuilder->getClient()); } - public function testSetHttpClient() + public function testSetHttpClient(): void { - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + /** @var HttpAsyncClient|MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -105,9 +99,9 @@ public function testSetHttpClient() $this->assertAttributeSame($httpClient, 'client', $this->getObjectAttribute($transport, 'httpClient')); } - public function testAddHttpClientPlugin() + public function testAddHttpClientPlugin(): void { - /** @var Plugin|\PHPUnit_Framework_MockObject_MockObject $plugin */ + /** @var Plugin|MockObject $plugin */ $plugin = $this->createMock(Plugin::class); $clientBuilder = new ClientBuilder(); @@ -119,7 +113,7 @@ public function testAddHttpClientPlugin() $this->assertSame($plugin, $plugins[0]); } - public function testRemoveHttpClientPlugin() + public function testRemoveHttpClientPlugin(): void { $plugin = new PluginStub1(); $plugin2 = new PluginStub2(); @@ -139,7 +133,7 @@ public function testRemoveHttpClientPlugin() $this->assertSame($plugin2, reset($plugins)); } - public function testGetClient() + public function testGetClient(): void { $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); $client = $clientBuilder->getClient(); @@ -161,7 +155,7 @@ public function testGetClient() * @expectedException \BadMethodCallException * @expectedExceptionMessage The method named "methodThatDoesNotExists" does not exists. */ - public function testCallInvalidMethodThrowsException() + public function testCallInvalidMethodThrowsException(): void { $clientBuilder = new ClientBuilder(); $clientBuilder->methodThatDoesNotExists(); @@ -170,7 +164,7 @@ public function testCallInvalidMethodThrowsException() /** * @dataProvider optionsDataProvider */ - public function testCallExistingMethodForwardsCallToConfiguration($setterMethod, $value) + public function testCallExistingMethodForwardsCallToConfiguration(string $setterMethod, $value): void { $options = $this->createMock(Options::class); $options->expects($this->once()) @@ -187,7 +181,7 @@ public function testCallExistingMethodForwardsCallToConfiguration($setterMethod, $clientBuilder->$setterMethod($value); } - public function optionsDataProvider() + public function optionsDataProvider(): array { return [ ['setPrefixes', ['foo', 'bar']], @@ -241,14 +235,14 @@ public function getClientTogglesCompressionPluginInHttpClientDataProvider(): arr } } -class PluginStub1 implements Plugin +final class PluginStub1 implements Plugin { public function handleRequest(RequestInterface $request, callable $next, callable $first) { } } -class PluginStub2 implements Plugin +final class PluginStub2 implements Plugin { public function handleRequest(RequestInterface $request, callable $next, callable $first) { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 51f848817..ac92c5f31 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1,18 +1,13 @@ createMock(TransportInterface::class); $transport->expects($this->once()) @@ -51,7 +46,7 @@ public function testTransactionEventAttributeIsPopulated() public function testTransactionEventAttributeIsNotPopulatedInCli() { - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -68,7 +63,7 @@ public function testTransactionEventAttributeIsNotPopulatedInCli() public function testCaptureMessage() { - /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + /** @var Client|MockObject $client */ $client = $this->getMockBuilder(Client::class) ->disableOriginalConstructor() ->setMethodsExcept(['captureMessage']) @@ -88,7 +83,7 @@ public function testCaptureException() { $exception = new \Exception(); - /** @var Client|\PHPUnit_Framework_MockObject_MockObject $client */ + /** @var Client|MockObject $client */ $client = $this->getMockBuilder(Client::class) ->disableOriginalConstructor() ->setMethodsExcept(['captureException']) @@ -105,7 +100,7 @@ public function testCaptureException() public function testCapture() { - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -129,7 +124,7 @@ public function testCapture() public function testCaptureLastError() { - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -155,7 +150,7 @@ public function testCaptureLastError() public function testCaptureLastErrorDoesNothingWhenThereIsNoError() { - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) ->method('send'); @@ -191,7 +186,7 @@ public function testSendChecksBeforeSendOption() { $beforeSendCalled = false; - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) ->method('send'); @@ -330,7 +325,7 @@ public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallb public function testHandlingExceptionThrowingAnException() { - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -357,7 +352,7 @@ public function testConvertException(\Exception $exception, array $clientConfig, $assertHasStacktrace = $options->getAutoLogStacks(); - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -463,7 +458,7 @@ public function testConvertExceptionContainingLatin1Characters() $utf8String = 'äöü'; $latin1String = utf8_decode($utf8String); - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -488,7 +483,7 @@ public function testConvertExceptionContainingInvalidUtf8Characters() { $malformedString = "\xC2\xA2\xC2"; // ill-formed 2-byte character U+00A2 (CENT SIGN) - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -516,7 +511,7 @@ public function testConvertExceptionThrownInLatin1File() 'mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8'], ]); - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -555,7 +550,7 @@ public function testConvertExceptionWithAutoLogStacksDisabled() { $options = new Options(['auto_log_stacks' => false]); - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') diff --git a/tests/Context/AbstractContextTest.php b/tests/Context/AbstractContextTest.php index d4c16f121..2237f9012 100644 --- a/tests/Context/AbstractContextTest.php +++ b/tests/Context/AbstractContextTest.php @@ -1,14 +1,5 @@ callbackMock = $this->createPartialMock(\stdClass::class, ['__invoke']); + } + + public function testConstructor(): void + { + try { + $errorHandler = ErrorHandler::register($this->callbackMock); + $previousErrorHandler = set_error_handler('var_dump'); + + restore_error_handler(); + + $this->assertSame([$errorHandler, 'handleError'], $previousErrorHandler); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + /** + * @dataProvider constructorThrowsWhenReservedMemorySizeIsWrongDataProvider + * + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage The $reservedMemorySize argument must be greater than 0. + */ + public function testConstructorThrowsWhenReservedMemorySizeIsWrong(int $reservedMemorySize): void + { + ErrorHandler::register($this->callbackMock, $reservedMemorySize); + } + + public function constructorThrowsWhenReservedMemorySizeIsWrongDataProvider(): array + { + return [ + [-1], + [0], + ]; + } + + /** + * @dataProvider handleErrorShouldNotCaptureDataProvider + */ + public function testHandleErrorShouldNotCapture(bool $expectedToCapture, int $captureAt): void + { + if (!$expectedToCapture) { + $this->callbackMock->expects($this->never()) + ->method('__invoke'); + } + + $errorHandler = ErrorHandler::register($this->callbackMock); + $errorHandler->captureAt($captureAt, true); + + $prevErrorReporting = error_reporting(E_ERROR); // to avoid making the test error bubble up and make the test fail + + try { + $this->assertFalse($errorHandler->handleError(E_WARNING, 'Test', __FILE__, __LINE__)); + } finally { + error_reporting($prevErrorReporting); + restore_error_handler(); + restore_exception_handler(); + $this->addToAssertionCount(1); + } + } + + public function handleErrorShouldNotCaptureDataProvider(): array + { + return [ + [false, E_ERROR], + [true, E_ALL], + ]; + } + + /** + * @dataProvider captureAtDataProvider + */ + public function testCaptureAt($levels, $replace, $expectedCapturedErrors): void + { + try { + $errorHandler = ErrorHandler::register($this->callbackMock); + $previousCapturedErrors = $this->getObjectAttribute($errorHandler, 'capturedErrors'); + + $this->assertEquals($previousCapturedErrors, $errorHandler->captureAt($levels, $replace)); + $this->assertAttributeEquals($expectedCapturedErrors, 'capturedErrors', $errorHandler); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function captureAtDataProvider(): array + { + return [ + [E_USER_NOTICE, false, E_ALL], + [E_USER_NOTICE, true, E_USER_NOTICE], + ]; + } + + public function testHandleError(): void { $this->callbackMock->expects($this->exactly(1)) ->method('__invoke') @@ -33,7 +125,7 @@ public function testHandleError() $this->assertGreaterThanOrEqual(2, $backtrace); $this->assertEquals('handleError', $backtrace[0]['function']); - $this->assertEquals(AbstractErrorHandler::class, $backtrace[0]['class']); + $this->assertEquals(ErrorHandler::class, $backtrace[0]['class']); $this->assertEquals('->', $backtrace[0]['type']); $this->assertEquals('testHandleError', $backtrace[1]['function']); @@ -44,10 +136,10 @@ public function testHandleError() })); try { - $errorHandler = $this->createErrorHandler($this->callbackMock); + $errorHandler = ErrorHandler::register($this->callbackMock); $errorHandler->captureAt(0, true); - $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousErrorHandler'); + $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousErrorHandler'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($errorHandler, null); $reflectionProperty->setAccessible(false); @@ -64,7 +156,10 @@ public function testHandleError() } } - public function testHandleErrorWithPreviousErrorHandler() + /** + * @dataProvider handleErrorWithPreviousErrorHandlerDataProvider + */ + public function testHandleErrorWithPreviousErrorHandler($previousErrorHandlerErrorReturnValue, bool $expectedHandleErrorReturnValue): void { $this->callbackMock->expects($this->once()) ->method('__invoke') @@ -81,7 +176,7 @@ public function testHandleErrorWithPreviousErrorHandler() $this->assertGreaterThanOrEqual(2, $backtrace); $this->assertEquals('handleError', $backtrace[0]['function']); - $this->assertEquals(AbstractErrorHandler::class, $backtrace[0]['class']); + $this->assertEquals(ErrorHandler::class, $backtrace[0]['class']); $this->assertEquals('->', $backtrace[0]['type']); $this->assertEquals('testHandleErrorWithPreviousErrorHandler', $backtrace[1]['function']); @@ -95,24 +190,36 @@ public function testHandleErrorWithPreviousErrorHandler() $previousErrorHandler->expects($this->once()) ->method('__invoke') ->with(E_USER_NOTICE, 'foo bar', __FILE__, 123) - ->willReturn(false); + ->willReturn($previousErrorHandlerErrorReturnValue); try { - $errorHandler = $this->createErrorHandler($this->callbackMock); + $errorHandler = ErrorHandler::register($this->callbackMock); - $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousErrorHandler'); + $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousErrorHandler'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($errorHandler, $previousErrorHandler); $reflectionProperty->setAccessible(false); - $errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, 123); + $this->assertEquals($expectedHandleErrorReturnValue, $errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, 123)); } finally { restore_error_handler(); restore_exception_handler(); } } - public function testHandleFatalError() + public function handleErrorWithPreviousErrorHandlerDataProvider(): array + { + return [ + [false, false], + [true, true], + [0, true], // check that we're using strict comparison instead of shallow + [1, true], // check that we're using strict comparison instead of shallow + ['0', true], // check that we're using strict comparison instead of shallow + ['1', true], // check that we're using strict comparison instead of shallow + ]; + } + + public function testHandleFatalError(): void { $this->callbackMock->expects($this->exactly(1)) ->method('__invoke') @@ -128,7 +235,7 @@ public function testHandleFatalError() })); try { - $errorHandler = $this->createErrorHandler($this->callbackMock); + $errorHandler = ErrorHandler::register($this->callbackMock); $errorHandler->handleFatalError([ 'type' => E_PARSE, 'message' => 'foo bar', @@ -141,13 +248,13 @@ public function testHandleFatalError() } } - public function testHandleFatalErrorWithNonFatalErrorDoesNothing() + public function testHandleFatalErrorWithNonFatalErrorDoesNothing(): void { $this->callbackMock->expects($this->never()) ->method('__invoke'); try { - $errorHandler = $this->createErrorHandler($this->callbackMock); + $errorHandler = ErrorHandler::register($this->callbackMock); $errorHandler->handleFatalError([ 'type' => E_USER_NOTICE, 'message' => 'foo bar', @@ -160,7 +267,7 @@ public function testHandleFatalErrorWithNonFatalErrorDoesNothing() } } - public function testHandleException() + public function testHandleException(): void { $exception = new \Exception('foo bar'); @@ -169,7 +276,7 @@ public function testHandleException() ->with($this->identicalTo($exception)); try { - $errorHandler = $this->createErrorHandler($this->callbackMock); + $errorHandler = ErrorHandler::register($this->callbackMock); try { $errorHandler->handleException($exception); @@ -184,7 +291,7 @@ public function testHandleException() } } - public function testHandleExceptionWithPreviousExceptionHandler() + public function testHandleExceptionWithPreviousExceptionHandler(): void { $exception = new \Exception('foo bar'); @@ -198,9 +305,9 @@ public function testHandleExceptionWithPreviousExceptionHandler() ->with($this->identicalTo($exception)); try { - $errorHandler = $this->createErrorHandler($this->callbackMock); + $errorHandler = ErrorHandler::register($this->callbackMock); - $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousExceptionHandler'); + $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousExceptionHandler'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); $reflectionProperty->setAccessible(false); @@ -218,7 +325,7 @@ public function testHandleExceptionWithPreviousExceptionHandler() } } - public function testHandleExceptionWithThrowingPreviousExceptionHandler() + public function testHandleExceptionWithThrowingPreviousExceptionHandler(): void { $exception1 = new \Exception('foo bar'); $exception2 = new \Exception('bar foo'); @@ -234,9 +341,9 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler() ->will($this->throwException($exception2)); try { - $errorHandler = $this->createErrorHandler($this->callbackMock); + $errorHandler = ErrorHandler::register($this->callbackMock); - $reflectionProperty = new \ReflectionProperty(AbstractErrorHandler::class, 'previousExceptionHandler'); + $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousExceptionHandler'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); $reflectionProperty->setAccessible(false); @@ -253,9 +360,4 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler() restore_exception_handler(); } } - - protected function createErrorHandler(...$arguments): ErrorHandler - { - return ErrorHandler::register(...$arguments); - } } diff --git a/tests/EventTest.php b/tests/EventTest.php index d5d183e91..acc8eac46 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -1,12 +1,15 @@ uuidGeneratorInvokationCount = 0; $this->originalUuidFactory = new UuidFactory(); $this->client = ClientBuilder::create()->getClient(); $this->options = $this->client->getOptions(); - /** @var UuidFactoryInterface|\PHPUnit_Framework_MockObject_MockObject $uuidFactory */ + /** @var UuidFactoryInterface|MockObject $uuidFactory */ $uuidFactory = $this->getMockBuilder(UuidFactoryInterface::class) ->getMock(); @@ -68,12 +74,12 @@ protected function setUp() Uuid::setFactory($uuidFactory); } - protected function tearDown() + protected function tearDown(): void { Uuid::setFactory($this->originalUuidFactory); } - public function testEventIsGeneratedWithUniqueIdentifier() + public function testEventIsGeneratedWithUniqueIdentifier(): void { $event1 = new Event(); $event2 = new Event(); @@ -82,7 +88,7 @@ public function testEventIsGeneratedWithUniqueIdentifier() $this->assertEquals(str_replace('-', '', static::GENERATED_UUID[1]), $event2->getId()); } - public function testToArray() + public function testToArray(): void { $this->options->setRelease('1.2.3-dev'); @@ -114,7 +120,7 @@ public function testToArray() $this->assertEquals($expected, $event->toArray()); } - public function testToArrayWithMessage() + public function testToArrayWithMessage(): void { $event = new Event(); $event->setMessage('foo bar'); @@ -125,7 +131,7 @@ public function testToArrayWithMessage() $this->assertEquals('foo bar', $data['message']); } - public function testToArrayWithMessageWithParams() + public function testToArrayWithMessageWithParams(): void { $expected = [ 'message' => 'foo %s', @@ -142,7 +148,7 @@ public function testToArrayWithMessageWithParams() $this->assertEquals($expected, $data['message']); } - public function testToArrayWithBreadcrumbs() + public function testToArrayWithBreadcrumbs(): void { $breadcrumbs = [ new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'foo'), @@ -160,35 +166,35 @@ public function testToArrayWithBreadcrumbs() $this->assertSame($breadcrumbs, $data['breadcrumbs']['values']); } - public function testGetServerOsContext() + public function testGetServerOsContext(): void { $event = new Event(); $this->assertInstanceOf(ServerOsContext::class, $event->getServerOsContext()); } - public function testGetRuntimeContext() + public function testGetRuntimeContext(): void { $event = new Event(); $this->assertInstanceOf(RuntimeContext::class, $event->getRuntimeContext()); } - public function testGetUserContext() + public function testGetUserContext(): void { $event = new Event(); $this->assertInstanceOf(Context::class, $event->getUserContext()); } - public function testGetExtraContext() + public function testGetExtraContext(): void { $event = new Event(); $this->assertInstanceOf(Context::class, $event->getExtraContext()); } - public function getTagsContext() + public function getTagsContext(): void { $event = new Event(); @@ -198,7 +204,7 @@ public function getTagsContext() /** * @dataProvider gettersAndSettersDataProvider */ - public function testGettersAndSetters($propertyName, $propertyValue, $expectedValue) + public function testGettersAndSetters(string $propertyName, $propertyValue, $expectedValue): void { $getterMethod = 'get' . ucfirst($propertyName); $setterMethod = 'set' . ucfirst($propertyName); @@ -210,7 +216,7 @@ public function testGettersAndSetters($propertyName, $propertyValue, $expectedVa $this->assertArraySubset($expectedValue, $event->toArray()); } - public function gettersAndSettersDataProvider() + public function gettersAndSettersDataProvider(): array { return [ ['level', Severity::info(), ['level' => Severity::info()]], @@ -224,7 +230,7 @@ public function gettersAndSettersDataProvider() ]; } - public function testEventJsonSerialization() + public function testEventJsonSerialization(): void { $event = new Event(); diff --git a/tests/FrameTest.php b/tests/FrameTest.php index 67b885edb..4ff159263 100644 --- a/tests/FrameTest.php +++ b/tests/FrameTest.php @@ -1,22 +1,15 @@ $setterMethod($expectedData); @@ -36,7 +29,7 @@ public function testGettersAndSetters($getterMethod, $setterMethod, $expectedDat $this->assertEquals($expectedData, $frame->$getterMethod()); } - public function gettersAndSettersDataProvider() + public function gettersAndSettersDataProvider(): array { return [ ['getPreContext', 'setPreContext', ['foo' => 'bar', 'bar' => 'baz']], @@ -50,7 +43,7 @@ public function gettersAndSettersDataProvider() /** * @dataProvider toArrayAndJsonSerializeDataProvider */ - public function testToArrayAndJsonSerialize($setterMethod, $expectedDataKey, $expectedData) + public function testToArrayAndJsonSerialize(string $setterMethod, string $expectedDataKey, $expectedData): void { $frame = new Frame('foo', 'bar', 10); $frame->$setterMethod($expectedData); @@ -66,7 +59,7 @@ public function testToArrayAndJsonSerialize($setterMethod, $expectedDataKey, $ex $this->assertArraySubset($expectedResult, $frame->jsonSerialize()); } - public function toArrayAndJsonSerializeDataProvider() + public function toArrayAndJsonSerializeDataProvider(): array { return [ ['setPreContext', 'pre_context', ['foo' => 'bar']], diff --git a/tests/HttpClient/Authentication/SentryAuthTest.php b/tests/HttpClient/Authentication/SentryAuthTest.php index bdf4f9000..a6b0bd7ed 100644 --- a/tests/HttpClient/Authentication/SentryAuthTest.php +++ b/tests/HttpClient/Authentication/SentryAuthTest.php @@ -1,16 +1,10 @@ 'http://public:secret@example.com/']); $authentication = new SentryAuth($configuration); - /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $request */ + /** @var RequestInterface|MockObject $request */ $request = $this->getMockBuilder(RequestInterface::class) ->getMock(); - /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $newRequest */ + /** @var RequestInterface|MockObject $newRequest */ $newRequest = $this->getMockBuilder(RequestInterface::class) ->getMock(); @@ -50,16 +44,16 @@ public function testAuthenticate() $this->assertSame($newRequest, $authentication->authenticate($request)); } - public function testAuthenticateWithNoSecretKey() + public function testAuthenticateWithNoSecretKey(): void { $configuration = new Options(['dsn' => 'http://public@example.com/']); $authentication = new SentryAuth($configuration); - /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $request */ + /** @var RequestInterface|MockObject $request */ $request = $this->getMockBuilder(RequestInterface::class) ->getMock(); - /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $newRequest */ + /** @var RequestInterface|MockObject $newRequest */ $newRequest = $this->getMockBuilder(RequestInterface::class) ->getMock(); diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index e22d968c4..7f189dea8 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -9,9 +9,9 @@ use Sentry\Event; use Sentry\Integration\ModulesIntegration; -class ModulesIntegrationTest extends TestCase +final class ModulesIntegrationTest extends TestCase { - public function testInvoke() + public function testInvoke(): void { $event = new Event(); $integration = new ModulesIntegration(); diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index b6ccc628e..06cb8259e 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -1,13 +1,6 @@ assertEquals($expectedValue, $event->getRequest()); } - public function testInvokeWithRequestHavingIpAddress() + public function testInvokeWithRequestHavingIpAddress(): void { $event = new Event(); $event->getUserContext()->setData(['foo' => 'bar']); @@ -57,7 +50,7 @@ public function testInvokeWithRequestHavingIpAddress() $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $event->getUserContext()->toArray()); } - public function invokeDataProvider() + public function invokeDataProvider(): array { return [ [ diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index ba1bc7119..de5044529 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -1,25 +1,18 @@ $value]); @@ -29,7 +22,7 @@ public function testConstructor($option, $value, $getterMethod) /** * @dataProvider optionsDataProvider */ - public function testGettersAndSetters($option, $value, $getterMethod, $setterMethod = null) + public function testGettersAndSetters(string $option, $value, string $getterMethod, ?string $setterMethod = null): void { $configuration = new Options(); @@ -40,7 +33,7 @@ public function testGettersAndSetters($option, $value, $getterMethod, $setterMet $this->assertEquals($value, $configuration->$getterMethod()); } - public function optionsDataProvider() + public function optionsDataProvider(): array { return [ ['send_attempts', 1, 'getSendAttempts', 'setSendAttempts'], @@ -73,7 +66,7 @@ public function optionsDataProvider() /** * @dataProvider serverOptionDataProvider */ - public function testServerOption($dsn, $options) + public function testServerOption(string $dsn, array $options): void { $configuration = new Options(['dsn' => $dsn]); @@ -83,7 +76,7 @@ public function testServerOption($dsn, $options) $this->assertEquals($options['server'], $configuration->getDsn()); } - public function serverOptionDataProvider() + public function serverOptionDataProvider(): array { return [ [ @@ -158,12 +151,12 @@ public function serverOptionDataProvider() * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException * @expectedExceptionMessageRegExp /^The option "dsn" with value "(.*)" is invalid.$/ */ - public function testServerOptionsWithInvalidServer($dsn) + public function testServerOptionsWithInvalidServer(string $dsn): void { new Options(['dsn' => $dsn]); } - public function invalidServerOptionDataProvider() + public function invalidServerOptionDataProvider(): array { return [ ['http://public:secret@/1'], diff --git a/tests/ReprSerializerTest.php b/tests/ReprSerializerTest.php index 37058a86a..4cd366ea0 100644 --- a/tests/ReprSerializerTest.php +++ b/tests/ReprSerializerTest.php @@ -1,13 +1,6 @@ willReturn('92db40a886c0458288c7c83935a350ef'); Hub::getCurrent()->bindClient($client); + $this->assertEquals($client, Hub::getCurrent()->getClient()); $this->assertEquals('92db40a886c0458288c7c83935a350ef', captureMessage('foo')); } @@ -98,6 +99,7 @@ public function testAddBreadcrumb(): void ->with($breadcrumb, Hub::getCurrent()->getScope()); Hub::getCurrent()->bindClient($client); + addBreadcrumb($breadcrumb); } diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index 2c25434a9..8c8c848be 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -1,10 +1,12 @@ spool = new MemorySpool(); } - public function testQueueEvent() + public function testQueueEvent(): void { $this->assertAttributeEmpty('events', $this->spool); - - $this->spool->queueEvent(new Event()); - + $this->assertTrue($this->spool->queueEvent(new Event())); $this->assertAttributeNotEmpty('events', $this->spool); } - public function testFlushQueue() + public function testFlushQueue(): void { $event1 = new Event(); $event2 = new Event(); - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->exactly(2)) ->method('send') @@ -56,9 +48,9 @@ public function testFlushQueue() $this->assertAttributeEmpty('events', $this->spool); } - public function testFlushQueueWithEmptyQueue() + public function testFlushQueueWithEmptyQueue(): void { - /** @var TransportInterface|\PHPUnit_Framework_MockObject_MockObject $transport */ + /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) ->method('send'); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index f17a6af85..4b18d5503 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -1,13 +1,6 @@ options = new Options(); $this->serializer = new Serializer($this->options->getMbDetectOrder()); $this->representationSerializer = new ReprSerializer($this->options->getMbDetectOrder()); } - public function testGetFramesAndToArray() + public function testGetFramesAndToArray(): void { $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); @@ -57,7 +50,7 @@ public function testGetFramesAndToArray() $this->assertFrameEquals($frames[1], 'test_function', 'path/to/file', 1); } - public function testStacktraceJsonSerialization() + public function testStacktraceJsonSerialization(): void { $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); @@ -72,7 +65,7 @@ public function testStacktraceJsonSerialization() $this->assertJsonStringEqualsJsonString($frames, $serializedStacktrace); } - public function testAddFrame() + public function testAddFrame(): void { $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $frames = [ @@ -93,7 +86,7 @@ public function testAddFrame() $this->assertFrameEquals($frames[2], 'test_function', 'path/to/file', 12); } - public function testAddFrameSerializesMethodArguments() + public function testAddFrameSerializesMethodArguments(): void { $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); $stacktrace->addFrame('path/to/file', 12, [ @@ -110,7 +103,7 @@ public function testAddFrameSerializesMethodArguments() $this->assertEquals(['param1' => 1, 'param2' => 'foo'], $frames[0]->getVars()); } - public function testAddFrameStripsPath() + public function testAddFrameStripsPath(): void { $this->options->setPrefixes(['path/to/', 'path/to/app']); @@ -129,7 +122,7 @@ public function testAddFrameStripsPath() $this->assertFrameEquals($frames[3], 'test_function_parent_parent_parent', 'app/file', 12); } - public function testAddFrameMarksAsInApp() + public function testAddFrameMarksAsInApp(): void { $this->options->setProjectRoot('path/to'); $this->options->setExcludedProjectPaths(['path/to/excluded/path']); @@ -145,7 +138,7 @@ public function testAddFrameMarksAsInApp() $this->assertFalse($frames[1]->isInApp()); } - public function testAddFrameReadsCodeFromShortFile() + public function testAddFrameReadsCodeFromShortFile(): void { $fileContent = explode("\n", $this->getFixture('code/ShortFile.php')); $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); @@ -169,7 +162,7 @@ public function testAddFrameReadsCodeFromShortFile() } } - public function testAddFrameReadsCodeFromLongFile() + public function testAddFrameReadsCodeFromLongFile(): void { $fileContent = explode("\n", $this->getFixture('code/LongFile.php')); $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); @@ -198,7 +191,7 @@ public function testAddFrameReadsCodeFromLongFile() /** * @dataProvider removeFrameDataProvider */ - public function testRemoveFrame($index, $throwException) + public function testRemoveFrame(int $index, bool $throwException): void { if ($throwException) { $this->expectException(\OutOfBoundsException::class); @@ -225,7 +218,7 @@ public function testRemoveFrame($index, $throwException) $this->assertFrameEquals($frames[0], 'test_function_parent', 'path/to/file', 12); } - public function removeFrameDataProvider() + public function removeFrameDataProvider(): array { return [ [-1, true], @@ -234,7 +227,7 @@ public function removeFrameDataProvider() ]; } - public function testFromBacktrace() + public function testFromBacktrace(): void { $fixture = $this->getJsonFixture('backtraces/exception.json'); $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); @@ -244,7 +237,7 @@ public function testFromBacktrace() $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); } - public function testFromBacktraceWithAnonymousFrame() + public function testFromBacktraceWithAnonymousFrame(): void { $fixture = $this->getJsonFixture('backtraces/anonymous_frame.json'); $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); @@ -254,7 +247,7 @@ public function testFromBacktraceWithAnonymousFrame() $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); } - public function testInAppWithEmptyFrame() + public function testInAppWithEmptyFrame(): void { $stack = [ [ @@ -272,7 +265,7 @@ public function testInAppWithEmptyFrame() $this->assertFalse($frames[0]->isInApp()); } - public function testGetFrameArgumentsDoesNotModifyCapturedArgs() + public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void { // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. // Modification of these would be really bad, since if control is returned (non-fatal error) we'll have altered the state of things! @@ -303,22 +296,40 @@ public function testGetFrameArgumentsDoesNotModifyCapturedArgs() $this->assertEquals($result['param2']['key'], 'xxxxx'); } - protected function getFixturePath($file) + private function getFixturePath(string $file): string { - return realpath(__DIR__ . \DIRECTORY_SEPARATOR . 'Fixtures' . \DIRECTORY_SEPARATOR . $file); + $filePath = realpath(__DIR__ . \DIRECTORY_SEPARATOR . 'Fixtures' . \DIRECTORY_SEPARATOR . $file); + + if (false === $filePath) { + throw new \RuntimeException(sprintf('The fixture file at path "%s" could not be found.', $file)); + } + + return $filePath; } - protected function getFixture($file) + private function getFixture(string $file): string { - return file_get_contents($this->getFixturePath($file)); + $fileContent = file_get_contents($this->getFixturePath($file)); + + if (false === $fileContent) { + throw new \RuntimeException(sprintf('The fixture file at path "%s" could not be read.', $file)); + } + + return $fileContent; } - protected function getJsonFixture($file) + private function getJsonFixture(string $file): array { - return json_decode($this->getFixture($file), true); + $decodedData = json_decode($this->getFixture($file), true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \RuntimeException(sprintf('Could not decode the fixture file at path "%s". Error was: %s', $this->getFixturePath($file), json_last_error_msg())); + } + + return $decodedData; } - protected function assertFrameEquals(Frame $frame, $method, $file, $line) + private function assertFrameEquals(Frame $frame, ?string $method, string $file, int $line): void { $this->assertSame($method, $frame->getFunctionName()); $this->assertSame($file, $frame->getFile()); diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 7c83741a0..06a31431e 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -1,21 +1,12 @@ assertSame($event, $scope->applyToEvent($event, [])); $this->assertTrue($callback1Called); - $scope->addEventProcessor(function () use ($callback1Called, &$callback2Called, $callback3Called): ?Event { + $scope->addEventProcessor(function () use ($callback1Called, &$callback2Called, $callback3Called) { $this->assertTrue($callback1Called); $this->assertFalse($callback3Called); @@ -131,7 +122,7 @@ public function testAddEventProcessor(): void return null; }); - $scope->addEventProcessor(function () use (&$callback3Called): ?Event { + $scope->addEventProcessor(function () use (&$callback3Called) { $callback3Called = true; return null; diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 925d32ea6..203bbb56d 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -1,13 +1,6 @@ createMock(Promise::class); $promise->expects($this->once()) ->method('wait'); - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + /** @var HttpAsyncClient|MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); $httpClient->expects($this->once()) ->method('sendAsyncRequest') @@ -49,20 +41,20 @@ public function testDestructor() $transport->__destruct(); } - public function testCleanupPendingRequests() + public function testCleanupPendingRequests(): void { - /** @var Promise|\PHPUnit_Framework_MockObject_MockObject $promise1 */ + /** @var Promise|MockObject $promise1 */ $promise1 = $this->createMock(Promise::class); $promise1->expects($this->once()) ->method('wait') ->willThrowException(new \Exception()); - /** @var Promise|\PHPUnit_Framework_MockObject_MockObject $promise2 */ + /** @var Promise|MockObject $promise2 */ $promise2 = $this->createMock(Promise::class); $promise2->expects($this->once()) ->method('wait'); - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + /** @var HttpAsyncClient|MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); $httpClient->expects($this->exactly(2)) ->method('sendAsyncRequest') @@ -84,44 +76,14 @@ public function testCleanupPendingRequests() $reflectionMethod->setAccessible(false); } - public function testSendWithoutCompressedEncoding() + public function testSendFailureCleanupPendingRequests(): void { - $config = new Options(['enable_compression' => false]); - $event = new Event(); - - $promise = $this->createMock(Promise::class); - $promise->expects($this->once()) - ->method('wait'); - - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); - $httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->with($this->callback(function (RequestInterface $request) use ($event) { - $request->getBody()->rewind(); - - return 'application/json' === $request->getHeaderLine('Content-Type') - && JSON::encode($event) === $request->getBody()->getContents(); - })) - ->willReturn($promise); - - $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); - $transport->send($event); - - $reflectionMethod = new \ReflectionMethod(HttpTransport::class, 'cleanupPendingRequests'); - $reflectionMethod->setAccessible(true); - $reflectionMethod->invoke($transport); - $reflectionMethod->setAccessible(false); - } - - public function testSendFailureCleanupPendingRequests() - { - /** @var HttpException|\PHPUnit_Framework_MockObject_MockObject $exception */ + /** @var HttpException|MockObject $exception */ $exception = $this->createMock(HttpException::class); $promise = new PromiseMock($exception, PromiseMock::REJECTED); - /** @var HttpAsyncClient|\PHPUnit_Framework_MockObject_MockObject $httpClient */ + /** @var HttpAsyncClient|MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); $httpClient->expects($this->once()) ->method('sendAsyncRequest') @@ -138,7 +100,7 @@ public function testSendFailureCleanupPendingRequests() } } -class PromiseMock implements Promise +final class PromiseMock implements Promise { private $result; diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php index c18d89a0a..c9133442b 100644 --- a/tests/Transport/NullTransportTest.php +++ b/tests/Transport/NullTransportTest.php @@ -1,13 +1,6 @@ spool = $this->createMock(SpoolInterface::class); $this->transport = new SpoolTransport($this->spool); } - public function testGetSpool() + public function testGetSpool(): void { $this->assertSame($this->spool, $this->transport->getSpool()); } - public function testSend() + public function testSend(): void { $event = new Event(); diff --git a/tests/Util/Fixtures/JsonSerializableClass.php b/tests/Util/Fixtures/JsonSerializableClass.php index ca9727b10..602d9c9b9 100644 --- a/tests/Util/Fixtures/JsonSerializableClass.php +++ b/tests/Util/Fixtures/JsonSerializableClass.php @@ -1,13 +1,6 @@ assertEquals($expectedResult, JSON::encode($value)); } - public function encodeDataProvider() + public function encodeDataProvider(): array { - $obj = new \stdClass(); - $obj->key = 'value'; - return [ - [['key' => 'value'], '{"key":"value"}'], - ['string', '"string"'], - [123.45, '123.45'], - [null, 'null'], - [$obj, '{"key":"value"}'], - [new SimpleClass(), '{"keyPublic":"public"}'], - [new JsonSerializableClass(), '{"key":"value"}'], + [ + [ + 'key' => 'value', + ], + '{"key":"value"}', + ], + [ + 'string', + '"string"', + ], + [ + 123.45, + '123.45', + ], + [ + null, + 'null', + ], + [ + (object) [ + 'key' => 'value', + ], + '{"key":"value"}', + ], + [ + new SimpleClass(), + '{"keyPublic":"public"}', + ], + [ + new JsonSerializableClass(), + '{"key":"value"}', + ], ]; } @@ -46,7 +61,7 @@ public function encodeDataProvider() * @expectedException \InvalidArgumentException * @expectedExceptionMessage Could not encode value into JSON format. Error was: "Type is not supported". */ - public function testEncodeThrowsIfValueIsResource() + public function testEncodeThrowsIfValueIsResource(): void { $resource = fopen('php://memory', 'rb'); diff --git a/tests/Util/PHPVersionTest.php b/tests/Util/PHPVersionTest.php index e3ce1cbf5..00e4fddc7 100644 --- a/tests/Util/PHPVersionTest.php +++ b/tests/Util/PHPVersionTest.php @@ -1,41 +1,38 @@ assertSame($expected, PHPVersion::parseVersion($rawVersion)); + $this->assertEquals($expectedVersion, PHPVersion::parseVersion($rawVersion)); } - public function versionProvider() + public function versionProvider(): array { - $baseVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; - $phpExtraVersions = [ - '' => $baseVersion, - '-1+ubuntu17.04.1+deb.sury.org+1' => $baseVersion, - '-beta3-1+ubuntu17.04.1+deb.sury.org+1' => "{$baseVersion}-beta3", - '-beta5-dev-1+ubuntu17.04.1+deb.sury.org+1' => "{$baseVersion}-beta5-dev", - '-rc-9-1+ubuntu17.04.1+deb.sury.org+1' => "{$baseVersion}-rc-9", - '-2~ubuntu16.04.1+deb.sury.org+1' => $baseVersion, - '-beta1-dev' => "{$baseVersion}-beta1-dev", - '-rc10' => "{$baseVersion}-rc10", - '-RC10' => "{$baseVersion}-RC10", - '-rc2-dev' => "{$baseVersion}-rc2-dev", - '-beta-2-dev' => "{$baseVersion}-beta-2-dev", - '-beta2' => "{$baseVersion}-beta2", - '-beta-9' => "{$baseVersion}-beta-9", + return [ + ['1.2.3', '1.2.3'], + ['1.2.3-1+ubuntu17.04.1+deb.sury.org+1', '1.2.3'], + ['1.2.3-beta3-1+ubuntu17.04.1+deb.sury.org+1', '1.2.3-beta3'], + ['1.2.3-beta5-dev-1+ubuntu17.04.1+deb.sury.org+1', '1.2.3-beta5-dev'], + ['1.2.3-rc-9-1+ubuntu17.04.1+deb.sury.org+1', '1.2.3-rc-9'], + ['1.2.3-2~ubuntu16.04.1+deb.sury.org+1', '1.2.3'], + ['1.2.3-beta1-dev', '1.2.3-beta1-dev'], + ['1.2.3-rc10', '1.2.3-rc10'], + ['1.2.3-RC10', '1.2.3-RC10'], + ['1.2.3-rc2-dev', '1.2.3-rc2-dev'], + ['1.2.3-beta-2-dev', '1.2.3-beta-2-dev'], + ['1.2.3-beta2', '1.2.3-beta2'], + ['1.2.3-beta-9', '1.2.3-beta-9'], ]; - - foreach ($phpExtraVersions as $fullVersion => $parsedVersion) { - yield [$parsedVersion, $baseVersion . $fullVersion]; - } } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1fb223f0e..57982bce2 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,13 +1,6 @@ Date: Fri, 23 Nov 2018 09:42:56 +0100 Subject: [PATCH 0388/1161] Remove the ability to pass a client builder instance to the `init` method (#703) --- src/Sdk.php | 9 +++------ tests/phpt/fatal_error_not_captured_twice.phpt | 6 ++++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Sdk.php b/src/Sdk.php index 3a2373c88..8153bc4b0 100644 --- a/src/Sdk.php +++ b/src/Sdk.php @@ -9,14 +9,11 @@ /** * Creates a new Client and Hub which will be set as current. * - * @param array $options The client options - * @param null|ClientBuilder $clientBuilder An optional client builder instance + * @param array $options The client options */ -function init(array $options = [], ?ClientBuilder $clientBuilder = null): void +function init(array $options = []): void { - $builder = $clientBuilder ?? ClientBuilder::create($options); - - Hub::setCurrent(new Hub($builder->getClient())); + Hub::setCurrent(new Hub(ClientBuilder::create($options)->getClient())); } /** diff --git a/tests/phpt/fatal_error_not_captured_twice.phpt b/tests/phpt/fatal_error_not_captured_twice.phpt index c4afbc4c3..53be00ff2 100644 --- a/tests/phpt/fatal_error_not_captured_twice.phpt +++ b/tests/phpt/fatal_error_not_captured_twice.phpt @@ -9,7 +9,7 @@ use PHPUnit\Framework\Assert; use Sentry\Spool\MemorySpool; use Sentry\Transport\SpoolTransport; use Sentry\ClientBuilder; -use function Sentry\init; +use Sentry\State\Hub; $vendor = __DIR__; @@ -22,7 +22,9 @@ require $vendor . '/vendor/autoload.php'; $spool = new MemorySpool(); $transport = new SpoolTransport($spool); -init([], ClientBuilder::create()->setTransport($transport)); +$builder = ClientBuilder::create()->setTransport($transport); + +Hub::getCurrent()->bindClient($builder->getClient()); register_shutdown_function('register_shutdown_function', function () use ($spool) { Assert::assertAttributeCount(1, 'events', $spool); From 5d4a2b29edd0b74b87a85e3fea966ca9a53f3717 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 26 Nov 2018 11:46:01 +0100 Subject: [PATCH 0389/1161] Rename some options and drop others that are not needed anymore (#705) --- docs/configuration.rst | 2 +- src/Client.php | 28 +++++++----- src/ClientBuilder.php | 16 +++---- src/Options.php | 90 +++++++++---------------------------- tests/ClientBuilderTest.php | 8 ++-- tests/ClientTest.php | 45 ++++--------------- tests/OptionsTest.php | 6 +-- 7 files changed, 58 insertions(+), 137 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 8f26bbcb8..5fc8eeb88 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -173,7 +173,7 @@ automatically captured or not. .. code-block:: php - $configuration = new Configuration(['auto_log_stacks' => true]); + $configuration = new Configuration(['attach_stacktrace' => true]); $configuration->setAutoLogStacks(true); By default this option is set to ``true``. diff --git a/src/Client.php b/src/Client.php index ff589a872..708355e2e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -124,6 +124,10 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope $payload['message'] = $message; $payload['level'] = $level; + if ($this->getOptions()->shouldAttachStacktrace()) { + $payload['stacktrace'] = Stacktrace::createFromBacktrace($this->getOptions(), $this->serializer, $this->representationSerializer, \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), __FILE__, __LINE__); + } + return $this->captureEvent($payload, $scope); } @@ -203,8 +207,8 @@ private function prepareEvent(array $payload, ?Scope $scope = null): ?Event $event = new Event(); $event->setServerName($this->getOptions()->getServerName()); $event->setRelease($this->getOptions()->getRelease()); - $event->setEnvironment($this->getOptions()->getCurrentEnvironment()); $event->getTagsContext()->merge($this->getOptions()->getTags()); + $event->setEnvironment($this->getOptions()->getEnvironment()); if (isset($payload['transaction'])) { $event->setTransaction($payload['transaction']); @@ -232,6 +236,10 @@ private function prepareEvent(array $payload, ?Scope $scope = null): ?Event $this->addThrowableToEvent($event, $payload['exception']); } + if (isset($payload['stacktrace']) && $payload['stacktrace'] instanceof Stacktrace) { + $event->setStacktrace($payload['stacktrace']); + } + if (null !== $scope) { $event = $scope->applyToEvent($event, $payload); } @@ -264,16 +272,14 @@ private function addThrowableToEvent(Event $event, \Throwable $exception): void 'value' => $this->serializer->serialize($currentException->getMessage()), ]; - if ($this->getOptions()->getAutoLogStacks()) { - $data['stacktrace'] = Stacktrace::createFromBacktrace( - $this->getOptions(), - $this->serializer, - $this->representationSerializer, - $currentException->getTrace(), - $currentException->getFile(), - $currentException->getLine() - ); - } + $data['stacktrace'] = Stacktrace::createFromBacktrace( + $this->getOptions(), + $this->serializer, + $this->representationSerializer, + $currentException->getTrace(), + $currentException->getFile(), + $currentException->getLine() + ); $exceptions[] = $data; } while ($currentException = $currentException->getPrevious()); diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index a977e710c..17d6c43fd 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -41,20 +41,16 @@ * @method setSampleRate(float $sampleRate) * @method string getMbDetectOrder() * @method setMbDetectOrder(string $detectOrder) - * @method bool getAutoLogStacks() - * @method setAutoLogStacks(bool $enable) + * @method bool shouldAttachStacktrace() + * @method setAttachStacktrace(bool $enable) * @method int getContextLines() * @method setContextLines(int $contextLines) - * @method string getCurrentEnvironment() - * @method setCurrentEnvironment(string $environment) - * @method string[] getEnvironments() - * @method setEnvironments(string[] $environments) - * @method string[] getExcludedLoggers() - * @method setExcludedLoggers(string[] $loggers) - * @method string[] getExcludedExceptions() - * @method setExcludedExceptions(string[] $exceptions) + * @method null|string getEnvironment() + * @method setEnvironment(null|string $environment) * @method string[] getExcludedProjectPaths() * @method setExcludedProjectPaths(string[] $paths) + * @method setExcludedLoggers(string[] $loggers) + * @method string[] getExcludedExceptions() * @method string getProjectRoot() * @method setProjectRoot(string $path) * @method string getLogger() diff --git a/src/Options.php b/src/Options.php index d31dcf66f..60ebe1b4a 100644 --- a/src/Options.php +++ b/src/Options.php @@ -179,23 +179,23 @@ public function setMbDetectOrder($detectOrder): void } /** - * Gets whether the stacktrace must be auto-filled. + * Gets whether the stacktrace will be attached on captureMessage. * * @return bool */ - public function getAutoLogStacks(): bool + public function shouldAttachStacktrace(): bool { - return $this->options['auto_log_stacks']; + return $this->options['attach_stacktrace']; } /** - * Sets whether the stacktrace must be auto-filled. + * Sets whether the stacktrace will be attached on captureMessage. * - * @param bool $enable Flag indicating if the stacktrace must be auto-filled + * @param bool $enable Flag indicating if the stacktrace will be attached to captureMessage calls */ - public function setAutoLogStacks(bool $enable): void + public function setAttachStacktrace(bool $enable): void { - $options = array_merge($this->options, ['auto_log_stacks' => $enable]); + $options = array_merge($this->options, ['attach_stacktrace' => $enable]); $this->options = $this->resolver->resolve($options); } @@ -245,69 +245,23 @@ public function setEnableCompression(bool $enabled): void } /** - * Gets the current environment. + * Gets the environment. * - * @return string + * @return null|string */ - public function getCurrentEnvironment(): string + public function getEnvironment(): ?string { - return $this->options['current_environment']; + return $this->options['environment']; } /** - * Sets the current environment. + * Sets the environment. * * @param string $environment The environment */ - public function setCurrentEnvironment(string $environment): void - { - $options = array_merge($this->options, ['current_environment' => $environment]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Gets the whitelist of environments that will send notifications to - * Sentry. - * - * @return string[] - */ - public function getEnvironments(): array - { - return $this->options['environments']; - } - - /** - * Sets the whitelist of environments that will send notifications to - * Sentry. - * - * @param string[] $environments The environments - */ - public function setEnvironments(array $environments): void - { - $options = array_merge($this->options, ['environments' => $environments]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Gets the list of logger 'progname's to exclude from breadcrumbs. - * - * @return string[] - */ - public function getExcludedLoggers(): array - { - return $this->options['excluded_loggers']; - } - - /** - * Sets the list of logger 'progname's to exclude from breadcrumbs. - * - * @param string[] $loggers The list of logger 'progname's - */ - public function setExcludedLoggers(array $loggers): void + public function setEnvironment(string $environment): void { - $options = array_merge($this->options, ['excluded_loggers' => $loggers]); + $options = array_merge($this->options, ['environment' => $environment]); $this->options = $this->resolver->resolve($options); } @@ -660,14 +614,10 @@ private function configureOptions(OptionsResolver $resolver) 'serialize_all_object' => false, 'sample_rate' => 1, 'mb_detect_order' => null, - 'auto_log_stacks' => true, + 'attach_stacktrace' => false, 'context_lines' => 3, 'enable_compression' => true, - 'current_environment' => 'default', - 'environments' => [], - 'excluded_loggers' => [], - 'excluded_exceptions' => [], - 'excluded_app_paths' => [], + 'environment' => null, 'project_root' => null, 'logger' => 'php', 'release' => null, @@ -682,6 +632,8 @@ private function configureOptions(OptionsResolver $resolver) 'before_breadcrumb' => function (Breadcrumb $breadcrumb): ?Breadcrumb { return $breadcrumb; }, + 'excluded_exceptions' => [], + 'excluded_app_paths' => [], ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -689,12 +641,10 @@ private function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('serialize_all_object', 'bool'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('mb_detect_order', ['null', 'array', 'string']); - $resolver->setAllowedTypes('auto_log_stacks', 'bool'); + $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', 'int'); $resolver->setAllowedTypes('enable_compression', 'bool'); - $resolver->setAllowedTypes('current_environment', 'string'); - $resolver->setAllowedTypes('environments', 'array'); - $resolver->setAllowedTypes('excluded_loggers', 'array'); + $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('excluded_exceptions', 'array'); $resolver->setAllowedTypes('excluded_app_paths', 'array'); $resolver->setAllowedTypes('project_root', ['null', 'string']); diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 3b9208b32..3a02727f8 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -188,14 +188,12 @@ public function optionsDataProvider(): array ['setSerializeAllObjects', false], ['setSampleRate', 0.5], ['setMbDetectOrder', ['foo', 'bar']], - ['setAutoLogStacks', false], + ['setAttachStacktrace', true], ['setContextLines', 0], ['setEnableCompression', false], - ['setCurrentEnvironment', 'test'], - ['setEnvironments', ['default']], - ['setExcludedLoggers', ['foo', 'bar']], - ['setExcludedExceptions', ['foo', 'bar']], + ['setEnvironment', 'test'], ['setExcludedProjectPaths', ['foo', 'bar']], + ['setExcludedExceptions', ['foo', 'bar']], ['setProjectRoot', 'foo'], ['setLogger', 'bar'], ['setRelease', 'dev'], diff --git a/tests/ClientTest.php b/tests/ClientTest.php index ac92c5f31..4994f6959 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -350,24 +350,18 @@ public function testConvertException(\Exception $exception, array $clientConfig, { $options = new Options($clientConfig); - $assertHasStacktrace = $options->getAutoLogStacks(); - /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') - ->with($this->callback(function (Event $event) use ($expectedResult, $assertHasStacktrace): bool { + ->with($this->callback(function (Event $event) use ($expectedResult): bool { $this->assertArraySubset($expectedResult, $event->toArray()); $this->assertArrayNotHasKey('values', $event->getExceptions()); $this->assertArrayHasKey('values', $event->toArray()['exception']); foreach ($event->getExceptions() as $exceptionData) { - if ($assertHasStacktrace) { - $this->assertArrayHasKey('stacktrace', $exceptionData); - $this->assertInstanceOf(Stacktrace::class, $exceptionData['stacktrace']); - } else { - $this->assertArrayNotHasKey('stacktrace', $exceptionData); - } + $this->assertArrayHasKey('stacktrace', $exceptionData); + $this->assertInstanceOf(Stacktrace::class, $exceptionData['stacktrace']); } return true; @@ -395,23 +389,6 @@ public function convertExceptionDataProvider() ], ], ], - [ - new \RuntimeException('foo'), - [ - 'auto_log_stacks' => false, - ], - [ - 'level' => Severity::ERROR, - 'exception' => [ - 'values' => [ - [ - 'type' => \RuntimeException::class, - 'value' => 'foo', - ], - ], - ], - ], - ], [ new \ErrorException('foo', 0, E_USER_WARNING), [], @@ -507,7 +484,6 @@ public function testConvertExceptionContainingInvalidUtf8Characters() public function testConvertExceptionThrownInLatin1File() { $options = new Options([ - 'auto_log_stacks' => true, 'mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8'], ]); @@ -546,28 +522,25 @@ public function testConvertExceptionThrownInLatin1File() $client->captureException(require_once __DIR__ . '/Fixtures/code/Latin1File.php'); } - public function testConvertExceptionWithAutoLogStacksDisabled() + public function testAttachStacktrace() { - $options = new Options(['auto_log_stacks' => false]); + $options = new Options(['attach_stacktrace' => true]); /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') ->with($this->callback(function (Event $event): bool { - $result = $event->getExceptions(); + $result = $event->getStacktrace(); - $this->assertNotEmpty($result); - $this->assertInternalType('array', $result[0]); - $this->assertEquals(\Exception::class, $result[0]['type']); - $this->assertEquals('foo', $result[0]['value']); - $this->assertArrayNotHasKey('stacktrace', $result[0]); + $this->assertNotNull($result); + $this->assertNotEmpty($result->getFrames()); return true; })); $client = new Client($options, $transport, []); - $client->captureException(new \Exception('foo')); + $client->captureMessage('test'); } /** diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index de5044529..bee271c96 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -43,12 +43,10 @@ public function optionsDataProvider(): array ['mb_detect_order', null, 'getMbDetectOrder', 'setMbDetectOrder'], ['mb_detect_order', 'UTF-8', 'getMbDetectOrder', 'setMbDetectOrder'], ['mb_detect_order', ['UTF-8'], 'getMbDetectOrder', 'setMbDetectOrder'], - ['auto_log_stacks', false, 'getAutoLogStacks', 'setAutoLogStacks'], + ['attach_stacktrace', false, 'shouldAttachStacktrace', 'setAttachStacktrace'], ['context_lines', 3, 'getContextLines', 'setContextLines'], ['enable_compression', false, 'isCompressionEnabled', 'setEnableCompression'], - ['current_environment', 'foo', 'getCurrentEnvironment', 'setCurrentEnvironment'], - ['environments', ['foo', 'bar'], 'getEnvironments', 'setEnvironments'], - ['excluded_loggers', ['bar', 'foo'], 'getExcludedLoggers', 'setExcludedLoggers'], + ['environment', 'foo', 'getEnvironment', 'setEnvironment'], ['excluded_exceptions', ['foo', 'bar', 'baz'], 'getExcludedExceptions', 'setExcludedExceptions'], ['excluded_app_paths', ['foo', 'bar'], 'getExcludedProjectPaths', 'setExcludedProjectPaths'], ['project_root', 'baz', 'getProjectRoot', 'setProjectRoot'], From 1dfada9c56bc49ab5ca122aea9228821da0836ec Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 26 Nov 2018 16:11:53 +0100 Subject: [PATCH 0390/1161] Add const visibility --- src/ErrorHandler.php | 2 +- src/Serializer.php | 4 ++-- src/Stacktrace.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index a56c08014..e1c3003e9 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -60,7 +60,7 @@ final class ErrorHandler /** * @var array List of error levels and their description */ - const ERROR_LEVELS_DESCRIPTION = [ + private const ERROR_LEVELS_DESCRIPTION = [ E_DEPRECATED => 'Deprecated', E_USER_DEPRECATED => 'User Deprecated', E_NOTICE => 'Notice', diff --git a/src/Serializer.php b/src/Serializer.php index 710cbf6c5..92fad652b 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -33,12 +33,12 @@ class Serializer * * @see http://php.net/manual/en/function.mb-detect-encoding.php */ - const DEFAULT_MB_DETECT_ORDER = 'auto'; + public const DEFAULT_MB_DETECT_ORDER = 'auto'; /* * Suggested detect order for western countries */ - const WESTERN_MB_DETECT_ORDER = 'UTF-8, ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-8859-10, ISO-8859-13, ISO-8859-14, ISO-8859-15, ISO-8859-16, Windows-1251, Windows-1252, Windows-1254'; + public const WESTERN_MB_DETECT_ORDER = 'UTF-8, ASCII, ISO-8859-1, ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6, ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-8859-10, ISO-8859-13, ISO-8859-14, ISO-8859-15, ISO-8859-16, Windows-1251, Windows-1252, Windows-1254'; /** * This is the default mb detect order for the detection of encoding. diff --git a/src/Stacktrace.php b/src/Stacktrace.php index fd1ff0316..76472f3b1 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -14,7 +14,7 @@ class Stacktrace implements \JsonSerializable /** * This constant defines the default number of lines of code to include. */ - const CONTEXT_NUM_LINES = 5; + private const CONTEXT_NUM_LINES = 5; /** * @var Options The client options From 2a0f717eae54f75da1623cd01979f7438a086ce7 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 26 Nov 2018 16:22:11 +0100 Subject: [PATCH 0391/1161] Apply minimal code fixes --- src/Context/ServerOsContext.php | 2 +- src/Options.php | 4 ++-- src/Stacktrace.php | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Context/ServerOsContext.php b/src/Context/ServerOsContext.php index 1639d5fb4..2ecc21da9 100644 --- a/src/Context/ServerOsContext.php +++ b/src/Context/ServerOsContext.php @@ -186,7 +186,7 @@ public function setKernelVersion(string $kernelVersion): void private function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'name' => php_uname('s'), + 'name' => PHP_OS, 'version' => php_uname('r'), 'build' => php_uname('v'), 'kernel_version' => php_uname('a'), diff --git a/src/Options.php b/src/Options.php index 60ebe1b4a..866c6aaee 100644 --- a/src/Options.php +++ b/src/Options.php @@ -605,7 +605,7 @@ public function getIntegrations(): ?array * @throws \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException * @throws \Symfony\Component\OptionsResolver\Exception\AccessException */ - private function configureOptions(OptionsResolver $resolver) + private function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'integrations' => [], @@ -621,7 +621,7 @@ private function configureOptions(OptionsResolver $resolver) 'project_root' => null, 'logger' => 'php', 'release' => null, - 'dsn' => isset($_SERVER['SENTRY_DSN']) ? $_SERVER['SENTRY_DSN'] : null, + 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), 'before_send' => function (Event $event): ?Event { return $event; diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 76472f3b1..a80feb86f 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -79,8 +79,8 @@ public static function createFromBacktrace(Options $options, Serializer $seriali foreach ($backtrace as $frame) { $stacktrace->addFrame($file, $line, $frame); - $file = isset($frame['file']) ? $frame['file'] : '[internal]'; - $line = isset($frame['line']) ? $frame['line'] : 0; + $file = $frame['file'] ?? '[internal]'; + $line = $frame['line'] ?? 0; } // Add a final stackframe for the first method ever of this stacktrace @@ -305,8 +305,8 @@ protected static function getFrameArgumentsValues(array $frame, int $maxValueLen $result = []; - for ($i = 0; $i < \count($frame['args']); ++$i) { - $result['param' . ($i + 1)] = self::serializeArgument($frame['args'][$i], $maxValueLength); + foreach ($frame['args'] as $i => $argument) { + $result['param' . ($i + 1)] = self::serializeArgument($argument, $maxValueLength); } return $result; From dde903694dcae80eb23bd5ff96c6bed90a494db2 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 26 Nov 2018 17:28:25 +0100 Subject: [PATCH 0392/1161] Rename variable --- src/Stacktrace.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Stacktrace.php b/src/Stacktrace.php index a80feb86f..247170438 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -305,8 +305,8 @@ protected static function getFrameArgumentsValues(array $frame, int $maxValueLen $result = []; - foreach ($frame['args'] as $i => $argument) { - $result['param' . ($i + 1)] = self::serializeArgument($argument, $maxValueLength); + foreach ($frame['args'] as $index => $argument) { + $result['param' . ($index + 1)] = self::serializeArgument($argument, $maxValueLength); } return $result; From f440d8417e6c734d5f0548798d2438ea69c4bf67 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 27 Nov 2018 23:43:47 +0100 Subject: [PATCH 0393/1161] Small refactor of the SentryAuth class (#711) --- src/HttpClient/Authentication/SentryAuth.php | 34 +++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/HttpClient/Authentication/SentryAuth.php b/src/HttpClient/Authentication/SentryAuth.php index e564327a1..293fc3e8e 100644 --- a/src/HttpClient/Authentication/SentryAuth.php +++ b/src/HttpClient/Authentication/SentryAuth.php @@ -18,18 +18,18 @@ final class SentryAuth implements Authentication { /** - * @var Options The Raven client configuration + * @var Options The Sentry client configuration */ - private $configuration; + private $options; /** * Constructor. * - * @param Options $configuration The Raven client configuration + * @param Options $options The Sentry client configuration */ - public function __construct(Options $configuration) + public function __construct(Options $options) { - $this->configuration = $configuration; + $this->options = $options; } /** @@ -37,27 +37,23 @@ public function __construct(Options $configuration) */ public function authenticate(RequestInterface $request): RequestInterface { - $headerKeys = array_filter([ + $data = [ 'sentry_version' => Client::PROTOCOL_VERSION, 'sentry_client' => Client::USER_AGENT, 'sentry_timestamp' => sprintf('%F', microtime(true)), - 'sentry_key' => $this->configuration->getPublicKey(), - 'sentry_secret' => $this->configuration->getSecretKey(), - ]); + 'sentry_key' => $this->options->getPublicKey(), + ]; - $isFirstItem = true; - $header = 'Sentry '; - - foreach ($headerKeys as $headerKey => $headerValue) { - if (!$isFirstItem) { - $header .= ', '; - } + if ($this->options->getSecretKey()) { + $data['sentry_secret'] = $this->options->getSecretKey(); + } - $header .= $headerKey . '=' . $headerValue; + $headers = []; - $isFirstItem = false; + foreach ($data as $headerKey => $headerValue) { + $headers[] = $headerKey . '=' . $headerValue; } - return $request->withHeader('X-Sentry-Auth', $header); + return $request->withHeader('X-Sentry-Auth', 'Sentry ' . implode(', ', $headers)); } } From 2a72b469dbc2f37192f1d5ada1503d3745597394 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 28 Nov 2018 11:02:38 +0100 Subject: [PATCH 0394/1161] Implement an option to do basic sanitization of PII in the Request event context (#707) --- src/ClientBuilder.php | 4 +- src/Integration/RequestIntegration.php | 58 +++++++-- src/Options.php | 24 ++++ tests/Integration/RequestIntegrationTest.php | 124 ++++++++++++++++--- 4 files changed, 183 insertions(+), 27 deletions(-) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 17d6c43fd..e17c5d9ce 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -62,6 +62,8 @@ * @method setServerName(string $serverName) * @method string[] getTags() * @method setTags(string[] $tags) + * @method bool shouldSendDefaultPii() + * @method setSendDefaultPii(bool $enable) */ final class ClientBuilder implements ClientBuilderInterface { @@ -112,7 +114,7 @@ public function __construct(array $options = []) if (null !== $this->options->getIntegrations()) { $this->integrations = \array_merge([ new ErrorHandlerIntegration(), - new RequestIntegration(), + new RequestIntegration($this->options), ], $this->options->getIntegrations()); } } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 077e10a83..bc527b447 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -6,6 +6,7 @@ use Psr\Http\Message\ServerRequestInterface; use Sentry\Event; +use Sentry\Options; use Sentry\State\Hub; use Sentry\State\Scope; use Zend\Diactoros\ServerRequestFactory; @@ -18,6 +19,21 @@ */ final class RequestIntegration implements IntegrationInterface { + /** + * @var Options The client options + */ + private $options; + + /** + * RequestIntegration constructor. + * + * @param Options $options The Client Options + */ + public function __construct(Options $options) + { + $this->options = $options; + } + /** * {@inheritdoc} */ @@ -30,7 +46,7 @@ public function setupOnce(): void return $event; } - self::applyToEvent($event); + self::applyToEvent($self, $event); return $event; }); @@ -39,10 +55,11 @@ public function setupOnce(): void /** * Applies the information gathered by the this integration to the event. * + * @param self $self The current instance of RequestIntegration * @param Event $event The event that will be enriched with a request * @param null|ServerRequestInterface $request The Request that will be processed and added to the event */ - public static function applyToEvent(Event $event, ?ServerRequestInterface $request = null): void + public static function applyToEvent(self $self, Event $event, ?ServerRequestInterface $request = null): void { if (null === $request) { $request = isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null; @@ -55,24 +72,45 @@ public static function applyToEvent(Event $event, ?ServerRequestInterface $reque $requestData = [ 'url' => (string) $request->getUri(), 'method' => $request->getMethod(), - 'headers' => $request->getHeaders(), - 'cookies' => $request->getCookieParams(), ]; if ($request->getUri()->getQuery()) { $requestData['query_string'] = $request->getUri()->getQuery(); } - if ($request->hasHeader('REMOTE_ADDR')) { - $requestData['env']['REMOTE_ADDR'] = $request->getHeaderLine('REMOTE_ADDR'); + if ($self->options->shouldSendDefaultPii()) { + if ($request->hasHeader('REMOTE_ADDR')) { + $requestData['env']['REMOTE_ADDR'] = $request->getHeaderLine('REMOTE_ADDR'); + } + + $requestData['cookies'] = $request->getCookieParams(); + $requestData['headers'] = $request->getHeaders(); + + $userContext = $event->getUserContext(); + + if (null === $userContext->getIpAddress() && $request->hasHeader('REMOTE_ADDR')) { + $userContext->setIpAddress($request->getHeaderLine('REMOTE_ADDR')); + } + } else { + $requestData['headers'] = $self->removePiiFromHeaders($request->getHeaders()); } $event->setRequest($requestData); + } - $userContext = $event->getUserContext(); + /** + * Removes headers containing potential PII. + * + * @param array $headers Array containing request headers + * + * @return array + */ + private function removePiiFromHeaders(array $headers): array + { + $keysToRemove = ['authorization', 'cookie', 'set-cookie', 'remote_addr']; - if (null === $userContext->getIpAddress() && $request->hasHeader('REMOTE_ADDR')) { - $userContext->setIpAddress($request->getHeaderLine('REMOTE_ADDR')); - } + return array_filter($headers, function ($key) use ($keysToRemove) { + return !\in_array(\strtolower($key), $keysToRemove, true); + }, ARRAY_FILTER_USE_KEY); } } diff --git a/src/Options.php b/src/Options.php index 866c6aaee..4e9376bd7 100644 --- a/src/Options.php +++ b/src/Options.php @@ -597,6 +597,28 @@ public function getIntegrations(): ?array return $this->options['integrations']; } + /** + * Should default PII be sent by default. + * + * @return bool + */ + public function shouldSendDefaultPii(): bool + { + return $this->options['send_default_pii']; + } + + /** + * Sets if default PII should be sent with every event (if possible). + * + * @param bool $enable Flag indicating if default PII will be sent + */ + public function setSendDefaultPii(bool $enable): void + { + $options = array_merge($this->options, ['send_default_pii' => $enable]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -634,6 +656,7 @@ private function configureOptions(OptionsResolver $resolver): void }, 'excluded_exceptions' => [], 'excluded_app_paths' => [], + 'send_default_pii' => false, ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -658,6 +681,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('max_breadcrumbs', 'int'); $resolver->setAllowedTypes('before_breadcrumb', ['callable']); $resolver->setAllowedTypes('integrations', ['null', 'array']); + $resolver->setAllowedTypes('send_default_pii', 'bool'); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 06cb8259e..eb1279943 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -8,52 +8,76 @@ use Psr\Http\Message\ServerRequestInterface; use Sentry\Event; use Sentry\Integration\RequestIntegration; +use Sentry\Options; use Zend\Diactoros\ServerRequest; use Zend\Diactoros\Uri; final class RequestIntegrationTest extends TestCase { /** - * @dataProvider invokeDataProvider + * @dataProvider invokeUserContextPiiDataProvider */ - public function testInvoke(array $requestData, array $expectedValue): void + public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $expectedValue): void { $event = new Event(); + $event->getUserContext()->setData(['foo' => 'bar']); $request = new ServerRequest(); - $request = $request->withCookieParams($requestData['cookies']); - $request = $request->withUri(new Uri($requestData['uri'])); - $request = $request->withMethod($requestData['method']); - - foreach ($requestData['headers'] as $name => $value) { - $request = $request->withHeader($name, $value); - } + $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); $this->assertInstanceOf(ServerRequestInterface::class, $request); - RequestIntegration::applyToEvent($event, $request); + $integration = new RequestIntegration(new Options(['send_default_pii' => $shouldSendPii])); + + RequestIntegration::applyToEvent($integration, $event, $request); - $this->assertEquals($expectedValue, $event->getRequest()); + $this->assertEquals($expectedValue, $event->getUserContext()->toArray()); } - public function testInvokeWithRequestHavingIpAddress(): void + public function invokeUserContextPiiDataProvider(): array + { + return [ + [ + true, + ['ip_address' => '127.0.0.1', 'foo' => 'bar'], + ], + [ + false, + ['foo' => 'bar'], + ], + ]; + } + + /** + * @dataProvider invokeDataProvider + */ + public function testInvoke(bool $shouldSendPii, array $requestData, array $expectedResult): void { $event = new Event(); - $event->getUserContext()->setData(['foo' => 'bar']); $request = new ServerRequest(); - $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); + $request = $request->withCookieParams($requestData['cookies']); + $request = $request->withUri(new Uri($requestData['uri'])); + $request = $request->withMethod($requestData['method']); + + foreach ($requestData['headers'] as $name => $value) { + $request = $request->withHeader($name, $value); + } + $this->assertInstanceOf(ServerRequestInterface::class, $request); - RequestIntegration::applyToEvent($event, $request); + $integration = new RequestIntegration(new Options(['send_default_pii' => $shouldSendPii])); - $this->assertEquals(['ip_address' => '127.0.0.1', 'foo' => 'bar'], $event->getUserContext()->toArray()); + RequestIntegration::applyToEvent($integration, $event, $request); + + $this->assertEquals($expectedResult, $event->getRequest()); } public function invokeDataProvider(): array { return [ [ + true, [ 'uri' => 'http://www.example.com/foo', 'method' => 'GET', @@ -74,6 +98,25 @@ public function invokeDataProvider(): array ], ], [ + false, + [ + 'uri' => 'http://www.example.com/foo', + 'method' => 'GET', + 'cookies' => [ + 'foo' => 'bar', + ], + 'headers' => [], + ], + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'GET', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + ], + ], + [ + true, [ 'uri' => 'http://www.example.com:123/foo', 'method' => 'GET', @@ -89,7 +132,26 @@ public function invokeDataProvider(): array ], ], ], + + [ + false, + [ + 'uri' => 'http://www.example.com:123/foo', + 'method' => 'GET', + 'cookies' => [], + 'headers' => [], + ], + [ + 'url' => 'http://www.example.com:123/foo', + 'method' => 'GET', + 'headers' => [ + 'Host' => ['www.example.com:123'], + ], + ], + ], + [ + true, [ 'uri' => 'http://www.example.com/foo?foo=bar&bar=baz', 'method' => 'GET', @@ -97,6 +159,9 @@ public function invokeDataProvider(): array 'headers' => [ 'Host' => ['www.example.com'], 'REMOTE_ADDR' => ['127.0.0.1'], + 'Authorization' => 'x', + 'Cookie' => 'y', + 'Set-Cookie' => 'z', ], ], [ @@ -107,12 +172,39 @@ public function invokeDataProvider(): array 'headers' => [ 'Host' => ['www.example.com'], 'REMOTE_ADDR' => ['127.0.0.1'], + 'Authorization' => ['x'], + 'Cookie' => ['y'], + 'Set-Cookie' => ['z'], ], 'env' => [ 'REMOTE_ADDR' => '127.0.0.1', ], ], ], + + [ + false, + [ + 'uri' => 'http://www.example.com/foo?foo=bar&bar=baz', + 'method' => 'GET', + 'cookies' => [], + 'headers' => [ + 'Host' => ['www.example.com'], + 'REMOTE_ADDR' => ['127.0.0.1'], + 'Authorization' => 'x', + 'Cookie' => 'y', + 'Set-Cookie' => 'z', + ], + ], + [ + 'url' => 'http://www.example.com/foo?foo=bar&bar=baz', + 'method' => 'GET', + 'query_string' => 'foo=bar&bar=baz', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + ], + ], ]; } } From f48ea529783b1aeb615d8d820cbd407e1e4c222d Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 28 Nov 2018 20:45:07 +0100 Subject: [PATCH 0395/1161] Refactorize the serializer and add an interface (#701) --- phpstan.neon | 1 + src/Client.php | 27 +- src/ClientBuilder.php | 44 +- src/ClientBuilderInterface.php | 23 + src/Options.php | 44 -- src/ReprSerializer.php | 35 -- src/Serializer.php | 303 --------- src/Serializer/AbstractSerializer.php | 338 +++++++++++ src/Serializer/RepresentationSerializer.php | 52 ++ .../RepresentationSerializerInterface.php | 24 + src/Serializer/Serializer.php | 19 + src/Serializer/SerializerInterface.php | 20 + src/Stacktrace.php | 35 +- tests/AbstractSerializerTest.php | 546 ----------------- tests/ClientBuilderTest.php | 2 - tests/ClientTest.php | 90 +-- tests/OptionsTest.php | 4 - tests/ReprSerializerTest.php | 108 ---- tests/Serializer/AbstractSerializerTest.php | 573 ++++++++++++++++++ .../RepresentationSerializerTest.php | 134 ++++ tests/Serializer/SerializerTest.php | 105 ++++ tests/SerializerTest.php | 15 - tests/StacktraceTest.php | 10 +- .../resources/callable_without_namespace.inc | 5 + tests/resources/php70_serializing.inc | 28 - tests/resources/php71_serializing.inc | 36 -- 26 files changed, 1393 insertions(+), 1228 deletions(-) delete mode 100644 src/ReprSerializer.php delete mode 100644 src/Serializer.php create mode 100644 src/Serializer/AbstractSerializer.php create mode 100644 src/Serializer/RepresentationSerializer.php create mode 100644 src/Serializer/RepresentationSerializerInterface.php create mode 100644 src/Serializer/Serializer.php create mode 100644 src/Serializer/SerializerInterface.php delete mode 100644 tests/AbstractSerializerTest.php delete mode 100644 tests/ReprSerializerTest.php create mode 100644 tests/Serializer/AbstractSerializerTest.php create mode 100644 tests/Serializer/RepresentationSerializerTest.php create mode 100644 tests/Serializer/SerializerTest.php delete mode 100644 tests/SerializerTest.php create mode 100644 tests/resources/callable_without_namespace.inc delete mode 100644 tests/resources/php70_serializing.inc delete mode 100644 tests/resources/php71_serializing.inc diff --git a/phpstan.neon b/phpstan.neon index 3c9523dd1..e8cd53958 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,6 +7,7 @@ parameters: - '/Call to an undefined method Sentry\\ClientBuilder::methodThatDoesNotExists\(\)/' - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' + - '/Method Sentry\\Serializer\\RepresentationSerializer::(representationSerialize|serializeValue)\(\) should return [\w|]+ but returns [\w|]+/' excludes_analyse: - tests/resources includes: diff --git a/src/Client.php b/src/Client.php index 708355e2e..f3cc7a77a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -6,6 +6,10 @@ use Sentry\Integration\Handler; use Sentry\Integration\IntegrationInterface; +use Sentry\Serializer\RepresentationSerializer; +use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\Serializer\Serializer; +use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; use Sentry\Transport\TransportInterface; use Zend\Diactoros\ServerRequestFactory; @@ -59,34 +63,31 @@ class Client implements ClientInterface private $integrations; /** - * @var Serializer The serializer + * @var SerializerInterface The serializer */ private $serializer; /** - * @var ReprSerializer The representation serializer + * @var RepresentationSerializerInterface The representation serializer */ private $representationSerializer; /** * Constructor. * - * @param Options $options The client configuration - * @param TransportInterface $transport The transport - * @param IntegrationInterface[] $integrations The integrations used by the client + * @param Options $options The client configuration + * @param TransportInterface $transport The transport + * @param SerializerInterface $serializer The serializer used for events + * @param RepresentationSerializerInterface $representationSerializer The representation serializer to be used with stacktrace frames + * @param IntegrationInterface[] $integrations The integrations used by the client */ - public function __construct(Options $options, TransportInterface $transport, array $integrations = []) + public function __construct(Options $options, TransportInterface $transport, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer, array $integrations = []) { $this->options = $options; $this->transport = $transport; $this->integrations = Handler::setupIntegrations($integrations); - $this->serializer = new Serializer($this->options->getMbDetectOrder()); - $this->representationSerializer = new ReprSerializer($this->options->getMbDetectOrder()); - - if ($this->options->getSerializeAllObjects()) { - $this->serializer->setAllObjectSerialize($this->options->getSerializeAllObjects()); - $this->representationSerializer->setAllObjectSerialize($this->options->getSerializeAllObjects()); - } + $this->serializer = $serializer; + $this->representationSerializer = $representationSerializer; } /** diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index e17c5d9ce..c9e57272f 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -22,6 +22,10 @@ use Sentry\Integration\ErrorHandlerIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; +use Sentry\Serializer\RepresentationSerializer; +use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\Serializer\Serializer; +use Sentry\Serializer\SerializerInterface; use Sentry\Transport\HttpTransport; use Sentry\Transport\NullTransport; use Sentry\Transport\TransportInterface; @@ -35,12 +39,8 @@ * @method setSendAttempts(int $attemptsCount) * @method string[] getPrefixes() * @method setPrefixes(array $prefixes) - * @method bool getSerializeAllObjects() - * @method setSerializeAllObjects(bool $serializeAllObjects) * @method float getSampleRate() * @method setSampleRate(float $sampleRate) - * @method string getMbDetectOrder() - * @method setMbDetectOrder(string $detectOrder) * @method bool shouldAttachStacktrace() * @method setAttachStacktrace(bool $enable) * @method int getContextLines() @@ -97,6 +97,16 @@ final class ClientBuilder implements ClientBuilderInterface */ private $httpClientPlugins = []; + /** + * @var SerializerInterface The serializer to be injected in the client + */ + private $serializer; + + /** + * @var RepresentationSerializerInterface The representation serializer to be injected in the client + */ + private $representationSerializer; + /** * @var IntegrationInterface[] List of default integrations */ @@ -193,6 +203,26 @@ public function removeHttpClientPlugin(string $className): self return $this; } + /** + * {@inheritdoc} + */ + public function setSerializer(SerializerInterface $serializer): self + { + $this->serializer = $serializer; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self + { + $this->representationSerializer = $representationSerializer; + + return $this; + } + /** * {@inheritdoc} */ @@ -202,10 +232,10 @@ public function getClient(): ClientInterface $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); $this->transport = $this->transport ?? $this->createTransportInstance(); + $this->serializer = $this->serializer ?? new Serializer(); + $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer(); - $client = new Client($this->options, $this->transport, $this->integrations); - - return $client; + return new Client($this->options, $this->transport, $this->serializer, $this->representationSerializer, $this->integrations); } /** diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index dbcc37619..dd7424cd9 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -8,6 +8,8 @@ use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory; use Http\Message\UriFactory; +use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\Serializer\SerializerInterface; use Sentry\Transport\TransportInterface; /** @@ -86,4 +88,25 @@ public function removeHttpClientPlugin(string $className); * @return ClientInterface */ public function getClient(): ClientInterface; + + /** + * Sets a serializer instance to be injected as a dependency of the client. + * + * @param SerializerInterface $serializer The serializer to be used by the client to fill the events + * + * @return $this + */ + public function setSerializer(SerializerInterface $serializer); + + /** + * Sets a representation serializer instance to be injected as a dependency of the client. + * + * @param RepresentationSerializerInterface $representationSerializer + * The representation serializer, used to serialize function + * arguments in stack traces, to have string representation + * of non-string values + * + * @return $this + */ + public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer); } diff --git a/src/Options.php b/src/Options.php index 4e9376bd7..3a46a1331 100644 --- a/src/Options.php +++ b/src/Options.php @@ -110,28 +110,6 @@ public function setPrefixes(array $prefixes): void $this->options = $this->resolver->resolve($options); } - /** - * Gets whether all the objects should be serialized. - * - * @return bool - */ - public function getSerializeAllObjects(): bool - { - return $this->options['serialize_all_object']; - } - - /** - * Sets whether all the objects should be serialized. - * - * @param bool $serializeAllObjects Flag indicating if all objects should be serialized - */ - public function setSerializeAllObjects(bool $serializeAllObjects): void - { - $options = array_merge($this->options, ['serialize_all_object' => $serializeAllObjects]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the sampling factor to apply to events. A value of 0 will deny * sending any events, and a value of 1 will send 100% of events. @@ -156,28 +134,6 @@ public function setSampleRate(float $sampleRate): void $this->options = $this->resolver->resolve($options); } - /** - * Gets the character encoding detection order. - * - * @return string|string[]|null - */ - public function getMbDetectOrder() - { - return $this->options['mb_detect_order']; - } - - /** - * Sets the character encoding detection order. - * - * @param string|string[]|null $detectOrder The detection order - */ - public function setMbDetectOrder($detectOrder): void - { - $options = array_merge($this->options, ['mb_detect_order' => $detectOrder]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets whether the stacktrace will be attached on captureMessage. * diff --git a/src/ReprSerializer.php b/src/ReprSerializer.php deleted file mode 100644 index d79ae85ef..000000000 --- a/src/ReprSerializer.php +++ /dev/null @@ -1,35 +0,0 @@ -serializeString($value); - } - } -} diff --git a/src/Serializer.php b/src/Serializer.php deleted file mode 100644 index 92fad652b..000000000 --- a/src/Serializer.php +++ /dev/null @@ -1,303 +0,0 @@ -mb_detect_order = $mb_detect_order; - } - - $this->messageLimit = (int) $messageLimit; - } - - /** - * Serialize an object (recursively) into something safe for data - * sanitization and encoding. - * - * @param mixed $value - * @param int $max_depth - * @param int $_depth - * - * @return string|bool|float|int|null|object|array - */ - public function serialize($value, $max_depth = 3, $_depth = 0) - { - if ($_depth < $max_depth) { - if (\is_array($value)) { - $new = []; - foreach ($value as $k => $v) { - $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); - } - - return $new; - } - - if (\is_object($value)) { - if (\is_callable($value)) { - return $this->serializeCallable($value); - } - - if ($this->_all_object_serialize || ('stdClass' === \get_class($value))) { - return $this->serializeObject($value, $max_depth, $_depth, []); - } - } - } - - return $this->serializeValue($value); - } - - /** - * @param object $object - * @param int $max_depth - * @param int $_depth - * @param string[] $hashes - * - * @return array|string|bool|float|int|null - */ - public function serializeObject($object, $max_depth = 3, $_depth = 0, $hashes = []) - { - if (($_depth >= $max_depth) or \in_array(spl_object_hash($object), $hashes)) { - return $this->serializeValue($object); - } - $hashes[] = spl_object_hash($object); - $return = []; - foreach ($object as $key => &$value) { - if (\is_object($value)) { - $new_value = $this->serializeObject($value, $max_depth, $_depth + 1, $hashes); - } else { - $new_value = $this->serialize($value, $max_depth, $_depth + 1); - } - $return[$key] = $new_value; - } - - return $return; - } - - protected function serializeString($value) - { - $value = (string) $value; - - if (\extension_loaded('mbstring')) { - // we always guarantee this is coerced, even if we can't detect encoding - if ($currentEncoding = mb_detect_encoding($value, $this->mb_detect_order)) { - $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); - } else { - $value = mb_convert_encoding($value, 'UTF-8'); - } - - if (mb_strlen($value) > $this->messageLimit) { - $value = mb_substr($value, 0, $this->messageLimit - 10, 'UTF-8') . ' {clipped}'; - } - } else { - if (\strlen($value) > $this->messageLimit) { - $value = substr($value, 0, $this->messageLimit - 10) . ' {clipped}'; - } - } - - return $value; - } - - /** - * @param mixed $value - * - * @return string|bool|float|int|null - */ - protected function serializeValue($value) - { - if ((null === $value) || \is_bool($value) || \is_float($value) || \is_int($value)) { - return $value; - } elseif (\is_object($value) || 'object' == \gettype($value)) { - return 'Object ' . \get_class($value); - } elseif (\is_resource($value)) { - return 'Resource ' . get_resource_type($value); - } elseif (\is_array($value)) { - return 'Array of length ' . \count($value); - } else { - return $this->serializeString($value); - } - } - - /** - * @param callable $callable - * - * @return string - */ - public function serializeCallable(callable $callable) - { - if (\is_array($callable)) { - $reflection = new \ReflectionMethod($callable[0], $callable[1]); - $class = $reflection->getDeclaringClass(); - } elseif ($callable instanceof \Closure || \is_string($callable)) { - $reflection = new \ReflectionFunction($callable); - $class = null; - } else { - throw new \InvalidArgumentException('Unrecognized type of callable'); - } - - $value = $reflection->isClosure() ? 'Lambda ' : 'Callable '; - - if ($reflection->getReturnType()) { - $value .= $reflection->getReturnType() . ' '; - } - - if ($class) { - $value .= $class->getName() . '::'; - } - - return $value . $reflection->getName() . ' ' . $this->serializeCallableParameters($reflection); - } - - /** - * @param \ReflectionFunctionAbstract $reflection - * - * @return string - */ - private function serializeCallableParameters(\ReflectionFunctionAbstract $reflection) - { - $params = []; - foreach ($reflection->getParameters() as &$param) { - $paramType = null; - if (version_compare(PHP_VERSION, '7.0.0') >= 0) { - $paramType = $param->hasType() ? $param->getType() : 'mixed'; - } else { - if ($param->isArray()) { - $paramType = 'array'; - } elseif ($param->isCallable()) { - $paramType = 'callable'; - } - } - if ($paramType && $param->allowsNull()) { - $paramType .= '|null'; - } - - $paramName = ($param->isPassedByReference() ? '&' : '') . $param->getName(); - if ($param->isOptional()) { - $paramName = '[' . $paramName . ']'; - } - - if ($paramType) { - $params[] = $paramType . ' ' . $paramName; - } else { - $params[] = $paramName; - } - } - - return '[' . implode('; ', $params) . ']'; - } - - /** - * @return string|string[] - * @codeCoverageIgnore - */ - public function getMbDetectOrder() - { - return $this->mb_detect_order; - } - - /** - * @param string $mb_detect_order - * - * @return \Sentry\Serializer - * @codeCoverageIgnore - */ - public function setMbDetectOrder($mb_detect_order) - { - $this->mb_detect_order = $mb_detect_order; - - return $this; - } - - /** - * @param bool $value - */ - public function setAllObjectSerialize($value) - { - $this->_all_object_serialize = $value; - } - - /** - * @return bool - */ - public function getAllObjectSerialize() - { - return $this->_all_object_serialize; - } - - /** - * @return int - */ - public function getMessageLimit() - { - return $this->messageLimit; - } - - /** - * @param int $messageLimit - */ - public function setMessageLimit($messageLimit) - { - $this->messageLimit = $messageLimit; - } -} diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php new file mode 100644 index 000000000..0830ece75 --- /dev/null +++ b/src/Serializer/AbstractSerializer.php @@ -0,0 +1,338 @@ +maxDepth = $maxDepth; + + if (null != $mbDetectOrder) { + $this->mbDetectOrder = $mbDetectOrder; + } + + $this->messageLimit = $messageLimit; + } + + /** + * Serialize an object (recursively) into something safe for data + * sanitization and encoding. + * + * @param mixed $value + * @param int $_depth + * + * @return string|bool|float|int|null|object|array + */ + protected function serializeRecursively($value, int $_depth = 0) + { + if ($_depth >= $this->maxDepth) { + return $this->serializeValue($value); + } + + if (\is_callable($value)) { + return $this->serializeCallable($value); + } + + if (\is_array($value)) { + $serializedArray = []; + + foreach ($value as $k => $v) { + $serializedArray[$k] = $this->serializeRecursively($v, $_depth + 1); + } + + return $serializedArray; + } + + if (\is_object($value)) { + if ($this->serializeAllObjects || ('stdClass' === \get_class($value))) { + return $this->serializeObject($value, $_depth, []); + } + } + + return $this->serializeValue($value); + } + + /** + * @param object $object + * @param int $_depth + * @param string[] $hashes + * + * @return array|string|bool|float|int|null + */ + protected function serializeObject($object, int $_depth = 0, array $hashes = []) + { + if ($_depth >= $this->maxDepth || \in_array(spl_object_hash($object), $hashes, true)) { + return $this->serializeValue($object); + } + + $hashes[] = spl_object_hash($object); + $serializedObject = []; + + foreach ($object as $key => &$value) { + if (\is_object($value)) { + $serializedObject[$key] = $this->serializeObject($value, $_depth + 1, $hashes); + } else { + $serializedObject[$key] = $this->serializeRecursively($value, $_depth + 1); + } + } + + return $serializedObject; + } + + protected function serializeString($value) + { + $value = (string) $value; + + if ($this->isMbStringEnabled()) { + // we always guarantee this is coerced, even if we can't detect encoding + if ($currentEncoding = \mb_detect_encoding($value, $this->mbDetectOrder)) { + $value = \mb_convert_encoding($value, 'UTF-8', $currentEncoding); + } else { + $value = \mb_convert_encoding($value, 'UTF-8'); + } + + if (\mb_strlen($value) > $this->messageLimit) { + $value = \mb_substr($value, 0, $this->messageLimit - 10, 'UTF-8') . ' {clipped}'; + } + } else { + if (\strlen($value) > $this->messageLimit) { + $value = \substr($value, 0, $this->messageLimit - 10) . ' {clipped}'; + } + } + + return $value; + } + + /** + * @param mixed $value + * + * @return string|bool|float|int|null + */ + protected function serializeValue($value) + { + if ((null === $value) || \is_bool($value) || \is_numeric($value)) { + return $value; + } + + if (\is_object($value)) { + return 'Object ' . \get_class($value); + } + + if (\is_resource($value)) { + return 'Resource ' . get_resource_type($value); + } + + if (\is_callable($value)) { + return $this->serializeCallable($value); + } + + if (\is_array($value)) { + return 'Array of length ' . \count($value); + } + + return $this->serializeString($value); + } + + /** + * @param callable $callable + * + * @return string + */ + protected function serializeCallable(callable $callable): string + { + try { + if (\is_array($callable)) { + $reflection = new \ReflectionMethod($callable[0], $callable[1]); + $class = $reflection->getDeclaringClass(); + } elseif ($callable instanceof \Closure || \is_string($callable)) { + $reflection = new \ReflectionFunction($callable); + $class = null; + } elseif (\is_object($callable) && \method_exists($callable, '__invoke')) { + $reflection = new \ReflectionMethod($callable, '__invoke'); + $class = $reflection->getDeclaringClass(); + } else { + throw new \InvalidArgumentException('Unrecognized type of callable'); + } + } catch (\ReflectionException $exception) { + return '{unserializable callable, reflection error}'; + } + + $value = $reflection->isClosure() ? 'Lambda ' : 'Callable '; + + if ($reflection->getReturnType()) { + $value .= $reflection->getReturnType() . ' '; + } + + if ($class) { + $value .= $class->getName() . '::'; + } + + return $value . $reflection->getName() . ' ' . $this->serializeCallableParameters($reflection); + } + + /** + * @param \ReflectionFunctionAbstract $reflection + * + * @return string + */ + private function serializeCallableParameters(\ReflectionFunctionAbstract $reflection): string + { + $params = []; + foreach ($reflection->getParameters() as &$param) { + $paramType = $param->hasType() ? $param->getType() : 'mixed'; + + if ($param->allowsNull()) { + $paramType .= '|null'; + } + + $paramName = ($param->isPassedByReference() ? '&' : '') . $param->getName(); + + if ($param->isOptional()) { + $paramName = '[' . $paramName . ']'; + } + + if ($paramType) { + $params[] = $paramType . ' ' . $paramName; + } else { + $params[] = $paramName; + } + } + + return '[' . implode('; ', $params) . ']'; + } + + public function getMbDetectOrder(): string + { + return $this->mbDetectOrder; + } + + /** + * @param string $mbDetectOrder + * + * @return $this + */ + public function setMbDetectOrder(string $mbDetectOrder): self + { + $this->mbDetectOrder = $mbDetectOrder; + + return $this; + } + + /** + * @param bool $value + */ + public function setSerializeAllObjects(bool $value): void + { + $this->serializeAllObjects = $value; + } + + /** + * @return bool + */ + public function getSerializeAllObjects(): bool + { + return $this->serializeAllObjects; + } + + /** + * @return int + */ + public function getMessageLimit(): int + { + return $this->messageLimit; + } + + /** + * @param int $messageLimit + * + * @return AbstractSerializer + */ + public function setMessageLimit(int $messageLimit): self + { + $this->messageLimit = $messageLimit; + + return $this; + } + + private function isMbStringEnabled(): bool + { + if (null === $this->mbStringEnabled) { + $this->mbStringEnabled = \extension_loaded('mbstring'); + } + + return $this->mbStringEnabled; + } +} diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php new file mode 100644 index 000000000..4da2830e7 --- /dev/null +++ b/src/Serializer/RepresentationSerializer.php @@ -0,0 +1,52 @@ +serializeRecursively($value); + } + + /** + * This method is overridden to return even basic types as strings. + * + * @param mixed $value The value that needs to be serialized + * + * @return string + */ + protected function serializeValue($value) + { + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + if (\is_float($value) && (int) $value == $value) { + return $value . '.0'; + } + + if (\is_numeric($value)) { + return (string) $value; + } + + return parent::serializeValue($value); + } +} diff --git a/src/Serializer/RepresentationSerializerInterface.php b/src/Serializer/RepresentationSerializerInterface.php new file mode 100644 index 000000000..b26c2a646 --- /dev/null +++ b/src/Serializer/RepresentationSerializerInterface.php @@ -0,0 +1,24 @@ +serializeRecursively($value); + } +} diff --git a/src/Serializer/SerializerInterface.php b/src/Serializer/SerializerInterface.php new file mode 100644 index 000000000..9e11f5dfa --- /dev/null +++ b/src/Serializer/SerializerInterface.php @@ -0,0 +1,20 @@ +options = $options; $this->serializer = $serializer; @@ -63,16 +68,16 @@ public function __construct(Options $options, Serializer $serializer, ReprSerial /** * Creates a new instance of this class from the given backtrace. * - * @param Options $options The client options - * @param Serializer $serializer The serializer - * @param ReprSerializer $representationSerializer The representation serializer - * @param array $backtrace The backtrace - * @param string $file The file that originated the backtrace - * @param int $line The line at which the backtrace originated + * @param Options $options The client options + * @param SerializerInterface $serializer The serializer + * @param RepresentationSerializerInterface $representationSerializer The representation serializer + * @param array $backtrace The backtrace + * @param string $file The file that originated the backtrace + * @param int $line The line at which the backtrace originated * * @return static */ - public static function createFromBacktrace(Options $options, Serializer $serializer, ReprSerializer $representationSerializer, array $backtrace, string $file, int $line): self + public static function createFromBacktrace(Options $options, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer, array $backtrace, string $file, int $line) { $stacktrace = new static($options, $serializer, $representationSerializer); @@ -160,10 +165,10 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void if (!empty($frameArguments)) { foreach ($frameArguments as $argumentName => $argumentValue) { - $argumentValue = $this->representationSerializer->serialize($argumentValue); + $argumentValue = $this->representationSerializer->representationSerialize($argumentValue); if (\is_string($argumentValue) || is_numeric($argumentValue)) { - $frameArguments[(string) $argumentName] = substr((string) $argumentValue, 0, Client::MESSAGE_MAX_LENGTH_LIMIT); + $frameArguments[(string) $argumentName] = substr($argumentValue, 0, Client::MESSAGE_MAX_LENGTH_LIMIT); } else { $frameArguments[(string) $argumentName] = $argumentValue; } diff --git a/tests/AbstractSerializerTest.php b/tests/AbstractSerializerTest.php deleted file mode 100644 index d5e155aac..000000000 --- a/tests/AbstractSerializerTest.php +++ /dev/null @@ -1,546 +0,0 @@ - false], - ['serializeAllObjects' => true], - ]; - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testArraysAreArrays($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - $input = [1, 2, 3]; - $result = $serializer->serialize($input); - $this->assertEquals(['1', '2', '3'], $result); - - $result = $serializer->serialize([Client::class, 'getOptions']); - $this->assertEquals([Client::class, 'getOptions'], $result); - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testStdClassAreArrays($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - $input = new \stdClass(); - $input->foo = 'BAR'; - $result = $serializer->serialize($input); - $this->assertEquals(['foo' => 'BAR'], $result); - } - - public function testObjectsAreStrings() - { - $serializer = $this->getSerializerUnderTest(); - $input = new SerializerTestObject(); - $result = $serializer->serialize($input); - $this->assertEquals('Object Sentry\Tests\SerializerTestObject', $result); - } - - public function testObjectsAreNotStrings() - { - $serializer = $this->getSerializerUnderTest(); - $serializer->setAllObjectSerialize(true); - $input = new SerializerTestObject(); - $result = $serializer->serialize($input); - $this->assertEquals(['key' => 'value'], $result); - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testIntsAreInts($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - $input = 1; - $result = $serializer->serialize($input); - $this->assertInternalType('integer', $result); - $this->assertEquals(1, $result); - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testFloats($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - $input = 1.5; - $result = $serializer->serialize($input); - $this->assertInternalType('double', $result); - $this->assertEquals(1.5, $result); - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testBooleans($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - $input = true; - $result = $serializer->serialize($input); - $this->assertTrue($result); - - $input = false; - $result = $serializer->serialize($input); - $this->assertFalse($result); - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testNull($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - $input = null; - $result = $serializer->serialize($input); - $this->assertNull($result); - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testRecursionMaxDepth($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - $input = []; - $input[] = &$input; - $result = $serializer->serialize($input, 3); - $this->assertEquals([[['Array of length 1']]], $result); - - $result = $serializer->serialize([], 3); - $this->assertEquals([], $result); - - $result = $serializer->serialize([[]], 3); - $this->assertEquals([[]], $result); - - $result = $serializer->serialize([[[]]], 3); - $this->assertEquals([[[]]], $result); - - $result = $serializer->serialize([[[[]]]], 3); - $this->assertEquals([[['Array of length 0']]], $result); - } - - public function dataRecursionInObjectsDataProvider() - { - $object = new SerializerTestObject(); - $object->key = $object; - yield [ - 'object' => $object, - 'expectedResult' => ['key' => 'Object Sentry\Tests\SerializerTestObject'], - ]; - - $object = new SerializerTestObject(); - $object2 = new SerializerTestObject(); - $object2->key = $object; - $object->key = $object2; - yield [ - 'object' => $object, - 'expectedResult' => ['key' => ['key' => 'Object Sentry\Tests\SerializerTestObject']], - ]; - - $object = new SerializerTestObject(); - $object2 = new SerializerTestObject(); - $object2->key = 'foobar'; - $object->key = $object2; - yield [ - 'object' => $object, - 'expectedResult' => ['key' => ['key' => 'foobar']], - ]; - - $object3 = new SerializerTestObject(); - $object3->key = 'foobar'; - $object2 = new SerializerTestObject(); - $object2->key = $object3; - $object = new SerializerTestObject(); - $object->key = $object2; - yield [ - 'object' => $object, - 'expectedResult' => ['key' => ['key' => ['key' => 'foobar']]], - ]; - - $object4 = new SerializerTestObject(); - $object4->key = 'foobar'; - $object3 = new SerializerTestObject(); - $object3->key = $object4; - $object2 = new SerializerTestObject(); - $object2->key = $object3; - $object = new SerializerTestObject(); - $object->key = $object2; - yield [ - 'object' => $object, - 'expectedResult' => ['key' => ['key' => ['key' => 'Object Sentry\\Tests\\SerializerTestObject']]], - ]; - - $object3 = new SerializerTestObject(); - $object2 = new SerializerTestObject(); - $object2->key = $object3; - $object2->keys = 'keys'; - $object = new SerializerTestObject(); - $object->key = $object2; - $object3->key = $object2; - yield [ - 'object' => $object, - 'expectedResult' => ['key' => ['key' => ['key' => 'Object Sentry\\Tests\\SerializerTestObject'], 'keys' => 'keys']], - ]; - } - - /** - * @param object $object - * @param array $expectedResult - * - * @dataProvider dataRecursionInObjectsDataProvider - */ - public function testRecursionInObjects($object, $expectedResult) - { - $serializer = $this->getSerializerUnderTest(); - $serializer->setAllObjectSerialize(true); - - $result1 = $serializer->serialize($object, 3); - $result2 = $serializer->serializeObject($object, 3); - $this->assertEquals($expectedResult, $result1); - $this->assertContains(\gettype($result1), ['array', 'string', 'null', 'float', 'integer', 'object']); - $this->assertEquals($expectedResult, $result2); - $this->assertContains(\gettype($result2), ['array', 'string']); - } - - public function testRecursionMaxDepthForObject() - { - $serializer = $this->getSerializerUnderTest(); - $serializer->setAllObjectSerialize(true); - - $result = $serializer->serialize((object) ['key' => (object) ['key' => 12345]], 3); - $this->assertEquals(['key' => ['key' => 12345]], $result); - - $result = $serializer->serialize((object) ['key' => (object) ['key' => (object) ['key' => 12345]]], 3); - $this->assertEquals(['key' => ['key' => ['key' => 12345]]], $result); - - $result = $serializer->serialize( - (object) ['key' => (object) ['key' => (object) ['key' => (object) ['key' => 12345]]]], - 3 - ); - $this->assertEquals(['key' => ['key' => ['key' => 'Object stdClass']]], $result); - } - - public function testObjectInArray() - { - $serializer = $this->getSerializerUnderTest(); - $input = ['foo' => new SerializerTestObject()]; - $result = $serializer->serialize($input); - $this->assertEquals(['foo' => 'Object Sentry\\Tests\\SerializerTestObject'], $result); - } - - public function testObjectInArraySerializeAll() - { - $serializer = $this->getSerializerUnderTest(); - $serializer->setAllObjectSerialize(true); - $input = ['foo' => new SerializerTestObject()]; - $result = $serializer->serialize($input); - $this->assertEquals(['foo' => ['key' => 'value']], $result); - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testBrokenEncoding($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - foreach (['7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90'] as $key) { - $input = pack('H*', $key); - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - if (\function_exists('mb_detect_encoding')) { - $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); - } - } - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testLongString($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - - foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { - $input = str_repeat('x', $length); - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(1024, \strlen($result)); - } - } - - public function testLongStringWithOverwrittenMessageLength() - { - $serializer = $this->getSerializerUnderTest(); - $serializer->setMessageLimit(500); - - foreach ([100, 490, 499, 500, 501, 1000, 10000] as $length) { - $input = str_repeat('x', $length); - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(500, \strlen($result)); - } - } - - /** - * @param bool $serializeAllObjects - * @dataProvider serializeAllObjectsProvider - */ - public function testSerializeValueResource($serializeAllObjects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serializeAllObjects) { - $serializer->setAllObjectSerialize(true); - } - $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); - $this->assertNotFalse($filename, 'Temp file creation failed'); - $fo = fopen($filename, 'wb'); - - $result = $serializer->serialize($fo); - $this->assertInternalType('string', $result); - $this->assertEquals('Resource stream', $result); - } - - public function testSetAllObjectSerialize() - { - $serializer = $this->getSerializerUnderTest(); - $serializer->setAllObjectSerialize(true); - $this->assertTrue($serializer->getAllObjectSerialize()); - $serializer->setAllObjectSerialize(false); - $this->assertFalse($serializer->getAllObjectSerialize()); - } - - public function testClippingUTF8Characters() - { - if (!\extension_loaded('mbstring')) { - $this->markTestSkipped('mbstring extension is not enabled.'); - } - - $testString = 'Прекратите надеÑтьÑÑ, что ваши пользователи будут Ñообщать об ошибках'; - $class_name = static::getSerializerUnderTest(); - /** @var \Sentry\Serializer $serializer */ - $serializer = new $class_name(null, 19); - - $clipped = $serializer->serialize($testString); - - $this->assertEquals('Прекратит {clipped}', $clipped); - $this->assertNotNull(json_encode($clipped)); - $this->assertSame(JSON_ERROR_NONE, json_last_error()); - } - - public function serializableCallableProvider() - { - $closure1 = function (array $param1) { - return $param1 * 2; - }; - $closure2 = function ($param1a) { - throw new \Exception('Don\'t even think about invoke me'); - }; - $closure4 = function (callable $param1c) { - throw new \Exception('Don\'t even think about invoke me'); - }; - $closure5 = function (\stdClass $param1d) { - throw new \Exception('Don\'t even think about invoke me'); - }; - $closure6 = function (\stdClass $param1e = null) { - throw new \Exception('Don\'t even think about invoke me'); - }; - $closure7 = function (array &$param1f) { - throw new \Exception('Don\'t even think about invoke me'); - }; - $closure8 = function (array &$param1g = null) { - throw new \Exception('Don\'t even think about invoke me'); - }; - - if (version_compare(PHP_VERSION, '7.0.0') >= 0) { - $data = [ - [ - 'callable' => $closure1, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [array param1]', - ], [ - 'callable' => $closure2, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [mixed|null param1a]', - ], [ - 'callable' => $closure4, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [callable param1c]', - ], [ - 'callable' => $closure5, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [stdClass param1d]', - ], [ - 'callable' => $closure6, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [stdClass|null [param1e]]', - ], [ - 'callable' => $closure7, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [array ¶m1f]', - ], [ - 'callable' => $closure8, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [array|null [¶m1g]]', - ], [ - 'callable' => [$this, 'serializableCallableProvider'], - 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::serializableCallableProvider []', - ], [ - 'callable' => [TestCase::class, 'setUpBeforeClass'], - 'expected' => 'Callable PHPUnit\\Framework\\TestCase::setUpBeforeClass []', - ], [ - 'callable' => [$this, 'setUpBeforeClass'], - 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', - ], [ - 'callable' => [self::class, 'setUpBeforeClass'], - 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', - ], [ - 'callable' => [SerializerTestObject::class, 'testy'], - 'expected' => 'Callable void Sentry\Tests\SerializerTestObject::testy []', - ], - ]; - require_once 'resources/php70_serializing.inc'; - - if (version_compare(PHP_VERSION, '7.1.0') >= 0) { - require_once 'resources/php71_serializing.inc'; - } - - return $data; - } - - return [ - [ - 'callable' => $closure1, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [array param1]', - ], [ - 'callable' => $closure2, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [param1a]', - ], [ - 'callable' => $closure4, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [callable param1c]', - ], [ - 'callable' => $closure5, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [param1d]', - ], [ - 'callable' => $closure6, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [[param1e]]', - ], [ - 'callable' => $closure7, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [array ¶m1f]', - ], [ - 'callable' => $closure8, - 'expected' => 'Lambda Sentry\\Tests\\{closure} [array|null [¶m1g]]', - ], [ - 'callable' => [$this, 'serializableCallableProvider'], - 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::serializableCallableProvider []', - ], [ - 'callable' => [TestCase::class, 'setUpBeforeClass'], - 'expected' => 'Callable PHPUnit_Framework_TestCase::setUpBeforeClass []', - ], [ - 'callable' => [$this, 'setUpBeforeClass'], - 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', - ], [ - 'callable' => [self::class, 'setUpBeforeClass'], - 'expected' => 'Callable Sentry\Tests\AbstractSerializerTest::setUpBeforeClass []', - ], [ - 'callable' => [SerializerTestObject::class, 'testy'], - 'expected' => 'Callable void Sentry\Tests\SerializerTestObject::testy []', - ], - ]; - } - - /** - * @param callable $callable - * @param string $expected - * - * @dataProvider serializableCallableProvider - */ - public function testSerializeCallable($callable, $expected) - { - $serializer = $this->getSerializerUnderTest(); - $actual = $serializer->serializeCallable($callable); - $this->assertEquals($expected, $actual); - - $actual2 = $serializer->serialize($callable); - $actual3 = $serializer->serialize([$callable]); - if (\is_array($callable)) { - $this->assertInternalType('array', $actual2); - $this->assertInternalType('array', $actual3); - } else { - $this->assertEquals($expected, $actual2); - $this->assertEquals([$expected], $actual3); - } - } -} - -/** - * Class SerializerTestObject. - * - * @property mixed $keys - */ -class SerializerTestObject -{ - private $foo = 'bar'; - - public $key = 'value'; - - public static function testy(): void - { - throw new \Exception('We should not reach this'); - } -} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 3a02727f8..36a45ba4b 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -185,9 +185,7 @@ public function optionsDataProvider(): array { return [ ['setPrefixes', ['foo', 'bar']], - ['setSerializeAllObjects', false], ['setSampleRate', 0.5], - ['setMbDetectOrder', ['foo', 'bar']], ['setAttachStacktrace', true], ['setContextLines', 0], ['setEnableCompression', false], diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 4994f6959..858703271 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -12,6 +12,9 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; +use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\Serializer\Serializer; +use Sentry\Serializer\SerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Hub; @@ -37,7 +40,7 @@ public function testTransactionEventAttributeIsPopulated() return true; })); - $client = new Client(new Options(), $transport); + $client = new Client(new Options(), $transport, $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class)); $client->captureMessage('test'); unset($_SERVER['PATH_INFO']); @@ -57,7 +60,7 @@ public function testTransactionEventAttributeIsNotPopulatedInCli() return true; })); - $client = new Client(new Options(), $transport); + $client = new Client(new Options(), $transport, $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class)); $client->captureMessage('test'); } @@ -338,7 +341,8 @@ public function testHandlingExceptionThrowingAnException() return true; })); - $client = new Client(new Options(), $transport, []); + $client = new Client(new Options(), $transport, $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class), []); + Hub::getCurrent()->bindClient($client); $client->captureException($this->createCarelessExceptionWithStacktrace(), Hub::getCurrent()->getScope()); } @@ -348,8 +352,6 @@ public function testHandlingExceptionThrowingAnException() */ public function testConvertException(\Exception $exception, array $clientConfig, array $expectedResult) { - $options = new Options($clientConfig); - /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -367,7 +369,10 @@ public function testConvertException(\Exception $exception, array $clientConfig, return true; })); - $client = new Client($options, $transport, []); + $client = ClientBuilder::create($clientConfig) + ->setTransport($transport) + ->getClient(); + $client->captureException($exception); } @@ -428,65 +433,8 @@ public function convertExceptionDataProvider() ]; } - public function testConvertExceptionContainingLatin1Characters() - { - $options = new Options(['mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8']]); - - $utf8String = 'äöü'; - $latin1String = utf8_decode($utf8String); - - /** @var TransportInterface|MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event) use ($utf8String): bool { - $expectedValue = [ - [ - 'type' => \Exception::class, - 'value' => $utf8String, - ], - ]; - - $this->assertArraySubset($expectedValue, $event->getExceptions()); - - return true; - })); - - $client = new Client($options, $transport, []); - $client->captureException(new \Exception($latin1String)); - } - - public function testConvertExceptionContainingInvalidUtf8Characters() - { - $malformedString = "\xC2\xA2\xC2"; // ill-formed 2-byte character U+00A2 (CENT SIGN) - - /** @var TransportInterface|MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $expectedValue = [ - [ - 'type' => \Exception::class, - 'value' => "\xC2\xA2\x3F", - ], - ]; - - $this->assertArraySubset($expectedValue, $event->getExceptions()); - - return true; - })); - - $client = new Client(new Options(), $transport, []); - $client->captureException(new \Exception($malformedString)); - } - public function testConvertExceptionThrownInLatin1File() { - $options = new Options([ - 'mb_detect_order' => ['ISO-8859-1', 'ASCII', 'UTF-8'], - ]); - /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -518,14 +466,19 @@ public function testConvertExceptionThrownInLatin1File() return true; })); - $client = new Client($options, $transport, []); + $serializer = new Serializer(); + $serializer->setMbDetectOrder('ISO-8859-1, ASCII, UTF-8'); + + $client = ClientBuilder::create() + ->setTransport($transport) + ->setSerializer($serializer) + ->getClient(); + $client->captureException(require_once __DIR__ . '/Fixtures/code/Latin1File.php'); } public function testAttachStacktrace() { - $options = new Options(['attach_stacktrace' => true]); - /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -539,7 +492,10 @@ public function testAttachStacktrace() return true; })); - $client = new Client($options, $transport, []); + $client = ClientBuilder::create(['attach_stacktrace' => true]) + ->setTransport($transport) + ->getClient(); + $client->captureMessage('test'); } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index bee271c96..afd1da2c3 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -38,11 +38,7 @@ public function optionsDataProvider(): array return [ ['send_attempts', 1, 'getSendAttempts', 'setSendAttempts'], ['prefixes', ['foo', 'bar'], 'getPrefixes', 'setPrefixes'], - ['serialize_all_object', false, 'getSerializeAllObjects', 'setSerializeAllObjects'], ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], - ['mb_detect_order', null, 'getMbDetectOrder', 'setMbDetectOrder'], - ['mb_detect_order', 'UTF-8', 'getMbDetectOrder', 'setMbDetectOrder'], - ['mb_detect_order', ['UTF-8'], 'getMbDetectOrder', 'setMbDetectOrder'], ['attach_stacktrace', false, 'shouldAttachStacktrace', 'setAttachStacktrace'], ['context_lines', 3, 'getContextLines', 'setContextLines'], ['enable_compression', false, 'isCompressionEnabled', 'setEnableCompression'], diff --git a/tests/ReprSerializerTest.php b/tests/ReprSerializerTest.php deleted file mode 100644 index 4cd366ea0..000000000 --- a/tests/ReprSerializerTest.php +++ /dev/null @@ -1,108 +0,0 @@ -getSerializerUnderTest(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = 1; - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertEquals(1, $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider serializeAllObjectsProvider - */ - public function testFloats($serialize_all_objects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = 1.5; - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertEquals('1.5', $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider serializeAllObjectsProvider - */ - public function testBooleans($serialize_all_objects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = true; - $result = $serializer->serialize($input); - $this->assertEquals('true', $result); - - $input = false; - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertEquals('false', $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider serializeAllObjectsProvider - */ - public function testNull($serialize_all_objects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - $input = null; - $result = $serializer->serialize($input); - $this->assertInternalType('string', $result); - $this->assertEquals('null', $result); - } - - /** - * @param bool $serialize_all_objects - * @dataProvider serializeAllObjectsProvider - * @covers \Sentry\ReprSerializer::serializeValue - */ - public function testSerializeRoundedFloat($serialize_all_objects) - { - $serializer = $this->getSerializerUnderTest(); - if ($serialize_all_objects) { - $serializer->setAllObjectSerialize(true); - } - - $result = $serializer->serialize((float) 1); - $this->assertInternalType('string', $result); - $this->assertEquals('1.0', $result); - - $result = $serializer->serialize(floor(5 / 2)); - $this->assertInternalType('string', $result); - $this->assertEquals('2.0', $result); - - $result = $serializer->serialize(floor(12345.678901234)); - $this->assertInternalType('string', $result); - $this->assertEquals('12345.0', $result); - } -} diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php new file mode 100644 index 000000000..01bf87123 --- /dev/null +++ b/tests/Serializer/AbstractSerializerTest.php @@ -0,0 +1,573 @@ + false], + ['serializeAllObjects' => true], + ]; + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testStdClassAreArrays(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $input = ['foo' => 'BAR']; + $result = $this->invokeSerialization($serializer, (object) $input); + + $this->assertSame($input, $result); + } + + public function testObjectsAreStrings(): void + { + $serializer = $this->createSerializer(); + $input = new SerializerTestObject(); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObject', $result); + } + + public function testObjectsAreNotStrings(): void + { + $serializer = $this->createSerializer(); + $serializer->setSerializeAllObjects(true); + + $input = new SerializerTestObject(); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame(['key' => 'value'], $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testRecursionMaxDepth(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $input = []; + $input[] = &$input; + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame([[['Array of length 1']]], $result); + + $result = $this->invokeSerialization($serializer, []); + + $this->assertSame([], $result); + + $result = $this->invokeSerialization($serializer, [[]]); + + $this->assertSame([[]], $result); + + $result = $this->invokeSerialization($serializer, [[[]]]); + + $this->assertSame([[[]]], $result); + + $result = $this->invokeSerialization($serializer, [[[[]]]]); + + $this->assertSame([[['Array of length 0']]], $result); + } + + public function dataRecursionInObjectsDataProvider(): \Generator + { + $object = new SerializerTestObject(); + $object->key = $object; + + yield [ + 'object' => $object, + 'expectedResult' => ['key' => 'Object ' . SerializerTestObject::class], + ]; + + $object = new SerializerTestObject(); + $object2 = new SerializerTestObject(); + $object2->key = $object; + $object->key = $object2; + + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => 'Object ' . SerializerTestObject::class]], + ]; + + $object = new SerializerTestObject(); + $object2 = new SerializerTestObject(); + $object2->key = 'foobar'; + $object->key = $object2; + + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => 'foobar']], + ]; + + $object3 = new SerializerTestObject(); + $object3->key = 'foobar'; + $object2 = new SerializerTestObject(); + $object2->key = $object3; + $object = new SerializerTestObject(); + $object->key = $object2; + + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => ['key' => 'foobar']]], + ]; + + $object4 = new SerializerTestObject(); + $object4->key = 'foobar'; + $object3 = new SerializerTestObject(); + $object3->key = $object4; + $object2 = new SerializerTestObject(); + $object2->key = $object3; + $object = new SerializerTestObject(); + $object->key = $object2; + + yield [ + 'object' => $object, + 'expectedResult' => ['key' => ['key' => ['key' => 'Object ' . SerializerTestObject::class]]], + ]; + + $object3 = new SerializerTestObject(); + $object2 = new SerializerTestObject(); + $object2->key = $object3; + $object2->keys = 'keys'; + $object = new SerializerTestObject(); + $object->key = $object2; + $object3->key = $object2; + + yield [ + 'object' => $object, + 'expectedResult' => [ + 'key' => [ + 'key' => ['key' => 'Object ' . SerializerTestObject::class], + 'keys' => 'keys', + ], + ], + ]; + } + + /** + * @dataProvider dataRecursionInObjectsDataProvider + */ + public function testRecursionInObjects($object, array $expectedResult): void + { + $serializer = $this->createSerializer(); + $serializer->setSerializeAllObjects(true); + + $result = $this->invokeSerialization($serializer, $object); + + $this->assertSame($expectedResult, $result); + $this->assertContains(\gettype($result), ['array', 'string', 'null', 'float', 'integer', 'object']); + } + + /** + * @dataProvider recursionMaxDepthForObjectDataProvider + */ + public function testRecursionMaxDepthForObject($value, $expectedResult): void + { + $serializer = $this->createSerializer(); + $serializer->setSerializeAllObjects(true); + + $result = $this->invokeSerialization($serializer, $value); + + $this->assertEquals($expectedResult, $result); + } + + public function recursionMaxDepthForObjectDataProvider(): array + { + return [ + [ + (object) [ + 'key' => (object) [ + 'key' => 12345, + ], + ], + [ + 'key' => [ + 'key' => 12345, + ], + ], + ], + [ + (object) [ + 'key' => (object) [ + 'key' => (object) [ + 'key' => 12345, + ], + ], + ], + [ + 'key' => [ + 'key' => [ + 'key' => 12345, + ], + ], + ], + ], + [ + (object) [ + 'key' => (object) [ + 'key' => (object) [ + 'key' => (object) [ + 'key' => 12345, + ], + ], + ], + ], + [ + 'key' => [ + 'key' => [ + 'key' => 'Object stdClass', + ], + ], + ], + ], + ]; + } + + public function testObjectInArray(): void + { + $serializer = $this->createSerializer(); + $input = ['foo' => new SerializerTestObject()]; + + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame(['foo' => 'Object ' . SerializerTestObject::class], $result); + } + + public function testObjectInArraySerializeAll(): void + { + $serializer = $this->createSerializer(); + $serializer->setSerializeAllObjects(true); + $input = ['foo' => new SerializerTestObject()]; + + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame(['foo' => ['key' => 'value']], $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testBrokenEncoding(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + foreach (['7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90'] as $key) { + $input = pack('H*', $key); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertInternalType('string', $result); + + if (\function_exists('mb_detect_encoding')) { + $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); + } + } + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testLongString(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + foreach ([100, 1000, 1010, 1024, 1050, 1100, 10000] as $length) { + $input = str_repeat('x', $length); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(1024, \strlen($result)); + } + } + + public function testLongStringWithOverwrittenMessageLength(): void + { + $serializer = $this->createSerializer(); + $serializer->setMessageLimit(500); + + foreach ([100, 490, 499, 500, 501, 1000, 10000] as $length) { + $input = str_repeat('x', $length); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(500, \strlen($result)); + } + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testSerializeValueResource(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); + + $this->assertNotFalse($filename, 'Temp file creation failed'); + + $resource = fopen($filename, 'wb'); + + $result = $this->invokeSerialization($serializer, $resource); + + $this->assertInternalType('string', $result); + $this->assertSame('Resource stream', $result); + } + + public function testSetAllObjectSerialize(): void + { + $serializer = $this->createSerializer(); + $serializer->setSerializeAllObjects(true); + + $this->assertTrue($serializer->getSerializeAllObjects()); + + $serializer->setSerializeAllObjects(false); + + $this->assertFalse($serializer->getSerializeAllObjects()); + } + + public function testClippingUTF8Characters(): void + { + if (!\extension_loaded('mbstring')) { + $this->markTestSkipped('mbstring extension is not enabled.'); + } + + $serializer = static::createSerializer(); + $serializer->setMessageLimit(19); + + $clipped = $this->invokeSerialization($serializer, 'Прекратите надеÑтьÑÑ, что ваши пользователи будут Ñообщать об ошибках'); + + $this->assertSame('Прекратит {clipped}', $clipped); + $this->assertNotNull(json_encode($clipped)); + $this->assertSame(JSON_ERROR_NONE, json_last_error()); + } + + public function serializableCallableProvider(): array + { + $filename = \dirname(__DIR__) . '/resources/callable_without_namespace.inc'; + $this->assertFileExists($filename); + $callableWithoutNamespaces = require $filename; + + return [ + [ + 'callable' => function (array $param1) { + return $param1 * 2; + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array param1]', + ], + [ + 'callable' => function ($param1a) { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [mixed|null param1a]', + ], + [ + 'callable' => function (callable $param1c) { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [callable param1c]', + ], + [ + 'callable' => function (\stdClass $param1d) { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass param1d]', + ], + [ + 'callable' => function (\stdClass $param1e = null) { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass|null [param1e]]', + ], + [ + 'callable' => function (array &$param1f) { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array ¶m1f]', + ], + [ + 'callable' => function (array &$param1g = null) { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array|null [¶m1g]]', + ], + [ + 'callable' => [$this, 'serializableCallableProvider'], + 'expected' => 'Callable array ' . __CLASS__ . '::serializableCallableProvider []', + ], + [ + 'callable' => [TestCase::class, 'setUpBeforeClass'], + 'expected' => 'Callable PHPUnit\\Framework\\TestCase::setUpBeforeClass []', + ], + [ + 'callable' => [$this, 'setUpBeforeClass'], + 'expected' => 'Callable ' . __CLASS__ . '::setUpBeforeClass []', + ], + [ + 'callable' => [self::class, 'setUpBeforeClass'], + 'expected' => 'Callable ' . __CLASS__ . '::setUpBeforeClass []', + ], + [ + 'callable' => [SerializerTestObject::class, 'testy'], + 'expected' => 'Callable void ' . SerializerTestObject::class . '::testy []', + ], + [ + 'callable' => function (int $param1_70a) { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [int param1_70a]', + ], + [ + 'callable' => function (&$param): int { + return (int) $param; + }, + 'expected' => 'Lambda int ' . __NAMESPACE__ . '\\{closure} [mixed|null ¶m]', + ], + [ + 'callable' => function (int $param): ?int { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda int ' . __NAMESPACE__ . '\\{closure} [int param]', + ], + [ + 'callable' => function (?int $param1_70b) { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [int|null param1_70b]', + ], + [ + 'callable' => function (?int $param1_70c): void { + throw new \Exception('Don\'t even think about invoke me'); + }, + 'expected' => 'Lambda void ' . __NAMESPACE__ . '\\{closure} [int|null param1_70c]', + ], + [ + 'callable' => $callableWithoutNamespaces, + 'expected' => 'Lambda void {closure} [int|null param1_70ns]', + ], + ]; + } + + /** + * @dataProvider serializableCallableProvider + */ + public function testSerializeCallable(callable $callable, string $expected): void + { + $serializer = $this->createSerializer(); + $actual = $this->invokeSerialization($serializer, $callable); + + $this->assertSame($expected, $actual); + + $actual = $this->invokeSerialization($serializer, [$callable]); + + $this->assertSame([$expected], $actual); + } + + /** + * @dataProvider serializationForBadStringsDataProvider + */ + public function testSerializationForBadStrings(string $string, string $expected, string $mbDetectOrder = null): void + { + $serializer = $this->createSerializer(); + + if ($mbDetectOrder) { + $serializer->setMbDetectOrder($mbDetectOrder); + } + + $this->assertSame($expected, $this->invokeSerialization($serializer, $string)); + } + + public function serializationForBadStringsDataProvider(): array + { + $utf8String = 'äöü'; + + return [ + [utf8_decode($utf8String), $utf8String, 'ISO-8859-1, ASCII, UTF-8'], + ["\xC2\xA2\xC2", "\xC2\xA2\x3F"], // ill-formed 2-byte character U+00A2 (CENT SIGN) + ]; + } + + public function testSerializationOfInvokable(): void + { + $serializer = $this->createSerializer(); + $this->assertSame('Callable bool Sentry\Tests\Serializer\Invokable::__invoke []', $this->invokeSerialization($serializer, new Invokable())); + } + + protected function invokeSerialization(AbstractSerializer $serializer, $input) + { + if ($serializer instanceof SerializerInterface) { + return $serializer->serialize($input); + } + + if ($serializer instanceof RepresentationSerializerInterface) { + return $serializer->representationSerialize($input); + } + + throw new \InvalidArgumentException('Unrecognized AbstractSerializer: ' . \get_class($serializer)); + } +} + +/** + * Class SerializerTestObject. + * + * @property mixed $keys + */ +class SerializerTestObject +{ + private $foo = 'bar'; + + public $key = 'value'; + + public static function testy(): void + { + throw new \Exception('We should not reach this'); + } +} + +class Invokable +{ + public function __invoke(): bool + { + return true; + } +} diff --git a/tests/Serializer/RepresentationSerializerTest.php b/tests/Serializer/RepresentationSerializerTest.php new file mode 100644 index 000000000..65877af21 --- /dev/null +++ b/tests/Serializer/RepresentationSerializerTest.php @@ -0,0 +1,134 @@ +createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $this->invokeSerialization($serializer, [1, 2, 3]); + + $this->assertSame(['1', '2', '3'], $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testIntsBecomeStrings(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $serializer->representationSerialize(1); + + $this->assertInternalType('string', $result); + $this->assertSame('1', $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testFloatsBecomeStrings(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $serializer->representationSerialize(1.5); + + $this->assertInternalType('string', $result); + $this->assertSame('1.5', $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testBooleansBecomeStrings(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $serializer->representationSerialize(true); + + $this->assertSame('true', $result); + + $result = $serializer->representationSerialize(false); + + $this->assertInternalType('string', $result); + $this->assertSame('false', $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testNullsBecomeString(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $serializer->representationSerialize(null); + + $this->assertInternalType('string', $result); + $this->assertSame('null', $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testSerializeRoundedFloat(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $serializer->representationSerialize((float) 1); + + $this->assertInternalType('string', $result); + $this->assertSame('1.0', $result); + + $result = $serializer->representationSerialize(floor(5 / 2)); + + $this->assertInternalType('string', $result); + $this->assertSame('2.0', $result); + + $result = $serializer->representationSerialize(floor(12345.678901234)); + + $this->assertInternalType('string', $result); + $this->assertSame('12345.0', $result); + } + + /** + * @return RepresentationSerializer + */ + protected function createSerializer(): AbstractSerializer + { + return new RepresentationSerializer(); + } +} diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php new file mode 100644 index 000000000..179acc886 --- /dev/null +++ b/tests/Serializer/SerializerTest.php @@ -0,0 +1,105 @@ +createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $this->invokeSerialization($serializer, [1, 2, 3]); + + $this->assertSame([1, 2, 3], $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testIntsAreInts(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $this->invokeSerialization($serializer, 1); + + $this->assertInternalType('integer', $result); + $this->assertSame(1, $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testFloats(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $this->invokeSerialization($serializer, 1.5); + + $this->assertInternalType('double', $result); + $this->assertSame(1.5, $result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testBooleans(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $this->invokeSerialization($serializer, true); + + $this->assertTrue($result); + + $result = $this->invokeSerialization($serializer, false); + + $this->assertFalse($result); + } + + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testNull(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $result = $this->invokeSerialization($serializer, null); + + $this->assertNull($result); + } + + /** + * @return Serializer + */ + protected function createSerializer(): AbstractSerializer + { + return new Serializer(); + } +} diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php deleted file mode 100644 index 8c8c848be..000000000 --- a/tests/SerializerTest.php +++ /dev/null @@ -1,15 +0,0 @@ -options = new Options(); - $this->serializer = new Serializer($this->options->getMbDetectOrder()); - $this->representationSerializer = new ReprSerializer($this->options->getMbDetectOrder()); + $this->serializer = new Serializer(); + $this->representationSerializer = new RepresentationSerializer(); } public function testGetFramesAndToArray(): void diff --git a/tests/resources/callable_without_namespace.inc b/tests/resources/callable_without_namespace.inc new file mode 100644 index 000000000..f2b74c1c1 --- /dev/null +++ b/tests/resources/callable_without_namespace.inc @@ -0,0 +1,5 @@ + $closure_701, - 'expected' => 'Lambda {closure} [int param1_70a]', - ], [ - 'callable' => $closure_702, - 'expected' => 'Lambda int {closure} [mixed|null ¶m]', - ], - ] -); diff --git a/tests/resources/php71_serializing.inc b/tests/resources/php71_serializing.inc deleted file mode 100644 index 5acddf87a..000000000 --- a/tests/resources/php71_serializing.inc +++ /dev/null @@ -1,36 +0,0 @@ - $closure_711, - 'expected' => 'Lambda int {closure} [int param]', - ], [ - 'callable' => $closure_712, - 'expected' => 'Lambda {closure} [int|null param1_70b]', - ], [ - 'callable' => $closure_713, - 'expected' => 'Lambda void {closure} [int|null param1_70c]', - ] - ] -); From 44727e7fa5de765352cd51536d3bc2640ca61b29 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 28 Nov 2018 21:32:20 +0100 Subject: [PATCH 0396/1161] Move all the logic that handles the recording of the breadcrumbs from the client to the Hub (#709) --- src/Client.php | 19 ---------- src/ClientInterface.php | 8 ----- src/Options.php | 16 +++++++-- src/State/Hub.php | 24 +++++++++++-- tests/ClientTest.php | 77 ----------------------------------------- tests/OptionsTest.php | 28 +++++++++++++++ tests/SdkTest.php | 10 ++---- tests/State/HubTest.php | 72 +++++++++++++++++++++++++++++++++----- 8 files changed, 128 insertions(+), 126 deletions(-) diff --git a/src/Client.php b/src/Client.php index f3cc7a77a..2086eb535 100644 --- a/src/Client.php +++ b/src/Client.php @@ -98,25 +98,6 @@ public function getOptions(): Options return $this->options; } - /** - * {@inheritdoc} - */ - public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null): void - { - $beforeBreadcrumbCallback = $this->options->getBeforeBreadcrumbCallback(); - $maxBreadcrumbs = $this->options->getMaxBreadcrumbs(); - - if ($maxBreadcrumbs <= 0) { - return; - } - - $breadcrumb = \call_user_func($beforeBreadcrumbCallback, $breadcrumb); - - if (null !== $breadcrumb && null !== $scope) { - $scope->addBreadcrumb($breadcrumb, $maxBreadcrumbs); - } - } - /** * {@inheritdoc} */ diff --git a/src/ClientInterface.php b/src/ClientInterface.php index a83e774bb..8cd6967d9 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -21,14 +21,6 @@ interface ClientInterface */ public function getOptions(): Options; - /** - * Records the given breadcrumb. - * - * @param Breadcrumb $breadcrumb The breadcrumb instance - * @param Scope|null $scope An optional scope to store this breadcrumb in - */ - public function addBreadcrumb(Breadcrumb $breadcrumb, ?Scope $scope = null): void; - /** * Logs a message. * diff --git a/src/Options.php b/src/Options.php index 3a46a1331..b885a7c5f 100644 --- a/src/Options.php +++ b/src/Options.php @@ -641,9 +641,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); - $resolver->setAllowedValues('max_breadcrumbs', function ($value) { - return $value <= self::DEFAULT_MAX_BREADCRUMBS; - }); + $resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions'])); $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); $resolver->setNormalizer('project_root', function (SymfonyOptions $options, $value) { @@ -809,4 +807,16 @@ private function validateIntegrationsOption(?array $integrations): bool return true; } + + /** + * Validates if the value of the max_breadcrumbs option is in range. + * + * @param int $value The value to validate + * + * @return bool + */ + private function validateMaxBreadcrumbsOptions(int $value): bool + { + return $value >= 0 && $value <= self::DEFAULT_MAX_BREADCRUMBS; + } } diff --git a/src/State/Hub.php b/src/State/Hub.php index feab6985f..0d5cc027a 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -247,14 +247,32 @@ public function captureLastError(): ?string * actions prior to an error or crash. * * @param Breadcrumb $breadcrumb The breadcrumb to record + * + * @return bool Whether the breadcrumb was actually added to the current scope */ - public function addBreadcrumb(Breadcrumb $breadcrumb): void + public function addBreadcrumb(Breadcrumb $breadcrumb): bool { $client = $this->getClient(); - if (null !== $client) { - $client->addBreadcrumb($breadcrumb, $this->getScope()); + if (null === $client) { + return false; } + + $options = $client->getOptions(); + $beforeBreadcrumbCallback = $options->getBeforeBreadcrumbCallback(); + $maxBreadcrumbs = $options->getMaxBreadcrumbs(); + + if ($maxBreadcrumbs <= 0) { + return false; + } + + $breadcrumb = \call_user_func($beforeBreadcrumbCallback, $breadcrumb); + + if (null !== $breadcrumb) { + $this->getScope()->addBreadcrumb($breadcrumb, $maxBreadcrumbs); + } + + return null !== $breadcrumb; } /** diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 858703271..484794d16 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -7,7 +7,6 @@ use Http\Mock\Client as MockClient; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Sentry\Breadcrumb; use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; @@ -18,7 +17,6 @@ use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Hub; -use Sentry\State\Scope; use Sentry\Tests\Fixtures\classes\CarelessException; use Sentry\Transport\TransportInterface; @@ -251,81 +249,6 @@ public function sampleRateAbsoluteDataProvider() ]; } - /** - * @dataProvider addBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsTooLowDataProvider - */ - public function testAddBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsTooLow(int $maxBreadcrumbs): void - { - $client = ClientBuilder::create(['max_breadcrumbs' => $maxBreadcrumbs])->getClient(); - $scope = new Scope(); - - $client->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'), $scope); - - $this->assertEmpty($scope->getBreadcrumbs()); - } - - public function addBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsTooLowDataProvider(): array - { - return [ - [0], - [-1], - ]; - } - - public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void - { - $client = ClientBuilder::create(['max_breadcrumbs' => 2])->getClient(); - $scope = new Scope(); - - $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'foo'); - $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'bar'); - $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'baz'); - - $client->addBreadcrumb($breadcrumb1, $scope); - $client->addBreadcrumb($breadcrumb2, $scope); - - $this->assertSame([$breadcrumb1, $breadcrumb2], $scope->getBreadcrumbs()); - - $client->addBreadcrumb($breadcrumb3, $scope); - - $this->assertSame([$breadcrumb2, $breadcrumb3], $scope->getBreadcrumbs()); - } - - public function testAddBreadcrumbDoesNothingWhenBeforeBreadcrumbCallbackReturnsNull(): void - { - $scope = new Scope(); - $client = ClientBuilder::create( - [ - 'before_breadcrumb' => function (): ?Breadcrumb { - return null; - }, - ] - )->getClient(); - - $client->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'), $scope); - - $this->assertEmpty($scope->getBreadcrumbs()); - } - - public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallback(): void - { - $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - - $scope = new Scope(); - $client = ClientBuilder::create( - [ - 'before_breadcrumb' => function () use ($breadcrumb2): ?Breadcrumb { - return $breadcrumb2; - }, - ] - )->getClient(); - - $client->addBreadcrumb($breadcrumb1, $scope); - - $this->assertSame([$breadcrumb2], $scope->getBreadcrumbs()); - } - public function testHandlingExceptionThrowingAnException() { /** @var TransportInterface|MockObject $transport */ diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index afd1da2c3..b1be791b6 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Sentry\Options; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; final class OptionsTest extends TestCase { @@ -239,4 +240,31 @@ public function excludedPathProviders() [__FILE__, __FILE__], ]; } + + /** + * @dataProvider maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider + */ + public function testMaxBreadcrumbsOptionIsValidatedCorrectly(bool $isValid, $value): void + { + if (!$isValid) { + $this->expectException(InvalidOptionsException::class); + } + + $options = new Options(['max_breadcrumbs' => $value]); + + $this->assertSame($value, $options->getMaxBreadcrumbs()); + } + + public function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array + { + return [ + [false, -1], + [true, 0], + [true, 1], + [true, Options::DEFAULT_MAX_BREADCRUMBS], + [false, Options::DEFAULT_MAX_BREADCRUMBS + 1], + [false, 'string'], + [false, '1'], + ]; + } } diff --git a/tests/SdkTest.php b/tests/SdkTest.php index 6a695c38d..4271282c4 100644 --- a/tests/SdkTest.php +++ b/tests/SdkTest.php @@ -92,15 +92,9 @@ public function testAddBreadcrumb(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('addBreadcrumb') - ->with($breadcrumb, Hub::getCurrent()->getScope()); - - Hub::getCurrent()->bindClient($client); - addBreadcrumb($breadcrumb); + + $this->assertSame([$breadcrumb], Hub::getCurrent()->getScope()->getBreadcrumbs()); } public function testWithScope(): void diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 06a31431e..931e54049 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\ClientBuilder; use Sentry\ClientInterface; use Sentry\Severity; use Sentry\State\Hub; @@ -240,17 +241,72 @@ public function testAddBreadcrumbDoesNothingIfClientIsNotBinded(): void public function testAddBreadcrumb(): void { - $scope = new Scope(); + $client = ClientBuilder::create()->getClient(); + $hub = new Hub($client); $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('addBreadcrumb') - ->with($breadcrumb, $scope); - - $hub = new Hub($client, $scope); $hub->addBreadcrumb($breadcrumb); + + $this->assertSame([$breadcrumb], $hub->getScope()->getBreadcrumbs()); + } + + public function testAddBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsZero(): void + { + $client = ClientBuilder::create(['max_breadcrumbs' => 0])->getClient(); + $hub = new Hub($client); + + $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); + + $this->assertEmpty($hub->getScope()->getBreadcrumbs()); + } + + public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void + { + $client = ClientBuilder::create(['max_breadcrumbs' => 2])->getClient(); + $hub = new Hub($client); + $scope = $hub->getScope(); + + $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'foo'); + $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'bar'); + $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'baz'); + + $hub->addBreadcrumb($breadcrumb1); + $hub->addBreadcrumb($breadcrumb2); + + $this->assertSame([$breadcrumb1, $breadcrumb2], $scope->getBreadcrumbs()); + + $hub->addBreadcrumb($breadcrumb3); + + $this->assertSame([$breadcrumb2, $breadcrumb3], $scope->getBreadcrumbs()); + } + + public function testAddBreadcrumbDoesNothingWhenBeforeBreadcrumbCallbackReturnsNull(): void + { + $callback = function (): ?Breadcrumb { + return null; + }; + $client = ClientBuilder::create(['before_breadcrumb' => $callback])->getClient(); + $hub = new Hub($client); + + $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); + + $this->assertEmpty($hub->getScope()->getBreadcrumbs()); + } + + public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallback(): void + { + $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + + $callback = function () use ($breadcrumb2): ?Breadcrumb { + return $breadcrumb2; + }; + $client = ClientBuilder::create(['before_breadcrumb' => $callback])->getClient(); + $hub = new Hub($client); + + $hub->addBreadcrumb($breadcrumb1); + + $this->assertSame([$breadcrumb2], $hub->getScope()->getBreadcrumbs()); } public function testCaptureEvent(): void From cd109c04965b7b14184dc9c99194e584d891919b Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 28 Nov 2018 21:47:59 +0100 Subject: [PATCH 0397/1161] fix: Invert in_app detection (#715) --- src/Frame.php | 2 +- src/Stacktrace.php | 2 +- tests/StacktraceTest.php | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Frame.php b/src/Frame.php index 7200d3700..be1b2fdbe 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -48,7 +48,7 @@ final class Frame implements \JsonSerializable * @var bool Flag telling whether the frame is related to the execution of * the relevant code in this stacktrace */ - private $inApp = false; + private $inApp = true; /** * @var array A mapping of variables which were available within this diff --git a/src/Stacktrace.php b/src/Stacktrace.php index b0d9e4959..92a7ddbd6 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -153,7 +153,7 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void if ($isApplicationFile && !empty($excludedAppPaths)) { foreach ($excludedAppPaths as $path) { if (0 === strpos($absoluteFilePath, $path)) { - $frame->setIsInApp($isApplicationFile); + $frame->setIsInApp(false); break; } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index e3fce31c7..1d386c6e2 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -134,8 +134,8 @@ public function testAddFrameMarksAsInApp(): void $frames = $stacktrace->getFrames(); - $this->assertTrue($frames[0]->isInApp()); - $this->assertFalse($frames[1]->isInApp()); + $this->assertFalse($frames[0]->isInApp()); + $this->assertTrue($frames[1]->isInApp()); } public function testAddFrameReadsCodeFromShortFile(): void @@ -262,7 +262,7 @@ public function testInAppWithEmptyFrame(): void $this->assertCount(1, $frames); $this->assertContainsOnlyInstancesOf(Frame::class, $frames); - $this->assertFalse($frames[0]->isInApp()); + $this->assertTrue($frames[0]->isInApp()); } public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void From 0c4c2ba71be31157dcdb4ee32705b89125de2b86 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 28 Nov 2018 21:51:56 +0100 Subject: [PATCH 0398/1161] Implement HubInterface interface and refactor the Hub class (#712) --- src/ClientBuilderInterface.php | 3 +- src/State/Hub.php | 130 +++++++--------------------- src/State/HubInterface.php | 153 +++++++++++++++++++++++++++++++++ tests/State/HubTest.php | 56 +++--------- 4 files changed, 198 insertions(+), 144 deletions(-) create mode 100644 src/State/HubInterface.php diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index dd7424cd9..2cecddd31 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -101,8 +101,7 @@ public function setSerializer(SerializerInterface $serializer); /** * Sets a representation serializer instance to be injected as a dependency of the client. * - * @param RepresentationSerializerInterface $representationSerializer - * The representation serializer, used to serialize function + * @param RepresentationSerializerInterface $representationSerializer The representation serializer, used to serialize function * arguments in stack traces, to have string representation * of non-string values * diff --git a/src/State/Hub.php b/src/State/Hub.php index 0d5cc027a..1b7b58041 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -10,10 +10,9 @@ use Sentry\Severity; /** - * This class is responsible for maintaining a stack of pairs of clients and - * scopes. It is the main entry point to talk with the Sentry client. + * This class is a basic implementation of the {@see HubInterface} interface. */ -final class Hub +final class Hub implements HubInterface { /** * @var Layer[] The stack of client/scope pairs @@ -26,9 +25,7 @@ final class Hub private $lastEventId; /** - * Constructor. - * - * @var Hub + * @var HubInterface The hub that is set as the current one */ private static $currentHub; @@ -48,9 +45,7 @@ public function __construct(?ClientInterface $client = null, ?Scope $scope = nul } /** - * Gets the client binded to the top of the stack. - * - * @return ClientInterface|null + * {@inheritdoc} */ public function getClient(): ?ClientInterface { @@ -58,9 +53,7 @@ public function getClient(): ?ClientInterface } /** - * Gets the scope binded to the top of the stack. - * - * @return Scope + * {@inheritdoc} */ public function getScope(): Scope { @@ -68,29 +61,7 @@ public function getScope(): Scope } /** - * Gets the stack of clients and scopes. - * - * @return Layer[] - */ - public function getStack(): array - { - return $this->stack; - } - - /** - * Gets the topmost client/layer pair in the stack. - * - * @return Layer - */ - public function getStackTop(): Layer - { - return $this->stack[\count($this->stack) - 1]; - } - - /** - * Gets the ID of the last captured event. - * - * @return null|string + * {@inheritdoc} */ public function getLastEventId(): ?string { @@ -98,13 +69,7 @@ public function getLastEventId(): ?string } /** - * Creates a new scope to store context information that will be layered on - * top of the current one. It is isolated, i.e. all breadcrumbs and context - * information added to this scope will be removed once the scope ends. Be - * sure to always remove this scope with {@see Hub::popScope} when the - * operation finishes or throws. - * - * @return Scope + * {@inheritdoc} */ public function pushScope(): Scope { @@ -116,11 +81,7 @@ public function pushScope(): Scope } /** - * Removes a previously pushed scope from the stack. This restores the state - * before the scope was pushed. All breadcrumbs and context information added - * since the last call to {@see Hub::pushScope} are discarded. - * - * @return bool + * {@inheritdoc} */ public function popScope(): bool { @@ -132,10 +93,7 @@ public function popScope(): bool } /** - * Creates a new scope with and executes the given operation within. The scope - * is automatically removed once the operation finishes or throws. - * - * @param callable $callback The callback to be executed + * {@inheritdoc} */ public function withScope(callable $callback): void { @@ -149,10 +107,7 @@ public function withScope(callable $callback): void } /** - * Calls the given callback passing to it the current scope so that any - * operation can be run within its context. - * - * @param callable $callback The callback to be executed + * {@inheritdoc} */ public function configureScope(callable $callback): void { @@ -160,9 +115,7 @@ public function configureScope(callable $callback): void } /** - * Binds the given client to the current scope. - * - * @param ClientInterface $client The client + * {@inheritdoc} */ public function bindClient(ClientInterface $client): void { @@ -171,12 +124,7 @@ public function bindClient(ClientInterface $client): void } /** - * Captures a message event and sends it to Sentry. - * - * @param string $message The message - * @param Severity $level The severity level of the message - * - * @return null|string + * {@inheritdoc} */ public function captureMessage(string $message, ?Severity $level = null): ?string { @@ -190,11 +138,7 @@ public function captureMessage(string $message, ?Severity $level = null): ?strin } /** - * Captures an exception event and sends it to Sentry. - * - * @param \Throwable $exception The exception - * - * @return null|string + * {@inheritdoc} */ public function captureException(\Throwable $exception): ?string { @@ -208,11 +152,7 @@ public function captureException(\Throwable $exception): ?string } /** - * Captures a new event using the provided data. - * - * @param array $payload The data of the event being captured - * - * @return null|string + * {@inheritdoc} */ public function captureEvent(array $payload): ?string { @@ -226,9 +166,7 @@ public function captureEvent(array $payload): ?string } /** - * Captures an event that logs the last occurred error. - * - * @return null|string + * {@inheritdoc} */ public function captureLastError(): ?string { @@ -242,13 +180,7 @@ public function captureLastError(): ?string } /** - * Records a new breadcrumb which will be attached to future events. They - * will be added to subsequent events to provide more context on user's - * actions prior to an error or crash. - * - * @param Breadcrumb $breadcrumb The breadcrumb to record - * - * @return bool Whether the breadcrumb was actually added to the current scope + * {@inheritdoc} */ public function addBreadcrumb(Breadcrumb $breadcrumb): bool { @@ -276,11 +208,9 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool } /** - * Returns the current global Hub. - * - * @return Hub + * {@inheritdoc} */ - public static function getCurrent(): self + public static function getCurrent(): HubInterface { if (null === self::$currentHub) { self::$currentHub = new self(); @@ -290,13 +220,9 @@ public static function getCurrent(): self } /** - * Sets the Hub as the current. - * - * @param self $hub - * - * @return Hub + * {@inheritdoc} */ - public static function setCurrent(self $hub): self + public static function setCurrent(HubInterface $hub): HubInterface { self::$currentHub = $hub; @@ -304,11 +230,7 @@ public static function setCurrent(self $hub): self } /** - * Gets the integration whose FQCN matches the given one if it's available on the current client. - * - * @param string $className The FQCN of the integration - * - * @return null|IntegrationInterface + * {@inheritdoc} */ public function getIntegration(string $className): ?IntegrationInterface { @@ -319,4 +241,14 @@ public function getIntegration(string $className): ?IntegrationInterface return null; } + + /** + * Gets the topmost client/layer pair in the stack. + * + * @return Layer + */ + private function getStackTop(): Layer + { + return $this->stack[\count($this->stack) - 1]; + } } diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php new file mode 100644 index 000000000..ef627553f --- /dev/null +++ b/src/State/HubInterface.php @@ -0,0 +1,153 @@ +assertSame($scope, $hub->getScope()); } - public function testGetStack(): void - { - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $scope = new Scope(); - $hub = new Hub($client, $scope); - - $stack = $hub->getStack(); - - $this->assertCount(1, $stack); - $this->assertSame($client, $stack[0]->getClient()); - $this->assertSame($scope, $stack[0]->getScope()); - } - - public function testGetStackTop(): void - { - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $scope = new Scope(); - $hub = new Hub($client, $scope); - - $stackTop = $hub->getStackTop(); - - $this->assertSame($client, $stackTop->getClient()); - $this->assertSame($scope, $stackTop->getScope()); - - $scope = $hub->pushScope(); - - $stackTop = $hub->getStackTop(); - - $this->assertSame($client, $stackTop->getClient()); - $this->assertSame($scope, $stackTop->getScope()); - } - public function testGetLastEventId(): void { /** @var ClientInterface|MockObject $client */ @@ -91,35 +57,39 @@ public function testPushScope(): void { $hub = new Hub($this->createMock(ClientInterface::class)); - $this->assertCount(1, $hub->getStack()); - $scope1 = $hub->getScope(); - $scope2 = $hub->pushScope(); + $client1 = $hub->getClient(); - $layers = $hub->getStack(); + $scope2 = $hub->pushScope(); + $client2 = $hub->getClient(); - $this->assertCount(2, $layers); $this->assertNotSame($scope1, $scope2); - $this->assertSame($scope1, $layers[0]->getScope()); - $this->assertSame($scope2, $layers[1]->getScope()); + $this->assertSame($scope2, $hub->getScope()); + $this->assertSame($client1, $client2); + $this->assertSame($client1, $hub->getClient()); } public function testPopScope(): void { $hub = new Hub($this->createMock(ClientInterface::class)); - $this->assertCount(1, $hub->getStack()); - $scope1 = $hub->getScope(); + $client = $hub->getClient(); + $scope2 = $hub->pushScope(); $this->assertSame($scope2, $hub->getScope()); + $this->assertSame($client, $hub->getClient()); $this->assertTrue($hub->popScope()); + $this->assertSame($scope1, $hub->getScope()); + $this->assertSame($client, $hub->getClient()); $this->assertFalse($hub->popScope()); + $this->assertSame($scope1, $hub->getScope()); + $this->assertSame($client, $hub->getClient()); } public function testWithScope(): void From 58fb5fe40e450e44509c41b957898b1f3150b925 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 5 Dec 2018 11:17:12 +0100 Subject: [PATCH 0399/1161] Add default_integrations option (#718) --- src/Client.php | 5 ++-- src/ClientBuilder.php | 16 +++++------- src/Options.php | 42 ++++++++++++++++++++++-------- tests/ClientBuilderTest.php | 52 +++++++++++++++++++++++++++++++++++++ tests/ClientTest.php | 2 +- tests/OptionsTest.php | 2 ++ 6 files changed, 94 insertions(+), 25 deletions(-) diff --git a/src/Client.php b/src/Client.php index 2086eb535..05d9cc777 100644 --- a/src/Client.php +++ b/src/Client.php @@ -79,13 +79,12 @@ class Client implements ClientInterface * @param TransportInterface $transport The transport * @param SerializerInterface $serializer The serializer used for events * @param RepresentationSerializerInterface $representationSerializer The representation serializer to be used with stacktrace frames - * @param IntegrationInterface[] $integrations The integrations used by the client */ - public function __construct(Options $options, TransportInterface $transport, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer, array $integrations = []) + public function __construct(Options $options, TransportInterface $transport, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer) { $this->options = $options; $this->transport = $transport; - $this->integrations = Handler::setupIntegrations($integrations); + $this->integrations = Handler::setupIntegrations($options->getIntegrations()); $this->serializer = $serializer; $this->representationSerializer = $representationSerializer; } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index c9e57272f..aa741d44c 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -20,7 +20,6 @@ use Http\Message\UriFactory; use Sentry\HttpClient\Authentication\SentryAuth; use Sentry\Integration\ErrorHandlerIntegration; -use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; @@ -64,6 +63,8 @@ * @method setTags(string[] $tags) * @method bool shouldSendDefaultPii() * @method setSendDefaultPii(bool $enable) + * @method bool hasDefaultIntegrations() + * @method setDefaultIntegrations(bool $enable) */ final class ClientBuilder implements ClientBuilderInterface { @@ -107,11 +108,6 @@ final class ClientBuilder implements ClientBuilderInterface */ private $representationSerializer; - /** - * @var IntegrationInterface[] List of default integrations - */ - private $integrations = []; - /** * Class constructor. * @@ -121,11 +117,11 @@ public function __construct(array $options = []) { $this->options = new Options($options); - if (null !== $this->options->getIntegrations()) { - $this->integrations = \array_merge([ + if ($this->options->hasDefaultIntegrations()) { + $this->options->setIntegrations(\array_merge([ new ErrorHandlerIntegration(), new RequestIntegration($this->options), - ], $this->options->getIntegrations()); + ], $this->options->getIntegrations())); } } @@ -235,7 +231,7 @@ public function getClient(): ClientInterface $this->serializer = $this->serializer ?? new Serializer(); $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer(); - return new Client($this->options, $this->transport, $this->serializer, $this->representationSerializer, $this->integrations); + return new Client($this->options, $this->transport, $this->serializer, $this->representationSerializer); } /** diff --git a/src/Options.php b/src/Options.php index b885a7c5f..db8234af8 100644 --- a/src/Options.php +++ b/src/Options.php @@ -534,9 +534,9 @@ public function setBeforeBreadcrumbCallback(callable $callback): void /** * Set integrations that will be used by the created client. * - * @param null|IntegrationInterface[] $integrations The integrations + * @param IntegrationInterface[] $integrations The integrations */ - public function setIntegrations(?array $integrations): void + public function setIntegrations(array $integrations): void { $options = array_merge($this->options, ['integrations' => $integrations]); @@ -546,9 +546,9 @@ public function setIntegrations(?array $integrations): void /** * Returns all configured integrations that will be used by the Client. * - * @return null|IntegrationInterface[] + * @return IntegrationInterface[] */ - public function getIntegrations(): ?array + public function getIntegrations(): array { return $this->options['integrations']; } @@ -575,6 +575,28 @@ public function setSendDefaultPii(bool $enable): void $this->options = $this->resolver->resolve($options); } + /** + * Returns whether the default integrations are enabled. + * + * @return bool + */ + public function hasDefaultIntegrations(): bool + { + return $this->options['default_integrations']; + } + + /** + * Sets whether the default integrations are enabled. + * + * @param bool $enable Flag indicating whether the default integrations should be enabled + */ + public function setDefaultIntegrations(bool $enable): void + { + $options = array_merge($this->options, ['default_integrations' => $enable]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -587,6 +609,7 @@ private function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'integrations' => [], + 'default_integrations' => true, 'send_attempts' => 6, 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'serialize_all_object' => false, @@ -636,8 +659,9 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('error_types', ['int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); $resolver->setAllowedTypes('before_breadcrumb', ['callable']); - $resolver->setAllowedTypes('integrations', ['null', 'array']); + $resolver->setAllowedTypes('integrations', 'array'); $resolver->setAllowedTypes('send_default_pii', 'bool'); + $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); @@ -789,16 +813,12 @@ private function validateDsnOption(?string $dsn): bool * Validates that the elements of this option are all class instances that * implements the {@see IntegrationInterface} interface. * - * @param array|null $integrations The value to validate + * @param array $integrations The value to validate * * @return bool */ - private function validateIntegrationsOption(?array $integrations): bool + private function validateIntegrationsOption(array $integrations): bool { - if (null === $integrations) { - return true; - } - foreach ($integrations as $integration) { if (!$integration instanceof IntegrationInterface) { return false; diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 36a45ba4b..ad3a5ba95 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -14,6 +14,9 @@ use Psr\Http\Message\RequestInterface; use Sentry\Client; use Sentry\ClientBuilder; +use Sentry\Integration\ErrorHandlerIntegration; +use Sentry\Integration\IntegrationInterface; +use Sentry\Integration\RequestIntegration; use Sentry\Options; use Sentry\Transport\HttpTransport; use Sentry\Transport\NullTransport; @@ -151,6 +154,48 @@ public function testGetClient(): void $this->assertAttributeSame($this->getObjectAttribute($clientBuilder, 'httpClient'), 'client', $httpClientPlugin); } + /** + * @dataProvider integrationsAreAddedToClientCorrectlyDataProvider + */ + public function testIntegrationsAreAddedToClientCorrectly(bool $defaultIntegrations, array $integrations, array $expectedIntegrations): void + { + $clientBuilder = new ClientBuilder([ + 'default_integrations' => $defaultIntegrations, + 'integrations' => $integrations, + ]); + + $client = $clientBuilder->getClient(); + $actualIntegrationsClassNames = array_map('get_class', $client->getOptions()->getIntegrations()); + + $this->assertEquals($expectedIntegrations, $actualIntegrationsClassNames, '', 0, 10, true); + } + + public function integrationsAreAddedToClientCorrectlyDataProvider(): array + { + return [ + [ + false, + [], + [], + ], + [ + false, + [new StubIntegration()], + [StubIntegration::class], + ], + [ + true, + [], + [RequestIntegration::class, ErrorHandlerIntegration::class], + ], + [ + true, + [new StubIntegration()], + [RequestIntegration::class, ErrorHandlerIntegration::class, StubIntegration::class], + ], + ]; + } + /** * @expectedException \BadMethodCallException * @expectedExceptionMessage The method named "methodThatDoesNotExists" does not exists. @@ -231,6 +276,13 @@ public function getClientTogglesCompressionPluginInHttpClientDataProvider(): arr } } +final class StubIntegration implements IntegrationInterface +{ + public function setupOnce(): void + { + } +} + final class PluginStub1 implements Plugin { public function handleRequest(RequestInterface $request, callable $next, callable $first) diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 484794d16..6bfc3acb4 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -264,7 +264,7 @@ public function testHandlingExceptionThrowingAnException() return true; })); - $client = new Client(new Options(), $transport, $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class), []); + $client = new Client(new Options(), $transport, $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class)); Hub::getCurrent()->bindClient($client); $client->captureException($this->createCarelessExceptionWithStacktrace(), Hub::getCurrent()->getScope()); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index b1be791b6..791d40d21 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -55,6 +55,8 @@ public function optionsDataProvider(): array ['max_breadcrumbs', 50, 'getMaxBreadcrumbs', 'setMaxBreadcrumbs'], ['before_send', function () {}, 'getBeforeSendCallback', 'setBeforeSendCallback'], ['before_breadcrumb', function () {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback'], + ['send_default_pii', true, 'shouldSendDefaultPii', 'setSendDefaultPii'], + ['default_integrations', false, 'hasDefaultIntegrations', 'setDefaultIntegrations'], ]; } From 11380811fdeef96010d0ec754e664f8e0475821f Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 7 Dec 2018 17:39:19 +0100 Subject: [PATCH 0400/1161] feat: Remove docs from repo (#721) --- .gitmodules | 3 - docs/Makefile | 153 ---------------- docs/_sentryext | 1 - docs/conf.py | 247 ------------------------- docs/configuration.rst | 238 ------------------------ docs/error_handling.rst | 91 ---------- docs/index.rst | 117 ------------ docs/integrations/index.rst | 9 - docs/integrations/laravel.rst | 319 --------------------------------- docs/integrations/monolog.rst | 54 ------ docs/integrations/symfony2.rst | 42 ----- docs/make.bat | 190 -------------------- docs/middleware.rst | 137 -------------- docs/quickstart.rst | 122 ------------- docs/sentry-doc-config.json | 41 ----- docs/transport.rst | 86 --------- 16 files changed, 1850 deletions(-) delete mode 100644 docs/Makefile delete mode 160000 docs/_sentryext delete mode 100644 docs/conf.py delete mode 100644 docs/configuration.rst delete mode 100644 docs/error_handling.rst delete mode 100644 docs/index.rst delete mode 100644 docs/integrations/index.rst delete mode 100644 docs/integrations/laravel.rst delete mode 100644 docs/integrations/monolog.rst delete mode 100644 docs/integrations/symfony2.rst delete mode 100644 docs/make.bat delete mode 100644 docs/middleware.rst delete mode 100644 docs/quickstart.rst delete mode 100644 docs/sentry-doc-config.json delete mode 100644 docs/transport.rst diff --git a/.gitmodules b/.gitmodules index 1e6464ab9..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "docs/_sentryext"] - path = docs/_sentryext - url = https://github.com/getsentry/sentry-doc-support diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 60f8b8439..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/raven-js.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/raven-js.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/raven-js" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/raven-js" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/_sentryext b/docs/_sentryext deleted file mode 160000 index c523fdd0c..000000000 --- a/docs/_sentryext +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c523fdd0cb366fed58df9be098cfc6eca572f7b2 diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 04ee267bd..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,247 +0,0 @@ -# -*- coding: utf-8 -*- -# -# sentry-php documentation build configuration file, created by -# sphinx-quickstart on Mon Jan 21 21:04:27 2013. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import re, sys, os, datetime - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'sentry-php' -copyright = u'%s, Functional Software Inc.' % datetime.date.today().year - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# - -with open('../lib/Raven/Client.php') as f: - release = re.search('const VERSION = \'(.*?)\'', f.read()).group(1) - version = '.'.join(release.split('.', 2)[:-1]) - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'classic' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'sentry-phpdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'sentry-php.tex', u'sentry-php Documentation', - u'Functional Software Inc.', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'sentry-php', u'sentry-php Documentation', - [u'Functional Software Inc.'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'sentry-php', u'sentry-php Documentation', - u'Functional Software Inc.', 'sentry-php', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -if os.environ.get('SENTRY_FEDERATED_DOCS') != '1': - sys.path.insert(0, os.path.abspath('_sentryext')) - import sentryext - sentryext.activate() diff --git a/docs/configuration.rst b/docs/configuration.rst deleted file mode 100644 index 5fc8eeb88..000000000 --- a/docs/configuration.rst +++ /dev/null @@ -1,238 +0,0 @@ -Configuration options -##################### - -The Raven client can be configured through a bunch of configuration options. -Some of them are automatically populated by environment variables when a new -instance of the ``Configuration`` class is created. The following is an exhaustive -list of them with a description of what they do and some example values. - -Send attempts -============= - -The number of attempts that should should be made to send an event before erroring -and dropping it from the queue. - -.. code-block:: php - - $configuration = new Configuration(['send_attempts' => 1]); - $configuration->setSendAttempts(1); - -By default this option is set to 6. - -Server -====== - -The DSN of the Sentry server the authenticated user is bound to. - -.. code-block:: php - - // Before Sentry 9 the private DSN must be used - $configuration = new Configuration(['server' => 'http://public:private@example.com/1']); - - // After Sentry 9 the public DSN must be used - $configuration = new Configuration(['server' => 'http://public@example.com/1']); - -By default this option is borrowed from the ``SENTRY_DSN`` environment variable, -if it exists. A value of ``null`` disables completely the sending of any event. -Since Sentry 9 the public DSN must be used instead of the private one, which -is deprecated and whose support will be removed in future releases of the server. - -Server name -=========== - -The name of the server the SDK is running on (it's usually the hostname). - -.. code-block:: php - - $configuration = new Configuration(['server_name' => 'www.my-site.com']); - $configuration->setServerName('www.my-site.com'); - -By default this option is set to the hostname of the server the SDK is running -on retrieved from a call to the ``gethostname()`` method. - -Release -======= - -The release tag to be passed with every event sent to Sentry. This permits to -track in which versions of your application each error happens. - -.. code-block:: php - - $configuration = new Configuration(['release' => '1.2.3']); - $configuration->setRelease('1.2.3'); - -Logger -====== - -The name of the logger which creates the events. - -.. code-block:: php - - $configuration = new Configuration(['logger' => 'foo']); - $configuration->setLogger('foo'); - -By default this option is set to ``php``. - -Excluded loggers -================ - -The list of logger 'progname's to exclude from breadcrumbs. - -.. code-block:: php - - $configuration = new Configuration(['excluded_loggers' => ['foo']); - $configuration->setExcludedLoggers(['foo']); - -Project root -============ - -The root of the project source code. As Sentry is able to distinguish project -files from third-parties ones (e.g. vendors), this option can be configured to -mark the directory containing all the source code of the application. - -.. code-block:: php - - $configuration = new Configuration(['project_root' => 'project-folder/src/']); - $configuration->setProjectRoot('project-folder/src/'); - -For example, assuming that the directory structure shown below exists, marking -the project root as ``project-folder/src/`` means that every file inside that -directory that is part of a stacktrace frame will be marked as "application -code". - -.. code-block:: - - project-folder/ - ├── vendor/ - ├── foo/ - ├── src/ - ├── bar/ <-- these are going to be marked as application files - -Current environment -=================== - -The name of the current environment. There can be multiple environments per -application, and each event belongs to one of them. - -.. code-block:: php - - $configuration = new Configuration(['current_environment' => 'development']); - $configuration->setCurrentEnvironment('development'); - -Environments -============ - -The environments are a feature that allows events to be easily filtered in -Sentry. An application can have multiple environments, but just one is active -at the same time. This option let you configure the environments names: if -the current environment is not whitelisted here, any event tagged with it -won't be sent. If no environment is listed here, the behavior of checking -the whitelist won't be considered and any event will be sent regardeless. - -.. code-block:: php - - $configuration = new Configuration(['environments' => ['development', 'production']]); - $configuration->setEnvironments(['development', 'production']); - -By default this option is set to an empty array. - -Encoding -======== - -This option sets the encoding type of the requests sent to the Sentry server. -There are two supported values: ``json`` and ``gzip``. The first one sends data -using plain JSON, so the request size will be bigger. The second one compresses -the request using GZIP, which can use more CPU power but will reduce the size -of the payload. - -.. code-block:: php - - $configuration = new Configuration(['encoding' => 'json']); - $configuration->setEncoding('json'); - -By default this option is set to ``gzip``. - -Context lines -============= - -This option sets the number of lines of code context to capture. If ``null`` is -set as the value, no source code lines will be added to each stacktrace frame. - -.. code-block:: php - - $configuration = new Configuration(['context_lines' => 3]); - $configuration->setContextLines(3); - -By default this option is set to 3. - -Stacktrace logging -================== - -This option sets whether the stacktrace of the captured errors should be -automatically captured or not. - -.. code-block:: php - - $configuration = new Configuration(['attach_stacktrace' => true]); - $configuration->setAutoLogStacks(true); - -By default this option is set to ``true``. - -Excluded exceptions -=================== - -Sometimes you may want to skip capturing certain exceptions. This option sets -the FCQN of the classes of the exceptions that you don't want to capture. The -check is done using the ``instanceof`` operator against each item of the array -and if at least one of them passes the event will be discarded. - -.. code-block:: php - - $configuration = new Configuration(['excluded_exceptions' => ['RuntimeException']); - $configuration->setExcludedExceptions(['RuntimeException']); - -Sample rate -=========== - -The sampling factor to apply to events. A value of 0 will deny sending any -events, and a value of 1 will send 100% of events. - -.. code-block:: php - - $configuration = new Configuration(['sample_rate' => 1]); - $configuration->setSampleRate(1); - -By default this option is set to 1, so all events will be sent regardeless. - -Excluded application paths -========================== - -This option configures the list of paths to exclude from the `app_path` detection. - -.. code-block:: php - - $configuration = new Configuration(['excluded_app_paths' => ['foo']); - $configuration->setExcludedProjectPaths(['foo']); - - -Prefixes -======== - -This option sets the list of prefixes which should be stripped from the filenames -to create relative paths. - -.. code-block:: php - - $configuration = new Configuration(['prefixes' => ['/var/www']); - $configuration->setPrefixes(['/var/www']); - -Should capture callback -======================= - -This option sets a callable that will be called before sending an event and is -the last place where you can stop it from being sent. - -.. code-block:: php - - $configuration = new Configuration(['should_capture' => function () { return true }]); - $configuration->setShouldCapture(function () { return true }); diff --git a/docs/error_handling.rst b/docs/error_handling.rst deleted file mode 100644 index 3d0d0c373..000000000 --- a/docs/error_handling.rst +++ /dev/null @@ -1,91 +0,0 @@ -Error handling -############## - -To capture unhandled errors and exceptions you have to manually register the -error handler and associate it to an instance of the client. The easiest way -to do it is to simply call the ``ErrorHandler::register`` method and pass the -client instance as first argument. - -.. code-block:: php - - use Sentry\ErrorHandler; - - // Initialize the client - - ErrorHandler::register($client); - -By default the error handler reserves 10 megabytes of memory to handle fatal -errors. You can customize the amount by specifying it in bytes as the second -argument. For example, the code below will reserve 20 megabytes of memory. - -.. code-block:: php - - use Sentry\ErrorHandler; - - // Initialize the client - - ErrorHandler::register($client, 20480); - -For some frameworks or projects there are specific integrations provided both -officially and by third party users that automatically register the error -handlers. In that case please refer to their documentation. - -Capture errors -============== - -The error handler can be customized to set which error types should be captured -and sent to the Sentry server: you may want to report all the errors but capture -only some of them. For example, the code below will capture all errors except -``E_DEPRECATED`` and ``E_USER_DEPRECATED``. Note that the ``error_reporting`` -PHP ini option will be respected and any other handler that was present before -the Sentry error handler was registered will still be called regardeless. - -.. code-block:: php - - use Sentry\ErrorHandler; - - // Initialize the client - - $errorHandler = ErrorHandler::register($client); - $errorHandler->captureAt(E_ALL &~ E_DEPRECATED &~ E_USER_DEPRECATED, true); - -While calling the ``ErrorHandler::captureAt`` method you can decide whether the -new mask will replace entirely the previous one or not by changing the value of -the second argument. For example, suppose that you first disable the capturing -of the ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` error types and sometime later -you want to re-enable only the first type of errors. In this case you have two -ways to do the same thing: know in advance the old mask and replace it like you -did in the example above or set to ``false`` the ``$replace`` argument (this is -the default value) and the new value will be appended to the existing mask: - -.. code-block:: php - - use Sentry\ErrorHandler; - - // Initialize the client - - $errorHandler = ErrorHandler::register($client); - $errorHandler->captureAt(E_ALL &~ E_DEPRECATED &~ E_USER_DEPRECATED, true); - - // some time later you decide to re-enable capturing of E_DEPRECATED errors - - $errorHandler->captureAt(E_DEPRECATED, false); - -Capture breadcrumbs -=================== - -Sentry supports logging breadcrumbs, which are a set of steps that led to an -event. By default the client logs the URL of the page being visited as the -first breadcrumb (if the context in which the app is running is a web request). -To automate the capturing of errors as breadcrumbs (e.g. you may want to automate -logging of warnings leading to an event) you can register an additional error -handler the same way as before. - -.. code-block:: php - - use Sentry\BreadcrumbErrorHandler; - - // Initialize the client - - $errorHandler = BreadcrumbErrorHandler::register($client); - $errorHandler->captureAt(E_WARNING | E_USER_WARNING, true); diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 58b996c02..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,117 +0,0 @@ -.. sentry:edition:: self - - Sentry-PHP - ========== - -.. sentry:edition:: on-premise, hosted - - .. class:: platform-php - - PHP - === - -The PHP SDK for Sentry supports PHP 5.6 and higher and is available as a BSD -licensed Open Source library. - -Installation ------------- -To install the SDK you will need to be using Composer_ in your project. To install -it please see the `docs `_. - -Sentry PHP is not tied to any specific library that sends HTTP messages. Instead, -it uses Httplug_ to let users choose whichever PSR-7 implementation and HTTP client -they want to use. - -If you just want to get started quickly you should run the following command: - -.. code-block:: bash - - php composer.phar require sentry/sentry php-http/curl-client guzzlehttp/psr7 - -This will install the library itself along with an HTTP client adapter that uses -cURL as transport method (provided by Httplug) and a PSR-7 implementation -(provided by Guzzle). You do not have to use those packages if you do not want to. -The SDK does not care about which transport method you want to use because it's -an implementation detail of your application. You may use any package that provides -`php-http/async-client-implementation`_ and `http-message-implementation`_. - -If you want to use Guzzle as underlying HTTP client, you just need to run the -following command to install the adapter and Guzzle itself: - -.. code-block:: bash - - php composer.phar require php-http/guzzle6-adapter - -You can then use the client builder to create a Raven client instance that will -use the configured HTTP client based on Guzzle. The code looks like the one -below: - -.. code-block:: php - - use Http\Adapter\Guzzle6\Client as GuzzleClientAdapter; - use Sentry\ClientBuilder; - - require 'vendor/autoload.php'; - - // The client will use a Guzzle client to send the HTTP requests - $client = ClientBuilder::create(['server' => 'http://___PUBLIC_DSN___@example.com/1']) - ->setHttpClient(new GuzzleClientAdapter()) - ->getClient(); - -If you want to have more control on the Guzzle HTTP client you can create the -instance yourself and tell the adapter to use it instead of creating one -on-the-fly. Please refer to the `HTTPlug Guzzle 6 Adapter documentation`_ for -instructions on how to do it. - -Usage ------ - -.. code-block:: php - - use Sentry\ClientBuilder; - use Sentry\ErrorHandler; - - require 'vendor/autoload.php'; - - // Instantiate the SDK with your DSN - $client = ClientBuilder::create(['server' => 'http://___PUBLIC_DSN___@example.com/1'])->getClient(); - - // Register error handler to automatically capture errors and exceptions - ErrorHandler::register($client); - - // Capture an exception manually - $eventId = $client->captureException(new \RuntimeException('Hello World!')); - - // Give the user feedback - echo 'Sorry, there was an error!'; - echo 'Your reference ID is ' . $eventId; - -Deep Dive ---------- - -Want more? Have a look at the full documentation for more information. - -.. toctree:: - :maxdepth: 2 - :titlesonly: - - quickstart - configuration - transport - middleware - error_handling - integrations/index - -Resources: - -* `Bug Tracker `_ -* `Github Project `_ -* `Code `_ -* `Mailing List `_ -* `IRC (irc.freenode.net, #sentry) `_ - -.. _Httplug: https://github.com/php-http/httplug -.. _Composer: https://getcomposer.org -.. _php-http/async-client-implementation: https://packagist.org/providers/php-http/async-client-implementation -.. _http-message-implementation: https://packagist.org/providers/psr/http-message-implementation -.. _HTTPlug Guzzle 6 Adapter documentation: http://docs.php-http.org/en/latest/clients/guzzle6-adapter.html diff --git a/docs/integrations/index.rst b/docs/integrations/index.rst deleted file mode 100644 index 558d3e545..000000000 --- a/docs/integrations/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -Integrations -============ - -.. toctree:: - :maxdepth: 1 - - laravel - monolog - symfony2 diff --git a/docs/integrations/laravel.rst b/docs/integrations/laravel.rst deleted file mode 100644 index d750ab763..000000000 --- a/docs/integrations/laravel.rst +++ /dev/null @@ -1,319 +0,0 @@ -Laravel -======= - -Laravel is supported via a native package, `sentry-laravel `_. - -Laravel 5.x ------------ - -Install the ``sentry/sentry-laravel`` package: - -.. code-block:: bash - - $ composer require sentry/sentry-laravel - -If you're on Laravel 5.4 or earlier, you'll need to add the following to your ``config/app.php`` (for Laravel 5.5+ these will be auto-discovered by Laravel): - -.. code-block:: php - - 'providers' => array( - // ... - Sentry\SentryLaravel\SentryLaravelServiceProvider::class, - ) - - 'aliases' => array( - // ... - 'Sentry' => Sentry\SentryLaravel\SentryFacade::class, - ) - - -Add Sentry reporting to ``App/Exceptions/Handler.php``: - -.. code-block:: php - - public function report(Exception $exception) - { - if (app()->bound('sentry') && $this->shouldReport($exception)) { - app('sentry')->captureException($exception); - } - - parent::report($exception); - } - -Create the Sentry configuration file (``config/sentry.php``): - -.. code-block:: bash - - $ php artisan vendor:publish --provider="Sentry\SentryLaravel\SentryLaravelServiceProvider" - - -Add your DSN to ``.env``: - -.. code-block:: bash - - SENTRY_LARAVEL_DSN=___PUBLIC_DSN___ - -Finally, if you wish to wire up User Feedback, you can do so by creating a custom -error view in `resources/views/errors/500.blade.php`. - -For Laravel 5 up to 5.4 you need to open up ``App/Exceptions/Handler.php`` and extend the -``render`` method to make sure the 500 error is rendered as a view correctly, in 5.5+ this -step is not required anymore an you can skip ahead to the next one: - -.. code-block:: php - - bound('sentry') && $this->shouldReport($exception)) { - app('sentry')->captureException($exception); - } - - parent::report($exception); - } - - public function render($request, Exception $exception) - { - // Convert all non-http exceptions to a proper 500 http exception - // if we don't do this exceptions are shown as a default template - // instead of our own view in resources/views/errors/500.blade.php - if ($this->shouldReport($exception) && !$this->isHttpException($exception) && !config('app.debug')) { - $exception = new HttpException(500, 'Whoops!'); - } - - return parent::render($request, $exception); - } - } - -Next, create ``resources/views/errors/500.blade.php``, and embed the feedback code: - -.. code-block:: html - -
-
Something went wrong.
- - @if(app()->bound('sentry') && !empty(Sentry::getLastEventID())) -
Error ID: {{ Sentry::getLastEventID() }}
- - - - - - @endif -
- -That's it! - -Laravel 4.x ------------ - -Install the ``sentry/sentry-laravel`` package: - -Laravel 4.x is supported until version 0.8.x. - -.. code-block:: bash - - $ composer require "sentry/sentry-laravel:0.8.*" - -Add the Sentry service provider and facade in ``config/app.php``: - -.. code-block:: php - - 'providers' => array( - // ... - 'Sentry\SentryLaravel\SentryLaravelServiceProvider', - ) - - 'aliases' => array( - // ... - 'Sentry' => 'Sentry\SentryLaravel\SentryFacade', - ) - -Create the Sentry configuration file (``config/sentry.php``): - -.. code-block:: php - - $ php artisan config:publish sentry/sentry-laravel - -Add your DSN to ``config/sentry.php``: - -.. code-block:: php - - '___PUBLIC_DSN___', - - // ... - ); - -If you wish to wire up Sentry anywhere outside of the standard error handlers, or -if you need to configure additional settings, you can access the Sentry instance -through ``$app``: - -.. code-block:: php - - $app['sentry']->setRelease(Git::sha()); - -Lumen 5.x ---------- - -Install the ``sentry/sentry-laravel`` package: - -.. code-block:: bash - - $ composer require sentry/sentry-laravel - -Register Sentry in ``bootstrap/app.php``: - -.. code-block:: php - - $app->register('Sentry\SentryLaravel\SentryLumenServiceProvider'); - - # Sentry must be registered before routes are included - require __DIR__ . '/../app/Http/routes.php'; - -Add Sentry reporting to ``app/Exceptions/Handler.php``: - -.. code-block:: php - - public function report(Exception $e) - { - if (app()->bound('sentry') && $this->shouldReport($e)) { - app('sentry')->captureException($e); - } - - parent::report($e); - } - -Create the Sentry configuration file (``config/sentry.php``): - -.. code-block:: php - - '___PUBLIC_DSN___', - - // capture release as git sha - // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), - ); - -Testing with Artisan --------------------- - -You can test your configuration using the provided ``artisan`` command: - -.. code-block:: bash - - $ php artisan sentry:test - [sentry] Client configuration: - -> server: https://app.getsentry.com/api/3235/store/ - -> project: 3235 - -> public_key: e9ebbd88548a441288393c457ec90441 - -> secret_key: 399aaee02d454e2ca91351f29bdc3a07 - [sentry] Generating test event - [sentry] Sending test event with ID: 5256614438cf4e0798dc9688e9545d94 - -Adding Context --------------- - -The mechanism to add context will vary depending on which version of Laravel you're using, but the general approach is the same. Find a good entry point to your application in which the context you want to add is available, ideally early in the process. - -In the following example, we'll use a middleware: - -.. code-block:: php - - namespace App\Http\Middleware; - - use Closure; - - class SentryContext - { - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * - * @return mixed - */ - public function handle($request, Closure $next) - { - if (app()->bound('sentry')) { - /** @var \Raven_Client $sentry */ - $sentry = app('sentry'); - - // Add user context - if (auth()->check()) { - $sentry->user_context([...]); - } else { - $sentry->user_context(['id' => null]); - } - - // Add tags context - $sentry->tags_context([...]); - } - - return $next($request); - } - } - -Configuration -------------- - -The following settings are available for the client: - -.. describe:: dsn - - The DSN to authenticate with Sentry. - - .. code-block:: php - - 'dsn' => '___PUBLIC_DSN___', - -.. describe:: release - - The version of your application (e.g. git SHA) - - .. code-block:: php - - 'release' => MyApp::getReleaseVersion(), - - -.. describe:: breadcrumbs.sql_bindings - - Capture bindings on SQL queries. - - Defaults to ``true``. - - .. code-block:: php - - 'breadcrumbs.sql_bindings' => false, - - -.. describe:: user_context - - Capture user_context automatically. - - Defaults to ``true``. - - .. code-block:: php - - 'user_context' => false, - diff --git a/docs/integrations/monolog.rst b/docs/integrations/monolog.rst deleted file mode 100644 index 475bcb536..000000000 --- a/docs/integrations/monolog.rst +++ /dev/null @@ -1,54 +0,0 @@ -Monolog -======= - -Capturing Errors ----------------- - -Monolog supports Sentry out of the box, so you'll just need to configure a handler: - -.. sourcecode:: php - - $client = new \Sentry\Client('___PUBLIC_DSN___'); - - $handler = new Monolog\Handler\RavenHandler($client); - $handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n")); - - $monolog->pushHandler($handler); - -Adding Context --------------- - -Capturing context can be done via a monolog processor: - -.. sourcecode:: php - - $monolog->pushProcessor(function ($record) { - // record the current user - $user = Acme::getCurrentUser(); - $record['context']['user'] = array( - 'name' => $user->getName(), - 'username' => $user->getUsername(), - 'email' => $user->getEmail(), - ); - - // Add various tags - $record['context']['tags'] = array('key' => 'value'); - - // Add various generic context - $record['extra']['key'] = 'value'; - - return $record; - }); - - -Breadcrumbs ------------ - -Sentry provides a breadcrumb handler to automatically send logs along as crumbs: - -.. sourcecode:: php - - $client = new \Sentry\Client('___PUBLIC_DSN___'); - - $handler = new \Sentry\Breadcrumbs\MonologHandler($client); - $monolog->pushHandler($handler); diff --git a/docs/integrations/symfony2.rst b/docs/integrations/symfony2.rst deleted file mode 100644 index 1f18eb13f..000000000 --- a/docs/integrations/symfony2.rst +++ /dev/null @@ -1,42 +0,0 @@ -Symfony -======= - -Symfony is supported via the `sentry-symfony `_ package as a native bundle. - -Symfony 2+ ----------- - -Install the ``sentry/sentry-symfony`` package: - -.. code-block:: bash - - $ composer require sentry/sentry-symfony - - -Enable the bundle in ``app/AppKernel.php``: - -.. code-block:: php - - ` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\raven-js.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\raven-js.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/docs/middleware.rst b/docs/middleware.rst deleted file mode 100644 index a98439380..000000000 --- a/docs/middleware.rst +++ /dev/null @@ -1,137 +0,0 @@ -Middlewares -########### - -Middlewares are an essential part of the event sending lifecycle. Each captured -event is passed through the middleware chain before being sent to the server and -each middleware can edit the data to add, change or remove information. There are -several built-in middlewares whose list is: - -- ``ContextInterfaceMiddleware``: adds the data stored in the contexts to the - event. -- ``ExceptionInterfaceMiddleware``: fetches the stacktrace for the captured event - if it's an exception (and has an stacktrace) and integrates additional data like - a small piece of source code for each stackframe. -- ``MessageInterfaceMiddleware``: adds a message (if present) to the event - and optionally format it using ``vsprintf``. -- ``ModulesMiddleware``: fetches informations about the packages installed through - Composer. The ``composer.lock`` file must be present for this middleware to work. -- ``ProcessorMiddleware``: executes the registered processors by passing to them - the event instance. -- ``RequestInterfaceMiddleware``: adds the HTTP request information (e.g. the - headers or the query string) to the event. -- ``SanitizerMiddleware``: sanitizes the data of the event to ensure that it - can be encoded correctly as JSON and the data is serialized in the appropriate - format for their representation. -- ``UserInterfaceMiddleware``: adds some user-related information like the client - IP address to the event. - -There are also some "special" middlewares that should be executed after all the -other middlewares so that they can sanitize and remove sensitive information before -they reach the Sentry server: - -- ``RemoveHttpBodyMiddleware``: sanitizes the data sent as body of a POST - request. -- ``SanitizeCookiesMiddleware``: sanitizes the cookies sent with the request - by hiding sensitive information. -- ``SanitizeDataMiddleware``: sanitizes the data of the event by removing - sensitive information. -- ``SanitizeHttpHeadersMiddleware``: sanitizes the headers of the request by - hiding sensitive information. -- ``RemoveStacktraceContextMiddleware``: sanitizes the captured stacktrace by - removing the excerpts of source code attached to each frame. This middleware - is not enabled by default. - -Writing a middleware -==================== - -The only requirement for a middleware is that it must be a callable. What this -means is that you can register an anonymous function as middleware as well as -create a class with the magic method ``__invoke`` and they will both work fine. -The signature of the function that will be called must be the following: - -.. code-block:: php - - function (\Sentry\Event $event, callable $next, \Psr\Http\Message\ServerRequestInterface $request = null, $exception = null, array $payload = []) - -The middleware can call the next one in the chain or can directly return the -event instance and break the chain. Additional data supplied by the user while -calling the ``capture*`` methods of the ``Client`` class will be passed to each -middleware in the ``$payload`` argument. The example below shows how a simple -middleware that customizes the message captured with an event can be written: - -.. code-block:: php - - use Psr\Http\Message\ServerRequestInterface; - use Sentry\ClientBuilder; - use Sentry\Event; - - final class CustomMiddleware - { - public function __invoke(Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) - { - $event->setMessage('hello world'); - - return $next($event, $request, $exception, $payload); - } - } - -Using a middleware -================== - -The middleware needs to be added to the stack before it can be used. Each one -can have a priority which defines in which order they will run. If you don't -specify a priority the default one of 0 will be assigned. The built-in middlewares -have the following priorities: - -- ``SanitizerMiddleware``: -255 (this middleware should always be the last one) -- ``SanitizeDataMiddleware``: -200 (this middleware should always be after - all "standard" middlewares) -- ``SanitizeCookiesMiddleware``: -200 (this middleware should always be after - all "standard" middlewares) -- ``RemoveHttpBodyMiddleware``: -200 (this middleware should always be after - all "standard" middlewares) -- ``SanitizeHttpHeadersMiddleware``: -200 (this middleware should always be after - all "standard" middlewares) -- ``RemoveStacktraceContextMiddleware``: -200 (this middleware should always be after - all "standard" middlewares) -- ``MessageInterfaceMiddleware``: 0 -- ``RequestInterfaceMiddleware``: 0 -- ``UserInterfaceMiddleware``: 0 -- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about - the tags context) -- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about - the user context) -- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about - the extra context) -- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about - the runtime context) -- ``ContextInterfaceMiddleware``: 0 (this middleware fills the information about - the server OS context) -- ``ExceptionInterfaceMiddleware``: 0 -- ``ModulesMiddleware``: 0 (this middleware is not enabled by default) - -The higher the priority value is, the earlier a middleware will be executed in -the chain. To add the middleware to the stack you can use the ``addMiddleware`` -method which can be found in both the ``Client`` and ``ClientBuilder`` classes. -To remove a middleware you can use the ``removeMiddleware`` method instead. You -can manage the middlewares at runtime and the chain will be recomputed accordingly. - -.. code-block:: php - - use Psr\Http\Message\ServerRequestInterface; - use Sentry\ClientBuilder; - use Sentry\Event; - - $middleware = function (Event $event, callable $next, ServerRequestInterface $request = null, $exception = null, array $payload = []) { - // Do something here - - return $next($event, $request, $exception, $payload); - }; - - $clientBuiler = new ClientBuilder(); - $clientBuilder->addMiddleware($middleware, 10); - $clientBuilder->removeMiddleware($middleware); - - $client = $clientBuilder->getClient(); - $client->addMiddleware($middleware, -10); - $client->removeMiddleware($middleware); diff --git a/docs/quickstart.rst b/docs/quickstart.rst deleted file mode 100644 index 0d849783b..000000000 --- a/docs/quickstart.rst +++ /dev/null @@ -1,122 +0,0 @@ -Quickstart -########## - -Using Sentry with PHP is straightforward. After installation of the library you -can directly interface with the client and start submitting data. - -Creating a Client -================= - -The most important part is the creation of the client instance. To ease the -instantiation of the object a builder class is provided which permits an easy -configuration of the options and the features of the client. Using it is the -recommended approach as it hides the complexity of directly using the constructor -of the ``Client`` class which needs two arguments: - -``$config`` - (``Configuration``) Storage for the client configuration. While the DSN is - immutable after it has been set, almost all other options can be changed at - any time while the client is already in use. - -``$transport`` - (``TransportInterface``) Class that is responsible for sending the events - over the wire. - -When using the client builder both these arguments are filled when getting the -instance of the client. The configuration options can be set by calling on the -client builder instance the same methods available in the ``Configuration`` -class. The transport, the middlewares and the processors can either be managed -before the client instance is initialized. - -.. code-block:: php - - use Sentry\ClientBuilder; - - $client = ClientBuilder::create(['server' => 'http://public:secret@example.com/1'])->getClient(); - -Sending errors -============== - -The client provides some methods to send both the last thrown error or a catched -exception: - -.. code-block:: php - - $client->captureLastError(); - $client->captureException(new \Exception('foo')); - -Sending messages -================ - -You may want to report messages instead of errors. To do it you can use the -``captureMessage`` method, which accept a string representing the message and -an optional list of parameters that will be substituted in it by the ``vsprintf`` -function before sending the event. - -.. code-block:: php - - // Both lines will report the same message - $client->captureMessage('foo bar'); - $client->captureMessage('foo %s', ['bar']); - -Sending other data -================== - -Sometimes you want to report an event in which you want to fill data yourself. -This is where the generic ``capture`` method comes in: it takes a payload of -data and creates an event from it. - -.. code-block:: php - - $client->capture(['level' => 'debug', 'message' => 'foo bar']); - -Sending additional data -======================= - -You may want to send additional data with an event that has been captured by -one of the ``capture*`` methods along the lines of what you can do in the -previous doc section. A payload of data can always be specified as last argument -of the methods discussed above and according to the key you pass you can set -additional data or override some preset information. - -.. code-block:: php - - // The error level of the captured error will be overwritten - $client->captureLastError(['level' => 'warning']); - - // An additional parametrized message will be sent with the captured exception - $client->captureException(new \RuntimeException('foo bar'), ['message' => 'bar %s', 'message_params' => ['baz']]); - - // The logger will be overwritten - $client->captureMessage('foo', [], ['logger' => 'custom']); - -Getting back an event ID -======================== - -An event ID is a UUID4 globally uniqued ID that is generated for the event and -that you can use to find the exact event from within Sentry. It is returned from -any ``capture*`` method. There is also a function called ``getLastEvent`` which -you can use to retrieve the lsat captured event and from there get its own ID. - -.. code-block:: php - - // Both the following lines will return the same ID, but it's recommended to always get it from the capture method - $eventId = $client->captureLastError(); - $eventId = $client->getLastEvent()->getId(); - -Capturing breadcrumbs manually -============================== - -Even though breadcrumbs can be captured automatically when an error or exception -occurs, you may want to report them manually too. The client gives access to some -methods to report and clear the breadcrumbs. - -.. code-block:: php - - use Sentry\Breadcrumbs\Breadcrumb; - - $client->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting', 'foo bar')); - -The default implementation of the breadcrumbs recorder is a circular buffer, so -when you reach out the maximum number of items that it can store at the same time -(100 by default) the oldest items will be replaced with the newest ones. diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json deleted file mode 100644 index cd92a4793..000000000 --- a/docs/sentry-doc-config.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "support_level": "production", - "platforms": { - "php": { - "name": "PHP", - "type": "language", - "doc_link": "", - "wizard": [ - "index#installation", - "quickstart#creating-a-client" - ] - }, - "php.laravel": { - "name": "Laravel", - "type": "framework", - "doc_link": "integrations/laravel/", - "wizard": [ - "integrations/laravel#laravel-5-x", - "integrations/laravel#laravel-4-x", - "integrations/laravel#lumen-5-x" - ] - }, - "php.monolog": { - "name": "Monolog", - "type": "framework", - "doc_link": "integrations/monolog/", - "wizard": [ - "index#installation", - "integrations/monolog#monolog" - ] - }, - "php.symfony2": { - "name": "Symfony2", - "type": "framework", - "doc_link": "integrations/symfony2/", - "wizard": [ - "integrations/symfony2#symfony-2" - ] - } - } -} diff --git a/docs/transport.rst b/docs/transport.rst deleted file mode 100644 index 2fd1056e7..000000000 --- a/docs/transport.rst +++ /dev/null @@ -1,86 +0,0 @@ -Sending events -############## - -Sending an event is very straightforward: you create an instance of the client -by configuring it and passing to it a transport and then you can use it to send -the event. - -Transport types -=============== - -Transports are the classes in Sentry PHP that are responsible for communicating -with a service in order to deliver an event. There are several types of transports -available out-of-the-box, all of which implement the ``TransportInterface`` -interface; - -- The ``NullTransport`` which is used in case no server where errors should be - sent is set in the client configuration. -- The ``HttpTransport`` which is the default and will be used when the server - is set in the client configuration. -- The ``SpoolTransport`` which can be used to defer the sending of events (e.g. - by putting them into a queue). - -The null transport -================== - -Although not so common there could be cases in which you don't want to send -events at all. The ``NullTransport`` transport does this: it simply ignores -the events, but report them as sent. - -.. code-block:: php - - use Sentry\Client; - use Sentry\Configuration; - use Sentry\Transport\NullTransport; - - // Even though the server is configured for the client, using the NullTransport - // transport won't send any event - - $configuration = new Configuration(['server' => 'http://public:secret@example.com/1']); - $client = new Client($configuration, new NullTransport()); - -The HTTP transport -================== - -The ``HttpTransport`` sends events over the HTTP protocol using Httplug_: the -best adapter available is automatically selected when creating a client instance -through the client builder, but you can override it using the appropriate methods. - -.. code-block:: php - - use Sentry\Client; - use Sentry\Configuration; - use Sentry\Transport\HttpTransport; - - $configuration = new Configuration(['server' => 'http://public:secret@example.com/1']); - $transport = new HttpTransport($configuration, HttpAsyncClientDiscovery::find(), MessageFactoryDiscovery::find()); - $client = new Client($configuration, $transport); - -The spool transport -=================== - -The default behavior is to send events immediatly. You may, however, want to -avoid waiting for the communication to the Sentry server that could be slow -or unreliable. This can be avoided by choosing the ``SpoolTransport`` which -stores the events in a queue so that another process can read it and and take -care of sending them. Currently only spooling to memory is supported. - -.. code-block:: php - - use Sentry\Client; - use Sentry\Configuration; - use Sentry\Transport\SpoolTransport; - - // The transport used by the client to send the events uses the memory spool - // which stores the events in a queue in-memory - - $spool = new MemorySpool(); - $transport = new NullTransport(); - $client = new Client(new Configuration(), new SpoolTransport($spool)); - - // When the spool queue is flushed the events are sent using the transport - // passed as parameter of the flushQueue method. - - $spool->flushQueue($transport); - -.. _Httplug: http://httplug.io/ From 63987c609db2f7ab867d06efc78f9eee2364f266 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 11 Dec 2018 12:32:32 +0100 Subject: [PATCH 0401/1161] Implement an event factory (#716) --- composer.json | 5 +- src/Client.php | 149 +++----------- src/ClientBuilder.php | 82 +++++++- src/ClientBuilderInterface.php | 22 +++ src/Event.php | 67 ++++++- src/EventFactory.php | 170 ++++++++++++++++ src/EventFactoryInterface.php | 29 +++ src/Exception/EventCreationException.php | 22 +++ src/HttpClient/Authentication/SentryAuth.php | 20 +- src/Integration/ModulesIntegration.php | 7 - tests/ClientBuilderTest.php | 44 +++++ tests/ClientTest.php | 136 ++++++++----- tests/EventFactoryTest.php | 183 ++++++++++++++++++ tests/EventTest.php | 7 +- .../Authentication/SentryAuthTest.php | 8 +- 15 files changed, 750 insertions(+), 201 deletions(-) create mode 100644 src/EventFactory.php create mode 100644 src/EventFactoryInterface.php create mode 100644 src/Exception/EventCreationException.php create mode 100644 tests/EventFactoryTest.php diff --git a/composer.json b/composer.json index 9571d4172..8927cfdb7 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "php": "^7.1", "ext-json": "*", "ext-mbstring": "*", + "jean85/pretty-package-versions": "^1.2", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5", "php-http/discovery": "^1.2", @@ -26,7 +27,6 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", - "jean85/pretty-package-versions": "^1.2", "monolog/monolog": "^1.3", "php-http/curl-client": "^1.7.1", "php-http/message": "^1.5", @@ -36,9 +36,6 @@ "phpunit/phpunit": "^7.0", "symfony/phpunit-bridge": "^4.1.6" }, - "suggest": { - "jean85/pretty-package-versions": "To use the ModulesIntegration, that lists all installed dependencies in every sent event" - }, "conflict": { "php-http/client-common": "1.8.0", "raven/raven": "*" diff --git a/src/Client.php b/src/Client.php index 05d9cc777..097eca843 100644 --- a/src/Client.php +++ b/src/Client.php @@ -6,13 +6,8 @@ use Sentry\Integration\Handler; use Sentry\Integration\IntegrationInterface; -use Sentry\Serializer\RepresentationSerializer; -use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; -use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; use Sentry\Transport\TransportInterface; -use Zend\Diactoros\ServerRequestFactory; /** * Default implementation of the {@see ClientInterface} interface. @@ -21,11 +16,6 @@ */ class Client implements ClientInterface { - /** - * The version of the library. - */ - public const VERSION = '2.0.x-dev'; - /** * The version of the protocol to communicate with the Sentry server. */ @@ -36,11 +26,6 @@ class Client implements ClientInterface */ public const SDK_IDENTIFIER = 'sentry.php'; - /** - * This constant defines the client's user-agent string. - */ - public const USER_AGENT = self:: SDK_IDENTIFIER . '/' . self::VERSION; - /** * This constant defines the maximum length of the message captured by the * message SDK interface. @@ -58,35 +43,28 @@ class Client implements ClientInterface private $transport; /** - * @var IntegrationInterface[] The stack of integrations - */ - private $integrations; - - /** - * @var SerializerInterface The serializer + * @var EventFactoryInterface The factory to create {@see Event} from raw data */ - private $serializer; + private $eventFactory; /** - * @var RepresentationSerializerInterface The representation serializer + * @var IntegrationInterface[] The stack of integrations */ - private $representationSerializer; + private $integrations; /** * Constructor. * - * @param Options $options The client configuration - * @param TransportInterface $transport The transport - * @param SerializerInterface $serializer The serializer used for events - * @param RepresentationSerializerInterface $representationSerializer The representation serializer to be used with stacktrace frames + * @param Options $options The client configuration + * @param TransportInterface $transport The transport + * @param EventFactoryInterface $eventFactory The factory for events */ - public function __construct(Options $options, TransportInterface $transport, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer) + public function __construct(Options $options, TransportInterface $transport, EventFactoryInterface $eventFactory) { $this->options = $options; $this->transport = $transport; + $this->eventFactory = $eventFactory; $this->integrations = Handler::setupIntegrations($options->getIntegrations()); - $this->serializer = $serializer; - $this->representationSerializer = $representationSerializer; } /** @@ -102,14 +80,16 @@ public function getOptions(): Options */ public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string { - $payload['message'] = $message; - $payload['level'] = $level; + $payload = [ + 'message' => $message, + 'level' => $level, + ]; - if ($this->getOptions()->shouldAttachStacktrace()) { - $payload['stacktrace'] = Stacktrace::createFromBacktrace($this->getOptions(), $this->serializer, $this->representationSerializer, \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), __FILE__, __LINE__); + if ($event = $this->prepareEvent($payload, $scope, $this->options->shouldAttachStacktrace())) { + return $this->transport->send($event); } - return $this->captureEvent($payload, $scope); + return null; } /** @@ -128,7 +108,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? public function captureEvent(array $payload, ?Scope $scope = null): ?string { if ($event = $this->prepareEvent($payload, $scope)) { - return $this->send($event); + return $this->transport->send($event); } return null; @@ -158,67 +138,26 @@ public function getIntegration(string $className): ?IntegrationInterface return $this->integrations[$className] ?? null; } - /** - * Sends the given event to the Sentry server. - * - * @param Event $event The event to send - * - * @return null|string - */ - private function send(Event $event): ?string - { - return $this->transport->send($event); - } - /** * Assembles an event and prepares it to be sent of to Sentry. * - * @param array $payload the payload that will be converted to an Event - * @param null|Scope $scope optional scope which enriches the Event + * @param array $payload the payload that will be converted to an Event + * @param null|Scope $scope optional scope which enriches the Event + * @param bool $withStacktrace True if the event should have and attached stacktrace * * @return null|Event returns ready to send Event, however depending on options it can be discarded */ - private function prepareEvent(array $payload, ?Scope $scope = null): ?Event + private function prepareEvent(array $payload, ?Scope $scope = null, bool $withStacktrace = false): ?Event { $sampleRate = $this->getOptions()->getSampleRate(); if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { return null; } - $event = new Event(); - $event->setServerName($this->getOptions()->getServerName()); - $event->setRelease($this->getOptions()->getRelease()); - $event->getTagsContext()->merge($this->getOptions()->getTags()); - $event->setEnvironment($this->getOptions()->getEnvironment()); - - if (isset($payload['transaction'])) { - $event->setTransaction($payload['transaction']); + if ($withStacktrace) { + $event = $this->eventFactory->createWithStacktrace($payload); } else { - $request = ServerRequestFactory::fromGlobals(); - $serverParams = $request->getServerParams(); - - if (isset($serverParams['PATH_INFO'])) { - $event->setTransaction($serverParams['PATH_INFO']); - } - } - - if (isset($payload['logger'])) { - $event->setLogger($payload['logger']); - } - - $message = $payload['message'] ?? null; - $messageParams = $payload['message_params'] ?? []; - - if (null !== $message) { - $event->setMessage(substr($message, 0, self::MESSAGE_MAX_LENGTH_LIMIT), $messageParams); - } - - if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { - $this->addThrowableToEvent($event, $payload['exception']); - } - - if (isset($payload['stacktrace']) && $payload['stacktrace'] instanceof Stacktrace) { - $event->setStacktrace($payload['stacktrace']); + $event = $this->eventFactory->create($payload); } if (null !== $scope) { @@ -227,44 +166,4 @@ private function prepareEvent(array $payload, ?Scope $scope = null): ?Event return \call_user_func($this->options->getBeforeSendCallback(), $event); } - - /** - * Stores the given exception in the passed event. - * - * @param Event $event The event that will be enriched with the exception - * @param \Throwable $exception The exception that will be processed and added to the event - */ - private function addThrowableToEvent(Event $event, \Throwable $exception): void - { - if ($exception instanceof \ErrorException) { - $event->setLevel(Severity::fromError($exception->getSeverity())); - } - - $exceptions = []; - $currentException = $exception; - - do { - if ($this->getOptions()->isExcludedException($currentException)) { - continue; - } - - $data = [ - 'type' => \get_class($currentException), - 'value' => $this->serializer->serialize($currentException->getMessage()), - ]; - - $data['stacktrace'] = Stacktrace::createFromBacktrace( - $this->getOptions(), - $this->serializer, - $this->representationSerializer, - $currentException->getTrace(), - $currentException->getFile(), - $currentException->getLine() - ); - - $exceptions[] = $data; - } while ($currentException = $currentException->getPrevious()); - - $event->setExceptions($exceptions); - } } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index aa741d44c..ce5f23e7b 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -18,6 +18,7 @@ use Http\Discovery\UriFactoryDiscovery; use Http\Message\MessageFactory; use Http\Message\UriFactory; +use Jean85\PrettyVersions; use Sentry\HttpClient\Authentication\SentryAuth; use Sentry\Integration\ErrorHandlerIntegration; use Sentry\Integration\RequestIntegration; @@ -65,6 +66,8 @@ * @method setSendDefaultPii(bool $enable) * @method bool hasDefaultIntegrations() * @method setDefaultIntegrations(bool $enable) + * @method callable getBeforeSendCallback() + * @method setBeforeSendCallback(callable $beforeSend) */ final class ClientBuilder implements ClientBuilderInterface { @@ -108,6 +111,16 @@ final class ClientBuilder implements ClientBuilderInterface */ private $representationSerializer; + /** + * @var string The SDK identifier, to be used in {@see Event} and {@see SentryAuth} + */ + private $sdkIdentifier = Client::SDK_IDENTIFIER; + + /** + * @var string The SDK version of the Client + */ + private $sdkVersion; + /** * Class constructor. * @@ -219,6 +232,54 @@ public function setRepresentationSerializer(RepresentationSerializerInterface $r return $this; } + /** + * {@inheritdoc} + */ + public function setSdkIdentifier(string $sdkIdentifier): self + { + $this->sdkIdentifier = $sdkIdentifier; + + return $this; + } + + /** + * Gets the SDK version to be passed onto {@see Event} and HTTP User-Agent header. + * + * @return string + */ + private function getSdkVersion(): string + { + if (null === $this->sdkVersion) { + $this->setSdkVersionByPackageName('sentry/sentry'); + } + + return $this->sdkVersion; + } + + /** + * {@inheritdoc} + */ + public function setSdkVersion(string $sdkVersion): self + { + $this->sdkVersion = $sdkVersion; + + return $this; + } + + /** + * Sets the version of the SDK package that generated this Event using the Packagist name. + * + * @param string $packageName The package name that will be used to get the version from (i.e. "sentry/sentry") + * + * @return $this + */ + public function setSdkVersionByPackageName(string $packageName): self + { + $this->sdkVersion = PrettyVersions::getVersion($packageName)->getPrettyVersion(); + + return $this; + } + /** * {@inheritdoc} */ @@ -228,10 +289,8 @@ public function getClient(): ClientInterface $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); $this->transport = $this->transport ?? $this->createTransportInstance(); - $this->serializer = $this->serializer ?? new Serializer(); - $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer(); - return new Client($this->options, $this->transport, $this->serializer, $this->representationSerializer); + return new Client($this->options, $this->transport, $this->createEventFactory()); } /** @@ -272,8 +331,8 @@ private function createHttpClientInstance(): PluginClient $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->options->getDsn()))); } - $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => Client::USER_AGENT])); - $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuth($this->options))); + $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->getSdkVersion()])); + $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuth($this->options, $this->sdkIdentifier, $this->getSdkVersion()))); $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->options->getSendAttempts()])); $this->addHttpClientPlugin(new ErrorPlugin()); @@ -305,4 +364,17 @@ private function createTransportInstance(): TransportInterface return new HttpTransport($this->options, $this->createHttpClientInstance(), $this->messageFactory); } + + /** + * Instantiate the {@see EventFactory} with the configured serializers. + * + * @return EventFactoryInterface + */ + private function createEventFactory(): EventFactoryInterface + { + $this->serializer = $this->serializer ?? new Serializer(); + $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer(); + + return new EventFactory($this->serializer, $this->representationSerializer, $this->options, $this->sdkIdentifier, $this->getSdkVersion()); + } } diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 2cecddd31..eecbfbc03 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -108,4 +108,26 @@ public function setSerializer(SerializerInterface $serializer); * @return $this */ public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer); + + /** + * Sets the SDK identifier to be passed onto {@see Event} and HTTP User-Agent header. + * + * @param string $sdkIdentifier The SDK identifier to be sent in {@see Event} and HTTP User-Agent headers + * + * @return $this + * + * @internal + */ + public function setSdkIdentifier(string $sdkIdentifier); + + /** + * Sets the SDK version to be passed onto {@see Event} and HTTP User-Agent header. + * + * @param string $sdkVersion The version of the SDK in use, to be sent alongside the SDK identifier + * + * @return $this + * + * @internal + */ + public function setSdkVersion(string $sdkVersion); } diff --git a/src/Event.php b/src/Event.php index dec6c415c..a11e407c0 100644 --- a/src/Event.php +++ b/src/Event.php @@ -4,6 +4,7 @@ namespace Sentry; +use Jean85\PrettyVersions; use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; @@ -125,6 +126,16 @@ final class Event implements \JsonSerializable */ private $stacktrace; + /** + * @var string The Sentry SDK identifier + */ + private $sdkIdentifier = Client::SDK_IDENTIFIER; + + /** + * @var string The Sentry SDK version + */ + private $sdkVersion; + /** * Event constructor. * @@ -154,6 +165,58 @@ public function getId(): string return str_replace('-', '', $this->id->toString()); } + /** + * Gets the identifier of the SDK package that generated this event. + * + * @return string + * + * @internal + */ + public function getSdkIdentifier(): string + { + return $this->sdkIdentifier; + } + + /** + * Sets the identifier of the SDK package that generated this event. + * + * @param string $sdkIdentifier + * + * @internal + */ + public function setSdkIdentifier(string $sdkIdentifier): void + { + $this->sdkIdentifier = $sdkIdentifier; + } + + /** + * Gets the version of the SDK package that generated this Event. + * + * @return string + * + * @internal + */ + public function getSdkVersion(): string + { + if (null === $this->sdkVersion) { + $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + } + + return $this->sdkVersion; + } + + /** + * Sets the version of the SDK package that generated this Event. + * + * @param string $sdkVersion + * + * @internal + */ + public function setSdkVersion(string $sdkVersion): void + { + $this->sdkVersion = $sdkVersion; + } + /** * Gets the timestamp of when this event was generated. * @@ -503,8 +566,8 @@ public function toArray(): array 'level' => (string) $this->level, 'platform' => 'php', 'sdk' => [ - 'name' => Client::SDK_IDENTIFIER, - 'version' => Client::VERSION, + 'name' => $this->sdkIdentifier, + 'version' => $this->getSdkVersion(), ], ]; diff --git a/src/EventFactory.php b/src/EventFactory.php new file mode 100644 index 000000000..bbc196abb --- /dev/null +++ b/src/EventFactory.php @@ -0,0 +1,170 @@ +serializer = $serializer; + $this->representationSerializer = $representationSerializer; + $this->options = $options; + $this->sdkIdentifier = $sdkIdentifier; + $this->sdkVersion = $sdkVersion; + } + + /** + * {@inheritdoc} + */ + public function createWithStacktrace(array $payload): Event + { + $event = $this->create($payload); + + if (!$event->getStacktrace()) { + $stacktrace = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), __FILE__, __LINE__); + + $event->setStacktrace($stacktrace); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function create(array $payload): Event + { + try { + $event = new Event(); + } catch (\Throwable $error) { + throw new EventCreationException($error); + } + + $event->setSdkIdentifier($this->sdkIdentifier); + $event->setSdkVersion($this->sdkVersion); + $event->setServerName($this->options->getServerName()); + $event->setRelease($this->options->getRelease()); + $event->getTagsContext()->merge($this->options->getTags()); + $event->setEnvironment($this->options->getEnvironment()); + + if (isset($payload['transaction'])) { + $event->setTransaction($payload['transaction']); + } else { + $request = ServerRequestFactory::fromGlobals(); + $serverParams = $request->getServerParams(); + + if (isset($serverParams['PATH_INFO'])) { + $event->setTransaction($serverParams['PATH_INFO']); + } + } + + if (isset($payload['logger'])) { + $event->setLogger($payload['logger']); + } + + $message = $payload['message'] ?? null; + $messageParams = $payload['message_params'] ?? []; + + if (null !== $message) { + $event->setMessage(substr($message, 0, Client::MESSAGE_MAX_LENGTH_LIMIT), $messageParams); + } + + if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { + $this->addThrowableToEvent($event, $payload['exception']); + } + + if (isset($payload['level']) && $payload['level'] instanceof Severity) { + $event->setLevel($payload['level']); + } + + if (isset($payload['stacktrace']) && $payload['stacktrace'] instanceof Stacktrace) { + $event->setStacktrace($payload['stacktrace']); + } + + return $event; + } + + /** + * Stores the given exception in the passed event. + * + * @param Event $event The event that will be enriched with the exception + * @param \Throwable $exception The exception that will be processed and added to the event + */ + private function addThrowableToEvent(Event $event, \Throwable $exception): void + { + if ($exception instanceof \ErrorException) { + $event->setLevel(Severity::fromError($exception->getSeverity())); + } + + $exceptions = []; + $currentException = $exception; + + do { + if ($this->options->isExcludedException($currentException)) { + continue; + } + + $data = [ + 'type' => \get_class($currentException), + 'value' => $this->serializer->serialize($currentException->getMessage()), + ]; + + $data['stacktrace'] = Stacktrace::createFromBacktrace( + $this->options, + $this->serializer, + $this->representationSerializer, + $currentException->getTrace(), + $currentException->getFile(), + $currentException->getLine() + ); + + $exceptions[] = $data; + } while ($currentException = $currentException->getPrevious()); + + $event->setExceptions($exceptions); + } +} diff --git a/src/EventFactoryInterface.php b/src/EventFactoryInterface.php new file mode 100644 index 000000000..9441775eb --- /dev/null +++ b/src/EventFactoryInterface.php @@ -0,0 +1,29 @@ +options = $options; + $this->sdkIdentifier = $sdkIdentifier; + $this->sdkVersion = $sdkVersion; } /** @@ -39,7 +53,7 @@ public function authenticate(RequestInterface $request): RequestInterface { $data = [ 'sentry_version' => Client::PROTOCOL_VERSION, - 'sentry_client' => Client::USER_AGENT, + 'sentry_client' => $this->sdkIdentifier . '/' . $this->sdkVersion, 'sentry_timestamp' => sprintf('%F', microtime(true)), 'sentry_key' => $this->options->getPublicKey(), ]; diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index 3e5d64891..a49ab7647 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -21,13 +21,6 @@ final class ModulesIntegration implements IntegrationInterface */ private static $loadedModules = []; - public function __construct() - { - if (!class_exists(PrettyVersions::class)) { - throw new \LogicException('You need the "jean85/pretty-package-versions" package in order to use this integration.'); - } - } - /** * {@inheritdoc} */ diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index ad3a5ba95..a72db219f 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -9,11 +9,13 @@ use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory; use Http\Message\UriFactory; +use Jean85\PrettyVersions; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; use Sentry\Client; use Sentry\ClientBuilder; +use Sentry\Event; use Sentry\Integration\ErrorHandlerIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; @@ -246,6 +248,48 @@ public function optionsDataProvider(): array ]; } + public function testClientBuilderFallbacksToDefaultSdkIdentifierAndVersion(): void + { + $callbackCalled = false; + $expectedVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->setBeforeSendCallback(function (Event $event) use ($expectedVersion, &$callbackCalled) { + $callbackCalled = true; + + $this->assertSame(Client::SDK_IDENTIFIER, $event->getSdkIdentifier()); + $this->assertSame($expectedVersion, $event->getSdkVersion()); + + return null; + }); + + $clientBuilder->getClient()->captureMessage('test'); + + $this->assertTrue($callbackCalled, 'Callback not invoked, no assertions performed'); + } + + public function testClientBuilderSetsSdkIdentifierAndVersion(): void + { + $callbackCalled = false; + + $clientBuilder = new ClientBuilder(); + $clientBuilder->setBeforeSendCallback(function (Event $event) use (&$callbackCalled) { + $callbackCalled = true; + + $this->assertSame('sentry.test', $event->getSdkIdentifier()); + $this->assertSame('1.2.3-test', $event->getSdkVersion()); + + return null; + }); + + $clientBuilder->setSdkIdentifier('sentry.test') + ->setSdkVersion('1.2.3-test') + ->getClient() + ->captureMessage('test'); + + $this->assertTrue($callbackCalled, 'Callback not invoked, no assertions performed'); + } + /** * @dataProvider getClientTogglesCompressionPluginInHttpClientDataProvider */ diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 6bfc3acb4..037f56a98 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,6 +10,7 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventFactory; use Sentry\Options; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\Serializer; @@ -22,7 +23,7 @@ class ClientTest extends TestCase { - public function testTransactionEventAttributeIsPopulated() + public function testTransactionEventAttributeIsPopulated(): void { $_SERVER['PATH_INFO'] = '/foo'; $_SERVER['REQUEST_METHOD'] = 'GET'; @@ -38,14 +39,14 @@ public function testTransactionEventAttributeIsPopulated() return true; })); - $client = new Client(new Options(), $transport, $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class)); + $client = new Client(new Options(), $transport, $this->createEventFactory()); $client->captureMessage('test'); unset($_SERVER['PATH_INFO']); unset($_SERVER['REQUEST_METHOD']); } - public function testTransactionEventAttributeIsNotPopulatedInCli() + public function testTransactionEventAttributeIsNotPopulatedInCli(): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -58,48 +59,57 @@ public function testTransactionEventAttributeIsNotPopulatedInCli() return true; })); - $client = new Client(new Options(), $transport, $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class)); + $client = new Client(new Options(), $transport, $this->createEventFactory()); $client->captureMessage('test'); } - public function testCaptureMessage() + public function testCaptureMessage(): void { - /** @var Client|MockObject $client */ - $client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->setMethodsExcept(['captureMessage']) - ->getMock(); - - $client->expects($this->once()) - ->method('captureEvent') - ->with([ - 'message' => 'foo', - 'level' => Severity::fatal(), - ]); + /** @var TransportInterface|MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertSame('foo', $event->getMessage()); + $this->assertEquals(Severity::fatal(), $event->getLevel()); + + return true; + })); + + $client = ClientBuilder::create() + ->setTransport($transport) + ->getClient(); $client->captureMessage('foo', Severity::fatal()); } - public function testCaptureException() + public function testCaptureException(): void { - $exception = new \Exception(); + $exception = new \Exception('Some foo error'); - /** @var Client|MockObject $client */ - $client = $this->getMockBuilder(Client::class) - ->disableOriginalConstructor() - ->setMethodsExcept(['captureException']) - ->getMock(); + /** @var TransportInterface|MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event) use ($exception): bool { + $this->assertCount(1, $event->getExceptions()); + + $exceptionData = $event->getExceptions()[0]; - $client->expects($this->once()) - ->method('captureEvent') - ->with([ - 'exception' => $exception, - ]); + $this->assertSame(\get_class($exception), $exceptionData['type']); + $this->assertSame($exception->getMessage(), $exceptionData['value']); - $client->captureException(new \Exception()); + return true; + })); + + $client = ClientBuilder::create() + ->setTransport($transport) + ->getClient(); + + $client->captureException($exception); } - public function testCapture() + public function testCapture(): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -123,7 +133,7 @@ public function testCapture() $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent($inputData)); } - public function testCaptureLastError() + public function testCaptureLastError(): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -149,7 +159,7 @@ public function testCaptureLastError() $this->clearLastError(); } - public function testCaptureLastErrorDoesNothingWhenThereIsNoError() + public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -165,7 +175,7 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError() $client->captureLastError(); } - public function testAppPathLinux() + public function testAppPathLinux(): void { $client = ClientBuilder::create(['project_root' => '/foo/bar'])->getClient(); @@ -176,14 +186,14 @@ public function testAppPathLinux() $this->assertEquals('/foo/baz/', $client->getOptions()->getProjectRoot()); } - public function testAppPathWindows() + public function testAppPathWindows(): void { $client = ClientBuilder::create(['project_root' => 'C:\\foo\\bar\\'])->getClient(); $this->assertEquals('C:\\foo\\bar\\', $client->getOptions()->getProjectRoot()); } - public function testSendChecksBeforeSendOption() + public function testSendChecksBeforeSendOption(): void { $beforeSendCalled = false; @@ -209,7 +219,7 @@ public function testSendChecksBeforeSendOption() /** * @dataProvider sampleRateAbsoluteDataProvider */ - public function testSampleRateAbsolute($options) + public function testSampleRateAbsolute($options): void { $httpClient = new MockClient(); @@ -231,7 +241,7 @@ public function testSampleRateAbsolute($options) } } - public function sampleRateAbsoluteDataProvider() + public function sampleRateAbsoluteDataProvider(): array { return [ [ @@ -249,7 +259,7 @@ public function sampleRateAbsoluteDataProvider() ]; } - public function testHandlingExceptionThrowingAnException() + public function testHandlingExceptionThrowingAnException(): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -264,7 +274,7 @@ public function testHandlingExceptionThrowingAnException() return true; })); - $client = new Client(new Options(), $transport, $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class)); + $client = new Client(new Options(), $transport, $this->createEventFactory()); Hub::getCurrent()->bindClient($client); $client->captureException($this->createCarelessExceptionWithStacktrace(), Hub::getCurrent()->getScope()); @@ -273,7 +283,7 @@ public function testHandlingExceptionThrowingAnException() /** * @dataProvider convertExceptionDataProvider */ - public function testConvertException(\Exception $exception, array $clientConfig, array $expectedResult) + public function testConvertException(\Exception $exception, array $clientConfig, array $expectedResult): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -299,7 +309,7 @@ public function testConvertException(\Exception $exception, array $clientConfig, $client->captureException($exception); } - public function convertExceptionDataProvider() + public function convertExceptionDataProvider(): array { return [ [ @@ -356,7 +366,7 @@ public function convertExceptionDataProvider() ]; } - public function testConvertExceptionThrownInLatin1File() + public function testConvertExceptionThrownInLatin1File(): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -400,7 +410,7 @@ public function testConvertExceptionThrownInLatin1File() $client->captureException(require_once __DIR__ . '/Fixtures/code/Latin1File.php'); } - public function testAttachStacktrace() + public function testAttachStacktrace(): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -409,7 +419,7 @@ public function testAttachStacktrace() ->with($this->callback(function (Event $event): bool { $result = $event->getStacktrace(); - $this->assertNotNull($result); + $this->assertInstanceOf(Stacktrace::class, $result); $this->assertNotEmpty($result->getFrames()); return true; @@ -422,10 +432,29 @@ public function testAttachStacktrace() $client->captureMessage('test'); } + public function testAttachStacktraceShouldNotWorkWithCaptureEvent(): void + { + /** @var TransportInterface|MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertNull($event->getStacktrace()); + + return true; + })); + + $client = ClientBuilder::create(['attach_stacktrace' => true]) + ->setTransport($transport) + ->getClient(); + + $client->captureEvent([]); + } + /** * @see https://github.com/symfony/polyfill/blob/52332f49d18c413699d2dccf465234356f8e0b2c/src/Php70/Php70.php#L52-L61 */ - private function clearLastError() + private function clearLastError(): void { $handler = function () { return false; @@ -436,12 +465,23 @@ private function clearLastError() restore_error_handler(); } - private function createCarelessExceptionWithStacktrace() + private function createCarelessExceptionWithStacktrace(): CarelessException { try { throw new CarelessException('Foo bar'); - } catch (\Exception $ex) { + } catch (CarelessException $ex) { return $ex; } } + + private function createEventFactory(): EventFactory + { + return new EventFactory( + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class), + $this->createMock(Options::class), + 'sentry.sdk.identifier', + '1.2.3' + ); + } } diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php new file mode 100644 index 000000000..0aad12e2b --- /dev/null +++ b/tests/EventFactoryTest.php @@ -0,0 +1,183 @@ +setServerName('testServerName'); + $options->setRelease('testRelease'); + $options->setTags(['test' => 'tag']); + $options->setEnvironment('testEnvironment'); + + $_SERVER['PATH_INFO'] = 'testPathInfo'; + + $eventFactory = new EventFactory( + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class), + $options, + 'sentry.sdk.identifier', + '1.2.3' + ); + + $event = $eventFactory->create([]); + + $this->assertSame('sentry.sdk.identifier', $event->getSdkIdentifier()); + $this->assertSame('1.2.3', $event->getSdkVersion()); + $this->assertSame($options->getServerName(), $event->getServerName()); + $this->assertSame($options->getRelease(), $event->getRelease()); + $this->assertSame($options->getTags(), $event->getTagsContext()->toArray()); + $this->assertSame($options->getEnvironment(), $event->getEnvironment()); + $this->assertSame('testPathInfo', $event->getTransaction()); + $this->assertNull($event->getStacktrace()); + } + + /** + * @dataProvider createWithPayloadDataProvider + */ + public function testCreateWithPayload(array $payload, array $expectedSubset): void + { + $eventFactory = new EventFactory( + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class), + new Options(), + 'sentry.sdk.identifier', + '1.2.3' + ); + + $event = $eventFactory->create($payload); + + $this->assertArraySubset($expectedSubset, $event->toArray()); + } + + public function createWithPayloadDataProvider() + { + return [ + [ + ['transaction' => 'testTransaction'], + ['transaction' => 'testTransaction'], + ], + [ + ['logger' => 'testLogger'], + ['logger' => 'testLogger'], + ], + [ + ['message' => 'testMessage'], + ['message' => 'testMessage'], + ], + [ + [ + 'message' => 'testMessage %s', + 'message_params' => ['param'], + ], + [ + 'message' => [ + 'message' => 'testMessage %s', + 'params' => ['param'], + 'formatted' => 'testMessage param', + ], + ], + ], + ]; + } + + public function testCreateEventInCLIDoesntSetTransaction(): void + { + $eventFactory = new EventFactory( + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class), + new Options(), + 'sentry.sdk.identifier', + '1.2.3' + ); + + $event = $eventFactory->create([]); + + $this->assertNull($event->getTransaction()); + } + + public function testCreateWithException(): void + { + $previousException = new \RuntimeException('testMessage2'); + $exception = new \Exception('testMessage', 0, $previousException); + $eventFactory = new EventFactory( + new Serializer(), + $this->createMock(RepresentationSerializerInterface::class), + new Options(), + 'sentry.sdk.identifier', + '1.2.3' + ); + + $event = $eventFactory->create(['exception' => $exception]); + $expectedData = [ + [ + 'type' => \Exception::class, + 'value' => 'testMessage', + ], + [ + 'type' => \RuntimeException::class, + 'value' => 'testMessage2', + ], + ]; + + $this->assertArraySubset($expectedData, $event->getExceptions()); + + foreach ($event->getExceptions() as $exceptionData) { + $this->assertInstanceOf(Stacktrace::class, $exceptionData['stacktrace']); + } + } + + public function testCreateWithErrorException(): void + { + $exception = new \ErrorException('testMessage', 0, E_USER_ERROR); + $eventFactory = new EventFactory( + new Serializer(), + $this->createMock(RepresentationSerializerInterface::class), + new Options(), + 'sentry.sdk.identifier', + '1.2.3' + ); + + $event = $eventFactory->create(['exception' => $exception]); + + $this->assertTrue(Severity::error()->isEqualTo($event->getLevel())); + } + + public function testCreateWithStacktrace(): void + { + $options = new Options(); + $options->setAttachStacktrace(true); + + $eventFactory = new EventFactory( + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class), + $options, + 'sentry.sdk.identifier', + '1.2.3' + ); + + $event = $eventFactory->createWithStacktrace([]); + $stacktrace = $event->getStacktrace(); + + $this->assertInstanceOf(Stacktrace::class, $stacktrace); + + /** @var Frame $lastFrame */ + $lastFrame = array_reverse($stacktrace->getFrames())[0]; + + $this->assertSame('src/EventFactory.php', $lastFrame->getFile()); + } +} diff --git a/tests/EventTest.php b/tests/EventTest.php index acc8eac46..170a8c668 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -4,6 +4,7 @@ namespace Sentry\Tests; +use Jean85\PrettyVersions; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Ramsey\Uuid\Uuid; @@ -90,8 +91,6 @@ public function testEventIsGeneratedWithUniqueIdentifier(): void public function testToArray(): void { - $this->options->setRelease('1.2.3-dev'); - $expected = [ 'event_id' => str_replace('-', '', static::GENERATED_UUID[0]), 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), @@ -99,7 +98,7 @@ public function testToArray(): void 'platform' => 'php', 'sdk' => [ 'name' => Client::SDK_IDENTIFIER, - 'version' => Client::VERSION, + 'version' => PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(), ], 'contexts' => [ 'os' => [ @@ -219,6 +218,8 @@ public function testGettersAndSetters(string $propertyName, $propertyValue, $exp public function gettersAndSettersDataProvider(): array { return [ + ['sdkIdentifier', 'sentry.sdk.test-identifier', ['sdk' => ['name' => 'sentry.sdk.test-identifier']]], + ['sdkVersion', '1.2.3', ['sdk' => ['version' => '1.2.3']]], ['level', Severity::info(), ['level' => Severity::info()]], ['logger', 'ruby', ['logger' => 'ruby']], ['transaction', 'foo', ['transaction' => 'foo']], diff --git a/tests/HttpClient/Authentication/SentryAuthTest.php b/tests/HttpClient/Authentication/SentryAuthTest.php index a6b0bd7ed..901a0b847 100644 --- a/tests/HttpClient/Authentication/SentryAuthTest.php +++ b/tests/HttpClient/Authentication/SentryAuthTest.php @@ -19,7 +19,7 @@ final class SentryAuthTest extends TestCase public function testAuthenticate(): void { $configuration = new Options(['dsn' => 'http://public:secret@example.com/']); - $authentication = new SentryAuth($configuration); + $authentication = new SentryAuth($configuration, 'sentry.php.test', '1.2.3'); /** @var RequestInterface|MockObject $request */ $request = $this->getMockBuilder(RequestInterface::class) @@ -32,7 +32,7 @@ public function testAuthenticate(): void $headerValue = sprintf( 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=public, sentry_secret=secret', Client::PROTOCOL_VERSION, - Client::USER_AGENT, + 'sentry.php.test/1.2.3', microtime(true) ); @@ -47,7 +47,7 @@ public function testAuthenticate(): void public function testAuthenticateWithNoSecretKey(): void { $configuration = new Options(['dsn' => 'http://public@example.com/']); - $authentication = new SentryAuth($configuration); + $authentication = new SentryAuth($configuration, 'sentry.php.test', '2.0.0'); /** @var RequestInterface|MockObject $request */ $request = $this->getMockBuilder(RequestInterface::class) @@ -60,7 +60,7 @@ public function testAuthenticateWithNoSecretKey(): void $headerValue = sprintf( 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=public', Client::PROTOCOL_VERSION, - Client::USER_AGENT, + 'sentry.php.test/2.0.0', microtime(true) ); From efbac6e89ef756d7e1d084842497a4acc58b7538 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 13 Dec 2018 10:04:21 -0800 Subject: [PATCH 0402/1161] meta: Minor fix to composer.json --- composer.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 8927cfdb7..4d429b5c8 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,13 @@ { "name": "sentry/sentry", "type": "library", - "description": "A PHP client for Sentry (http://getsentry.com)", - "keywords": ["log", "logging", "error-monitoring", "error-handler", "crash-reporting", "crash-reports"], - "homepage": "http://getsentry.com", + "description": "A PHP SDK for Sentry (http://sentry.io)", + "keywords": ["sentry", "log", "logging", "error-monitoring", "error-handler", "crash-reporting", "crash-reports"], + "homepage": "http://sentry.io", "license": "BSD-3-Clause", "authors": [ { - "name": "David Cramer", - "email": "dcramer@gmail.com" + "name": "Sentry" } ], "require": { From 6e1f60484e292549938686299ce0363111eac65d Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 19 Dec 2018 09:52:57 +0100 Subject: [PATCH 0403/1161] Add return types to the ClientBuilder methods and make the Options class final (#728) --- phpstan.neon | 2 +- src/ClientBuilder.php | 93 +++++++++----------------------- src/ClientBuilderInterface.php | 31 ++++++----- src/Options.php | 2 +- src/Sdk.php | 3 +- tests/ClientBuilderTest.php | 98 +++++++++++----------------------- tests/ClientTest.php | 41 +++++++------- 7 files changed, 95 insertions(+), 175 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index e8cd53958..44470014f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,7 +4,7 @@ parameters: - src - tests ignoreErrors: - - '/Call to an undefined method Sentry\\ClientBuilder::methodThatDoesNotExists\(\)/' + - '/Method Sentry\\ClientBuilder::\w+\(\) should return \$this\(Sentry\\ClientBuilderInterface\) but returns \$this\(Sentry\\ClientBuilder\)/' - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' - '/Method Sentry\\Serializer\\RepresentationSerializer::(representationSerialize|serializeValue)\(\) should return [\w|]+ but returns [\w|]+/' diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index ce5f23e7b..63fae8a80 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -34,40 +34,6 @@ * The default implementation of {@link ClientBuilderInterface}. * * @author Stefano Arlandini - * - * @method int getSendAttempts() - * @method setSendAttempts(int $attemptsCount) - * @method string[] getPrefixes() - * @method setPrefixes(array $prefixes) - * @method float getSampleRate() - * @method setSampleRate(float $sampleRate) - * @method bool shouldAttachStacktrace() - * @method setAttachStacktrace(bool $enable) - * @method int getContextLines() - * @method setContextLines(int $contextLines) - * @method null|string getEnvironment() - * @method setEnvironment(null|string $environment) - * @method string[] getExcludedProjectPaths() - * @method setExcludedProjectPaths(string[] $paths) - * @method setExcludedLoggers(string[] $loggers) - * @method string[] getExcludedExceptions() - * @method string getProjectRoot() - * @method setProjectRoot(string $path) - * @method string getLogger() - * @method setLogger(string $logger) - * @method string getRelease() - * @method setRelease(string $release) - * @method string getDsn() - * @method string getServerName() - * @method setServerName(string $serverName) - * @method string[] getTags() - * @method setTags(string[] $tags) - * @method bool shouldSendDefaultPii() - * @method setSendDefaultPii(bool $enable) - * @method bool hasDefaultIntegrations() - * @method setDefaultIntegrations(bool $enable) - * @method callable getBeforeSendCallback() - * @method setBeforeSendCallback(callable $beforeSend) */ final class ClientBuilder implements ClientBuilderInterface { @@ -124,11 +90,11 @@ final class ClientBuilder implements ClientBuilderInterface /** * Class constructor. * - * @param array $options The client options + * @param Options|null $options The client options */ - public function __construct(array $options = []) + public function __construct(Options $options = null) { - $this->options = new Options($options); + $this->options = $options ?? new Options(); if ($this->options->hasDefaultIntegrations()) { $this->options->setIntegrations(\array_merge([ @@ -141,15 +107,23 @@ public function __construct(array $options = []) /** * {@inheritdoc} */ - public static function create(array $options = []): self + public static function create(array $options = []): ClientBuilderInterface { - return new static($options); + return new static(new Options($options)); } /** * {@inheritdoc} */ - public function setUriFactory(UriFactory $uriFactory): self + public function getOptions(): Options + { + return $this->options; + } + + /** + * {@inheritdoc} + */ + public function setUriFactory(UriFactory $uriFactory): ClientBuilderInterface { $this->uriFactory = $uriFactory; @@ -159,7 +133,7 @@ public function setUriFactory(UriFactory $uriFactory): self /** * {@inheritdoc} */ - public function setMessageFactory(MessageFactory $messageFactory): self + public function setMessageFactory(MessageFactory $messageFactory): ClientBuilderInterface { $this->messageFactory = $messageFactory; @@ -169,7 +143,7 @@ public function setMessageFactory(MessageFactory $messageFactory): self /** * {@inheritdoc} */ - public function setTransport(TransportInterface $transport): self + public function setTransport(TransportInterface $transport): ClientBuilderInterface { $this->transport = $transport; @@ -179,7 +153,7 @@ public function setTransport(TransportInterface $transport): self /** * {@inheritdoc} */ - public function setHttpClient(HttpAsyncClient $httpClient): self + public function setHttpClient(HttpAsyncClient $httpClient): ClientBuilderInterface { $this->httpClient = $httpClient; @@ -189,7 +163,7 @@ public function setHttpClient(HttpAsyncClient $httpClient): self /** * {@inheritdoc} */ - public function addHttpClientPlugin(Plugin $plugin): self + public function addHttpClientPlugin(Plugin $plugin): ClientBuilderInterface { $this->httpClientPlugins[] = $plugin; @@ -199,7 +173,7 @@ public function addHttpClientPlugin(Plugin $plugin): self /** * {@inheritdoc} */ - public function removeHttpClientPlugin(string $className): self + public function removeHttpClientPlugin(string $className): ClientBuilderInterface { foreach ($this->httpClientPlugins as $index => $httpClientPlugin) { if (!$httpClientPlugin instanceof $className) { @@ -215,7 +189,7 @@ public function removeHttpClientPlugin(string $className): self /** * {@inheritdoc} */ - public function setSerializer(SerializerInterface $serializer): self + public function setSerializer(SerializerInterface $serializer): ClientBuilderInterface { $this->serializer = $serializer; @@ -225,7 +199,7 @@ public function setSerializer(SerializerInterface $serializer): self /** * {@inheritdoc} */ - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self + public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): ClientBuilderInterface { $this->representationSerializer = $representationSerializer; @@ -235,7 +209,7 @@ public function setRepresentationSerializer(RepresentationSerializerInterface $r /** * {@inheritdoc} */ - public function setSdkIdentifier(string $sdkIdentifier): self + public function setSdkIdentifier(string $sdkIdentifier): ClientBuilderInterface { $this->sdkIdentifier = $sdkIdentifier; @@ -259,7 +233,7 @@ private function getSdkVersion(): string /** * {@inheritdoc} */ - public function setSdkVersion(string $sdkVersion): self + public function setSdkVersion(string $sdkVersion): ClientBuilderInterface { $this->sdkVersion = $sdkVersion; @@ -273,7 +247,7 @@ public function setSdkVersion(string $sdkVersion): self * * @return $this */ - public function setSdkVersionByPackageName(string $packageName): self + public function setSdkVersionByPackageName(string $packageName): ClientBuilderInterface { $this->sdkVersion = PrettyVersions::getVersion($packageName)->getPrettyVersion(); @@ -293,25 +267,6 @@ public function getClient(): ClientInterface return new Client($this->options, $this->transport, $this->createEventFactory()); } - /** - * This method forwards all methods calls to the options object. - * - * @param string $name The name of the method being called - * @param array $arguments Parameters passed to the $name'ed method - * - * @return $this - * - * @throws \BadMethodCallException If the called method does not exists - */ - public function __call($name, $arguments) - { - if (!method_exists($this->options, $name)) { - throw new \BadMethodCallException(sprintf('The method named "%s" does not exists.', $name)); - } - - return $this->options->$name(...$arguments); - } - /** * Creates a new instance of the HTTP client. * diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index eecbfbc03..6bb57b454 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -22,11 +22,18 @@ interface ClientBuilderInterface /** * Creates a new instance of this builder. * - * @param array $options The client options + * @param array $options The client options, in naked array form * * @return static */ - public static function create(array $options = []); + public static function create(array $options = []): self; + + /** + * The options that will be used to create the {@see Client}. + * + * @return Options + */ + public function getOptions(): Options; /** * Sets the factory to use to create URIs. @@ -35,7 +42,7 @@ public static function create(array $options = []); * * @return $this */ - public function setUriFactory(UriFactory $uriFactory); + public function setUriFactory(UriFactory $uriFactory): self; /** * Sets the factory to use to create PSR-7 messages. @@ -44,7 +51,7 @@ public function setUriFactory(UriFactory $uriFactory); * * @return $this */ - public function setMessageFactory(MessageFactory $messageFactory); + public function setMessageFactory(MessageFactory $messageFactory): self; /** * Sets the transport that will be used to send events. @@ -53,7 +60,7 @@ public function setMessageFactory(MessageFactory $messageFactory); * * @return $this */ - public function setTransport(TransportInterface $transport); + public function setTransport(TransportInterface $transport): self; /** * Sets the HTTP client. @@ -62,7 +69,7 @@ public function setTransport(TransportInterface $transport); * * @return $this */ - public function setHttpClient(HttpAsyncClient $httpClient); + public function setHttpClient(HttpAsyncClient $httpClient): self; /** * Adds a new HTTP client plugin to the end of the plugins chain. @@ -71,7 +78,7 @@ public function setHttpClient(HttpAsyncClient $httpClient); * * @return $this */ - public function addHttpClientPlugin(Plugin $plugin); + public function addHttpClientPlugin(Plugin $plugin): self; /** * Removes a HTTP client plugin by its fully qualified class name (FQCN). @@ -80,7 +87,7 @@ public function addHttpClientPlugin(Plugin $plugin); * * @return $this */ - public function removeHttpClientPlugin(string $className); + public function removeHttpClientPlugin(string $className): self; /** * Gets the instance of the client built using the configured options. @@ -96,7 +103,7 @@ public function getClient(): ClientInterface; * * @return $this */ - public function setSerializer(SerializerInterface $serializer); + public function setSerializer(SerializerInterface $serializer): self; /** * Sets a representation serializer instance to be injected as a dependency of the client. @@ -107,7 +114,7 @@ public function setSerializer(SerializerInterface $serializer); * * @return $this */ - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer); + public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self; /** * Sets the SDK identifier to be passed onto {@see Event} and HTTP User-Agent header. @@ -118,7 +125,7 @@ public function setRepresentationSerializer(RepresentationSerializerInterface $r * * @internal */ - public function setSdkIdentifier(string $sdkIdentifier); + public function setSdkIdentifier(string $sdkIdentifier): self; /** * Sets the SDK version to be passed onto {@see Event} and HTTP User-Agent header. @@ -129,5 +136,5 @@ public function setSdkIdentifier(string $sdkIdentifier); * * @internal */ - public function setSdkVersion(string $sdkVersion); + public function setSdkVersion(string $sdkVersion): self; } diff --git a/src/Options.php b/src/Options.php index db8234af8..615df288b 100644 --- a/src/Options.php +++ b/src/Options.php @@ -13,7 +13,7 @@ * * @author Stefano Arlandini */ -class Options +final class Options { /** * The default maximum number of breadcrumbs that will be sent with an event. diff --git a/src/Sdk.php b/src/Sdk.php index 8153bc4b0..d7efcb260 100644 --- a/src/Sdk.php +++ b/src/Sdk.php @@ -13,7 +13,8 @@ */ function init(array $options = []): void { - Hub::setCurrent(new Hub(ClientBuilder::create($options)->getClient())); + $client = ClientBuilder::create($options)->getClient(); + Hub::setCurrent(new Hub($client)); } /** diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index a72db219f..4e38a1834 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -35,7 +35,7 @@ public function testCreate(): void public function testHttpTransportIsUsedWhenServeIsConfigured(): void { - $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); @@ -56,7 +56,7 @@ public function testSetUriFactory(): void /** @var UriFactory|MockObject $uriFactory */ $uriFactory = $this->createMock(UriFactory::class); - $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setUriFactory($uriFactory); $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); @@ -67,7 +67,7 @@ public function testSetMessageFactory(): void /** @var MessageFactory|MockObject $messageFactory */ $messageFactory = $this->createMock(MessageFactory::class); - $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setMessageFactory($messageFactory); $this->assertAttributeSame($messageFactory, 'messageFactory', $clientBuilder); @@ -82,7 +82,7 @@ public function testSetTransport(): void /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); - $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setTransport($transport); $this->assertAttributeSame($transport, 'transport', $clientBuilder); @@ -94,7 +94,7 @@ public function testSetHttpClient(): void /** @var HttpAsyncClient|MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); - $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); $clientBuilder->setHttpClient($httpClient); $this->assertAttributeSame($httpClient, 'httpClient', $clientBuilder); @@ -140,7 +140,7 @@ public function testRemoveHttpClientPlugin(): void public function testGetClient(): void { - $clientBuilder = new ClientBuilder(['dsn' => 'http://public:secret@example.com/sentry/1']); + $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); $client = $clientBuilder->getClient(); $this->assertInstanceOf(Client::class, $client); @@ -161,13 +161,14 @@ public function testGetClient(): void */ public function testIntegrationsAreAddedToClientCorrectly(bool $defaultIntegrations, array $integrations, array $expectedIntegrations): void { - $clientBuilder = new ClientBuilder([ - 'default_integrations' => $defaultIntegrations, - 'integrations' => $integrations, - ]); + $options = new Options(); + $options->setDefaultIntegrations($defaultIntegrations); + $options->setIntegrations($integrations); + $clientBuilder = new ClientBuilder($options); $client = $clientBuilder->getClient(); - $actualIntegrationsClassNames = array_map('get_class', $client->getOptions()->getIntegrations()); + + $actualIntegrationsClassNames = array_map('\get_class', $client->getOptions()->getIntegrations()); $this->assertEquals($expectedIntegrations, $actualIntegrationsClassNames, '', 0, 10, true); } @@ -198,63 +199,13 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array ]; } - /** - * @expectedException \BadMethodCallException - * @expectedExceptionMessage The method named "methodThatDoesNotExists" does not exists. - */ - public function testCallInvalidMethodThrowsException(): void - { - $clientBuilder = new ClientBuilder(); - $clientBuilder->methodThatDoesNotExists(); - } - - /** - * @dataProvider optionsDataProvider - */ - public function testCallExistingMethodForwardsCallToConfiguration(string $setterMethod, $value): void - { - $options = $this->createMock(Options::class); - $options->expects($this->once()) - ->method($setterMethod) - ->with($this->equalTo($value)); - - $clientBuilder = new ClientBuilder(); - - $reflectionProperty = new \ReflectionProperty(ClientBuilder::class, 'options'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($clientBuilder, $options); - $reflectionProperty->setAccessible(false); - - $clientBuilder->$setterMethod($value); - } - - public function optionsDataProvider(): array - { - return [ - ['setPrefixes', ['foo', 'bar']], - ['setSampleRate', 0.5], - ['setAttachStacktrace', true], - ['setContextLines', 0], - ['setEnableCompression', false], - ['setEnvironment', 'test'], - ['setExcludedProjectPaths', ['foo', 'bar']], - ['setExcludedExceptions', ['foo', 'bar']], - ['setProjectRoot', 'foo'], - ['setLogger', 'bar'], - ['setRelease', 'dev'], - ['setServerName', 'example.com'], - ['setTags', ['foo', 'bar']], - ['setErrorTypes', 0], - ]; - } - public function testClientBuilderFallbacksToDefaultSdkIdentifierAndVersion(): void { $callbackCalled = false; $expectedVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); - $clientBuilder = new ClientBuilder(); - $clientBuilder->setBeforeSendCallback(function (Event $event) use ($expectedVersion, &$callbackCalled) { + $options = new Options(); + $options->setBeforeSendCallback(function (Event $event) use ($expectedVersion, &$callbackCalled) { $callbackCalled = true; $this->assertSame(Client::SDK_IDENTIFIER, $event->getSdkIdentifier()); @@ -263,7 +214,7 @@ public function testClientBuilderFallbacksToDefaultSdkIdentifierAndVersion(): vo return null; }); - $clientBuilder->getClient()->captureMessage('test'); + (new ClientBuilder($options))->getClient()->captureMessage('test'); $this->assertTrue($callbackCalled, 'Callback not invoked, no assertions performed'); } @@ -272,8 +223,8 @@ public function testClientBuilderSetsSdkIdentifierAndVersion(): void { $callbackCalled = false; - $clientBuilder = new ClientBuilder(); - $clientBuilder->setBeforeSendCallback(function (Event $event) use (&$callbackCalled) { + $options = new Options(); + $options->setBeforeSendCallback(function (Event $event) use (&$callbackCalled) { $callbackCalled = true; $this->assertSame('sentry.test', $event->getSdkIdentifier()); @@ -282,7 +233,8 @@ public function testClientBuilderSetsSdkIdentifierAndVersion(): void return null; }); - $clientBuilder->setSdkIdentifier('sentry.test') + (new ClientBuilder($options)) + ->setSdkIdentifier('sentry.test') ->setSdkVersion('1.2.3-test') ->getClient() ->captureMessage('test'); @@ -295,7 +247,9 @@ public function testClientBuilderSetsSdkIdentifierAndVersion(): void */ public function testGetClientTogglesCompressionPluginInHttpClient(bool $enabled): void { - $builder = ClientBuilder::create(['enable_compression' => $enabled, 'dsn' => 'http://public:secret@example.com/sentry/1']); + $options = new Options(['dsn' => 'http://public:secret@example.com/sentry/1']); + $options->setEnableCompression($enabled); + $builder = new ClientBuilder($options); $builder->getClient(); $decoderPluginFound = false; @@ -318,6 +272,14 @@ public function getClientTogglesCompressionPluginInHttpClientDataProvider(): arr [false], ]; } + + public function testCreateWithNoOptionsIsTheSameAsDefaultOptions(): void + { + $this->assertEquals( + new ClientBuilder(new Options()), + ClientBuilder::create([]) + ); + } } final class StubIntegration implements IntegrationInterface diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 037f56a98..2f696ac63 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -202,14 +202,16 @@ public function testSendChecksBeforeSendOption(): void $transport->expects($this->never()) ->method('send'); - $client = ClientBuilder::create([ - 'dsn' => 'http://public:secret@example.com/1', - 'before_send' => function () use (&$beforeSendCalled) { - $beforeSendCalled = true; + $options = new Options(['dsn' => 'http://public:secret@example.com/1']); + $options->setBeforeSendCallback(function () use (&$beforeSendCalled) { + $beforeSendCalled = true; - return null; - }, - ])->setTransport($transport)->getClient(); + return null; + }); + + $client = (new ClientBuilder($options)) + ->setTransport($transport) + ->getClient(); $client->captureEvent([]); @@ -219,11 +221,14 @@ public function testSendChecksBeforeSendOption(): void /** * @dataProvider sampleRateAbsoluteDataProvider */ - public function testSampleRateAbsolute($options): void + public function testSampleRateAbsolute(float $sampleRate): void { $httpClient = new MockClient(); - $client = ClientBuilder::create($options) + $options = new Options(['dsn' => 'http://public:secret@example.com/1']); + $options->setSampleRate($sampleRate); + + $client = (new ClientBuilder($options)) ->setHttpClient($httpClient) ->getClient(); @@ -231,7 +236,7 @@ public function testSampleRateAbsolute($options): void $client->captureMessage('foobar'); } - switch ($options['sample_rate']) { + switch ($sampleRate) { case 0: $this->assertEmpty($httpClient->getRequests()); break; @@ -244,18 +249,8 @@ public function testSampleRateAbsolute($options): void public function sampleRateAbsoluteDataProvider(): array { return [ - [ - [ - 'dsn' => 'http://public:secret@example.com/1', - 'sample_rate' => 0, - ], - ], - [ - [ - 'dsn' => 'http://public:secret@example.com/1', - 'sample_rate' => 1, - ], - ], + 'sample rate 0' => [0], + 'sample rate 1' => [1], ]; } @@ -479,7 +474,7 @@ private function createEventFactory(): EventFactory return new EventFactory( $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class), - $this->createMock(Options::class), + new Options(), 'sentry.sdk.identifier', '1.2.3' ); From 91348ae9a3a52af188752d7fa38ed8a045337e6f Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Sat, 5 Jan 2019 00:53:26 +0100 Subject: [PATCH 0404/1161] feat: Add spool drain test (#732) --- tests/SdkTest.php | 6 +-- tests/Serializer/AbstractSerializerTest.php | 2 +- tests/Util/JSONTest.php | 2 +- tests/phpt/spool_drain.phpt | 45 +++++++++++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 tests/phpt/spool_drain.phpt diff --git a/tests/SdkTest.php b/tests/SdkTest.php index 4271282c4..9f9b0b013 100644 --- a/tests/SdkTest.php +++ b/tests/SdkTest.php @@ -6,16 +6,16 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Sentry\Breadcrumb; -use Sentry\ClientInterface; -use Sentry\State\Hub; use function Sentry\addBreadcrumb; +use Sentry\Breadcrumb; use function Sentry\captureEvent; use function Sentry\captureException; use function Sentry\captureLastError; use function Sentry\captureMessage; +use Sentry\ClientInterface; use function Sentry\configureScope; use function Sentry\init; +use Sentry\State\Hub; use function Sentry\withScope; class SdkTest extends TestCase diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 01bf87123..76f057b02 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -346,7 +346,7 @@ public function testSerializeValueResource(bool $serializeAllObjects): void $this->assertNotFalse($filename, 'Temp file creation failed'); - $resource = fopen($filename, 'wb'); + $resource = fopen($filename, 'w'); $result = $this->invokeSerialization($serializer, $resource); diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index 7f1d24289..e9857fe83 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -63,7 +63,7 @@ public function encodeDataProvider(): array */ public function testEncodeThrowsIfValueIsResource(): void { - $resource = fopen('php://memory', 'rb'); + $resource = fopen('php://memory', 'r'); $this->assertNotFalse($resource); diff --git a/tests/phpt/spool_drain.phpt b/tests/phpt/spool_drain.phpt new file mode 100644 index 000000000..f021871f3 --- /dev/null +++ b/tests/phpt/spool_drain.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test emptying spool transport +--FILE-- +setTransport($transport); + +Hub::getCurrent()->bindClient($builder->getClient()); + +register_shutdown_function('register_shutdown_function', function () use ($spool, $nullTransport) { + Assert::assertAttributeCount(1, 'events', $spool); + + $spool->flushQueue($nullTransport); + + Assert::assertAttributeCount(0, 'events', $spool); + + echo 'Shutdown function called'; +}); + +\Foo\Bar::baz(); +?> +--EXPECTREGEX-- +Fatal error: (?:Class 'Foo\\Bar' not found in [^\r\n]+ on line \d+|Uncaught Error: Class 'Foo\\Bar' not found in [^\r\n]+:\d+) +(?:Stack trace:[\s\S]+)?Shutdown function called From adc312fb7ab5b229264cd58c4d0953776d89e842 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 7 Jan 2019 09:59:06 +0100 Subject: [PATCH 0405/1161] feat: Add .craft.yml (#733) * feat: Add .craft.yml * ref: Spacing --- .craft.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .craft.yml diff --git a/.craft.yml b/.craft.yml new file mode 100644 index 000000000..90039d2c9 --- /dev/null +++ b/.craft.yml @@ -0,0 +1,11 @@ +minVersion: '0.7.0' +github: + owner: getsentry + repo: sentry-php +changelogPolicy: simple +targets: + - name: github + - name: registry + type: sdk + config: + canonical: 'composer:sentry/sentry' From e7fb87f840ab026e9ee9444831b6c7c571f06ed0 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 11 Jan 2019 11:22:14 +0100 Subject: [PATCH 0406/1161] meta: Update readme, remove upgrade guide for now (#735) * meta: Update readme, remove upgrade guide for now * fix: CS * fix: Restore upgrade.md * fix: CR --- README.md | 121 ++---------------- src/Client.php | 4 +- src/ClientBuilder.php | 2 +- src/ClientInterface.php | 10 +- src/Context/UserContext.php | 16 +-- src/EventFactory.php | 2 +- src/Integration/RequestIntegration.php | 4 +- src/Options.php | 4 +- src/Sdk.php | 8 +- src/Serializer/AbstractSerializer.php | 18 +-- src/Serializer/RepresentationSerializer.php | 2 +- .../RepresentationSerializerInterface.php | 2 +- src/Serializer/SerializerInterface.php | 2 +- src/State/Hub.php | 2 +- src/State/HubInterface.php | 12 +- src/State/Scope.php | 4 +- src/Transport/TransportInterface.php | 2 +- tests/ErrorHandlerTest.php | 12 +- 18 files changed, 64 insertions(+), 163 deletions(-) diff --git a/README.md b/README.md index d777d65aa..a23e037a3 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,7 @@ The Sentry PHP error reporter tracks errors and exceptions that happen during the execution of your application and provides instant notification with detailed -informations needed to prioritize, identify, reproduce and fix each issue. Learn -more about [automatic PHP error reporting with Sentry](https://sentry.io/for/php/). - -## Features - -- Automatically report (un)handled exceptions and errors -- Send customized diagnostic data -- Process and sanitize data before sending it over the network +information needed to prioritize, identify, reproduce and fix each issue. ## Install @@ -51,30 +44,18 @@ and [`http-message-implementation`](https://packagist.org/providers/psr/http-mes ## Usage ```php -namespace XXX; - -use Sentry\ClientBuilder; - -require 'vendor/autoload.php'; +use function Sentry\init; +use function Sentry\captureException; -// Instantiate the SDK with your DSN -$client = ClientBuilder::create(['server' => 'http://public@example.com/1'])->getClient(); +init(['dsn' => '___PUBLIC_DSN___' ]); -// Capture an exception -$eventId = $client->captureException(new \RuntimeException('Hello World!')); - -// Give the user feedback -echo 'Sorry, there was an error!'; -echo 'Your reference ID is ' . $eventId; +try { + thisFunctionThrows(); // -> throw new \Exception('foo bar'); +} catch (\Exception $exception) { + captureException($exception); +} ``` -For more information, see our [documentation](https://docs.getsentry.com/hosted/clients/php/). - - -## Integration with frameworks - -Other packages exists to integrate this SDK into the most common frameworks. - ### Official integrations The following integrations are fully supported and maintained by the Sentry team. @@ -95,15 +76,11 @@ The following integrations are available and maintained by members of the Sentry ## Community -- [Documentation](https://docs.getsentry.com/hosted/clients/php/) +- [Documentation](https://docs.sentry.io/error-reporting/quickstart/?platform=php) - [Bug Tracker](http://github.com/getsentry/sentry-php/issues) - [Code](http://github.com/getsentry/sentry-php) -- [Mailing List](https://groups.google.com/group/getsentry) -- [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) - -Contributing ------------- +## Contributing Dependencies are managed through composer: @@ -116,79 +93,3 @@ Tests can then be run via phpunit: ``` $ vendor/bin/phpunit ``` - - -Tagging a Release ------------------ - -1. Make sure ``CHANGES`` is up to date (add the release date) and ``master`` is green. - -2. Create a new branch for the minor version (if not present): - -``` -$ git checkout -b releases/2.1.x -``` - -3. Update the hardcoded version tag in ``Client.php``: - -```php -namespace Sentry; - -class Client -{ - const VERSION = '2.1.0'; -} -``` - -4. Commit the change: - -``` -$ git commit -a -m "2.1.0" -``` - -5. Tag the branch: - -``` -git tag 2.1.0 -``` - -6. Push the tag: - -``` -git push --tags -``` - -7. Switch back to ``master``: - -``` -git checkout master -``` - -8. Add the next minor release to the ``CHANGES`` file: - -``` -## 2.1.0 (unreleased) -``` - -9. Update the version in ``Client.php``: - -```php -namespace Sentry; - -class Client implements ClientInterface -{ - const VERSION = '2.1.x-dev'; -} -``` - -10. Lastly, update the composer version in ``composer.json``: - -```json - "extra": { - "branch-alias": { - "dev-master": "2.1.x-dev" - } - } -``` - -All done! Composer will pick up the tag and configuration automatically. diff --git a/src/Client.php b/src/Client.php index 097eca843..e0e596d06 100644 --- a/src/Client.php +++ b/src/Client.php @@ -142,10 +142,10 @@ public function getIntegration(string $className): ?IntegrationInterface * Assembles an event and prepares it to be sent of to Sentry. * * @param array $payload the payload that will be converted to an Event - * @param null|Scope $scope optional scope which enriches the Event + * @param Scope|null $scope optional scope which enriches the Event * @param bool $withStacktrace True if the event should have and attached stacktrace * - * @return null|Event returns ready to send Event, however depending on options it can be discarded + * @return Event|null returns ready to send Event, however depending on options it can be discarded */ private function prepareEvent(array $payload, ?Scope $scope = null, bool $withStacktrace = false): ?Event { diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 63fae8a80..c7e9a08e5 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -97,7 +97,7 @@ public function __construct(Options $options = null) $this->options = $options ?? new Options(); if ($this->options->hasDefaultIntegrations()) { - $this->options->setIntegrations(\array_merge([ + $this->options->setIntegrations(array_merge([ new ErrorHandlerIntegration(), new RequestIntegration($this->options), ], $this->options->getIntegrations())); diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 8cd6967d9..9b4a8950a 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -28,7 +28,7 @@ public function getOptions(): Options; * @param Severity $level The level of the message to be sent * @param Scope|null $scope An optional scope keeping the state * - * @return null|string + * @return string|null */ public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string; @@ -38,7 +38,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope * @param \Throwable $exception The exception object * @param Scope|null $scope An optional scope keeping the state * - * @return null|string + * @return string|null */ public function captureException(\Throwable $exception, ?Scope $scope = null): ?string; @@ -47,7 +47,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? * * @param Scope|null $scope An optional scope keeping the state * - * @return null|string + * @return string|null */ public function captureLastError(?Scope $scope = null): ?string; @@ -57,7 +57,7 @@ public function captureLastError(?Scope $scope = null): ?string; * @param array $payload The data of the event being captured * @param Scope|null $scope An optional scope keeping the state * - * @return null|string + * @return string|null */ public function captureEvent(array $payload, ?Scope $scope = null): ?string; @@ -66,7 +66,7 @@ public function captureEvent(array $payload, ?Scope $scope = null): ?string; * * @param string $className the classname of the integration * - * @return null|IntegrationInterface + * @return IntegrationInterface|null */ public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/src/Context/UserContext.php b/src/Context/UserContext.php index 6f60a4ab5..3263b6f2e 100644 --- a/src/Context/UserContext.php +++ b/src/Context/UserContext.php @@ -13,7 +13,7 @@ final class UserContext extends Context /** * Gets the ID of the user. * - * @return null|string + * @return string|null */ public function getId(): ?string { @@ -23,7 +23,7 @@ public function getId(): ?string /** * Sets the ID of the user. * - * @param null|string $id The ID + * @param string|null $id The ID */ public function setId(?string $id): void { @@ -33,7 +33,7 @@ public function setId(?string $id): void /** * Gets the username of the user. * - * @return null|string + * @return string|null */ public function getUsername(): ?string { @@ -43,7 +43,7 @@ public function getUsername(): ?string /** * Sets the username of the user. * - * @param null|string $username The username + * @param string|null $username The username */ public function setUsername(?string $username): void { @@ -53,7 +53,7 @@ public function setUsername(?string $username): void /** * Gets the email of the user. * - * @return null|string + * @return string|null */ public function getEmail(): ?string { @@ -63,7 +63,7 @@ public function getEmail(): ?string /** * Sets the email of the user. * - * @param null|string $email The email + * @param string|null $email The email */ public function setEmail(?string $email): void { @@ -73,7 +73,7 @@ public function setEmail(?string $email): void /** * Gets the ip address of the user. * - * @return null|string + * @return string|null */ public function getIpAddress(): ?string { @@ -83,7 +83,7 @@ public function getIpAddress(): ?string /** * Sets the ip address of the user. * - * @param null|string $ipAddress The ip address + * @param string|null $ipAddress The ip address */ public function setIpAddress(?string $ipAddress): void { diff --git a/src/EventFactory.php b/src/EventFactory.php index bbc196abb..153bed574 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -65,7 +65,7 @@ public function createWithStacktrace(array $payload): Event $event = $this->create($payload); if (!$event->getStacktrace()) { - $stacktrace = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), __FILE__, __LINE__); + $stacktrace = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), __FILE__, __LINE__); $event->setStacktrace($stacktrace); } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index bc527b447..57935fc1a 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -57,7 +57,7 @@ public function setupOnce(): void * * @param self $self The current instance of RequestIntegration * @param Event $event The event that will be enriched with a request - * @param null|ServerRequestInterface $request The Request that will be processed and added to the event + * @param ServerRequestInterface|null $request The Request that will be processed and added to the event */ public static function applyToEvent(self $self, Event $event, ?ServerRequestInterface $request = null): void { @@ -110,7 +110,7 @@ private function removePiiFromHeaders(array $headers): array $keysToRemove = ['authorization', 'cookie', 'set-cookie', 'remote_addr']; return array_filter($headers, function ($key) use ($keysToRemove) { - return !\in_array(\strtolower($key), $keysToRemove, true); + return !\in_array(strtolower($key), $keysToRemove, true); }, ARRAY_FILTER_USE_KEY); } } diff --git a/src/Options.php b/src/Options.php index 615df288b..fe6318df2 100644 --- a/src/Options.php +++ b/src/Options.php @@ -203,7 +203,7 @@ public function setEnableCompression(bool $enabled): void /** * Gets the environment. * - * @return null|string + * @return string|null */ public function getEnvironment(): ?string { @@ -718,7 +718,7 @@ private function normalizeAbsolutePath($value) * @param SymfonyOptions $options The configuration options * @param mixed $dsn The actual value of the option to normalize * - * @return null|string + * @return string|null */ private function normalizeDsnOption(SymfonyOptions $options, $dsn): ?string { diff --git a/src/Sdk.php b/src/Sdk.php index d7efcb260..bc9253312 100644 --- a/src/Sdk.php +++ b/src/Sdk.php @@ -23,7 +23,7 @@ function init(array $options = []): void * @param string $message The message * @param Severity $level The severity level of the message * - * @return null|string + * @return string|null */ function captureMessage(string $message, ?Severity $level = null): ?string { @@ -35,7 +35,7 @@ function captureMessage(string $message, ?Severity $level = null): ?string * * @param \Throwable $exception The exception * - * @return null|string + * @return string|null */ function captureException(\Throwable $exception): ?string { @@ -47,7 +47,7 @@ function captureException(\Throwable $exception): ?string * * @param array $payload The data of the event being captured * - * @return null|string + * @return string|null */ function captureEvent(array $payload): ?string { @@ -57,7 +57,7 @@ function captureEvent(array $payload): ?string /** * Logs the most recent error (obtained with {@link error_get_last}). * - * @return null|string + * @return string|null */ function captureLastError(): ?string { diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 0830ece75..e4903e7b0 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -94,7 +94,7 @@ public function __construct(int $maxDepth = 3, ?string $mbDetectOrder = null, in * @param mixed $value * @param int $_depth * - * @return string|bool|float|int|null|object|array + * @return string|bool|float|int|object|array|null */ protected function serializeRecursively($value, int $_depth = 0) { @@ -158,18 +158,18 @@ protected function serializeString($value) if ($this->isMbStringEnabled()) { // we always guarantee this is coerced, even if we can't detect encoding - if ($currentEncoding = \mb_detect_encoding($value, $this->mbDetectOrder)) { - $value = \mb_convert_encoding($value, 'UTF-8', $currentEncoding); + if ($currentEncoding = mb_detect_encoding($value, $this->mbDetectOrder)) { + $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); } else { - $value = \mb_convert_encoding($value, 'UTF-8'); + $value = mb_convert_encoding($value, 'UTF-8'); } - if (\mb_strlen($value) > $this->messageLimit) { - $value = \mb_substr($value, 0, $this->messageLimit - 10, 'UTF-8') . ' {clipped}'; + if (mb_strlen($value) > $this->messageLimit) { + $value = mb_substr($value, 0, $this->messageLimit - 10, 'UTF-8') . ' {clipped}'; } } else { if (\strlen($value) > $this->messageLimit) { - $value = \substr($value, 0, $this->messageLimit - 10) . ' {clipped}'; + $value = substr($value, 0, $this->messageLimit - 10) . ' {clipped}'; } } @@ -183,7 +183,7 @@ protected function serializeString($value) */ protected function serializeValue($value) { - if ((null === $value) || \is_bool($value) || \is_numeric($value)) { + if ((null === $value) || \is_bool($value) || is_numeric($value)) { return $value; } @@ -220,7 +220,7 @@ protected function serializeCallable(callable $callable): string } elseif ($callable instanceof \Closure || \is_string($callable)) { $reflection = new \ReflectionFunction($callable); $class = null; - } elseif (\is_object($callable) && \method_exists($callable, '__invoke')) { + } elseif (\is_object($callable) && method_exists($callable, '__invoke')) { $reflection = new \ReflectionMethod($callable, '__invoke'); $class = $reflection->getDeclaringClass(); } else { diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index 4da2830e7..81b88a21f 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -43,7 +43,7 @@ protected function serializeValue($value) return $value . '.0'; } - if (\is_numeric($value)) { + if (is_numeric($value)) { return (string) $value; } diff --git a/src/Serializer/RepresentationSerializerInterface.php b/src/Serializer/RepresentationSerializerInterface.php index b26c2a646..6db3132d7 100644 --- a/src/Serializer/RepresentationSerializerInterface.php +++ b/src/Serializer/RepresentationSerializerInterface.php @@ -18,7 +18,7 @@ interface RepresentationSerializerInterface * * @param mixed $value * - * @return string|null|object|array + * @return string|object|array|null */ public function representationSerialize($value); } diff --git a/src/Serializer/SerializerInterface.php b/src/Serializer/SerializerInterface.php index 9e11f5dfa..33a399094 100644 --- a/src/Serializer/SerializerInterface.php +++ b/src/Serializer/SerializerInterface.php @@ -14,7 +14,7 @@ interface SerializerInterface * * @param mixed $value * - * @return string|bool|float|int|null|object|array + * @return string|bool|float|int|object|array|null */ public function serialize($value); } diff --git a/src/State/Hub.php b/src/State/Hub.php index 1b7b58041..6811b8062 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -89,7 +89,7 @@ public function popScope(): bool return false; } - return null !== \array_pop($this->stack); + return null !== array_pop($this->stack); } /** diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index ef627553f..d2569b0b9 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -33,7 +33,7 @@ public function getScope(): Scope; /** * Gets the ID of the last captured event. * - * @return null|string + * @return string|null */ public function getLastEventId(): ?string; @@ -86,7 +86,7 @@ public function bindClient(ClientInterface $client): void; * @param string $message The message * @param Severity $level The severity level of the message * - * @return null|string + * @return string|null */ public function captureMessage(string $message, ?Severity $level = null): ?string; @@ -95,7 +95,7 @@ public function captureMessage(string $message, ?Severity $level = null): ?strin * * @param \Throwable $exception The exception * - * @return null|string + * @return string|null */ public function captureException(\Throwable $exception): ?string; @@ -104,14 +104,14 @@ public function captureException(\Throwable $exception): ?string; * * @param array $payload The data of the event being captured * - * @return null|string + * @return string|null */ public function captureEvent(array $payload): ?string; /** * Captures an event that logs the last occurred error. * - * @return null|string + * @return string|null */ public function captureLastError(): ?string; @@ -147,7 +147,7 @@ public static function setCurrent(self $hub): self; * * @param string $className The FQCN of the integration * - * @return null|IntegrationInterface + * @return IntegrationInterface|null */ public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/src/State/Scope.php b/src/State/Scope.php index 0a33ce768..d0e58b71e 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -178,7 +178,7 @@ public function getFingerprint(): array /** * Sets the severity to apply to all events captured in this scope. * - * @param null|Severity $level The severity + * @param Severity|null $level The severity * * @return $this */ @@ -192,7 +192,7 @@ public function setLevel(?Severity $level): self /** * Gets the severity to apply to all events captured in this scope. * - * @return null|Severity + * @return Severity|null * * @internal */ diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index a1b3d54f9..b6d2a8567 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -19,7 +19,7 @@ interface TransportInterface * * @param Event $event The event * - * @return null|string Returns the ID of the event or `null` if it failed to be sent + * @return string|null Returns the ID of the event or `null` if it failed to be sent */ public function send(Event $event): ?string; } diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 903c5f07b..6e85eb7d3 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -282,8 +282,8 @@ public function testHandleException(): void $errorHandler->handleException($exception); $this->fail('Exception expected'); - } catch (\Exception $catchedException) { - $this->assertSame($exception, $catchedException); + } catch (\Exception $caughtException) { + $this->assertSame($exception, $caughtException); } } finally { restore_error_handler(); @@ -316,8 +316,8 @@ public function testHandleExceptionWithPreviousExceptionHandler(): void $errorHandler->handleException($exception); $this->fail('Exception expected'); - } catch (\Exception $catchedException) { - $this->assertSame($exception, $catchedException); + } catch (\Exception $caughtException) { + $this->assertSame($exception, $caughtException); } } finally { restore_error_handler(); @@ -352,8 +352,8 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler(): void $errorHandler->handleException($exception1); $this->fail('Exception expected'); - } catch (\Exception $catchedException) { - $this->assertSame($exception2, $catchedException); + } catch (\Exception $caughtException) { + $this->assertSame($exception2, $caughtException); } } finally { restore_error_handler(); From 495b253ac250e62ab7a07d4ceae943b443cc5a7a Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 14 Jan 2019 11:44:06 +0100 Subject: [PATCH 0407/1161] Update README.md (#737) --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a23e037a3..d09f92884 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,11 @@ The Sentry PHP error reporter tracks errors and exceptions that happen during th execution of your application and provides instant notification with detailed information needed to prioritize, identify, reproduce and fix each issue. +### Notice 2.0 + +> The current master branch is our new major release of the SDK `2.0`. +> We currently ship `2.0` with the `beta` tag, which means you have to install it by exactly providing the version otherwise you wont get `2.0`. We will drop the `beta` tag as soon as we do no longer expect any public API changes. + ## Install To install the SDK you will need to be using [Composer]([https://getcomposer.org/) @@ -30,7 +35,7 @@ PSR-7 implementation and HTTP client they want to use. If you just want to get started quickly you should run the following command: ```bash -php composer.phar require sentry/sentry php-http/curl-client guzzlehttp/psr7 +php composer.phar require sentry/sentry:2.0.0-beta1 php-http/curl-client guzzlehttp/psr7 ``` This will install the library itself along with an HTTP client adapter that uses From d0c0692787aa33aa536906c346c14fb0650cf678 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 16 Jan 2019 17:07:20 +0100 Subject: [PATCH 0408/1161] meta: Add zeus hook (#739) --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index 95a5fc6d1..e183017f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,3 +58,13 @@ jobs: REMOVE_XDEBUG: "1" script: - composer phpstan + +notifications: + webhooks: + urls: + - https://zeus.ci/hooks/cf8597c4-ffba-11e7-89c9-0a580a281308/public/provider/travis/webhook + on_success: always + on_failure: always + on_start: always + on_cancel: always + on_error: always From 5ec1fbec3ff7c32dda49f234a2d5db5759393676 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 16 Jan 2019 18:09:44 +0100 Subject: [PATCH 0409/1161] Update .craft.yml --- .craft.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.craft.yml b/.craft.yml index 90039d2c9..6ce48d286 100644 --- a/.craft.yml +++ b/.craft.yml @@ -8,4 +8,4 @@ targets: - name: registry type: sdk config: - canonical: 'composer:sentry/sentry' + canonical: 'composer:@sentry/sentry' From 7338be31b6a8c72125cea85a2ff6dff32cfb2584 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 22 Jan 2019 22:37:48 +0100 Subject: [PATCH 0410/1161] Add PHP 7.3 to Travis CI build and support EditorConfig (#742) --- .editorconfig | 17 +++ .gitmodules | 0 .scrutinizer.yml | 2 +- .travis.yml | 102 ++++++++---------- src/ClientBuilder.php | 4 +- ...entryAuth.php => SentryAuthentication.php} | 7 +- ...hTest.php => SentryAuthenticationTest.php} | 8 +- tests/SdkTest.php | 6 +- tests/Serializer/AbstractSerializerTest.php | 2 +- tests/Util/JSONTest.php | 2 +- 10 files changed, 78 insertions(+), 72 deletions(-) create mode 100644 .editorconfig delete mode 100644 .gitmodules rename src/HttpClient/Authentication/{SentryAuth.php => SentryAuthentication.php} (88%) rename tests/HttpClient/Authentication/{SentryAuthTest.php => SentryAuthenticationTest.php} (87%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..f2a39827d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +charset = utf-8 +end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.md] +max_line_length = 80 + +[COMMIT_EDITMSG] +max_line_length = 0 diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb..000000000 diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 6620f0be4..ac4e2eeee 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,6 +1,6 @@ tools: external_code_coverage: - runs: 1 + timeout: 600 build: nodes: diff --git a/.travis.yml b/.travis.yml index e183017f9..b521de777 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,70 +1,56 @@ language: php php: - - 7.1 - - 7.2 - -matrix: - fast_finish: true - allow_failures: - - php: nightly + - 7.1 + - 7.2 + - 7.3 + - nightly env: - - REMOVE_XDEBUG="0" - - REMOVE_XDEBUG="1" - -cache: - directories: - - $HOME/.composer/cache + - dependencies=lowest + - dependencies=highest -before_install: - - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi +matrix: + fast_finish: true + allow_failures: + - php: nightly -install: travis_retry composer install --no-interaction +cache: + directories: + - $HOME/.composer/cache -script: - - composer tests +stages: + - Code style & static analysis + - Test + - Code coverage jobs: - include: - - stage: Test - php: 7.2 - env: - REMOVE_XDEBUG: "0" - COVERAGE: true - script: - - vendor/bin/phpunit --verbose --configuration phpunit.xml.dist --coverage-clover tests/clover.xml - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover tests/clover.xml --revision=$TRAVIS_COMMIT - - php: 7.1 - env: - COMPOSER_OPTIONS: "--prefer-lowest" - REMOVE_XDEBUG: "1" - install: travis_retry composer update --no-interaction --prefer-lowest - - php: nightly - allow_failure: true - before_install: - - composer remove --dev friendsofphp/php-cs-fixer - env: - REMOVE_XDEBUG: "0" - - stage: Code style & static analysis - env: - CS-FIXER: true - REMOVE_XDEBUG: "1" - script: - - composer phpcs - - env: - PHPSTAN: true - REMOVE_XDEBUG: "1" - script: - - composer phpstan + include: + - stage: Code style & static analysis + name: PHP CS Fixer + script: composer phpcs + - script: composer phpstan + name: PHPStan + - stage: Code coverage + php: 7.3 + env: dependencies=highest + script: + - vendor/bin/phpunit --verbose --coverage-clover=build/logs/clover.xml + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml --revision=$TRAVIS_COMMIT + after_success: + - travis_retry php vendor/bin/php-coveralls --verbose + +install: + - if [ "$dependencies" = "lowest" ]; then composer update --no-interaction --prefer-lowest --prefer-dist; fi; + - if [ "$dependencies" = "highest" ]; then composer update --no-interaction --prefer-dist; fi; notifications: - webhooks: - urls: - - https://zeus.ci/hooks/cf8597c4-ffba-11e7-89c9-0a580a281308/public/provider/travis/webhook - on_success: always - on_failure: always - on_start: always - on_cancel: always - on_error: always + webhooks: + urls: + - https://zeus.ci/hooks/cf8597c4-ffba-11e7-89c9-0a580a281308/public/provider/travis/webhook + on_success: always + on_failure: always + on_start: always + on_cancel: always + on_error: always diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index c7e9a08e5..1ef1ab09f 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -19,7 +19,7 @@ use Http\Message\MessageFactory; use Http\Message\UriFactory; use Jean85\PrettyVersions; -use Sentry\HttpClient\Authentication\SentryAuth; +use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\Integration\ErrorHandlerIntegration; use Sentry\Integration\RequestIntegration; use Sentry\Serializer\RepresentationSerializer; @@ -287,7 +287,7 @@ private function createHttpClientInstance(): PluginClient } $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->getSdkVersion()])); - $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuth($this->options, $this->sdkIdentifier, $this->getSdkVersion()))); + $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuthentication($this->options, $this->sdkIdentifier, $this->getSdkVersion()))); $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->options->getSendAttempts()])); $this->addHttpClientPlugin(new ErrorPlugin()); diff --git a/src/HttpClient/Authentication/SentryAuth.php b/src/HttpClient/Authentication/SentryAuthentication.php similarity index 88% rename from src/HttpClient/Authentication/SentryAuth.php rename to src/HttpClient/Authentication/SentryAuthentication.php index ec7fc6607..5956ab18b 100644 --- a/src/HttpClient/Authentication/SentryAuth.php +++ b/src/HttpClient/Authentication/SentryAuthentication.php @@ -15,7 +15,7 @@ * * @author Stefano Arlandini */ -final class SentryAuth implements Authentication +final class SentryAuthentication implements Authentication { /** * @var Options The Sentry client configuration @@ -68,6 +68,9 @@ public function authenticate(RequestInterface $request): RequestInterface $headers[] = $headerKey . '=' . $headerValue; } - return $request->withHeader('X-Sentry-Auth', 'Sentry ' . implode(', ', $headers)); + /** @var RequestInterface $request */ + $request = $request->withHeader('X-Sentry-Auth', 'Sentry ' . implode(', ', $headers)); + + return $request; } } diff --git a/tests/HttpClient/Authentication/SentryAuthTest.php b/tests/HttpClient/Authentication/SentryAuthenticationTest.php similarity index 87% rename from tests/HttpClient/Authentication/SentryAuthTest.php rename to tests/HttpClient/Authentication/SentryAuthenticationTest.php index 901a0b847..620d27c47 100644 --- a/tests/HttpClient/Authentication/SentryAuthTest.php +++ b/tests/HttpClient/Authentication/SentryAuthenticationTest.php @@ -8,18 +8,18 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; use Sentry\Client; -use Sentry\HttpClient\Authentication\SentryAuth; +use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\Options; /** * @group time-sensitive */ -final class SentryAuthTest extends TestCase +final class SentryAuthenticationTest extends TestCase { public function testAuthenticate(): void { $configuration = new Options(['dsn' => 'http://public:secret@example.com/']); - $authentication = new SentryAuth($configuration, 'sentry.php.test', '1.2.3'); + $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '1.2.3'); /** @var RequestInterface|MockObject $request */ $request = $this->getMockBuilder(RequestInterface::class) @@ -47,7 +47,7 @@ public function testAuthenticate(): void public function testAuthenticateWithNoSecretKey(): void { $configuration = new Options(['dsn' => 'http://public@example.com/']); - $authentication = new SentryAuth($configuration, 'sentry.php.test', '2.0.0'); + $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '2.0.0'); /** @var RequestInterface|MockObject $request */ $request = $this->getMockBuilder(RequestInterface::class) diff --git a/tests/SdkTest.php b/tests/SdkTest.php index 9f9b0b013..4271282c4 100644 --- a/tests/SdkTest.php +++ b/tests/SdkTest.php @@ -6,16 +6,16 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use function Sentry\addBreadcrumb; use Sentry\Breadcrumb; +use Sentry\ClientInterface; +use Sentry\State\Hub; +use function Sentry\addBreadcrumb; use function Sentry\captureEvent; use function Sentry\captureException; use function Sentry\captureLastError; use function Sentry\captureMessage; -use Sentry\ClientInterface; use function Sentry\configureScope; use function Sentry\init; -use Sentry\State\Hub; use function Sentry\withScope; class SdkTest extends TestCase diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 76f057b02..01bf87123 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -346,7 +346,7 @@ public function testSerializeValueResource(bool $serializeAllObjects): void $this->assertNotFalse($filename, 'Temp file creation failed'); - $resource = fopen($filename, 'w'); + $resource = fopen($filename, 'wb'); $result = $this->invokeSerialization($serializer, $resource); diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index e9857fe83..7f1d24289 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -63,7 +63,7 @@ public function encodeDataProvider(): array */ public function testEncodeThrowsIfValueIsResource(): void { - $resource = fopen('php://memory', 'r'); + $resource = fopen('php://memory', 'rb'); $this->assertNotFalse($resource); From 1f708e67761382e471aa48ff62d465730f4b46a3 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 23 Jan 2019 12:52:54 +0100 Subject: [PATCH 0411/1161] Update CHANGELOG and UPGRADE documents (#738) * Update the UPGRADE.md file * Update the CHANGELOG.md file --- CHANGELOG.md | 22 ++ UPGRADE-2.0.md | 599 +++++++++++++++++++++++++------------------------ src/Client.php | 3 +- src/Sdk.php | 1 + 4 files changed, 335 insertions(+), 290 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8323963e4..27fc3d2da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ - ... +## 2.0.0-beta-1 (2018-12-19) + +- Require PHP >= 7.1 +- Refactorize the whole codebase to support the Unified API SDK specs +- See the UPGRADE.md document for more information. + +## 1.10.0 (2018-11-09) + +- Added passing data from context in monolog breadcrumb handler (#683) +- Do not return error id if we know we did not send the error (#667) +- Do not force IPv4 protocol by default (#654) + +## 1.9.2 (2018-08-18) + +- Remove secret_key from required keys for CLI test command. (#645) +- Proper case in Raven_Util class name usage. (#642) +- Support longer creditcard numbers. (#635) +- Use configured message limit when creating serializers. (#634) +- Do not truncate strings if message limit is set to zero. (#630) +- Add option to ignore SERVER_PORT getting added to url. (#629) +- Cleanup the PHP version reported. (#604) + ## 1.9.1 (2018-06-19) - Allow the use of a public DSN (private part of the DSN was deprecated in Sentry 9) (#615) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index eafc6e9e6..6b85fc94d 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -1,57 +1,97 @@ -# Upgrade from 1.7 to 2.0 +# Upgrade from 1.10 to 2.0 ### Client options -- The `environment` option has been renamed to `current_environment`. -- The `http_proxy` option has been renamed to `proxy`. -- The `processorOptions` option has been renamed to `processors_options`. -- The `exclude` option has been renamed to `excluded_exceptions`. Unlike `exclude`, - `excluded_exceptions` will also exclude all subclasses. -- The `send_callback` option has been renamed to `should_capture`. +- The `http_proxy` option has been removed. + +- The `exclude` option has been removed. + +- The `send_callback` option has been renamed to `before_send`. + - The `name` option has been renamed to `server_name`. + +- The `secret_key` option has been removed. + +- The `public_key` option has been removed. + +- The `message_limit` option has been removed. + - The `project` option has been removed. + +- The `severity_map` option has been removed. + +- The `ignore_server_port` option has been removed. + +- The `error_types` option has been removed. + +- The `trust_x_forwarded_proto` option has been removed. + +- The `mb_detect_order` option has been removed. + +- The `trace` option has been removed. + +- The `tags` option has been removed in favour of setting them in the scope. + +- The `site` option has been removed. + - The `extra_data` option has been removed in favour of setting additional data - directly in the context. -- The `curl_method` option has been removed in favour of leaving to the user the - choice of setting an HTTP client supporting syncronous, asyncronous or both - transport methods. + in the scope. + +- The `curl_method` option has been removed. + - The `curl_path` option has been removed. + - The `curl_ipv4` option has been removed. + - The `curl_ssl_version` option has been removed. + - The `verify_ssl` option has been removed. + - The `ca_cert` option has been removed. + - The `proxy` option has been removed in favour of leaving to the user the burden of configuring the HTTP client options using a custom client. -- The `processors` option has been removed in favour of leaving to the user the - choice of which processors add or remove using the appropriate methods of the - `Client` and `ClientBuilder` classes. -- The `processors_options` option has been removed in favour of leaving to the - user the burden of adding an already configured processor instance to the client. + +- The `processors` option has been removed. + +- The `processors_options` option has been removed. + - The `transport` option has been removed in favour of setting it using the client builder. + - The `install_default_breadcrumb_handlers` option has been removed. -- The `http_client_options` has been added to set the options that applies to the - HTTP client chosen by the user as underlying transport method. -- The `open_timeout` option has been added to set the maximum number of seconds - to wait for the server connection to open. -- The `excluded_loggers` option has been added to set the list of logger 'progname's - to exclude from breadcrumbs. -- The `environments` option has been added to set the whitelist of environments - that will send notifications to Sentry. + - The `serialize_all_object` option has been added to configure whether all the object instances should be serialized. + - The `context_lines` option has been added to configure the number of lines of code context to capture. + - The `server` option has been renamed to `dsn`. -### Client +### Misc + +- All the classes have been renamed and moved around to follow the PSR-4 + convention and `final` have been added where appropriate. + +- The `Raven_Autoloader` class has been removed. To install and use the + library you are required to use [Composer](https://getcomposer.org/). -- The `Raven_Client` class has been renamed to `Client` to follow the PSR-4 - convention. +- The `Raven_Compat` class has been removed. + +- The `Raven_Util` class has been removed. + +- The `Raven_CurlHandler` class has been removed. + +- The `Raven_TransactionStack` class has been removed. + +- The `Raven_Exception` class has been removed. + +### Client -- The constructor of the `Raven_Client` class has changed its signature. The only way - to set the DSN is now to set it in the options passed to the `Configuration` - class. +- The constructor of the `Raven_Client` class has changed its signature and + now requires to be passed a configuration object, an instance of a transport + and an event factory. Before: @@ -65,14 +105,16 @@ After: ```php - public function __construct(Configuration $config, HttpAsyncClient $httpClient, RequestFactory $requestFactory) + public function __construct(Options $options, TransportInterface $transport, EventFactoryInterface $eventFactory) { // ... } ``` +- The method `Raven_Client::close_all_children_link` has been removed and there + - The methods `Raven_Client::getRelease` and `Raven_Client::setRelease` have - been removed. You should use `Configuration::getRelease()` and `Configuration::setRelease` + been removed. You should use `Options::getRelease` and `Options::setRelease` instead. Before: @@ -85,13 +127,17 @@ After: ```php - $client->getConfig()->getRelease(); - $client->getConfig()->setRelease(...); + use Sentry\Hub; + + $options = Hub::getCurrent()->getClient()->getOptions(); + + $options->getRelease(); + $options->setRelease(...); ``` - The methods `Raven_Client::getEnvironment` and `Raven_Client::setEnvironment` - have been removed. You should use `Configuration::getCurrentEnvironment` and - `Configuration::setCurrentEnvironment` instead. + have been removed. You should use `Options::getEnvironment` and `Options::setEnvironment` + instead. Before: @@ -103,13 +149,19 @@ After: ```php - $client->getConfig()->getCurrentEnvironment(); - $client->getConfig()->setCurrentEnvironment(...); + use Sentry\Hub; + + $options = Hub::getCurrent()->getClient()->getOptions(); + + $options->getEnvironment(); + $options->setEnvironment(...); ``` +- The method `Raven_Client::getInputStream` has been removed. + - The methods `Raven_Client::getDefaultPrefixes` and `Raven_Client::setPrefixes` - have been removed. You should use `Configuration::getPrefixes` and - `Configuration::setPrefixes` instead. + have been removed. You should use `Options::getPrefixes` and + `Options::setPrefixes` instead. Before: @@ -121,12 +173,16 @@ After: ```php - $client->getConfig()->getPrefixes(); - $client->getConfig()->setPrefixes(...); + use Sentry\Hub; + + $options = Hub::getCurrent()->getClient()->getOptions(); + + $options->getPrefixes(); + $options->setPrefixes(...); ``` - The methods `Raven_Client::getAppPath` and `Raven_Client::setAppPath` have been - removed. You should use `Configuration::getProjectRoot` and `Configuration::setProjectRoot` + removed. You should use `Options::getProjectRoot` and `Options::setProjectRoot` instead. Before: @@ -139,12 +195,17 @@ After: ```php - $client->getConfig()->getProjectRoot(); - $client->getConfig()->setProjectRoot(...); + use Sentry\Hub; + + $options = Hub::getCurrent()->getClient()->getOptions(); + + $options->getProjectRoot(); + $options->setProjectRoot(...); + ``` - The methods `Raven_Client::getExcludedAppPaths` and `Raven_Client::setExcludedAppPaths` - have been removed. You should use `Configuration::getExcludedProjectPaths` - and `Configuration::setExcludedProjectPaths` instead. + have been removed. You should use `Options::getExcludedProjectPaths` + and `Options::setExcludedProjectPaths` instead. Before: @@ -156,12 +217,17 @@ After: ```php - $client->getConfig()->getExcludedProjectPaths(); - $client->getConfig()->setExcludedProjectPaths(...); + use Sentry\Hub; -- The methods `Raven_Client::getSendCallback` and `Raven_Client::setSendCallback` have been - removed. You should use `Configuration::shouldCapture` and `Configuration::setShouldCapture` - instead. + $options = Hub::getCurrent()->getClient()->getOptions(); + + $options->getExcludedAppPaths(); + $options->setExcludedAppPaths(...); + ``` + +- The methods `Raven_Client::getSendCallback` and `Raven_Client::setSendCallback` + have been removed. You should use `Options::getBeforeSendCallback` and + `Options::setBeforeSendCallback` instead. Before: @@ -173,11 +239,15 @@ After: ```php - $client->getConfig()->shouldCapture(); - $client->getConfig()->setShouldCapture(...); + use Sentry\Hub; + + $options = Hub::getCurrent()->getClient()->getOptions(); + + $options->getBeforeSendCallback(); + $options->setBeforeSendCallback(...); - The method `Raven_Client::getServerEndpoint` has been removed. You should use - `Configuration::getDsn` instead. + `Options::getDsn` instead. Before: @@ -188,23 +258,18 @@ After: ```php - $client->getConfig()->getServer(); - ``` + use Sentry\Hub; -- The method `Raven_Client::getTransport` has been removed. You should use - `Configuration::getTransport` instead. + $options = Hub::getCurrent()->getClient()->getOptions(); - Before: - - ```php - $client->getTransport(); + $options->getDsn(); ``` - After: +- The methods `Raven_Client::getTransport` and `Raven_Client::setTransport` have + been removed. The transport is now a required dependency of the client and must + be passed as required constructor argument. - ```php - $client->getConfig()->getTransport(); - ``` +- The method `Raven_Client::getUserAgent` has been removed. - The method `Raven_Client::getErrorTypes` has been removed. You should use `Configuration::getErrorTypes` instead. @@ -223,12 +288,37 @@ - The `Raven_Client::getDefaultProcessors` method has been removed. +- The `Raven_Client::setProcessorsFromOptions` method has been removed. + +- The `Raven_Client::getLastEventID` method has been removed. The ID of the + last event that was captured is now returned by each of the `Client::capture*` + methods. + +- The `Raven_Client::parseDSN` method has been removed. + +- The `Raven_Client::getLastError` method has been removed. + +- The `Raven_Client::getIdent` method has been removed. + +- The `Raven_Client::registerShutdownFunction` method has been removed. + +- The `Raven_Client::is_http_request` method has been removed. + +- The `Raven_Client::get_http_data` method has been removed. + +- The `Raven_Client::get_user_data` method has been removed. + +- The `Raven_Client::get_extra_data` method has been removed. + +- The `Raven_Client::get_default_data` method has been removed. + - The `Raven_Client::message` method has been removed. +- The `Raven_Client::exception` method has been removed. + - The `Raven_Client::captureQuery` method has been removed. -- The `Raven_Client::captureMessage` method has changed its signature by removing the - `$stack` and `$vars` arguments. +- The `Raven_Client::captureMessage` method has changed its signature. Before: @@ -242,14 +332,13 @@ After: ```php - public function captureMessage($message, array $params = [], array $payload = []) + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string { // ... } ``` -- The `Raven_Client::captureException` method has changed its signature by removing - the `$logger` and `$vars` arguments. +- The `Raven_Client::captureException` method has changed its signature. Before: @@ -263,339 +352,225 @@ After: ```php - public function captureException($exception, array $payload = []) + public function captureException(\Throwable $exception, ?Scope $scope = null): ?string { // ... } ``` -- The `$vars` argument of the `Raven_Client::captureException`, `Raven_Client::captureMessage` - and `Raven_Client::captureQuery` methods accepted some values that were - setting additional data in the event like the tags or the user data. - Some of them have changed name. +- The `Raven_Client::captureLastError` method has changed its signature. Before: ```php - $vars = array( - 'tags' => array(...), - 'extra' => array(...), - 'user' => array(...), - ); - - $client->captureException(new Exception(), null, null, $vars); + public function captureLastError() + { + // ... + } ``` After: ```php - $payload = array( - 'tags_context' => array(...), - 'extra_context' => array(...), - 'user_context' => array(...), - ); - - $client->captureException(new Exception(), $payload); + public function captureLastError(?Scope $scope = null): ?string + { + // ... + } ``` -- If an exception implemented the `getSeverity()` method its value was used as error - level of the event. This has been changed so that only the `ErrorException` or its - derivated classes are considered for this behavior. +- The method `Raven_Client::capture` has been removed. -- The method `Raven_Client::createProcessors` has been removed as there is no - need to create instances of the processors from outside the `Client` class. +- The method `Raven_Client::sanitize` has been removed. -- The method `Raven_Client::setProcessors` has been removed. You should use - `Client::addProcessor` and `Client::removeProcessor` instead to manage the - processors that will be executed. +- The method `Raven_Client::process` has been removed. - Before: +- The method `Raven_Client::sendUnsentErrors` has been removed. - ```php - $processor1 = new Processor(); - $processor2 = new Processor(); +- The method `Raven_Client::encode` has been removed. - $client->setProcessors(array($processor2, $processor1)); - ``` +- The method `Raven_Client::send` has been removed. - After: +- The method `Raven_Client::send_remote` has been removed. - ```php - $processor1 = new Processor(); - $processor2 = new Processor(); +- The method `Raven_Client::get_default_ca_cert` has been removed. - $client->addProcessor($processor2); - $client->addProcessor($processor1); +- The method `Raven_Client::get_curl_options` has been removed. - // or +- The method `Raven_Client::send_http` has been removed. - $client->addProcessor($processor1); - $client->addProcessor($processor2, 255); // Note the priority: higher the priority earlier a processor will be executed. This is equivalent to adding first $processor2 and then $processor1 - ``` +- The method `Raven_Client::buildCurlCommand` has been removed. -- The method `Raven_Client::process` has been removed as there is no need to process event data - from outside the `Client` class. +- The method `Raven_Client::send_http_asynchronous_curl_exec` has been removed. -- The method `Raven_Client::sanitize` has been removed and the sanitization - happens now inside the `SanitizerMiddleware` middleware. +- The method `Raven_Client::send_http_synchronous` has been removed. -- The `Raven_Client::user_context` method has been removed. You should use - `Client::getUserContext` instead. +- The method `Raven_Client::get_auth_header` has been removed. - Before: +- The method `Raven_Client::getAuthHeader` has been removed. - ```php - $client->user_context(array('foo' => 'bar')); - ``` +- The method `Raven_Client::uuid4` has been removed. - After: +- The method `Raven_Client::get_current_url` has been removed. - ```php - $client->getUserContext()->setData(array('foo' => 'bar')); - ``` +- The method `Raven_Client::isHttps` has been removed. -- The `Raven_Client::tags_context` method has been removed. You should use - `Client::getTagsContext` instead. +- The method `Raven_Client::translateSeverity` has been removed. - Before: +- The method `Raven_Client::registerSeverityMap` has been removed. - ```php - $client->tags_context(array('foo', 'bar')); - ``` +- The method `Raven_Client::set_user_data` has been removed. - After: +- The method `Raven_Client::onShutdown` has been removed. - ```php - $client->getTagsContext()->setData(array('foo', 'bar')); - ``` +- The method `Raven_Client::createProcessors` has been removed. -- The `Raven_Client::extra_context` method has been removed. You should use - `Client::getExtraContext` instead. +- The method `Raven_Client::setProcessors` has been removed. - Before: +- The method `Raven_Client::getLastSentryError` has been removed. - ```php - $client->extra_context(array('foo' => 'bar')); - ``` - - After: - - ```php - $client->getExtraContext()->setData(array('foo' => 'bar')); - ``` - -- The `transaction` property has been made private. You should use `Client::getTransactionStack` - instead to access the instance of the object. - - Before: - - ```php - $client->transaction->push('foo'); - ``` +- The method `Raven_Client::getShutdownFunctionHasBeenSet` has been removed. - After: +- The method `Raven_Client::close_curl_resource` has been removed. - ```php - $client->getTransactionStack()->push('foo'); - ``` - -- The method `Raven_Client::install` has been removed. You should use `ErrorHandler::register` - instead to register an error handler and associate it with the client. +- The method `Raven_Client::setSerializer` has been removed. You can set it + using the client builder. Before: ```php - $client->install(); + $client = new Raven_Client(); + $client->setSerializer(...); ``` After: ```php - use Sentry\ErrorHandler; + use Sentry\ClientBuilder; - ErrorHandler::register($client); + $clientBuilder = ClientBuilder::create(); + $clientBuilder->setSerializer(...); ``` -- The method `Raven_Client::registerDefaultBreadcrumbHandlers` has been removed. - You should use `BreadcrumbErrorHandler::register` instead to register an error - handler and associate it with the client. +- The method `Raven_Client::setReprSerializer` has been removed. You can set it + using the client builder. Before: ```php - $client = new Raven_Client(array('install_default_breadcrumb_handlers' => true)); + $client = new Raven_Client(); + $client->setSerializer(...); ``` After: ```php - use Sentry\BreadcrumbErrorHandler; + use Sentry\ClientBuilder; - $client = new Client([...]); - - BreadcrumbErrorHandler::register($client); + $clientBuilder = ClientBuilder::create(); + $clientBuilder->setRepresentationSerializer(...); ``` -### Client builder +- The method `Raven_Client::cleanup_php_version` has been removed. + +- The method `Raven_Client::registerDefaultBreadcrumbHandlers` has been removed. -- To simplify the creation of a `Client` object instance, a new builder class - has been added. +- The `Raven_Client::user_context` method has been removed. You can set this + data in the current active scope. Before: ```php - $client = new Raven_Client([...]); + $client->user_context(array('foo', 'bar')); ``` After: ```php - $httpClient = new HttpClient(); // This can be any Httplug client adapter - $requestFactory = new RequestFactory(); // This can be any Httplug PSR-7 request factory - $client = new Client(new Configuration([...], $httpClient, $requestFactory)); + use Sentry\Hub; + use Sentry\Scope; - // or - - $client = ClientBuilder::create([...])->getClient(); + Hub::getCurrent()->configureScope(function (Scope $scope): void { + $scope->setUser(['email' => 'foo@example.com']); + }) ``` -### Processors - -All processor have been refactored into middlewares, and have changed names -to follow PSR-4 convention. - -- The `Raven_Processor_RemoveCookiesProcessor` class has been renamed to - `SanitizeCookiesMiddleware` to better reflect its purpose. The constructor - accepts an array of options to make the behaviour of which cookies to - sanitize configurable. - -- The `Raven_Processor_SanitizeStacktraceProcessor` class has been renamed to - `RemoveStacktraceContextMiddleware` to follow the PSR-4 convention and better - reflect its purpose. - -- The `Raven_Processor_SanitizeHttpHeadersProcessor` class has been renamed to - `SanitizeHttpHeadersMiddleware` to follow the PSR-4 convention. - -- The `Raven_Processor_RemoveHttpBodyProcessor` class has been renamed to - `RemoveHttpBodyMiddleware` to follow the PSR-4 convention. - -- The `Raven_Processor_SanitizeDataProcessor` class has been renamed to - `SanitizeDataMiddleware` to follow the PSR-4 convention. - -- The `Raven_Processor` class has been removed. There is not anymore a base - abstract class for the processors, but a `ProcessorMiddlewareInterface` interface has - been introduced. - -### Context - -- The `Raven_Context` class has been renamed to `Context` and added to the `Raven` - namespace to follow the PSR-4 convention. - -- The `tags`, `extra` and `user` properties of the `Raven_Context` class have - been removed. Each instance of the new class represents now a single context - type only and provides some useful methods to interact with the data. - -### Transactions - -- The method `TransactionStack::push` has changed its signature. It used to accept - a single value only, but now more values can be passed in a single call. +- The `Raven_Client::tags_context` method has been removed. You can set this + data in the current active scope. Before: ```php - $client->transaction->push('foo'); - $client->transaction->push('bar'); + $client->tags_context(array('foo', 'bar')); ``` After: ```php - $client->getTransactionStack()->push('foo'); - $client->getTransactionStack()->push('bar'); + use Sentry\Hub; + use Sentry\Scope; - // or - - $client->getTransactionStack()->push('foo', 'bar'); + Hub::getCurrent()->configureScope(function (Scope $scope): void { + $scope->setTag('tag_name', 'tag_value'); + }) ``` -- The method `TransactionStack::pop` has changed its signature by removing the - `$context` argument. Consequently the behaviour of the method in regards to - the returned value changed as well: it's not possible anymore to pop all values - up to one that equals the value of `$context`. +- The `Raven_Client::extra_context` method has been removed. You can set this + data in the current active scope. Before: ```php - $client->transaction->push('foo', 'bar', 'baz'); - - $value = $client->transaction->pop(); // $value is 'baz' - $value = $client->transaction->pop('foo'); // $value is 'foo' - $value = $client->transaction->pop(); // $value is null + $client->extra_context(array('foo' => 'bar')); ``` After: ```php - $client->getTransactionStack()->push('foo', 'bar', 'baz'); - - while (!$client->getTransactionStack()->isEmpty()) { - $value = $client->getTransactionStack()->pop(); // $value is 'baz', then 'bar', then 'foo' - } + use Sentry\Hub; + use Sentry\Scope; - $value = $client->getTransactionStack()->pop(); // $value is null + Hub::getCurrent()->configureScope(function (Scope $scope): void { + $scope->setExtra('extra_key', 'extra_value'); + }) ``` -## Error handlers +- The method `Raven_Client::install` has been removed. The error handler is + registered automatically when using the `ErrorHandlerIntegration` integration + (which is enabled by default). -- The `Raven_Breadcrumbs_ErrorHandler` class has been renamed to `BreadcrumbErrorHandler` - to better reflect its purpose and to follow the PSR-4 convention. +### Processors -- The constructor of the `Raven_Breadcrumbs_ErrorHandler` class has changed its - visibility to `protected`. To create a new instance of the class use the new - method `BreadcrumbErrorHandler::register`. +- The `Raven_Processor_RemoveCookiesProcessor` class has been removed. -- The method `Raven_Breadcrumbs_ErrorHandler::install` has been renamed to - `register` and changed its signature to accept the instance of the Raven - client to associate with the handler itself. +- The `Raven_Processor_SanitizeStacktraceProcessor` class has been removed. - Before: +- The `Raven_Processor_SanitizeHttpHeadersProcessor` class has been removed. - ```php - $errorHandler = new Raven_Breadcrumbs_ErrorHandler($client); - $errorHandler->install(); - ``` +- The `Raven_Processor_RemoveHttpBodyProcessor` class has been removed. - After: +- The `Raven_Processor_SanitizeDataProcessor` class has been removed. - ```php - use Sentry\BreadcrumbErrorHandler; - - $errorHandler = BreadcrumbErrorHandler::register($client); - ``` +- The `Raven_Processor` class has been removed. -- The `Raven_ErrorHandler` class has been renamed to `ErrorHandler` to follow - the PSR-4 convention. +### Context -- The constructor of the `Raven_ErrorHandler` class has changed its visibility - to `protected`. To create a new instance of the class use the new method - `ErrorHandler::register`. +- The `Raven_Context` class has been renamed to `Context`. - Before: +- The `tags`, `extra` and `user` properties of the `Raven_Context` class have + been removed. Each instance of the new class represents now a single context + type at once. - ```php - $errorHandler = new Raven_ErrorHandler($client); - ``` +## Error handlers - After: +- The `Raven_Breadcrumbs_ErrorHandler` class has been removed. - ```php - use Sentry\ErrorHandler; +- The `Raven_Breadcrumbs_MonologHandler` class has been removed. - $errorHandler = ErrorHandler::register($client); - ``` +- The `Raven_ErrorHandler` class has been renamed to `ErrorHandler` and has + been made `final`. - The method `Raven_ErrorHandler::handleError` has changed its signature by removing - the `$context` argument and it has been marked as internal to make it clear that + the `$context` argument and it has been marked as `internal` to make it clear that it should not be called publicly and its method visibility is subject to changes without any notice. @@ -619,21 +594,23 @@ to follow PSR-4 convention. ```php use Sentry\ErrorHandler; - $errorHandler = ErrorHandler::register($client); + ErrorHandler::register(function (\Throwable $exception): void { + // ... + }); ``` - The method `Raven_ErrorHandler::handleError` has changed its signature by - removing the `$context` argument and it has been marked as internal to + removing the `$context` argument and it has been marked as `internal` to make it clear that it should not be called publicly and its method visibility is subject to changes without any notice. - The method `Raven_ErrorHandler::handleFatalError` has changed its signature - by adding an optional argument named `$error` and it has been marked as internal + by adding an optional argument named `$error` and it has been marked as `internal` to make it clear that it should not be called publicly and its method visibility is subject to changes without any notice. - The method `Raven_ErrorHandler::handleException` has changed its signature by - removing the `$isError` and `$vars` arguments and it has been marked as internal + removing the `$isError` and `$vars` arguments and it has been marked as `internal` to make it clear that it should not be called publicly and its method visibility is subject to changes without any notice. @@ -642,3 +619,47 @@ to follow PSR-4 convention. - The method `Raven_ErrorHandler::shouldCaptureFatalError` has been removed and there is no replacement for it. + +### Serializers + +- The `Raven_Serializer` class has been renamed to `Serializer` and its constructor + changed signature. + + Before: + + ```php + public function __construct($mb_detect_order = null, $message_limit = null) + { + // ... + } + ``` + + After: + + ```php + public function __construct(int $maxDepth = 3, ?string $mbDetectOrder = null, int $messageLimit = Client::MESSAGE_MAX_LENGTH_LIMIT) + { + // ... + } + ``` + +- The `Raven_ReprSerializer` class has been renamed to `RepresentationSerializer` + and its constructor changed signature. + + Before: + + ```php + public function __construct($mb_detect_order = null, $message_limit = null) + { + // ... + } + ``` + + After: + + ```php + public function __construct(int $maxDepth = 3, ?string $mbDetectOrder = null, int $messageLimit = Client::MESSAGE_MAX_LENGTH_LIMIT) + { + // ... + } + ``` diff --git a/src/Client.php b/src/Client.php index e0e596d06..42fc03e28 100644 --- a/src/Client.php +++ b/src/Client.php @@ -14,7 +14,7 @@ * * @author Stefano Arlandini */ -class Client implements ClientInterface +final class Client implements ClientInterface { /** * The version of the protocol to communicate with the Sentry server. @@ -150,6 +150,7 @@ public function getIntegration(string $className): ?IntegrationInterface private function prepareEvent(array $payload, ?Scope $scope = null, bool $withStacktrace = false): ?Event { $sampleRate = $this->getOptions()->getSampleRate(); + if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { return null; } diff --git a/src/Sdk.php b/src/Sdk.php index bc9253312..e76d9befd 100644 --- a/src/Sdk.php +++ b/src/Sdk.php @@ -14,6 +14,7 @@ function init(array $options = []): void { $client = ClientBuilder::create($options)->getClient(); + Hub::setCurrent(new Hub($client)); } From b15a6c10c32f30f8b40c02e3ac5e2190b129fc80 Mon Sep 17 00:00:00 2001 From: Christoph Lehmann Date: Thu, 24 Jan 2019 09:00:41 +0100 Subject: [PATCH 0412/1161] Mention TYPO3 integration (#744) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d09f92884..ce0005bf1 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ The following integrations are available and maintained by members of the Sentry - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [Drupal](https://www.drupal.org/project/raven) - [OpenCart](https://github.com/BurdaPraha/oc_sentry) +- [TYPO3](https://github.com/networkteam/sentry_client) - ... feel free to be famous, create a port to your favourite platform! ## Community From a13e23cc8139f33b876bfcc914b37019f96c2384 Mon Sep 17 00:00:00 2001 From: Charlotte Dunois Date: Thu, 24 Jan 2019 15:50:20 +0100 Subject: [PATCH 0413/1161] Create necessary factories only when using http transport (#747) --- src/ClientBuilder.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 1ef1ab09f..ff8df80ec 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -259,9 +259,6 @@ public function setSdkVersionByPackageName(string $packageName): ClientBuilderIn */ public function getClient(): ClientInterface { - $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); - $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); - $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); $this->transport = $this->transport ?? $this->createTransportInstance(); return new Client($this->options, $this->transport, $this->createEventFactory()); @@ -313,6 +310,10 @@ private function createTransportInstance(): TransportInterface return new NullTransport(); } + $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); + $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); + $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); + if (null === $this->messageFactory) { throw new \RuntimeException('The PSR-7 message factory must be set.'); } From 89a5fdadf7aa0d9f680377f8404303a888d2c5b3 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 28 Jan 2019 11:47:03 +0100 Subject: [PATCH 0414/1161] Remove unused 'serialize_all_object' option (#753) * Remove unused 'serialize_all_object' option * Fix upgrade guide --- UPGRADE-2.0.md | 3 +-- src/Options.php | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 6b85fc94d..b2353d554 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -61,8 +61,7 @@ - The `install_default_breadcrumb_handlers` option has been removed. -- The `serialize_all_object` option has been added to configure whether all the - object instances should be serialized. +- The `serialize_all_object` option has been removed. - The `context_lines` option has been added to configure the number of lines of code context to capture. diff --git a/src/Options.php b/src/Options.php index fe6318df2..c596b34da 100644 --- a/src/Options.php +++ b/src/Options.php @@ -612,7 +612,6 @@ private function configureOptions(OptionsResolver $resolver): void 'default_integrations' => true, 'send_attempts' => 6, 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), - 'serialize_all_object' => false, 'sample_rate' => 1, 'mb_detect_order' => null, 'attach_stacktrace' => false, @@ -640,7 +639,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'array'); - $resolver->setAllowedTypes('serialize_all_object', 'bool'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('mb_detect_order', ['null', 'array', 'string']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); From 5cfcdfba2c3e9c7a6b7cdecba8894cbfbfb00a5c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 28 Jan 2019 15:18:06 +0100 Subject: [PATCH 0415/1161] Remove unused option mb_detect_order (#754) --- src/Options.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Options.php b/src/Options.php index c596b34da..9b336715e 100644 --- a/src/Options.php +++ b/src/Options.php @@ -613,7 +613,6 @@ private function configureOptions(OptionsResolver $resolver): void 'send_attempts' => 6, 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'sample_rate' => 1, - 'mb_detect_order' => null, 'attach_stacktrace' => false, 'context_lines' => 3, 'enable_compression' => true, @@ -640,7 +639,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'array'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); - $resolver->setAllowedTypes('mb_detect_order', ['null', 'array', 'string']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', 'int'); $resolver->setAllowedTypes('enable_compression', 'bool'); From 679ffb26dfb461525bb4842a4dfd8aec9b7edc73 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 28 Jan 2019 21:26:07 +0100 Subject: [PATCH 0416/1161] Rename excluded_paths option as per Unified APIs specs (#755) --- UPGRADE-2.0.md | 6 ++++-- src/Options.php | 18 +++++++++--------- src/Stacktrace.php | 2 +- tests/OptionsTest.php | 6 +++--- tests/StacktraceTest.php | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index b2353d554..789f1dca2 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -6,6 +6,8 @@ - The `exclude` option has been removed. +- The `excluded_app_path` option has been renamed to `in_app_exclude` + - The `send_callback` option has been renamed to `before_send`. - The `name` option has been renamed to `server_name`. @@ -203,8 +205,8 @@ ``` - The methods `Raven_Client::getExcludedAppPaths` and `Raven_Client::setExcludedAppPaths` - have been removed. You should use `Options::getExcludedProjectPaths` - and `Options::setExcludedProjectPaths` instead. + have been removed. You should use `Options::getInAppExcludedPaths` + and `Options::setInAppExcludedPaths` instead. Before: diff --git a/src/Options.php b/src/Options.php index 9b336715e..f4c73cef5 100644 --- a/src/Options.php +++ b/src/Options.php @@ -266,23 +266,23 @@ public function isExcludedException(\Throwable $exception): bool } /** - * Gets the list of paths to exclude from app_path detection. + * Gets the list of paths to exclude from in_app detection. * * @return string[] */ - public function getExcludedProjectPaths(): array + public function getInAppExcludedPaths(): array { - return $this->options['excluded_app_paths']; + return $this->options['in_app_exclude']; } /** - * Sets the list of paths to exclude from app_path detection. + * Sets the list of paths to exclude from in_app detection. * * @param array $paths The list of paths */ - public function setExcludedProjectPaths(array $paths): void + public function setInAppExcludedPaths(array $paths): void { - $options = array_merge($this->options, ['excluded_app_paths' => $paths]); + $options = array_merge($this->options, ['in_app_exclude' => $paths]); $this->options = $this->resolver->resolve($options); } @@ -632,7 +632,7 @@ private function configureOptions(OptionsResolver $resolver): void return $breadcrumb; }, 'excluded_exceptions' => [], - 'excluded_app_paths' => [], + 'in_app_exclude' => [], 'send_default_pii' => false, ]); @@ -644,7 +644,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('enable_compression', 'bool'); $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('excluded_exceptions', 'array'); - $resolver->setAllowedTypes('excluded_app_paths', 'array'); + $resolver->setAllowedTypes('in_app_exclude', 'array'); $resolver->setAllowedTypes('project_root', ['null', 'string']); $resolver->setAllowedTypes('logger', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); @@ -676,7 +676,7 @@ private function configureOptions(OptionsResolver $resolver): void return array_map([$this, 'normalizeAbsolutePath'], $value); }); - $resolver->setNormalizer('excluded_app_paths', function (SymfonyOptions $options, $value) { + $resolver->setNormalizer('in_app_exclude', function (SymfonyOptions $options, $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); } diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 92a7ddbd6..ba70dceb2 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -146,7 +146,7 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void } if (null !== $this->options->getProjectRoot()) { - $excludedAppPaths = $this->options->getExcludedProjectPaths(); + $excludedAppPaths = $this->options->getInAppExcludedPaths(); $absoluteFilePath = @realpath($file) ?: $file; $isApplicationFile = 0 === strpos($absoluteFilePath, $this->options->getProjectRoot()); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 791d40d21..101d8117d 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -45,7 +45,7 @@ public function optionsDataProvider(): array ['enable_compression', false, 'isCompressionEnabled', 'setEnableCompression'], ['environment', 'foo', 'getEnvironment', 'setEnvironment'], ['excluded_exceptions', ['foo', 'bar', 'baz'], 'getExcludedExceptions', 'setExcludedExceptions'], - ['excluded_app_paths', ['foo', 'bar'], 'getExcludedProjectPaths', 'setExcludedProjectPaths'], + ['in_app_exclude', ['foo', 'bar'], 'getInAppExcludedPaths', 'setInAppExcludedPaths'], ['project_root', 'baz', 'getProjectRoot', 'setProjectRoot'], ['logger', 'foo', 'getLogger', 'setLogger'], ['release', 'dev', 'getRelease', 'setRelease'], @@ -228,9 +228,9 @@ public function excludedExceptionsDataProvider() */ public function testExcludedAppPathsPathRegressionWithFileName($value, $expected) { - $configuration = new Options(['excluded_app_paths' => [$value]]); + $configuration = new Options(['in_app_exclude' => [$value]]); - $this->assertSame([$expected], $configuration->getExcludedProjectPaths()); + $this->assertSame([$expected], $configuration->getInAppExcludedPaths()); } public function excludedPathProviders() diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 1d386c6e2..b3d174ffe 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -125,7 +125,7 @@ public function testAddFrameStripsPath(): void public function testAddFrameMarksAsInApp(): void { $this->options->setProjectRoot('path/to'); - $this->options->setExcludedProjectPaths(['path/to/excluded/path']); + $this->options->setInAppExcludedPaths(['path/to/excluded/path']); $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); From c1136a4e0e8a990e8d76083d2184898c60f58831 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 31 Jan 2019 10:39:03 +0100 Subject: [PATCH 0417/1161] feat: Add max_value_length option (#745) * feat: Add truncation length option * fix: CodeReview * fix: Description * fix: Remove options * ref: Rename truncation_length to max_value_length --- src/Client.php | 6 --- src/ClientBuilder.php | 4 +- src/EventFactory.php | 2 +- src/Options.php | 24 ++++++++++ src/Serializer/AbstractSerializer.php | 47 +++++++------------ src/Stacktrace.php | 38 +++++++-------- tests/ClientTest.php | 2 +- tests/EventFactoryTest.php | 10 ++-- tests/OptionsTest.php | 1 + tests/Serializer/AbstractSerializerTest.php | 30 ------------ .../RepresentationSerializerTest.php | 3 +- tests/Serializer/SerializerTest.php | 35 +++++++++++++- tests/StacktraceTest.php | 7 +-- 13 files changed, 108 insertions(+), 101 deletions(-) diff --git a/src/Client.php b/src/Client.php index 42fc03e28..56110a40b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -26,12 +26,6 @@ final class Client implements ClientInterface */ public const SDK_IDENTIFIER = 'sentry.php'; - /** - * This constant defines the maximum length of the message captured by the - * message SDK interface. - */ - public const MESSAGE_MAX_LENGTH_LIMIT = 1024; - /** * @var Options The client options */ diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index ff8df80ec..a7b54abe3 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -328,8 +328,8 @@ private function createTransportInstance(): TransportInterface */ private function createEventFactory(): EventFactoryInterface { - $this->serializer = $this->serializer ?? new Serializer(); - $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer(); + $this->serializer = $this->serializer ?? new Serializer($this->options); + $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer($this->options); return new EventFactory($this->serializer, $this->representationSerializer, $this->options, $this->sdkIdentifier, $this->getSdkVersion()); } diff --git a/src/EventFactory.php b/src/EventFactory.php index 153bed574..774d41ab7 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -110,7 +110,7 @@ public function create(array $payload): Event $messageParams = $payload['message_params'] ?? []; if (null !== $message) { - $event->setMessage(substr($message, 0, Client::MESSAGE_MAX_LENGTH_LIMIT), $messageParams); + $event->setMessage(substr($message, 0, $this->options->getMaxValueLength()), $messageParams); } if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { diff --git a/src/Options.php b/src/Options.php index f4c73cef5..03042478f 100644 --- a/src/Options.php +++ b/src/Options.php @@ -597,6 +597,28 @@ public function setDefaultIntegrations(bool $enable): void $this->options = $this->resolver->resolve($options); } + /** + * Gets the max length for values in the event payload. + * + * @return int + */ + public function getMaxValueLength(): int + { + return $this->options['max_value_length']; + } + + /** + * Sets the max length for specific values in the event payload. + * + * @param int $maxValueLength The number of characters after which the values containing text will be truncated + */ + public function setMaxValueLength(int $maxValueLength): void + { + $options = array_merge($this->options, ['max_value_length' => $maxValueLength]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -634,6 +656,7 @@ private function configureOptions(OptionsResolver $resolver): void 'excluded_exceptions' => [], 'in_app_exclude' => [], 'send_default_pii' => false, + 'max_value_length' => 1024, ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -658,6 +681,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('integrations', 'array'); $resolver->setAllowedTypes('send_default_pii', 'bool'); $resolver->setAllowedTypes('default_integrations', 'bool'); + $resolver->setAllowedTypes('max_value_length', 'int'); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index e4903e7b0..cee103287 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -20,7 +20,7 @@ namespace Sentry\Serializer; -use Sentry\Client; +use Sentry\Options; /** * This helper is based on code from Facebook's Phabricator project. @@ -63,11 +63,9 @@ abstract class AbstractSerializer protected $serializeAllObjects = false; /** - * The default maximum message lengths. Longer strings will be truncated. - * - * @var int + * @var Options The Sentry options */ - protected $messageLimit; + protected $options; /** * Whether the ext-mbstring PHP extension is enabled or not. @@ -76,7 +74,14 @@ abstract class AbstractSerializer */ private $mbStringEnabled; - public function __construct(int $maxDepth = 3, ?string $mbDetectOrder = null, int $messageLimit = Client::MESSAGE_MAX_LENGTH_LIMIT) + /** + * AbstractSerializer constructor. + * + * @param Options $options The SDK configuration options + * @param int $maxDepth + * @param string|null $mbDetectOrder + */ + public function __construct(Options $options, int $maxDepth = 3, ?string $mbDetectOrder = null) { $this->maxDepth = $maxDepth; @@ -84,7 +89,7 @@ public function __construct(int $maxDepth = 3, ?string $mbDetectOrder = null, in $this->mbDetectOrder = $mbDetectOrder; } - $this->messageLimit = $messageLimit; + $this->options = $options; } /** @@ -164,12 +169,12 @@ protected function serializeString($value) $value = mb_convert_encoding($value, 'UTF-8'); } - if (mb_strlen($value) > $this->messageLimit) { - $value = mb_substr($value, 0, $this->messageLimit - 10, 'UTF-8') . ' {clipped}'; + if (mb_strlen($value) > $this->options->getMaxValueLength()) { + $value = mb_substr($value, 0, $this->options->getMaxValueLength() - 10, 'UTF-8') . ' {clipped}'; } } else { - if (\strlen($value) > $this->messageLimit) { - $value = substr($value, 0, $this->messageLimit - 10) . ' {clipped}'; + if (\strlen($value) > $this->options->getMaxValueLength()) { + $value = substr($value, 0, $this->options->getMaxValueLength() - 10) . ' {clipped}'; } } @@ -307,26 +312,6 @@ public function getSerializeAllObjects(): bool return $this->serializeAllObjects; } - /** - * @return int - */ - public function getMessageLimit(): int - { - return $this->messageLimit; - } - - /** - * @param int $messageLimit - * - * @return AbstractSerializer - */ - public function setMessageLimit(int $messageLimit): self - { - $this->messageLimit = $messageLimit; - - return $this; - } - private function isMbStringEnabled(): bool { if (null === $this->mbStringEnabled) { diff --git a/src/Stacktrace.php b/src/Stacktrace.php index ba70dceb2..9f493c257 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -4,9 +4,7 @@ namespace Sentry; -use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; /** @@ -161,14 +159,14 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void } } - $frameArguments = self::getFrameArguments($backtraceFrame); + $frameArguments = $this->getFrameArguments($backtraceFrame); if (!empty($frameArguments)) { foreach ($frameArguments as $argumentName => $argumentValue) { $argumentValue = $this->representationSerializer->representationSerialize($argumentValue); if (\is_string($argumentValue) || is_numeric($argumentValue)) { - $frameArguments[(string) $argumentName] = substr($argumentValue, 0, Client::MESSAGE_MAX_LENGTH_LIMIT); + $frameArguments[(string) $argumentName] = substr($argumentValue, 0, $this->options->getMaxValueLength()); } else { $frameArguments[(string) $argumentName] = $argumentValue; } @@ -297,12 +295,11 @@ protected function stripPrefixFromFilePath(string $filePath): string /** * Gets the values of the arguments of the given stackframe. * - * @param array $frame The frame from where arguments are retrieved - * @param int $maxValueLength The maximum string length to get from the arguments values + * @param array $frame The frame from where arguments are retrieved * * @return array */ - protected static function getFrameArgumentsValues(array $frame, int $maxValueLength = Client::MESSAGE_MAX_LENGTH_LIMIT): array + protected function getFrameArgumentsValues(array $frame): array { if (!isset($frame['args'])) { return []; @@ -311,7 +308,7 @@ protected static function getFrameArgumentsValues(array $frame, int $maxValueLen $result = []; foreach ($frame['args'] as $index => $argument) { - $result['param' . ($index + 1)] = self::serializeArgument($argument, $maxValueLength); + $result['param' . ($index + 1)] = $this->serializeArgument($argument); } return $result; @@ -320,12 +317,11 @@ protected static function getFrameArgumentsValues(array $frame, int $maxValueLen /** * Gets the arguments of the given stackframe. * - * @param array $frame The frame from where arguments are retrieved - * @param int $maxValueLength The maximum string length to get from the arguments values + * @param array $frame The frame from where arguments are retrieved * * @return array */ - public static function getFrameArguments(array $frame, int $maxValueLength = Client::MESSAGE_MAX_LENGTH_LIMIT) + public function getFrameArguments(array $frame): array { if (!isset($frame['args'])) { return []; @@ -334,19 +330,19 @@ public static function getFrameArguments(array $frame, int $maxValueLength = Cli // The Reflection API seems more appropriate if we associate it with the frame // where the function is actually called (since we're treating them as function context) if (!isset($frame['function'])) { - return self::getFrameArgumentsValues($frame, $maxValueLength); + return $this->getFrameArgumentsValues($frame); } if (false !== strpos($frame['function'], '__lambda_func')) { - return self::getFrameArgumentsValues($frame, $maxValueLength); + return $this->getFrameArgumentsValues($frame); } if (false !== strpos($frame['function'], '{closure}')) { - return self::getFrameArgumentsValues($frame, $maxValueLength); + return $this->getFrameArgumentsValues($frame); } if (isset($frame['class']) && 'Closure' === $frame['class']) { - return self::getFrameArgumentsValues($frame, $maxValueLength); + return $this->getFrameArgumentsValues($frame); } if (\in_array($frame['function'], static::$importStatements, true)) { @@ -355,7 +351,7 @@ public static function getFrameArguments(array $frame, int $maxValueLength = Cli } return [ - 'param1' => self::serializeArgument($frame['args'][0], $maxValueLength), + 'param1' => $this->serializeArgument($frame['args'][0]), ]; } @@ -371,17 +367,17 @@ public static function getFrameArguments(array $frame, int $maxValueLength = Cli } elseif (\function_exists($frame['function'])) { $reflection = new \ReflectionFunction($frame['function']); } else { - return self::getFrameArgumentsValues($frame, $maxValueLength); + return $this->getFrameArgumentsValues($frame); } } catch (\ReflectionException $ex) { - return self::getFrameArgumentsValues($frame, $maxValueLength); + return $this->getFrameArgumentsValues($frame); } $params = $reflection->getParameters(); $args = []; foreach ($frame['args'] as $index => $arg) { - $arg = self::serializeArgument($arg, $maxValueLength); + $arg = $this->serializeArgument($arg); if (isset($params[$index])) { // Assign the argument by the parameter name @@ -394,8 +390,10 @@ public static function getFrameArguments(array $frame, int $maxValueLength = Cli return $args; } - protected static function serializeArgument($arg, int $maxValueLength) + protected function serializeArgument($arg) { + $maxValueLength = $this->options->getMaxValueLength(); + if (\is_array($arg)) { $result = []; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 2f696ac63..05c2d9960 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -394,7 +394,7 @@ public function testConvertExceptionThrownInLatin1File(): void return true; })); - $serializer = new Serializer(); + $serializer = new Serializer(new Options()); $serializer->setMbDetectOrder('ISO-8859-1, ASCII, UTF-8'); $client = ClientBuilder::create() diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index 0aad12e2b..a416e0913 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -112,12 +112,13 @@ public function testCreateEventInCLIDoesntSetTransaction(): void public function testCreateWithException(): void { + $options = new Options(); $previousException = new \RuntimeException('testMessage2'); $exception = new \Exception('testMessage', 0, $previousException); $eventFactory = new EventFactory( - new Serializer(), + new Serializer($options), $this->createMock(RepresentationSerializerInterface::class), - new Options(), + $options, 'sentry.sdk.identifier', '1.2.3' ); @@ -143,11 +144,12 @@ public function testCreateWithException(): void public function testCreateWithErrorException(): void { + $options = new Options(); $exception = new \ErrorException('testMessage', 0, E_USER_ERROR); $eventFactory = new EventFactory( - new Serializer(), + new Serializer($options), $this->createMock(RepresentationSerializerInterface::class), - new Options(), + $options, 'sentry.sdk.identifier', '1.2.3' ); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 101d8117d..11b0043e4 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -57,6 +57,7 @@ public function optionsDataProvider(): array ['before_breadcrumb', function () {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback'], ['send_default_pii', true, 'shouldSendDefaultPii', 'setSendDefaultPii'], ['default_integrations', false, 'hasDefaultIntegrations', 'setDefaultIntegrations'], + ['max_value_length', 50, 'getMaxValueLength', 'setMaxValueLength'], ]; } diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 01bf87123..a7c9b3daf 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -317,20 +317,6 @@ public function testLongString(bool $serializeAllObjects): void } } - public function testLongStringWithOverwrittenMessageLength(): void - { - $serializer = $this->createSerializer(); - $serializer->setMessageLimit(500); - - foreach ([100, 490, 499, 500, 501, 1000, 10000] as $length) { - $input = str_repeat('x', $length); - $result = $this->invokeSerialization($serializer, $input); - - $this->assertInternalType('string', $result); - $this->assertLessThanOrEqual(500, \strlen($result)); - } - } - /** * @dataProvider serializeAllObjectsDataProvider */ @@ -366,22 +352,6 @@ public function testSetAllObjectSerialize(): void $this->assertFalse($serializer->getSerializeAllObjects()); } - public function testClippingUTF8Characters(): void - { - if (!\extension_loaded('mbstring')) { - $this->markTestSkipped('mbstring extension is not enabled.'); - } - - $serializer = static::createSerializer(); - $serializer->setMessageLimit(19); - - $clipped = $this->invokeSerialization($serializer, 'Прекратите надеÑтьÑÑ, что ваши пользователи будут Ñообщать об ошибках'); - - $this->assertSame('Прекратит {clipped}', $clipped); - $this->assertNotNull(json_encode($clipped)); - $this->assertSame(JSON_ERROR_NONE, json_last_error()); - } - public function serializableCallableProvider(): array { $filename = \dirname(__DIR__) . '/resources/callable_without_namespace.inc'; diff --git a/tests/Serializer/RepresentationSerializerTest.php b/tests/Serializer/RepresentationSerializerTest.php index 65877af21..e2510759c 100644 --- a/tests/Serializer/RepresentationSerializerTest.php +++ b/tests/Serializer/RepresentationSerializerTest.php @@ -4,6 +4,7 @@ namespace Sentry\Tests\Serializer; +use Sentry\Options; use Sentry\Serializer\AbstractSerializer; use Sentry\Serializer\RepresentationSerializer; @@ -129,6 +130,6 @@ public function testSerializeRoundedFloat(bool $serializeAllObjects): void */ protected function createSerializer(): AbstractSerializer { - return new RepresentationSerializer(); + return new RepresentationSerializer(new Options()); } } diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index 179acc886..f43c14ffa 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -4,6 +4,7 @@ namespace Sentry\Tests\Serializer; +use Sentry\Options; use Sentry\Serializer\AbstractSerializer; use Sentry\Serializer\Serializer; @@ -95,11 +96,41 @@ public function testNull(bool $serializeAllObjects): void $this->assertNull($result); } + public function testLongStringWithOverwrittenMessageLength(): void + { + $serializer = $this->createSerializer(new Options(['max_value_length' => 500])); + + foreach ([100, 490, 499, 500, 501, 1000, 10000] as $length) { + $input = str_repeat('x', $length); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(500, \strlen($result)); + } + } + + public function testClippingUTF8Characters(): void + { + $serializer = $this->createSerializer(new Options(['max_value_length' => 19])); + + $clipped = $this->invokeSerialization($serializer, 'Прекратите надеÑтьÑÑ, что ваши пользователи будут Ñообщать об ошибках'); + + $this->assertSame('Прекратит {clipped}', $clipped); + $this->assertNotNull(json_encode($clipped)); + $this->assertSame(JSON_ERROR_NONE, json_last_error()); + } + /** + * @param Options $options|null + * * @return Serializer */ - protected function createSerializer(): AbstractSerializer + protected function createSerializer(?Options $options = null): AbstractSerializer { - return new Serializer(); + if (null === $options) { + $options = new Options(); + } + + return new Serializer($options); } } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index b3d174ffe..e02d5e65e 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -31,8 +31,8 @@ final class StacktraceTest extends TestCase protected function setUp(): void { $this->options = new Options(); - $this->serializer = new Serializer(); - $this->representationSerializer = new RepresentationSerializer(); + $this->serializer = new Serializer($this->options); + $this->representationSerializer = new RepresentationSerializer($this->options); } public function testGetFramesAndToArray(): void @@ -285,7 +285,8 @@ public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void 'function' => 'a_test', ]; - $result = Stacktrace::getFrameArguments($frame, 5); + $stacktrace = new Stacktrace(new Options(['max_value_length' => 5]), $this->serializer, $this->representationSerializer); + $result = $stacktrace->getFrameArguments($frame); // Check we haven't modified our vars. $this->assertEquals($originalFoo, 'bloopblarp'); From bf9184f4e111b7e5fd79823e8fd87c145e37370b Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 1 Feb 2019 16:05:58 +0100 Subject: [PATCH 0418/1161] Lower the default send_attempts option to 3 (#760) Because we use the [RetryPlugin](https://github.com/php-http/client-common/blob/master/src/Plugin/RetryPlugin.php#L148-L162) to handle the send attempts we should consider lowering this since it has an exponential backoff and with the 6 retries I think (if I'm reading it correctly) the total time it tries to send a request (ignoring the time it takes to fail the request) is about 32 seconds which is way to long (since default request timeout in php is 30 seconds). Retry backoff calculation converted to seconds: ``` >>> (pow(2, 0) * 500000) / 1000000 => 0.5 >>> (pow(2, 1) * 500000) / 1000000 => 1 >>> (pow(2, 2) * 500000) / 1000000 => 2 >>> (pow(2, 3) * 500000) / 1000000 => 4 >>> (pow(2, 4) * 500000) / 1000000 => 8 >>> (pow(2, 5) * 500000) / 1000000 => 16 ``` The 3,5 seconds backoff with 3 retries seems way more sensible to me considering the request also takes some time to actually timeout or generate an error. --- src/Options.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options.php b/src/Options.php index 03042478f..949b5db47 100644 --- a/src/Options.php +++ b/src/Options.php @@ -632,7 +632,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'integrations' => [], 'default_integrations' => true, - 'send_attempts' => 6, + 'send_attempts' => 3, 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'sample_rate' => 1, 'attach_stacktrace' => false, From aa444b7807087777f0dcfcebec19268ea667dd4f Mon Sep 17 00:00:00 2001 From: sztyup Date: Mon, 4 Feb 2019 21:51:32 +0100 Subject: [PATCH 0419/1161] Fix computing of method argument names from a XDebug backtrace (#763) --- src/Stacktrace.php | 10 +++++++--- tests/StacktraceTest.php | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 9f493c257..c75cdf1ce 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -301,14 +301,18 @@ protected function stripPrefixFromFilePath(string $filePath): string */ protected function getFrameArgumentsValues(array $frame): array { - if (!isset($frame['args'])) { + if (empty($frame['args'])) { return []; } $result = []; - foreach ($frame['args'] as $index => $argument) { - $result['param' . ($index + 1)] = $this->serializeArgument($argument); + if (\is_string(array_keys($frame['args'])[0])) { + $result = array_map([$this, 'serializeArgument'], $frame['args']); + } else { + foreach (array_values($frame['args']) as $index => $argument) { + $result['param' . ($index + 1)] = $this->serializeArgument($argument); + } } return $result; diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index e02d5e65e..ea6a5e052 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -297,6 +297,25 @@ public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void $this->assertEquals($result['param2']['key'], 'xxxxx'); } + public function testPreserveXdebugFrameArgumentNames(): void + { + $frame = [ + 'file' => __DIR__ . '/resources/a.php', + 'line' => 9, + 'args' => [ + 'foo' => 'bar', + 'alice' => 'bob', + ], + 'function' => 'a_test', + ]; + + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $result = $stacktrace->getFrameArguments($frame); + + $this->assertEquals('bar', $result['foo']); + $this->assertEquals('bob', $result['alice']); + } + private function getFixturePath(string $file): string { $filePath = realpath(__DIR__ . \DIRECTORY_SEPARATOR . 'Fixtures' . \DIRECTORY_SEPARATOR . $file); From a78a4b13996d49b3e441e3d448276fc22c6db3f0 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 5 Feb 2019 00:18:39 +0100 Subject: [PATCH 0420/1161] Add AppVeyor to CI pipeline (#758) --- .appveyor.yml | 33 +++++++++++++++++++++++++++++++++ src/Context/ServerOsContext.php | 2 +- src/Options.php | 8 -------- tests/ClientTest.php | 5 ++++- tests/EventFactoryTest.php | 5 ++++- tests/OptionsTest.php | 2 +- 6 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 000000000..58dcd90f8 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,33 @@ +version: 2.x-{build} +build: false +clone_depth: 2 +clone_folder: c:\projects\sentry-php +skip_branch_with_pr: true + +cache: + - composer.phar + - '%LocalAppData%\Composer\files' + - c:\projects\sentry-php\vendor + +init: + - SET PATH=c:\php;%PATH% + - SET COMPOSER_NO_INTERACTION=1 + - SET SYMFONY_DEPRECATIONS_HELPER=strict + - SET ANSICON=121x90 (121x90) + +install: + - mkdir c:\php && cd c:\php + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-7.1.3-Win32-VC14-x86.zip + - 7z x php-7.1.3-Win32-VC14-x86.zip -y >nul + - echo memory_limit=-1 >> php.ini + - echo extension=php_curl.dll >> php.ini + - echo extension=php_mbstring.dll >> php.ini + - echo extension=php_openssl.dll >> php.ini + - cd c:\projects\sentry-php + - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/1.7.1/composer.phar) + - php composer.phar self-update + - php composer.phar update --no-progress --no-suggest --ansi + +test_script: + - cd c:\projects\sentry-php + - vendor\bin\phpunit.bat diff --git a/src/Context/ServerOsContext.php b/src/Context/ServerOsContext.php index 2ecc21da9..1639d5fb4 100644 --- a/src/Context/ServerOsContext.php +++ b/src/Context/ServerOsContext.php @@ -186,7 +186,7 @@ public function setKernelVersion(string $kernelVersion): void private function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'name' => PHP_OS, + 'name' => php_uname('s'), 'version' => php_uname('r'), 'build' => php_uname('v'), 'kernel_version' => php_uname('a'), diff --git a/src/Options.php b/src/Options.php index 949b5db47..31663c5f4 100644 --- a/src/Options.php +++ b/src/Options.php @@ -720,14 +720,6 @@ private function normalizeAbsolutePath($value) $path = $value; } - if ( - \DIRECTORY_SEPARATOR === substr($path, 0, 1) - && \DIRECTORY_SEPARATOR !== substr($path, -1) - && '.php' !== substr($path, -4) - ) { - $path .= \DIRECTORY_SEPARATOR; - } - return $path; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 05c2d9960..87b4672f5 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -175,11 +175,14 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void $client->captureLastError(); } + /** + * @requires OSFAMILY Linux + */ public function testAppPathLinux(): void { $client = ClientBuilder::create(['project_root' => '/foo/bar'])->getClient(); - $this->assertEquals('/foo/bar/', $client->getOptions()->getProjectRoot()); + $this->assertEquals('/foo/bar', $client->getOptions()->getProjectRoot()); $client->getOptions()->setProjectRoot('/foo/baz/'); diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index a416e0913..8faaec466 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -180,6 +180,9 @@ public function testCreateWithStacktrace(): void /** @var Frame $lastFrame */ $lastFrame = array_reverse($stacktrace->getFrames())[0]; - $this->assertSame('src/EventFactory.php', $lastFrame->getFile()); + $this->assertSame( + 'src' . \DIRECTORY_SEPARATOR . 'EventFactory.php', + ltrim($lastFrame->getFile(), \DIRECTORY_SEPARATOR) + ); } } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 11b0043e4..2832584d9 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -239,7 +239,7 @@ public function excludedPathProviders() return [ ['some/path', 'some/path'], ['some/specific/file.php', 'some/specific/file.php'], - [__DIR__, __DIR__ . '/'], + [__DIR__, __DIR__], [__FILE__, __FILE__], ]; } From 9028676773d6a83b72b314532c67f9d90cbe4d4c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 7 Feb 2019 11:08:53 +0100 Subject: [PATCH 0421/1161] Make the error handler a singleton and add support for listeners (#762) --- .travis.yml | 4 +- UPGRADE-2.0.md | 4 +- src/ClientBuilder.php | 6 +- src/ErrorHandler.php | 175 ++++------ src/Integration/ErrorHandlerIntegration.php | 61 ---- src/Integration/ErrorListenerIntegration.php | 43 +++ .../ExceptionListenerIntegration.php | 25 ++ tests/ClientBuilderTest.php | 16 +- tests/ErrorHandlerTest.php | 311 ++++++++---------- tests/Fixtures/classes/StubErrorListener.php | 40 +++ tests/Fixtures/classes/StubTransport.php | 40 +++ tests/SdkTest.php | 6 +- tests/Serializer/AbstractSerializerTest.php | 2 +- tests/Util/JSONTest.php | 2 +- tests/phpt/error_handler_called.phpt | 9 +- .../error_handler_refuses_negative_value.phpt | 27 ++ .../error_handler_respects_captureAt.phpt | 32 -- tests/phpt/fatal_error.phpt | 20 +- tests/phpt/send_only_all_except_notice.phpt | 56 ++++ 19 files changed, 471 insertions(+), 408 deletions(-) delete mode 100644 src/Integration/ErrorHandlerIntegration.php create mode 100644 src/Integration/ErrorListenerIntegration.php create mode 100644 src/Integration/ExceptionListenerIntegration.php create mode 100644 tests/Fixtures/classes/StubErrorListener.php create mode 100644 tests/Fixtures/classes/StubTransport.php create mode 100644 tests/phpt/error_handler_refuses_negative_value.phpt delete mode 100644 tests/phpt/error_handler_respects_captureAt.phpt create mode 100644 tests/phpt/send_only_all_except_notice.phpt diff --git a/.travis.yml b/.travis.yml index b521de777..b78bf7bdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,8 @@ php: - nightly env: - - dependencies=lowest - dependencies=highest + - dependencies=lowest matrix: fast_finish: true @@ -28,8 +28,10 @@ jobs: include: - stage: Code style & static analysis name: PHP CS Fixer + env: dependencies=highest script: composer phpcs - script: composer phpstan + env: dependencies=highest name: PHPStan - stage: Code coverage php: 7.3 diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 789f1dca2..22ab7605a 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -536,8 +536,8 @@ ``` - The method `Raven_Client::install` has been removed. The error handler is - registered automatically when using the `ErrorHandlerIntegration` integration - (which is enabled by default). + registered automatically when using the `ExceptionListenerIntegration` + and `ErrorListenerIntegration` integrations (which are enabled by default). ### Processors diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index a7b54abe3..28a61293c 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -20,7 +20,8 @@ use Http\Message\UriFactory; use Jean85\PrettyVersions; use Sentry\HttpClient\Authentication\SentryAuthentication; -use Sentry\Integration\ErrorHandlerIntegration; +use Sentry\Integration\ErrorListenerIntegration; +use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\RequestIntegration; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; @@ -98,7 +99,8 @@ public function __construct(Options $options = null) if ($this->options->hasDefaultIntegrations()) { $this->options->setIntegrations(array_merge([ - new ErrorHandlerIntegration(), + new ExceptionListenerIntegration(), + new ErrorListenerIntegration($this->options), new RequestIntegration($this->options), ], $this->options->getIntegrations())); } diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index e1c3003e9..c514e0465 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -20,9 +20,19 @@ final class ErrorHandler private const DEFAULT_RESERVED_MEMORY_SIZE = 10240; /** - * @var callable Callback that will be invoked when an error is caught + * @var self The current registered handler (this class is a singleton) */ - private $callback; + private static $handlerInstance; + + /** + * @var callable[] List of listeners that will act on each captured error + */ + private $errorListeners = []; + + /** + * @var callable[] List of listeners that will act on each captured exception + */ + private $exceptionListeners = []; /** * @var \ReflectionProperty A reflection cached instance that points to the @@ -40,17 +50,6 @@ final class ErrorHandler */ private $previousExceptionHandler; - /** - * @var int The errors that will be catched by the error handler - */ - private $capturedErrors = E_ALL; - - /** - * @var bool Flag indicating whether this error handler is the first in the - * chain of registered error handlers - */ - private $isRoot = false; - /** * @var string|null A portion of pre-allocated memory data that will be reclaimed * in case a fatal error occurs to handle it @@ -81,26 +80,20 @@ final class ErrorHandler /** * Constructor. * - * @param callable $callback The callback that will be invoked in case an error is caught - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler - * - * @throws \ReflectionException + * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler */ - private function __construct(callable $callback, int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE) + private function __construct(int $reservedMemorySize) { if ($reservedMemorySize <= 0) { - throw new \UnexpectedValueException('The $reservedMemorySize argument must be greater than 0.'); + throw new \InvalidArgumentException('The $reservedMemorySize argument must be greater than 0.'); } - $this->callback = $callback; $this->exceptionReflection = new \ReflectionProperty(\Exception::class, 'trace'); $this->exceptionReflection->setAccessible(true); - if (null === self::$reservedMemory) { - self::$reservedMemory = str_repeat('x', $reservedMemorySize); + self::$reservedMemory = str_repeat('x', $reservedMemorySize); - register_shutdown_function([$this, 'handleFatalError']); - } + register_shutdown_function([$this, 'handleFatalError']); $this->previousErrorHandler = set_error_handler([$this, 'handleError']); @@ -111,52 +104,57 @@ private function __construct(callable $callback, int $reservedMemorySize = self: // first call to the set_error_handler method would cause the PHP // bug https://bugs.php.net/63206 if the handler is not the first // one in the chain of handlers - set_error_handler([$this, 'handleError'], $this->capturedErrors); - - $this->isRoot = true; + set_error_handler([$this, 'handleError'], E_ALL); } $this->previousExceptionHandler = set_exception_handler([$this, 'handleException']); } /** - * Registers this error handler and associates the given callback to the - * instance. + * Gets the current registered error handler; if none is present, it will register it. + * Subsequent calls will not change the reserved memory size. * - * @param callable $callback callback that will be called when exception is caught - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * @param int $reservedMemorySize The requested amount of memory to reserve * - * @return self - * - * @throws \ReflectionException + * @return self The ErrorHandler singleton */ - public static function register(callable $callback, int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE): self + public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE): self { - return new self($callback, $reservedMemorySize); + if (null === self::$handlerInstance) { + self::$handlerInstance = new self($reservedMemorySize); + } + + return self::$handlerInstance; } /** - * Sets the PHP error levels that will be captured by the Raven client when - * a PHP error occurs. - * - * @param int $levels A bit field of E_* constants for captured errors - * @param bool $replace Whether to replace or amend the previous value + * Adds a listener to the current error handler to be called upon each + * invoked captured error; if no handler is registered, this method will + * instantiate and register it. * - * @return int The previous value + * @param callable $listener A callable that will act as a listener; + * this callable will receive a single + * \ErrorException argument */ - public function captureAt(int $levels, bool $replace = false): int + public static function addErrorListener(callable $listener): void { - $prev = $this->capturedErrors; - - $this->capturedErrors = $levels; - - if (!$replace) { - $this->capturedErrors |= $prev; - } - - $this->reRegister($prev); + $handler = self::registerOnce(); + $handler->errorListeners[] = $listener; + } - return $prev; + /** + * Adds a listener to the current error handler to be called upon each + * invoked captured exception; if no handler is registered, this method + * will instantiate and register it. + * + * @param callable $listener A callable that will act as a listener; + * this callable will receive a single + * \Throwable argument + */ + public static function addExceptionListener(callable $listener): void + { + $handler = self::registerOnce(); + $handler->exceptionListeners[] = $listener; } /** @@ -178,22 +176,12 @@ public function captureAt(int $levels, bool $replace = false): int */ public function handleError(int $level, string $message, string $file, int $line): bool { - $shouldCaptureError = (bool) ($this->capturedErrors & $level); - - if (!$shouldCaptureError) { - return false; - } - $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $file, $line); $this->exceptionReflection->setValue($errorAsException, $backtrace); - try { - $this->handleException($errorAsException); - } catch (\Throwable $exception) { - // Do nothing as this error handler should be as transparent as possible - } + $this->invokeListeners($this->errorListeners, $errorAsException); if (null !== $this->previousErrorHandler) { return false !== \call_user_func($this->previousErrorHandler, $level, $message, $file, $line); @@ -227,30 +215,24 @@ public function handleFatalError(array $error = null): void if (!empty($error) && $error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)) { $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); - } - try { - if (null !== $errorAsException) { - $this->handleException($errorAsException); - } - } catch (\Throwable $errorAsException) { - // Ignore this re-throw + $this->invokeListeners($this->errorListeners, $errorAsException); } } /** - * Handles the given exception by capturing it through the Raven client and + * Handles the given exception by passing it to all the listeners, * then forwarding it to another handler. * * @param \Throwable $exception The exception to handle * * @throws \Throwable * - * @internal + * @internal This method is public only because it's used with set_exception_handler */ public function handleException(\Throwable $exception): void { - \call_user_func($this->callback, $exception); + $this->invokeListeners($this->exceptionListeners, $exception); $previousExceptionHandlerException = $exception; @@ -282,34 +264,6 @@ public function handleException(\Throwable $exception): void $this->handleException($previousExceptionHandlerException); } - /** - * Re-registers the error handler if the mask that configures the intercepted - * error types changed. - * - * @param int $previousThrownErrors The previous error mask - */ - private function reRegister(int $previousThrownErrors): void - { - if ($this->capturedErrors === $previousThrownErrors) { - return; - } - - $handler = set_error_handler('var_dump'); - $handler = \is_array($handler) ? $handler[0] : null; - - restore_error_handler(); - - if ($handler === $this) { - restore_error_handler(); - - if ($this->isRoot) { - set_error_handler([$this, 'handleError'], $this->capturedErrors); - } else { - set_error_handler([$this, 'handleError']); - } - } - } - /** * Cleans and returns the backtrace without the first frames that belong to * this error handler. @@ -337,4 +291,21 @@ private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $ return $cleanedBacktrace; } + + /** + * Invokes all the listeners and pass the exception to all of them. + * + * @param callable[] $listeners The array of listeners to be called + * @param \Throwable $throwable The exception to be passed onto listeners + */ + private function invokeListeners(array $listeners, \Throwable $throwable): void + { + foreach ($listeners as $listener) { + try { + \call_user_func($listener, $throwable); + } catch (\Throwable $exception) { + // Do nothing as this should be as transparent as possible + } + } + } } diff --git a/src/Integration/ErrorHandlerIntegration.php b/src/Integration/ErrorHandlerIntegration.php deleted file mode 100644 index 27b57df2f..000000000 --- a/src/Integration/ErrorHandlerIntegration.php +++ /dev/null @@ -1,61 +0,0 @@ -getIntegration(self::class); - - if ($self instanceof self) { - $self->addBreadcrumb($exception); - $self->captureException($exception); - } - }); - } - - /** - * Captures the exception and sends it to Sentry. - * - * @param \Throwable $exception The exception that will be captured by the current client - */ - private function captureException(\Throwable $exception): void - { - Hub::getCurrent()->captureException($exception); - } - - /** - * Adds a breadcrumb of the error. - * - * @param \Throwable $exception The exception used to create a breadcrumb - */ - private function addBreadcrumb(\Throwable $exception): void - { - if ($exception instanceof \ErrorException) { - /* @var \ErrorException $exception */ - Hub::getCurrent()->addBreadcrumb(new Breadcrumb( - Breadcrumb::levelFromErrorException($exception), - Breadcrumb::TYPE_ERROR, - 'error_reporting', - $exception->getMessage(), - [ - 'code' => $exception->getCode(), - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - ] - )); - } - } -} diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php new file mode 100644 index 000000000..cfc84343e --- /dev/null +++ b/src/Integration/ErrorListenerIntegration.php @@ -0,0 +1,43 @@ +options = $options; + } + + /** + * {@inheritdoc} + */ + public function setupOnce(): void + { + ErrorHandler::addErrorListener(function (\ErrorException $error): void { + if ($this->options->getErrorTypes() & $error->getSeverity()) { + Hub::getCurrent()->captureException($error); + } + }); + } +} diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php new file mode 100644 index 000000000..f0fb76a44 --- /dev/null +++ b/src/Integration/ExceptionListenerIntegration.php @@ -0,0 +1,25 @@ +captureException($throwable); + }); + } +} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 4e38a1834..b9e237d2d 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -16,7 +16,8 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\Integration\ErrorHandlerIntegration; +use Sentry\Integration\ErrorListenerIntegration; +use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; use Sentry\Options; @@ -189,12 +190,21 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array [ true, [], - [RequestIntegration::class, ErrorHandlerIntegration::class], + [ + ErrorListenerIntegration::class, + ExceptionListenerIntegration::class, + RequestIntegration::class, + ], ], [ true, [new StubIntegration()], - [RequestIntegration::class, ErrorHandlerIntegration::class, StubIntegration::class], + [ + ErrorListenerIntegration::class, + ExceptionListenerIntegration::class, + RequestIntegration::class, + StubIntegration::class, + ], ], ]; } diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 6e85eb7d3..81fe1ae6e 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -6,153 +6,51 @@ use PHPUnit\Framework\TestCase; use Sentry\ErrorHandler; +use Sentry\Tests\Fixtures\classes\StubErrorListener; final class ErrorHandlerTest extends TestCase { - protected $callbackMock; - - protected function setUp(): void - { - $this->callbackMock = $this->createPartialMock(\stdClass::class, ['__invoke']); - } - - public function testConstructor(): void - { - try { - $errorHandler = ErrorHandler::register($this->callbackMock); - $previousErrorHandler = set_error_handler('var_dump'); - - restore_error_handler(); - - $this->assertSame([$errorHandler, 'handleError'], $previousErrorHandler); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - /** - * @dataProvider constructorThrowsWhenReservedMemorySizeIsWrongDataProvider - * - * @expectedException \UnexpectedValueException - * @expectedExceptionMessage The $reservedMemorySize argument must be greater than 0. - */ - public function testConstructorThrowsWhenReservedMemorySizeIsWrong(int $reservedMemorySize): void - { - ErrorHandler::register($this->callbackMock, $reservedMemorySize); - } - - public function constructorThrowsWhenReservedMemorySizeIsWrongDataProvider(): array - { - return [ - [-1], - [0], - ]; - } - - /** - * @dataProvider handleErrorShouldNotCaptureDataProvider - */ - public function testHandleErrorShouldNotCapture(bool $expectedToCapture, int $captureAt): void - { - if (!$expectedToCapture) { - $this->callbackMock->expects($this->never()) - ->method('__invoke'); - } - - $errorHandler = ErrorHandler::register($this->callbackMock); - $errorHandler->captureAt($captureAt, true); - - $prevErrorReporting = error_reporting(E_ERROR); // to avoid making the test error bubble up and make the test fail - - try { - $this->assertFalse($errorHandler->handleError(E_WARNING, 'Test', __FILE__, __LINE__)); - } finally { - error_reporting($prevErrorReporting); - restore_error_handler(); - restore_exception_handler(); - $this->addToAssertionCount(1); - } - } - - public function handleErrorShouldNotCaptureDataProvider(): array + public function testRegisterOnce(): void { - return [ - [false, E_ERROR], - [true, E_ALL], - ]; - } - - /** - * @dataProvider captureAtDataProvider - */ - public function testCaptureAt($levels, $replace, $expectedCapturedErrors): void - { - try { - $errorHandler = ErrorHandler::register($this->callbackMock); - $previousCapturedErrors = $this->getObjectAttribute($errorHandler, 'capturedErrors'); - - $this->assertEquals($previousCapturedErrors, $errorHandler->captureAt($levels, $replace)); - $this->assertAttributeEquals($expectedCapturedErrors, 'capturedErrors', $errorHandler); - } finally { - restore_error_handler(); - restore_exception_handler(); - } - } - - public function captureAtDataProvider(): array - { - return [ - [E_USER_NOTICE, false, E_ALL], - [E_USER_NOTICE, true, E_USER_NOTICE], - ]; + $this->assertSame(ErrorHandler::registerOnce(), ErrorHandler::registerOnce()); } public function testHandleError(): void { - $this->callbackMock->expects($this->exactly(1)) - ->method('__invoke') - ->with($this->callback(function ($exception) { - /* @var \ErrorException $exception */ - $this->assertInstanceOf(\ErrorException::class, $exception); - $this->assertEquals(__FILE__, $exception->getFile()); - $this->assertEquals(123, $exception->getLine()); - $this->assertEquals(E_USER_NOTICE, $exception->getSeverity()); - $this->assertEquals('User Notice: foo bar', $exception->getMessage()); - - $backtrace = $exception->getTrace(); - - $this->assertGreaterThanOrEqual(2, $backtrace); - - $this->assertEquals('handleError', $backtrace[0]['function']); - $this->assertEquals(ErrorHandler::class, $backtrace[0]['class']); - $this->assertEquals('->', $backtrace[0]['type']); - - $this->assertEquals('testHandleError', $backtrace[1]['function']); - $this->assertEquals(__CLASS__, $backtrace[1]['class']); - $this->assertEquals('->', $backtrace[1]['type']); - - return true; - })); + $listener = new StubErrorListener(); + $errorLine = null; try { - $errorHandler = ErrorHandler::register($this->callbackMock); - $errorHandler->captureAt(0, true); + ErrorHandler::addErrorListener($listener); + + $errorHandler = ErrorHandler::registerOnce(); $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousErrorHandler'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($errorHandler, null); $reflectionProperty->setAccessible(false); - $this->assertFalse($errorHandler->handleError(0, 'foo bar', __FILE__, __LINE__)); + $errorLine = __LINE__ + 2; - $errorHandler->captureAt(E_USER_NOTICE, true); - - $this->assertFalse($errorHandler->handleError(E_USER_WARNING, 'foo bar', __FILE__, __LINE__)); - $this->assertFalse($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, 123)); + $this->assertFalse($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__)); } finally { restore_error_handler(); restore_exception_handler(); + + $exception = $listener->getError(); + + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertEquals($errorLine, $exception->getLine()); + $this->assertEquals('User Notice: foo bar', $exception->getMessage()); + $this->assertEquals(E_USER_NOTICE, $exception->getSeverity()); + + $backtrace = $exception->getTrace(); + + $this->assertGreaterThanOrEqual(2, $backtrace); + $this->assertEquals('testHandleError', $backtrace[0]['function']); + $this->assertEquals(self::class, $backtrace[0]['class']); + $this->assertEquals('->', $backtrace[0]['type']); } } @@ -161,39 +59,18 @@ public function testHandleError(): void */ public function testHandleErrorWithPreviousErrorHandler($previousErrorHandlerErrorReturnValue, bool $expectedHandleErrorReturnValue): void { - $this->callbackMock->expects($this->once()) - ->method('__invoke') - ->with($this->callback(function ($exception) { - /* @var \ErrorException $exception */ - $this->assertInstanceOf(\ErrorException::class, $exception); - $this->assertEquals(__FILE__, $exception->getFile()); - $this->assertEquals(123, $exception->getLine()); - $this->assertEquals(E_USER_NOTICE, $exception->getSeverity()); - $this->assertEquals('User Notice: foo bar', $exception->getMessage()); - - $backtrace = $exception->getTrace(); - - $this->assertGreaterThanOrEqual(2, $backtrace); - - $this->assertEquals('handleError', $backtrace[0]['function']); - $this->assertEquals(ErrorHandler::class, $backtrace[0]['class']); - $this->assertEquals('->', $backtrace[0]['type']); - - $this->assertEquals('testHandleErrorWithPreviousErrorHandler', $backtrace[1]['function']); - $this->assertEquals(__CLASS__, $backtrace[1]['class']); - $this->assertEquals('->', $backtrace[1]['type']); - - return true; - })); - $previousErrorHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); $previousErrorHandler->expects($this->once()) ->method('__invoke') ->with(E_USER_NOTICE, 'foo bar', __FILE__, 123) ->willReturn($previousErrorHandlerErrorReturnValue); + $listener = new StubErrorListener(); + try { - $errorHandler = ErrorHandler::register($this->callbackMock); + ErrorHandler::addErrorListener($listener); + + $errorHandler = ErrorHandler::registerOnce(); $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousErrorHandler'); $reflectionProperty->setAccessible(true); @@ -204,6 +81,24 @@ public function testHandleErrorWithPreviousErrorHandler($previousErrorHandlerErr } finally { restore_error_handler(); restore_exception_handler(); + + $exception = $listener->getError(); + + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertEquals(123, $exception->getLine()); + $this->assertEquals(E_USER_NOTICE, $exception->getSeverity()); + $this->assertEquals('User Notice: foo bar', $exception->getMessage()); + + $backtrace = $exception->getTrace(); + + $this->assertGreaterThanOrEqual(2, $backtrace); + $this->assertEquals('handleError', $backtrace[0]['function']); + $this->assertEquals(ErrorHandler::class, $backtrace[0]['class']); + $this->assertEquals('->', $backtrace[0]['type']); + $this->assertEquals('testHandleErrorWithPreviousErrorHandler', $backtrace[1]['function']); + $this->assertEquals(self::class, $backtrace[1]['class']); + $this->assertEquals('->', $backtrace[1]['type']); } } @@ -221,21 +116,13 @@ public function handleErrorWithPreviousErrorHandlerDataProvider(): array public function testHandleFatalError(): void { - $this->callbackMock->expects($this->exactly(1)) - ->method('__invoke') - ->with($this->callback(function ($exception) { - /* @var \ErrorException $exception */ - $this->assertInstanceOf(\ErrorException::class, $exception); - $this->assertEquals(__FILE__, $exception->getFile()); - $this->assertEquals(123, $exception->getLine()); - $this->assertEquals(E_PARSE, $exception->getSeverity()); - $this->assertEquals('Parse Error: foo bar', $exception->getMessage()); - - return true; - })); + $listener = new StubErrorListener(); try { - $errorHandler = ErrorHandler::register($this->callbackMock); + ErrorHandler::addErrorListener($listener); + + $errorHandler = ErrorHandler::registerOnce(); + $errorHandler->handleFatalError([ 'type' => E_PARSE, 'message' => 'foo bar', @@ -245,16 +132,26 @@ public function testHandleFatalError(): void } finally { restore_error_handler(); restore_exception_handler(); + + $exception = $listener->getError(); + + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertEquals(123, $exception->getLine()); + $this->assertEquals(E_PARSE, $exception->getSeverity()); + $this->assertEquals('Parse Error: foo bar', $exception->getMessage()); } } public function testHandleFatalErrorWithNonFatalErrorDoesNothing(): void { - $this->callbackMock->expects($this->never()) - ->method('__invoke'); + $listener = new StubErrorListener(); try { - $errorHandler = ErrorHandler::register($this->callbackMock); + ErrorHandler::addErrorListener($listener); + + $errorHandler = ErrorHandler::registerOnce(); + $errorHandler->handleFatalError([ 'type' => E_USER_NOTICE, 'message' => 'foo bar', @@ -264,19 +161,25 @@ public function testHandleFatalErrorWithNonFatalErrorDoesNothing(): void } finally { restore_error_handler(); restore_exception_handler(); + + $this->assertNull($listener->getError()); } } public function testHandleException(): void { + $listenerCalled = false; $exception = new \Exception('foo bar'); + $listener = function (\Throwable $throwable) use ($exception, &$listenerCalled): void { + $listenerCalled = true; - $this->callbackMock->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($exception)); + $this->assertSame($exception, $throwable); + }; try { - $errorHandler = ErrorHandler::register($this->callbackMock); + ErrorHandler::addExceptionListener($listener); + + $errorHandler = ErrorHandler::registerOnce(); try { $errorHandler->handleException($exception); @@ -288,16 +191,21 @@ public function testHandleException(): void } finally { restore_error_handler(); restore_exception_handler(); + + $this->assertTrue($listenerCalled, 'Listener was not called'); } } public function testHandleExceptionWithPreviousExceptionHandler(): void { + $listenerCalled = false; $exception = new \Exception('foo bar'); - $this->callbackMock->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($exception)); + $listener = function (\Throwable $throwable) use ($exception, &$listenerCalled): void { + $listenerCalled = true; + + $this->assertSame($exception, $throwable); + }; $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); $previousExceptionHandler->expects($this->once()) @@ -305,7 +213,9 @@ public function testHandleExceptionWithPreviousExceptionHandler(): void ->with($this->identicalTo($exception)); try { - $errorHandler = ErrorHandler::register($this->callbackMock); + ErrorHandler::addExceptionListener($listener); + + $errorHandler = ErrorHandler::registerOnce(); $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousExceptionHandler'); $reflectionProperty->setAccessible(true); @@ -322,17 +232,27 @@ public function testHandleExceptionWithPreviousExceptionHandler(): void } finally { restore_error_handler(); restore_exception_handler(); + + $this->assertTrue($listenerCalled, 'Listener was not called'); } } public function testHandleExceptionWithThrowingPreviousExceptionHandler(): void { + $listenerCalled = 0; $exception1 = new \Exception('foo bar'); $exception2 = new \Exception('bar foo'); + $captured1 = $captured2 = null; - $this->callbackMock->expects($this->exactly(2)) - ->method('__invoke') - ->withConsecutive($this->identicalTo($exception1), $this->identicalTo($exception2)); + $listener = function (\Throwable $throwable) use (&$captured1, &$captured2, &$listenerCalled): void { + if (0 === $listenerCalled) { + $captured1 = $throwable; + } elseif (1 === $listenerCalled) { + $captured2 = $throwable; + } + + ++$listenerCalled; + }; $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); $previousExceptionHandler->expects($this->once()) @@ -341,7 +261,9 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler(): void ->will($this->throwException($exception2)); try { - $errorHandler = ErrorHandler::register($this->callbackMock); + ErrorHandler::addExceptionListener($listener); + + $errorHandler = ErrorHandler::registerOnce(); $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousExceptionHandler'); $reflectionProperty->setAccessible(true); @@ -358,6 +280,33 @@ public function testHandleExceptionWithThrowingPreviousExceptionHandler(): void } finally { restore_error_handler(); restore_exception_handler(); + + $this->assertSame(2, $listenerCalled); + $this->assertSame($exception1, $captured1); + $this->assertSame($exception2, $captured2); + } + } + + public function testListenerThatThrowsExceptionShouldBeIgnored(): void + { + $exception = new \Exception(); + $exceptionRethrown = false; + $listener = function (\Throwable $throwable): void { + throw new \RuntimeException('This exception should not bubble up'); + }; + + try { + ErrorHandler::addExceptionListener($listener); + ErrorHandler::registerOnce()->handleException($exception); + } catch (\Throwable $rethrownException) { + $this->assertSame($exception, $rethrownException); + + $exceptionRethrown = true; + } finally { + $this->assertTrue($exceptionRethrown, 'Handler did not rethrow the exception'); + + restore_error_handler(); + restore_exception_handler(); } } } diff --git a/tests/Fixtures/classes/StubErrorListener.php b/tests/Fixtures/classes/StubErrorListener.php new file mode 100644 index 000000000..eee8ee627 --- /dev/null +++ b/tests/Fixtures/classes/StubErrorListener.php @@ -0,0 +1,40 @@ +callable = $callable; + } + + public function __invoke(\ErrorException $error): void + { + $this->error = $error; + + if ($this->callable) { + call_user_func($this->callable, $error); + } + } + + public function someCallable(\ErrorException $error): void + { + $this->__invoke($error); + } + + public function getError(): ?\ErrorException + { + return $this->error; + } +} diff --git a/tests/Fixtures/classes/StubTransport.php b/tests/Fixtures/classes/StubTransport.php new file mode 100644 index 000000000..f5c6eb232 --- /dev/null +++ b/tests/Fixtures/classes/StubTransport.php @@ -0,0 +1,40 @@ +events[] = $event; + $this->lastSent = $event; + + return $event->getId(); + } + + /** + * @return Event[] + */ + public function getEvents(): array + { + return $this->events; + } + + public function getLastSent(): ?Event + { + return $this->lastSent; + } +} diff --git a/tests/SdkTest.php b/tests/SdkTest.php index 4271282c4..9f9b0b013 100644 --- a/tests/SdkTest.php +++ b/tests/SdkTest.php @@ -6,16 +6,16 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Sentry\Breadcrumb; -use Sentry\ClientInterface; -use Sentry\State\Hub; use function Sentry\addBreadcrumb; +use Sentry\Breadcrumb; use function Sentry\captureEvent; use function Sentry\captureException; use function Sentry\captureLastError; use function Sentry\captureMessage; +use Sentry\ClientInterface; use function Sentry\configureScope; use function Sentry\init; +use Sentry\State\Hub; use function Sentry\withScope; class SdkTest extends TestCase diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index a7c9b3daf..a794005cb 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -332,7 +332,7 @@ public function testSerializeValueResource(bool $serializeAllObjects): void $this->assertNotFalse($filename, 'Temp file creation failed'); - $resource = fopen($filename, 'wb'); + $resource = fopen($filename, 'w'); $result = $this->invokeSerialization($serializer, $resource); diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index 7f1d24289..e9857fe83 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -63,7 +63,7 @@ public function encodeDataProvider(): array */ public function testEncodeThrowsIfValueIsResource(): void { - $resource = fopen('php://memory', 'rb'); + $resource = fopen('php://memory', 'r'); $this->assertNotFalse($resource); diff --git a/tests/phpt/error_handler_called.phpt b/tests/phpt/error_handler_called.phpt index 6489abaac..a9070611b 100644 --- a/tests/phpt/error_handler_called.phpt +++ b/tests/phpt/error_handler_called.phpt @@ -1,13 +1,12 @@ --TEST-- -Test that the error handler is not called regardless of the current -`error_reporting` setting if its own `captureAt` configuration doesn't match -the level of the thrown error. +Test that the error handler is called regardless of the current `error_reporting` setting --FILE-- getMessage(); +} +?> +--EXPECTF-- +Exception caught: The $reservedMemorySize argument must be greater than 0. diff --git a/tests/phpt/error_handler_respects_captureAt.phpt b/tests/phpt/error_handler_respects_captureAt.phpt deleted file mode 100644 index e4de50245..000000000 --- a/tests/phpt/error_handler_respects_captureAt.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Even if the error is catched by the current error_reporting setting, Sentry's error handler respects its own capture -level, and it should NOT be invoked in this case ---FILE-- -captureAt(E_ERROR, true); - -echo 'Triggering error'; -trigger_error('Triggered error which will be captured by PHP error handler'); -echo 'End'; -?> ---EXPECTREGEX-- -Triggering error -Notice: Triggered error which will be captured by PHP error handler .+ -End diff --git a/tests/phpt/fatal_error.phpt b/tests/phpt/fatal_error.phpt index c6d25f495..4d040c05f 100644 --- a/tests/phpt/fatal_error.phpt +++ b/tests/phpt/fatal_error.phpt @@ -5,9 +5,9 @@ Test catching fatal errors namespace Sentry\Tests; -use PHPUnit\Framework\Assert; +use Sentry\ErrorHandler; +use Sentry\Tests\Fixtures\classes\StubErrorListener; use function Sentry\init; -use Sentry\State\Hub; $vendor = __DIR__; @@ -22,17 +22,9 @@ init([ 'send_attempts' => 1, ]); -register_shutdown_function('register_shutdown_function', function () { - $client = Hub::getCurrent()->getClient(); - - /** @var \Sentry\Transport\HttpTransport $transport */ - $transport = Assert::getObjectAttribute($client, 'transport'); - - Assert::assertAttributeEmpty('pendingRequests', $transport); - Assert::assertNotNull(Hub::getCurrent()->getLastEventId()); - - echo 'Shutdown function called'; -}); +ErrorHandler::addErrorListener(new StubErrorListener(function () { + echo 'Listener called'; +})); class TestClass implements \Serializable { @@ -40,4 +32,4 @@ class TestClass implements \Serializable ?> --EXPECTF-- Fatal error: Class Sentry\Tests\TestClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d -Shutdown function called +Listener called diff --git a/tests/phpt/send_only_all_except_notice.phpt b/tests/phpt/send_only_all_except_notice.phpt new file mode 100644 index 000000000..31795a01e --- /dev/null +++ b/tests/phpt/send_only_all_except_notice.phpt @@ -0,0 +1,56 @@ +--TEST-- +Test catching fatal errors +--FILE-- + E_ALL & ~E_USER_NOTICE +]; + +init($options); + +error_reporting(E_ALL); + +$stubTransport = new StubTransport(); +$client = ClientBuilder::create($options) + ->setTransport($stubTransport) + ->getClient(); + +$hub = Hub::getCurrent(); +$hub->bindClient($client); + +trigger_error('Cannot divide by zero', E_USER_NOTICE); + +Assert::assertEmpty($stubTransport->getEvents()); + +trigger_error('Cannot divide by zero', E_USER_WARNING); + +Assert::assertCount(1, $stubTransport->getEvents()); + +$event = $stubTransport->getLastSent(); + +Assert::assertSame($event->getId(), $hub->getLastEventId()); + +echo 'All assertions executed'; +?> +--EXPECTREGEX-- +Notice: Cannot divide by zero in .* on line \d+ + +Warning: Cannot divide by zero in .* on line \d+ +All assertions executed From d451b2f5bdf7ce1f84fcc1e2380dc6d2178f7f5b Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 7 Feb 2019 11:10:02 +0100 Subject: [PATCH 0422/1161] Add AppVeyor status badge to the README (#765) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ce0005bf1..067d60f7b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ # Sentry for PHP [![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master)](http://travis-ci.org/getsentry/sentry-php) +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/getsentry/sentry-php?branch=master&svg=true)](https://ci.appveyor.com/project/sentry/sentry-php) [![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) [![Monthly Downloads](https://poser.pugx.org/sentry/sentry/d/monthly)](https://packagist.org/packages/sentry/sentry) [![Latest Stable Version](https://poser.pugx.org/sentry/sentry/v/stable)](https://packagist.org/packages/sentry/sentry) From ebf78d061646b5025f8c8ddbd314aa93a23ee008 Mon Sep 17 00:00:00 2001 From: Matt Zuba Date: Fri, 8 Feb 2019 01:26:20 -0700 Subject: [PATCH 0423/1161] Ensure context lines option is used (fixes #740) (#743) * Ensure context lines option is used (fixes #740) * Update tests for context lines to add dataprovider and new tests * Code style/convention updates for StacktraceTest * Yoda condition preferred --- src/Options.php | 10 +++---- src/Stacktrace.php | 7 +---- tests/StacktraceTest.php | 59 ++++++++++++++++++---------------------- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/Options.php b/src/Options.php index 31663c5f4..720685566 100644 --- a/src/Options.php +++ b/src/Options.php @@ -159,9 +159,9 @@ public function setAttachStacktrace(bool $enable): void /** * Gets the number of lines of code context to capture, or null if none. * - * @return int|null + * @return int */ - public function getContextLines(): ?int + public function getContextLines(): int { return $this->options['context_lines']; } @@ -169,9 +169,9 @@ public function getContextLines(): ?int /** * Sets the number of lines of code context to capture, or null if none. * - * @param int|null $contextLines The number of lines of code + * @param int $contextLines The number of lines of code */ - public function setContextLines(?int $contextLines): void + public function setContextLines(int $contextLines): void { $options = array_merge($this->options, ['context_lines' => $contextLines]); @@ -636,7 +636,7 @@ private function configureOptions(OptionsResolver $resolver): void 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'sample_rate' => 1, 'attach_stacktrace' => false, - 'context_lines' => 3, + 'context_lines' => 5, 'enable_compression' => true, 'environment' => null, 'project_root' => null, diff --git a/src/Stacktrace.php b/src/Stacktrace.php index c75cdf1ce..a3781eb98 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -14,11 +14,6 @@ */ class Stacktrace implements \JsonSerializable { - /** - * This constant defines the default number of lines of code to include. - */ - private const CONTEXT_NUM_LINES = 5; - /** * @var Options The client options */ @@ -129,7 +124,7 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void } $frame = new Frame($functionName, $this->stripPrefixFromFilePath($file), $line); - $sourceCodeExcerpt = self::getSourceCodeExcerpt($file, $line, self::CONTEXT_NUM_LINES); + $sourceCodeExcerpt = $this->getSourceCodeExcerpt($file, $line, $this->options->getContextLines()); if (isset($sourceCodeExcerpt['pre_context'])) { $frame->setPreContext($sourceCodeExcerpt['pre_context']); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index ea6a5e052..7c5bd2849 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -138,54 +138,47 @@ public function testAddFrameMarksAsInApp(): void $this->assertTrue($frames[1]->isInApp()); } - public function testAddFrameReadsCodeFromShortFile(): void + /** + * @dataProvider addFrameRespectsContextLinesOptionDataProvider + */ + public function testAddFrameRespectsContextLinesOption(string $fixture, int $lineNumber, ?int $contextLines, int $preContextCount, int $postContextCount): void { - $fileContent = explode("\n", $this->getFixture('code/ShortFile.php')); + if (null !== $contextLines) { + $this->options->setContextLines($contextLines); + } + + $fileContent = explode("\n", $this->getFixture($fixture)); $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); - $stacktrace->addFrame($this->getFixturePath('code/ShortFile.php'), 3, ['function' => '[unknown]']); + $stacktrace->addFrame($this->getFixturePath($fixture), $lineNumber, ['function' => '[unknown]']); $frames = $stacktrace->getFrames(); $this->assertCount(1, $frames); - $this->assertCount(2, $frames[0]->getPreContext()); - $this->assertCount(2, $frames[0]->getPostContext()); + $this->assertCount($preContextCount, $frames[0]->getPreContext()); + $this->assertCount($postContextCount, $frames[0]->getPostContext()); - for ($i = 0; $i < 2; ++$i) { - $this->assertEquals(rtrim($fileContent[$i]), $frames[0]->getPreContext()[$i]); + for ($i = 0; $i < $preContextCount; ++$i) { + $this->assertEquals(rtrim($fileContent[$i + ($lineNumber - $preContextCount - 1)]), $frames[0]->getPreContext()[$i]); } - $this->assertEquals(rtrim($fileContent[2]), $frames[0]->getContextLine()); + $this->assertEquals(rtrim($fileContent[$lineNumber - 1]), $frames[0]->getContextLine()); - for ($i = 0; $i < 2; ++$i) { - $this->assertEquals(rtrim($fileContent[$i + 3]), $frames[0]->getPostContext()[$i]); + for ($i = 0; $i < $postContextCount; ++$i) { + $this->assertEquals(rtrim($fileContent[$i + $lineNumber]), $frames[0]->getPostContext()[$i]); } } - public function testAddFrameReadsCodeFromLongFile(): void + public function addFrameRespectsContextLinesOptionDataProvider(): array { - $fileContent = explode("\n", $this->getFixture('code/LongFile.php')); - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); - - $stacktrace->addFrame($this->getFixturePath('code/LongFile.php'), 8, [ - 'function' => '[unknown]', - ]); - - $frames = $stacktrace->getFrames(); - - $this->assertCount(1, $frames); - $this->assertCount(5, $frames[0]->getPreContext()); - $this->assertCount(5, $frames[0]->getPostContext()); - - for ($i = 0; $i < 5; ++$i) { - $this->assertEquals(rtrim($fileContent[$i + 2]), $frames[0]->getPreContext()[$i]); - } - - $this->assertEquals(rtrim($fileContent[7]), $frames[0]->getContextLine()); - - for ($i = 0; $i < 5; ++$i) { - $this->assertEquals(rtrim($fileContent[$i + 8]), $frames[0]->getPostContext()[$i]); - } + return [ + 'read code from short file' => ['code/ShortFile.php', 3, 2, 2, 2], + 'read code from long file with default context' => ['code/LongFile.php', 8, null, 5, 5], + 'read code from long file with specified context' => ['code/LongFile.php', 8, 2, 2, 2], + 'read code from short file with no context' => ['code/ShortFile.php', 3, 0, 0, 0], + 'read code from long file near end of file' => ['code/LongFile.php', 11, 5, 5, 2], + 'read code from long file near beginning of file' => ['code/LongFile.php', 3, 5, 2, 5], + ]; } /** From 0010d94b5afb1a48c03b7e98029fd8e667ab58f1 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 8 Feb 2019 10:17:50 +0100 Subject: [PATCH 0424/1161] Update CHANGELOG.md and UPGRADE.md files (#769) --- CHANGELOG.md | 17 +++++++++++++++-- UPGRADE-2.0.md | 3 +++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27fc3d2da..738dc7b4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,23 @@ - ... +## 2.0.0-beta-2 (unreleased) +- Rename `SentryAuth` class to `SentryAuthentication` (#742) +- `Client` class is now final +- Fix issue with `ClientBuilder`: factories are not instantiated if transport is set manually (#747) +- Rename `excluded_paths` to `in_app_exclude` option to follow Unified API spec (#755) +- Add `max_value_length` option to trim long values during serialization (#754) +- Lower the default `send_attempts` to 3 (#760) +- Fix method argument name handling when Xdebug is enabled (#763) +- Add CI build under Windows with AppVeyor (#758) and fix some bugs +- Change the `ErrorHandler` and default integrations behavior: the handler is now a singleton, + and it's possible to attach a number of callables as listeners for errors and exceptions (#762) +- The `context_lines` options changed the default to `5` and is properly applied (#743) + ## 2.0.0-beta-1 (2018-12-19) - Require PHP >= 7.1 -- Refactorize the whole codebase to support the Unified API SDK specs +- Refactor the whole codebase to support the Unified API SDK specs - See the UPGRADE.md document for more information. ## 1.10.0 (2018-11-09) @@ -20,7 +33,7 @@ - Remove secret_key from required keys for CLI test command. (#645) - Proper case in Raven_Util class name usage. (#642) -- Support longer creditcard numbers. (#635) +- Support longer credit card numbers. (#635) - Use configured message limit when creating serializers. (#634) - Do not truncate strings if message limit is set to zero. (#630) - Add option to ignore SERVER_PORT getting added to url. (#629) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 22ab7605a..dc0a663eb 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -68,6 +68,9 @@ - The `context_lines` option has been added to configure the number of lines of code context to capture. +- The `max_value_length` option has been added to set an upper bound to the length + of serialized items. + - The `server` option has been renamed to `dsn`. ### Misc From 00615ea3e29e39f485d9a8e8ee00c7684df7fe12 Mon Sep 17 00:00:00 2001 From: WyskyNet Date: Fri, 8 Feb 2019 23:31:09 +0100 Subject: [PATCH 0425/1161] Remove link to unmaintained Nette community integration (#768) Salamek/raven-nette is abandoned package and can be replaced by kdyby/monolog Co-authored-by: Alex Bouma --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 067d60f7b..f1f655f63 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,6 @@ The following integrations are fully supported and maintained by the Sentry team The following integrations are available and maintained by members of the Sentry community. -- [Nette](https://github.com/Salamek/raven-nette) - [ZendFramework](https://github.com/facile-it/sentry-module) - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [Drupal](https://www.drupal.org/project/raven) From 341bd73169f32491f2d5b4af015352012f22fed7 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 11 Feb 2019 09:48:34 +0100 Subject: [PATCH 0426/1161] Test multiple PHP versions with lowest/highest dependencies on AppVeyor (#771) --- .appveyor.yml | 66 +++++++++++++++++++++++++++++++++++---------------- .travis.yml | 6 ++--- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 58dcd90f8..cb495dc34 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,31 +3,57 @@ build: false clone_depth: 2 clone_folder: c:\projects\sentry-php skip_branch_with_pr: true +branches: + only: + - master + +environment: + matrix: + - PHP_VERSION: 7.1-Win32-VC14 + DEPENDENCIES: lowest + - PHP_VERSION: 7.1-Win32-VC14 + DEPENDENCIES: highest + - PHP_VERSION: 7.2-Win32-VC15 + DEPENDENCIES: lowest + - PHP_VERSION: 7.2-Win32-VC15 + DEPENDENCIES: highest + - PHP_VERSION: 7.3-Win32-VC15 + DEPENDENCIES: lowest + - PHP_VERSION: 7.3-Win32-VC15 + DEPENDENCIES: highest + +matrix: + fast_finish: true cache: - - composer.phar - - '%LocalAppData%\Composer\files' - - c:\projects\sentry-php\vendor + - composer.phar + - '%LocalAppData%\Composer\files' + - 'c:\php -> .appveyor.yml' init: - - SET PATH=c:\php;%PATH% - - SET COMPOSER_NO_INTERACTION=1 - - SET SYMFONY_DEPRECATIONS_HELPER=strict - - SET ANSICON=121x90 (121x90) + - SET PATH=c:\php\%PHP_VERSION%;%PATH% + - SET SYMFONY_DEPRECATIONS_HELPER=strict + - SET ANSICON=121x90 (121x90) + - SET INSTALL_PHP=1 install: - - mkdir c:\php && cd c:\php - - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-7.1.3-Win32-VC14-x86.zip - - 7z x php-7.1.3-Win32-VC14-x86.zip -y >nul - - echo memory_limit=-1 >> php.ini - - echo extension=php_curl.dll >> php.ini - - echo extension=php_mbstring.dll >> php.ini - - echo extension=php_openssl.dll >> php.ini - - cd c:\projects\sentry-php - - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/1.7.1/composer.phar) - - php composer.phar self-update - - php composer.phar update --no-progress --no-suggest --ansi + - IF NOT EXIST c:\php mkdir c:\php + - IF NOT EXIST c:\php\%PHP_VERSION% mkdir c:\php\%PHP_VERSION% ELSE SET INSTALL_PHP=0 + - IF %INSTALL_PHP%==1 cd c:\php\%PHP_VERSION% + - IF %INSTALL_PHP%==1 curl --fail --location --silent --show-error -o php-%PHP_VERSION%-x64-latest.zip https://windows.php.net/downloads/releases/latest/php-%PHP_VERSION%-x64-latest.zip + - IF %INSTALL_PHP%==1 7z x php-%PHP_VERSION%-x64-latest.zip -y >nul + - IF %INSTALL_PHP%==1 del /Q php-%PHP_VERSION%-x64-latest.zip + - IF %INSTALL_PHP%==1 copy /Y php.ini-development php.ini >nul + - IF %INSTALL_PHP%==1 echo extension_dir=c:\php\%PHP_VERSION%\ext >> php.ini + - IF %INSTALL_PHP%==1 echo extension=php_curl.dll >> php.ini + - IF %INSTALL_PHP%==1 echo extension=php_mbstring.dll >> php.ini + - IF %INSTALL_PHP%==1 echo extension=php_openssl.dll >> php.ini + - cd c:\projects\sentry-php + - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/1.8.3/composer.phar + - php composer.phar self-update + - IF %DEPENDENCIES%==lowest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-lowest --prefer-dist + - IF %DEPENDENCIES%==highest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-dist test_script: - - cd c:\projects\sentry-php - - vendor\bin\phpunit.bat + - cd c:\projects\sentry-php + - vendor\bin\phpunit.bat diff --git a/.travis.yml b/.travis.yml index b78bf7bdb..152fb5244 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,12 +40,10 @@ jobs: - vendor/bin/phpunit --verbose --coverage-clover=build/logs/clover.xml - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml --revision=$TRAVIS_COMMIT - after_success: - - travis_retry php vendor/bin/php-coveralls --verbose install: - - if [ "$dependencies" = "lowest" ]; then composer update --no-interaction --prefer-lowest --prefer-dist; fi; - - if [ "$dependencies" = "highest" ]; then composer update --no-interaction --prefer-dist; fi; + - if [ "$dependencies" = "lowest" ]; then composer update --no-interaction --no-suggest --prefer-lowest --prefer-dist; fi; + - if [ "$dependencies" = "highest" ]; then composer update --no-interaction --no-suggest --prefer-dist; fi; notifications: webhooks: From b72dcf9b1ef53282e0c0a61b048836657c160a5f Mon Sep 17 00:00:00 2001 From: mark burdett Date: Mon, 11 Feb 2019 01:57:22 -0800 Subject: [PATCH 0427/1161] Allow formatted message to be passed in event raw data payload (#752) --- src/Context/Context.php | 2 +- src/Event.php | 25 ++++++-- src/EventFactory.php | 5 +- tests/EventFactoryTest.php | 14 +++++ tests/EventTest.php | 113 +++++++++++++++++++++++++++++++------ 5 files changed, 136 insertions(+), 23 deletions(-) diff --git a/src/Context/Context.php b/src/Context/Context.php index b395fc0d2..1a784b017 100644 --- a/src/Context/Context.php +++ b/src/Context/Context.php @@ -119,7 +119,7 @@ public function toArray(): array */ public function offsetExists($offset): bool { - return isset($this->data[$offset]) || array_key_exists($offset, $this->data); + return isset($this->data[$offset]) || \array_key_exists($offset, $this->data); } /** diff --git a/src/Event.php b/src/Event.php index a11e407c0..de3acef59 100644 --- a/src/Event.php +++ b/src/Event.php @@ -61,6 +61,11 @@ final class Event implements \JsonSerializable */ private $message; + /** + * @var string|null The formatted error message + */ + private $messageFormatted; + /** * @var array The parameters to use to format the message */ @@ -339,6 +344,16 @@ public function getMessage(): ?string return $this->message; } + /** + * Gets the formatted message. + * + * @return string|null + */ + public function getMessageFormatted(): ?string + { + return $this->messageFormatted; + } + /** * Gets the parameters to use to format the message. * @@ -352,13 +367,15 @@ public function getMessageParams(): array /** * Sets the error message. * - * @param string $message The message - * @param array $params The parameters to use to format the message + * @param string $message The message + * @param array $params The parameters to use to format the message + * @param string|null $formatted The formatted message */ - public function setMessage(string $message, array $params = []): void + public function setMessage(string $message, array $params = [], string $formatted = null): void { $this->message = $message; $this->messageParams = $params; + $this->messageFormatted = $formatted; } /** @@ -659,7 +676,7 @@ public function toArray(): array $data['message'] = [ 'message' => $this->message, 'params' => $this->messageParams, - 'formatted' => vsprintf($this->message, $this->messageParams), + 'formatted' => $this->messageFormatted ?? vsprintf($this->message, $this->messageParams), ]; } } diff --git a/src/EventFactory.php b/src/EventFactory.php index 774d41ab7..d59d5317b 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -106,11 +106,12 @@ public function create(array $payload): Event $event->setLogger($payload['logger']); } - $message = $payload['message'] ?? null; + $message = isset($payload['message']) ? mb_substr($payload['message'], 0, $this->options->getMaxValueLength()) : null; $messageParams = $payload['message_params'] ?? []; + $messageFormatted = isset($payload['message_formatted']) ? mb_substr($payload['message_formatted'], 0, $this->options->getMaxValueLength()) : null; if (null !== $message) { - $event->setMessage(substr($message, 0, $this->options->getMaxValueLength()), $messageParams); + $event->setMessage($message, $messageParams, $messageFormatted); } if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index 8faaec466..69525a7e3 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -92,6 +92,20 @@ public function createWithPayloadDataProvider() ], ], ], + [ + [ + 'message' => 'testMessage %foo', + 'message_params' => ['%foo' => 'param'], + 'message_formatted' => 'testMessage param', + ], + [ + 'message' => [ + 'message' => 'testMessage %foo', + 'params' => ['%foo' => 'param'], + 'formatted' => 'testMessage param', + ], + ], + ], ]; } diff --git a/tests/EventTest.php b/tests/EventTest.php index 170a8c668..fa6bf54d1 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -119,32 +119,62 @@ public function testToArray(): void $this->assertEquals($expected, $event->toArray()); } - public function testToArrayWithMessage(): void + /** + * @dataProvider toArrayWithMessageDataProvider + */ + public function testToArrayWithMessage(array $setMessageArguments, $expectedValue): void { $event = new Event(); - $event->setMessage('foo bar'); + + \call_user_func_array([$event, 'setMessage'], $setMessageArguments); $data = $event->toArray(); $this->assertArrayHasKey('message', $data); - $this->assertEquals('foo bar', $data['message']); + $this->assertSame($expectedValue, $data['message']); } - public function testToArrayWithMessageWithParams(): void + public function toArrayWithMessageDataProvider(): array { - $expected = [ - 'message' => 'foo %s', - 'params' => ['bar'], - 'formatted' => 'foo bar', + return [ + [ + [ + 'foo bar', + ], + 'foo bar', + ], + [ + [ + 'foo %s', + [ + 'bar', + ], + ], + [ + 'message' => 'foo %s', + 'params' => [ + 'bar', + ], + 'formatted' => 'foo bar', + ], + ], + [ + [ + 'foo %bar', + [ + '%bar' => 'baz', + ], + 'foo baz', + ], + [ + 'message' => 'foo %bar', + 'params' => [ + '%bar' => 'baz', + ], + 'formatted' => 'foo baz', + ], + ], ]; - - $event = new Event(); - $event->setMessage('foo %s', ['bar']); - - $data = $event->toArray(); - - $this->assertArrayHasKey('message', $data); - $this->assertEquals($expected, $data['message']); } public function testToArrayWithBreadcrumbs(): void @@ -165,6 +195,57 @@ public function testToArrayWithBreadcrumbs(): void $this->assertSame($breadcrumbs, $data['breadcrumbs']['values']); } + /** + * @dataProvider getMessageDataProvider + */ + public function testGetMessage(array $setMessageArguments, array $expectedValue): void + { + $event = new Event(); + + \call_user_func_array([$event, 'setMessage'], $setMessageArguments); + + $this->assertSame($expectedValue['message'], $event->getMessage()); + $this->assertSame($expectedValue['params'], $event->getMessageParams()); + $this->assertSame($expectedValue['formatted'], $event->getMessageFormatted()); + } + + public function getMessageDataProvider(): array + { + return [ + [ + [ + 'foo %s', + [ + 'bar', + ], + ], + [ + 'message' => 'foo %s', + 'params' => [ + 'bar', + ], + 'formatted' => null, + ], + ], + [ + [ + 'foo %bar', + [ + '%bar' => 'baz', + ], + 'foo baz', + ], + [ + 'message' => 'foo %bar', + 'params' => [ + '%bar' => 'baz', + ], + 'formatted' => 'foo baz', + ], + ], + ]; + } + public function testGetServerOsContext(): void { $event = new Event(); From c59db5ad72e5ce371f3262dbd62ebb6590c878d3 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Feb 2019 11:16:17 +0100 Subject: [PATCH 0428/1161] meta: Changelog 2.0 beta2 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 738dc7b4f..b444ce30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ... -## 2.0.0-beta-2 (unreleased) +## 2.0.0-beta-2 (2019-02-11) - Rename `SentryAuth` class to `SentryAuthentication` (#742) - `Client` class is now final - Fix issue with `ClientBuilder`: factories are not instantiated if transport is set manually (#747) @@ -16,6 +16,8 @@ - Change the `ErrorHandler` and default integrations behavior: the handler is now a singleton, and it's possible to attach a number of callables as listeners for errors and exceptions (#762) - The `context_lines` options changed the default to `5` and is properly applied (#743) +- Add support for "formatted messages" in `captureEvent` as payload (#752) +- Fix issue when capturing exceptions to remove warning when converting array args (#761) ## 2.0.0-beta-1 (2018-12-19) From 4536ef585f359403012d377159ca8145f01c2288 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Feb 2019 11:18:05 +0100 Subject: [PATCH 0429/1161] fix: beta entries --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b444ce30b..d43bb46e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ... -## 2.0.0-beta-2 (2019-02-11) +## 2.0.0-beta2 (2019-02-11) - Rename `SentryAuth` class to `SentryAuthentication` (#742) - `Client` class is now final - Fix issue with `ClientBuilder`: factories are not instantiated if transport is set manually (#747) @@ -19,7 +19,7 @@ - Add support for "formatted messages" in `captureEvent` as payload (#752) - Fix issue when capturing exceptions to remove warning when converting array args (#761) -## 2.0.0-beta-1 (2018-12-19) +## 2.0.0-beta1 (2018-12-19) - Require PHP >= 7.1 - Refactor the whole codebase to support the Unified API SDK specs From 70982d9af060c89228bc6d3e8230633673f81481 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Feb 2019 11:22:02 +0100 Subject: [PATCH 0430/1161] fix: PreRelease command --- .craft.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.craft.yml b/.craft.yml index 6ce48d286..95ce3501e 100644 --- a/.craft.yml +++ b/.craft.yml @@ -3,6 +3,7 @@ github: owner: getsentry repo: sentry-php changelogPolicy: simple +preReleaseCommand: "" targets: - name: github - name: registry From ba16d52e807d5568fe9de683a072fba34ca39bc3 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Feb 2019 15:24:51 +0100 Subject: [PATCH 0431/1161] ci: Add travis branches --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 152fb5244..32e30a020 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ language: php +branches: + only: + - master + - /^release\/.+$/ + php: - 7.1 - 7.2 From 83f164578ae6c87521754b0a345723ee4a2115b8 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Feb 2019 17:36:29 +0100 Subject: [PATCH 0432/1161] Update composer.json --- composer.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 4d429b5c8..8bb011e73 100644 --- a/composer.json +++ b/composer.json @@ -5,9 +5,14 @@ "keywords": ["sentry", "log", "logging", "error-monitoring", "error-handler", "crash-reporting", "crash-reports"], "homepage": "http://sentry.io", "license": "BSD-3-Clause", - "authors": [ + "authors": "authors": [ { - "name": "Sentry" + "name": "David Cramer", + "email": "dcramer@gmail.com" + }, + { + "name": "Daniel Griesser", + "email": "daniel.griesser.86@gmail.com" } ], "require": { From 157f8eb46f5e544bbe39db2a0c7e37d79bea6b7c Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Feb 2019 17:36:43 +0100 Subject: [PATCH 0433/1161] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8bb011e73..4871a35e6 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "keywords": ["sentry", "log", "logging", "error-monitoring", "error-handler", "crash-reporting", "crash-reports"], "homepage": "http://sentry.io", "license": "BSD-3-Clause", - "authors": "authors": [ + "authors": [ { "name": "David Cramer", "email": "dcramer@gmail.com" From 1d681f3569eb0bde162e434ad67bcb22c96d583a Mon Sep 17 00:00:00 2001 From: Vitaliy Ryaboy Date: Mon, 18 Feb 2019 10:02:47 +0100 Subject: [PATCH 0434/1161] Update .gitattributes (#770) --- .gitattributes | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index f4de12e11..ec85a1b1c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,24 @@ -/examples export-ignore -/docs export-ignore +# Auto-detect text files, ensure they use LF. +* text=auto eol=lf + +# These files are always considered text and should use LF. +# See core.whitespace @ http://git-scm.com/docs/git-config for whitespace flags. +*.php text eol=lf whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,tabwidth=4 diff=php +*.json text eol=lf whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,tabwidth=4 +*.yml text eol=lf whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,tabwidth=4 +*.md text eol=lf whitespace=blank-at-eol,blank-at-eof +*.rst text eol=lf whitespace=blank-at-eol,blank-at-eof + +# Exclude non-essential files from dist /tests export-ignore +/.appveyor.yml export-ignore +/.craft.yml export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php_cs export-ignore +/.scrutinizer.yml export-ignore +/.travis.yml export-ignore +/Makefile export-ignore +/phpstan.neon export-ignore +/phpunit.xml.dist export-ignore From dee22302443ccccbdec085ae3de1b57ca827618e Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 20 Feb 2019 19:04:39 +0100 Subject: [PATCH 0435/1161] Use mb_* functions to serialize the stacktrace and its frames and to handle unicode file paths (#774) --- src/Serializer/AbstractSerializer.php | 31 +++++++-------------------- src/Stacktrace.php | 12 +++++------ 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index cee103287..dfa4f49ce 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -161,21 +161,15 @@ protected function serializeString($value) { $value = (string) $value; - if ($this->isMbStringEnabled()) { - // we always guarantee this is coerced, even if we can't detect encoding - if ($currentEncoding = mb_detect_encoding($value, $this->mbDetectOrder)) { - $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); - } else { - $value = mb_convert_encoding($value, 'UTF-8'); - } - - if (mb_strlen($value) > $this->options->getMaxValueLength()) { - $value = mb_substr($value, 0, $this->options->getMaxValueLength() - 10, 'UTF-8') . ' {clipped}'; - } + // we always guarantee this is coerced, even if we can't detect encoding + if ($currentEncoding = mb_detect_encoding($value, $this->mbDetectOrder)) { + $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); } else { - if (\strlen($value) > $this->options->getMaxValueLength()) { - $value = substr($value, 0, $this->options->getMaxValueLength() - 10) . ' {clipped}'; - } + $value = mb_convert_encoding($value, 'UTF-8'); + } + + if (mb_strlen($value) > $this->options->getMaxValueLength()) { + $value = mb_substr($value, 0, $this->options->getMaxValueLength() - 10, 'UTF-8') . ' {clipped}'; } return $value; @@ -311,13 +305,4 @@ public function getSerializeAllObjects(): bool { return $this->serializeAllObjects; } - - private function isMbStringEnabled(): bool - { - if (null === $this->mbStringEnabled) { - $this->mbStringEnabled = \extension_loaded('mbstring'); - } - - return $this->mbStringEnabled; - } } diff --git a/src/Stacktrace.php b/src/Stacktrace.php index a3781eb98..1e2bc8251 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -145,7 +145,7 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void if ($isApplicationFile && !empty($excludedAppPaths)) { foreach ($excludedAppPaths as $path) { - if (0 === strpos($absoluteFilePath, $path)) { + if (0 === mb_strpos($absoluteFilePath, $path)) { $frame->setIsInApp(false); break; @@ -161,7 +161,7 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void $argumentValue = $this->representationSerializer->representationSerialize($argumentValue); if (\is_string($argumentValue) || is_numeric($argumentValue)) { - $frameArguments[(string) $argumentName] = substr($argumentValue, 0, $this->options->getMaxValueLength()); + $frameArguments[(string) $argumentName] = mb_substr($argumentValue, 0, $this->options->getMaxValueLength()); } else { $frameArguments[(string) $argumentName] = $argumentValue; } @@ -279,8 +279,8 @@ protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxL protected function stripPrefixFromFilePath(string $filePath): string { foreach ($this->options->getPrefixes() as $prefix) { - if (0 === strpos($filePath, $prefix)) { - return substr($filePath, \strlen($prefix)); + if (0 === mb_strpos($filePath, $prefix)) { + return mb_substr($filePath, mb_strlen($prefix)); } } @@ -398,7 +398,7 @@ protected function serializeArgument($arg) foreach ($arg as $key => $value) { if (\is_string($value) || is_numeric($value)) { - $result[$key] = substr((string) $value, 0, $maxValueLength); + $result[$key] = mb_substr((string) $value, 0, $maxValueLength); } else { $result[$key] = $value; } @@ -406,7 +406,7 @@ protected function serializeArgument($arg) return $result; } elseif (\is_string($arg) || is_numeric($arg)) { - return substr((string) $arg, 0, $maxValueLength); + return mb_substr((string) $arg, 0, $maxValueLength); } else { return $arg; } From aac858883ef71a914a82feef5fb4f83bcc2f4d35 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 22 Feb 2019 23:27:40 +0100 Subject: [PATCH 0436/1161] Make Hub::getScope() private (#776) --- CHANGELOG.md | 5 +++-- src/State/Hub.php | 18 +++++++++-------- src/State/HubInterface.php | 7 ------- tests/ClientTest.php | 9 +++++++-- tests/SdkTest.php | 7 ++++++- tests/State/HubTest.php | 41 +++++++++++++++++++++++--------------- 6 files changed, 51 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d43bb46e1..8dd862880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # CHANGELOG ## Unreleased - -- ... +- Updated .gitattributes to reduce package footprint (#770) +- Use multibyte functions to handle unicode paths (#774) +- Remove `Hub::getScope()` to deny direct access to `Scope` instances (#776) ## 2.0.0-beta2 (2019-02-11) - Rename `SentryAuth` class to `SentryAuthentication` (#742) diff --git a/src/State/Hub.php b/src/State/Hub.php index 6811b8062..a37cb5f3a 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -52,14 +52,6 @@ public function getClient(): ?ClientInterface return $this->getStackTop()->getClient(); } - /** - * {@inheritdoc} - */ - public function getScope(): Scope - { - return $this->getStackTop()->getScope(); - } - /** * {@inheritdoc} */ @@ -242,6 +234,16 @@ public function getIntegration(string $className): ?IntegrationInterface return null; } + /** + * Gets the scope bound to the top of the stack. + * + * @return Scope + */ + private function getScope(): Scope + { + return $this->getStackTop()->getScope(); + } + /** * Gets the topmost client/layer pair in the stack. * diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index d2569b0b9..af13d62e6 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -23,13 +23,6 @@ interface HubInterface */ public function getClient(): ?ClientInterface; - /** - * Gets the scope bound to the top of the stack. - * - * @return Scope - */ - public function getScope(): Scope; - /** * Gets the ID of the last captured event. * diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 87b4672f5..07290a129 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -274,8 +274,13 @@ public function testHandlingExceptionThrowingAnException(): void $client = new Client(new Options(), $transport, $this->createEventFactory()); - Hub::getCurrent()->bindClient($client); - $client->captureException($this->createCarelessExceptionWithStacktrace(), Hub::getCurrent()->getScope()); + $hub = Hub::getCurrent(); + $hub->bindClient($client); + + $method = new \ReflectionMethod($hub, 'getScope'); + $method->setAccessible(true); + + $client->captureException($this->createCarelessExceptionWithStacktrace(), $method->invoke($hub)); } /** diff --git a/tests/SdkTest.php b/tests/SdkTest.php index 9f9b0b013..6d0f55e37 100644 --- a/tests/SdkTest.php +++ b/tests/SdkTest.php @@ -94,7 +94,12 @@ public function testAddBreadcrumb(): void addBreadcrumb($breadcrumb); - $this->assertSame([$breadcrumb], Hub::getCurrent()->getScope()->getBreadcrumbs()); + $hub = Hub::getCurrent(); + + $method = new \ReflectionMethod($hub, 'getScope'); + $method->setAccessible(true); + + $this->assertSame([$breadcrumb], $method->invoke($hub)->getBreadcrumbs()); } public function testWithScope(): void diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 027f694c0..a95e73caf 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -11,6 +11,7 @@ use Sentry\ClientInterface; use Sentry\Severity; use Sentry\State\Hub; +use Sentry\State\HubInterface; use Sentry\State\Scope; final class HubTest extends TestCase @@ -19,7 +20,7 @@ public function testConstructorCreatesScopeAutomatically(): void { $hub = new Hub(null, null); - $this->assertNotNull($hub->getScope()); + $this->assertNotNull($this->getScope($hub)); } public function testGetClient(): void @@ -36,7 +37,7 @@ public function testGetScope(): void $scope = new Scope(); $hub = new Hub($this->createMock(ClientInterface::class), $scope); - $this->assertSame($scope, $hub->getScope()); + $this->assertSame($scope, $this->getScope($hub)); } public function testGetLastEventId(): void @@ -57,14 +58,14 @@ public function testPushScope(): void { $hub = new Hub($this->createMock(ClientInterface::class)); - $scope1 = $hub->getScope(); + $scope1 = $this->getScope($hub); $client1 = $hub->getClient(); $scope2 = $hub->pushScope(); $client2 = $hub->getClient(); $this->assertNotSame($scope1, $scope2); - $this->assertSame($scope2, $hub->getScope()); + $this->assertSame($scope2, $this->getScope($hub)); $this->assertSame($client1, $client2); $this->assertSame($client1, $hub->getClient()); } @@ -73,22 +74,22 @@ public function testPopScope(): void { $hub = new Hub($this->createMock(ClientInterface::class)); - $scope1 = $hub->getScope(); + $scope1 = $this->getScope($hub); $client = $hub->getClient(); $scope2 = $hub->pushScope(); - $this->assertSame($scope2, $hub->getScope()); + $this->assertSame($scope2, $this->getScope($hub)); $this->assertSame($client, $hub->getClient()); $this->assertTrue($hub->popScope()); - $this->assertSame($scope1, $hub->getScope()); + $this->assertSame($scope1, $this->getScope($hub)); $this->assertSame($client, $hub->getClient()); $this->assertFalse($hub->popScope()); - $this->assertSame($scope1, $hub->getScope()); + $this->assertSame($scope1, $this->getScope($hub)); $this->assertSame($client, $hub->getClient()); } @@ -97,7 +98,7 @@ public function testWithScope(): void $scope = new Scope(); $hub = new Hub($this->createMock(ClientInterface::class), $scope); - $this->assertSame($scope, $hub->getScope()); + $this->assertSame($scope, $this->getScope($hub)); $callbackInvoked = false; @@ -116,7 +117,7 @@ public function testWithScope(): void } $this->assertTrue($callbackInvoked); - $this->assertSame($scope, $hub->getScope()); + $this->assertSame($scope, $this->getScope($hub)); } public function testConfigureScope(): void @@ -134,7 +135,7 @@ public function testConfigureScope(): void }); $this->assertTrue($callbackInvoked); - $this->assertSame($scope, $hub->getScope()); + $this->assertSame($scope, $this->getScope($hub)); } public function testBindClient(): void @@ -217,7 +218,7 @@ public function testAddBreadcrumb(): void $hub->addBreadcrumb($breadcrumb); - $this->assertSame([$breadcrumb], $hub->getScope()->getBreadcrumbs()); + $this->assertSame([$breadcrumb], $this->getScope($hub)->getBreadcrumbs()); } public function testAddBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsZero(): void @@ -227,14 +228,14 @@ public function testAddBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsZero(): void $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); - $this->assertEmpty($hub->getScope()->getBreadcrumbs()); + $this->assertEmpty($this->getScope($hub)->getBreadcrumbs()); } public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void { $client = ClientBuilder::create(['max_breadcrumbs' => 2])->getClient(); $hub = new Hub($client); - $scope = $hub->getScope(); + $scope = $this->getScope($hub); $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'foo'); $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'bar'); @@ -260,7 +261,7 @@ public function testAddBreadcrumbDoesNothingWhenBeforeBreadcrumbCallbackReturnsN $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); - $this->assertEmpty($hub->getScope()->getBreadcrumbs()); + $this->assertEmpty($this->getScope($hub)->getBreadcrumbs()); } public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallback(): void @@ -276,7 +277,7 @@ public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallb $hub->addBreadcrumb($breadcrumb1); - $this->assertSame([$breadcrumb2], $hub->getScope()->getBreadcrumbs()); + $this->assertSame([$breadcrumb2], $this->getScope($hub)->getBreadcrumbs()); } public function testCaptureEvent(): void @@ -292,4 +293,12 @@ public function testCaptureEvent(): void $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureEvent(['message' => 'test'])); } + + private function getScope(HubInterface $hub): Scope + { + $method = new \ReflectionMethod($hub, 'getScope'); + $method->setAccessible(true); + + return $method->invoke($hub); + } } From 7d8698ab713141ac82e20f4eb44a6d9c367a34b4 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 22 Feb 2019 23:51:45 +0100 Subject: [PATCH 0437/1161] Switch CI from nightly to 7.4snapshot (#778) --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 32e30a020..e335ff246 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ php: - 7.1 - 7.2 - 7.3 - - nightly + - 7.4snapshot env: - dependencies=highest @@ -18,7 +18,7 @@ env: matrix: fast_finish: true allow_failures: - - php: nightly + - php: 7.4snapshot cache: directories: From f7f6ebc9f948398b048d91248eabae921830f048 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Sat, 23 Feb 2019 01:08:37 +0100 Subject: [PATCH 0438/1161] Add http_proxy option for cURL client (#775) --- CHANGELOG.md | 1 + UPGRADE-2.0.md | 2 -- src/ClientBuilder.php | 16 ++++++++++++++++ src/Options.php | 24 ++++++++++++++++++++++++ tests/ClientBuilderTest.php | 16 ++++++++++++++++ tests/OptionsTest.php | 1 + 6 files changed, 58 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd862880..f3a852651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Updated .gitattributes to reduce package footprint (#770) - Use multibyte functions to handle unicode paths (#774) - Remove `Hub::getScope()` to deny direct access to `Scope` instances (#776) +- Reintroduce `http_proxy` option (#775) ## 2.0.0-beta2 (2019-02-11) - Rename `SentryAuth` class to `SentryAuthentication` (#742) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index dc0a663eb..bc7d39319 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -2,8 +2,6 @@ ### Client options -- The `http_proxy` option has been removed. - - The `exclude` option has been removed. - The `excluded_app_path` option has been renamed to `in_app_exclude` diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 28a61293c..33ab7c922 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -12,6 +12,7 @@ use Http\Client\Common\Plugin\HeaderSetPlugin; use Http\Client\Common\Plugin\RetryPlugin; use Http\Client\Common\PluginClient; +use Http\Client\Curl\Client as HttpCurlClient; use Http\Client\HttpAsyncClient; use Http\Discovery\HttpAsyncClientDiscovery; use Http\Discovery\MessageFactoryDiscovery; @@ -314,6 +315,21 @@ private function createTransportInstance(): TransportInterface $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); + + if (null !== $this->options->getHttpProxy()) { + if (null !== $this->httpClient) { + throw new \RuntimeException('The `http_proxy` option does not work together with a custom client.'); + } + + if (HttpAsyncClientDiscovery::safeClassExists(HttpCurlClient::class)) { + $this->httpClient = new HttpCurlClient(null, null, [ + CURLOPT_PROXY => $this->options->getHttpProxy(), + ]); + } else { + throw new \RuntimeException('The `http_proxy` option requires the `php-http/curl-client` package to be installed.'); + } + } + $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); if (null === $this->messageFactory) { diff --git a/src/Options.php b/src/Options.php index 720685566..4db10a91c 100644 --- a/src/Options.php +++ b/src/Options.php @@ -619,6 +619,28 @@ public function setMaxValueLength(int $maxValueLength): void $this->options = $this->resolver->resolve($options); } + /** + * Gets the http proxy setting. + * + * @return string|null + */ + public function getHttpProxy(): ?string + { + return $this->options['http_proxy']; + } + + /** + * Sets the http proxy. Be aware this option only works when curl client is used. + * + * @param string|null $httpProxy The http proxy + */ + public function setHttpProxy(?string $httpProxy): void + { + $options = array_merge($this->options, ['http_proxy' => $httpProxy]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -657,6 +679,7 @@ private function configureOptions(OptionsResolver $resolver): void 'in_app_exclude' => [], 'send_default_pii' => false, 'max_value_length' => 1024, + 'http_proxy' => null, ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -682,6 +705,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_default_pii', 'bool'); $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); + $resolver->setAllowedTypes('http_proxy', ['null', 'string']); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index b9e237d2d..9dad066f5 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -290,6 +290,22 @@ public function testCreateWithNoOptionsIsTheSameAsDefaultOptions(): void ClientBuilder::create([]) ); } + + public function testCreateWithHttpProxyAndCustomTransportThrowsException(): void + { + $options = new Options([ + 'dsn' => 'http://public:secret@example.com/sentry/1', + 'http_proxy' => 'some-proxy', + ]); + + $clientBuilder = new ClientBuilder($options); + $clientBuilder->setHttpClient($this->createMock(HttpAsyncClient::class)); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The `http_proxy` option does not work together with a custom client.'); + + $clientBuilder->getClient(); + } } final class StubIntegration implements IntegrationInterface diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 2832584d9..688ff969c 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -58,6 +58,7 @@ public function optionsDataProvider(): array ['send_default_pii', true, 'shouldSendDefaultPii', 'setSendDefaultPii'], ['default_integrations', false, 'hasDefaultIntegrations', 'setDefaultIntegrations'], ['max_value_length', 50, 'getMaxValueLength', 'setMaxValueLength'], + ['http_proxy', '127.0.0.1', 'getHttpProxy', 'setHttpProxy'], ]; } From ef58615bb56bf5d256343841be0ddf515d5c66a4 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 25 Feb 2019 08:54:50 +0100 Subject: [PATCH 0439/1161] feat: Support HTTPlug 2 / PSR-18 (#777) * Allow HTTPlug 2 * Fix tests to allow HTTPlug 2 * Add a CI job to test HTTPlug 2 * Use MockClient everywhere in tests * Remove curl client; revert adding a dedicated CI job for HTTPlug 2 * Increase php-http/discovery constraint * Move php-http/message as an explicit dependency * Address review * Require Curl client in CI to fix PHPStan analysis * Require fixed version of the Discovery package * Revert CI changes * Ignore PHPStan errors about missing Curl client class * Fix error found in review --- composer.json | 9 ++++----- phpstan.neon | 1 + src/ClientBuilder.php | 11 ++++++----- tests/ClientBuilderTest.php | 5 +++-- tests/bootstrap.php | 5 +++++ tests/phpt/fatal_error.phpt | 5 +---- tests/phpt/out_of_memory.phpt | 26 ++++++++++++-------------- 7 files changed, 32 insertions(+), 30 deletions(-) diff --git a/composer.json b/composer.json index 4871a35e6..d22ff95e2 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,10 @@ "ext-mbstring": "*", "jean85/pretty-package-versions": "^1.2", "php-http/async-client-implementation": "^1.0", - "php-http/client-common": "^1.5", - "php-http/discovery": "^1.2", - "php-http/httplug": "^1.1", + "php-http/client-common": "^1.5|^2.0", + "php-http/discovery": "^1.6.1", + "php-http/httplug": "^1.1|^2.0", + "php-http/message": "^1.5", "psr/http-message-implementation": "^1.0", "ramsey/uuid": "^3.3", "symfony/options-resolver": "^2.7|^3.0|^4.0", @@ -32,8 +33,6 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", "monolog/monolog": "^1.3", - "php-http/curl-client": "^1.7.1", - "php-http/message": "^1.5", "php-http/mock-client": "^1.0", "phpstan/phpstan-phpunit": "^0.10", "phpstan/phpstan": "^0.10.3", diff --git a/phpstan.neon b/phpstan.neon index 44470014f..e6714eab8 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,6 +8,7 @@ parameters: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' - '/Method Sentry\\Serializer\\RepresentationSerializer::(representationSerialize|serializeValue)\(\) should return [\w|]+ but returns [\w|]+/' + - '/Http\\Client\\Curl\\Client/' excludes_analyse: - tests/resources includes: diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 33ab7c922..eb440736a 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -14,6 +14,7 @@ use Http\Client\Common\PluginClient; use Http\Client\Curl\Client as HttpCurlClient; use Http\Client\HttpAsyncClient; +use Http\Discovery\ClassDiscovery; use Http\Discovery\HttpAsyncClientDiscovery; use Http\Discovery\MessageFactoryDiscovery; use Http\Discovery\UriFactoryDiscovery; @@ -321,13 +322,13 @@ private function createTransportInstance(): TransportInterface throw new \RuntimeException('The `http_proxy` option does not work together with a custom client.'); } - if (HttpAsyncClientDiscovery::safeClassExists(HttpCurlClient::class)) { - $this->httpClient = new HttpCurlClient(null, null, [ - CURLOPT_PROXY => $this->options->getHttpProxy(), - ]); - } else { + if (!ClassDiscovery::safeClassExists(HttpCurlClient::class)) { throw new \RuntimeException('The `http_proxy` option requires the `php-http/curl-client` package to be installed.'); } + + $this->httpClient = new HttpCurlClient(null, null, [ + CURLOPT_PROXY => $this->options->getHttpProxy(), + ]); } $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 9dad066f5..984b0ca14 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -9,6 +9,7 @@ use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory; use Http\Message\UriFactory; +use Http\Promise\Promise; use Jean85\PrettyVersions; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -317,14 +318,14 @@ public function setupOnce(): void final class PluginStub1 implements Plugin { - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { } } final class PluginStub2 implements Plugin { - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 57982bce2..166f1c4ba 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,8 +2,13 @@ declare(strict_types=1); +use Http\Discovery\ClassDiscovery; +use Http\Discovery\Strategy\MockClientStrategy; + error_reporting(E_ALL | E_STRICT); session_start(); require_once __DIR__ . '/../vendor/autoload.php'; + +ClassDiscovery::appendStrategy(MockClientStrategy::class); diff --git a/tests/phpt/fatal_error.phpt b/tests/phpt/fatal_error.phpt index 4d040c05f..4f8539179 100644 --- a/tests/phpt/fatal_error.phpt +++ b/tests/phpt/fatal_error.phpt @@ -17,10 +17,7 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -init([ - 'dsn' => 'http://public:secret@local.host/1', - 'send_attempts' => 1, -]); +init(); ErrorHandler::addErrorListener(new StubErrorListener(function () { echo 'Listener called'; diff --git a/tests/phpt/out_of_memory.phpt b/tests/phpt/out_of_memory.phpt index 151cddbfb..ded7b8718 100644 --- a/tests/phpt/out_of_memory.phpt +++ b/tests/phpt/out_of_memory.phpt @@ -6,8 +6,9 @@ Test catching out of memory fatal error namespace Sentry\Tests; use PHPUnit\Framework\Assert; +use Sentry\Event; +use Sentry\Severity; use function Sentry\init; -use Sentry\State\Hub; ini_set('memory_limit', '20M'); @@ -20,23 +21,20 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; init([ - 'dsn' => 'http://public:secret@local.host/1', - 'send_attempts' => 1, -]); - -register_shutdown_function('register_shutdown_function', function () { - $client = Hub::getCurrent()->getClient(); + 'before_send' => function (Event $event): ?Event { + Assert::assertArrayHasKey(0, $event->getExceptions()); + $error = $event->getExceptions()[0]; + Assert::assertContains('Allowed memory size', $error['value']); + Assert::assertTrue($event->getLevel()->isEqualTo(Severity::fatal())); - /** @var \Sentry\Transport\HttpTransport $transport */ - $transport = Assert::getObjectAttribute($client, 'transport'); + echo 'Sending event'; - Assert::assertAttributeEmpty('pendingRequests', $transport); - - echo 'Shutdown function called'; -}); + return null; + }, +]); $foo = str_repeat('x', 1024 * 1024 * 30); ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d -Shutdown function called +Sending event From e3de43dd855cd9c31388f9a2828404098239b672 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 25 Feb 2019 09:20:05 +0100 Subject: [PATCH 0440/1161] meta: Changelog 2.0.0 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a852651..d9b62e622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ # CHANGELOG ## Unreleased + +## 2.0.0 (2019-02-25) + +**Version 2.0.0 is a complete rewrite of the existing SDK. Code Changes are needed. Please see [UPGRADE 2.0](https://github.com/getsentry/sentry-php/blob/master/UPGRADE-2.0.md) for more details.** + - Updated .gitattributes to reduce package footprint (#770) - Use multibyte functions to handle unicode paths (#774) - Remove `Hub::getScope()` to deny direct access to `Scope` instances (#776) - Reintroduce `http_proxy` option (#775) +- Added support for HTTPlug 2 / PSR-18 (#777) ## 2.0.0-beta2 (2019-02-11) - Rename `SentryAuth` class to `SentryAuthentication` (#742) From e1d853aa07e714c70d355a16dae7ca9caea020e4 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 25 Feb 2019 09:33:35 +0100 Subject: [PATCH 0441/1161] feat: Change readme to explain metapackage (#772) --- README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f1f655f63..15e1c053e 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,25 @@ information needed to prioritize, identify, reproduce and fix each issue. To install the SDK you will need to be using [Composer]([https://getcomposer.org/) in your project. To install it please see the [docs](https://getcomposer.org/download/). -Sentry PHP is not tied to any specific library that sends HTTP messages. Instead, +This is our "core" SDK, meaning that all the important code regarding error handling lives here. +If you are happy with using the HTTP client we recommend install the SDK like: [`sentry/sdk`](https://github.com/getsentry/sentry-php-sdk) + +```bash +php composer.phar require sentry/sdk +``` + +This package (`sentry/sentry`) is not tied to any specific library that sends HTTP messages. Instead, it uses [Httplug](https://github.com/php-http/httplug) to let users choose whichever PSR-7 implementation and HTTP client they want to use. If you just want to get started quickly you should run the following command: ```bash -php composer.phar require sentry/sentry:2.0.0-beta1 php-http/curl-client guzzlehttp/psr7 +php composer.phar require sentry/sentry:2.0.0-beta2 php-http/curl-client guzzlehttp/psr7 ``` +This is basically what our metapackage (`sentry/sdk`) provides. + This will install the library itself along with an HTTP client adapter that uses cURL as transport method (provided by Httplug) and a PSR-7 implementation (provided by Guzzle). You do not have to use those packages if you do not want to. @@ -50,15 +59,12 @@ and [`http-message-implementation`](https://packagist.org/providers/psr/http-mes ## Usage ```php -use function Sentry\init; -use function Sentry\captureException; - -init(['dsn' => '___PUBLIC_DSN___' ]); +\Sentry\init(['dsn' => '___PUBLIC_DSN___' ]); try { thisFunctionThrows(); // -> throw new \Exception('foo bar'); } catch (\Exception $exception) { - captureException($exception); + \Sentry\captureException($exception); } ``` From 478d0a21ad55665db5702e9c385e9e22dfb2aba5 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 25 Feb 2019 09:33:57 +0100 Subject: [PATCH 0442/1161] Update README.md --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 15e1c053e..b66fdfaa2 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,6 @@ The Sentry PHP error reporter tracks errors and exceptions that happen during th execution of your application and provides instant notification with detailed information needed to prioritize, identify, reproduce and fix each issue. -### Notice 2.0 - -> The current master branch is our new major release of the SDK `2.0`. -> We currently ship `2.0` with the `beta` tag, which means you have to install it by exactly providing the version otherwise you wont get `2.0`. We will drop the `beta` tag as soon as we do no longer expect any public API changes. - ## Install To install the SDK you will need to be using [Composer]([https://getcomposer.org/) From f99ee9747a4fe27f1a2004a4f4db1a05af9854a5 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 28 Feb 2019 15:43:10 +0100 Subject: [PATCH 0443/1161] Make configurable whether the silenced errors should be captured or not (#785) --- CHANGELOG.md | 2 + src/ErrorHandler.php | 9 ++++- src/Exception/SilencedErrorException.php | 13 +++++++ src/Integration/ErrorListenerIntegration.php | 5 +++ src/Options.php | 26 +++++++++++++ tests/OptionsTest.php | 1 + tests/phpt/silenced_errors.phpt | 41 ++++++++++++++++++++ 7 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/Exception/SilencedErrorException.php create mode 100644 tests/phpt/silenced_errors.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index d9b62e622..d2ae67fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # CHANGELOG ## Unreleased +- Do no longer report silenced errors by default (#785) +- New option `capture_silenced_error` to enable reporting of silenced errors, disabled by default (#785) ## 2.0.0 (2019-02-25) diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index c514e0465..053e45e2d 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -4,6 +4,8 @@ namespace Sentry; +use Sentry\Exception\SilencedErrorException; + /** * This class implements a simple error handler that catches all configured * error types and logs them using a certain Raven client. Registering more @@ -176,7 +178,12 @@ public static function addExceptionListener(callable $listener): void */ public function handleError(int $level, string $message, string $file, int $line): bool { - $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); + if (0 === error_reporting()) { + $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); + } else { + $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); + } + $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $file, $line); $this->exceptionReflection->setValue($errorAsException, $backtrace); diff --git a/src/Exception/SilencedErrorException.php b/src/Exception/SilencedErrorException.php new file mode 100644 index 000000000..4ee6711c6 --- /dev/null +++ b/src/Exception/SilencedErrorException.php @@ -0,0 +1,13 @@ +options->shouldCaptureSilencedErrors()) { + return; + } + if ($this->options->getErrorTypes() & $error->getSeverity()) { Hub::getCurrent()->captureException($error); } diff --git a/src/Options.php b/src/Options.php index 4db10a91c..ee3010c1e 100644 --- a/src/Options.php +++ b/src/Options.php @@ -641,6 +641,30 @@ public function setHttpProxy(?string $httpProxy): void $this->options = $this->resolver->resolve($options); } + /** + * Gets whether the silenced errors should be captured or not. + * + * @return bool If true, errors silenced through the @ operator will be reported, + * ignored otherwise + */ + public function shouldCaptureSilencedErrors(): bool + { + return $this->options['capture_silenced_errors']; + } + + /** + * Sets whether the silenced errors should be captured or not. + * + * @param bool $shouldCapture If set to true, errors silenced through the @ + * operator will be reported, ignored otherwise + */ + public function setCaptureSilencedErrors(bool $shouldCapture): void + { + $options = array_merge($this->options, ['capture_silenced_errors' => $shouldCapture]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -680,6 +704,7 @@ private function configureOptions(OptionsResolver $resolver): void 'send_default_pii' => false, 'max_value_length' => 1024, 'http_proxy' => null, + 'capture_silenced_errors' => false, ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -706,6 +731,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); $resolver->setAllowedTypes('http_proxy', ['null', 'string']); + $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 688ff969c..dde79cce7 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -59,6 +59,7 @@ public function optionsDataProvider(): array ['default_integrations', false, 'hasDefaultIntegrations', 'setDefaultIntegrations'], ['max_value_length', 50, 'getMaxValueLength', 'setMaxValueLength'], ['http_proxy', '127.0.0.1', 'getHttpProxy', 'setHttpProxy'], + ['capture_silenced_errors', true, 'shouldCaptureSilencedErrors', 'setCaptureSilencedErrors'], ]; } diff --git a/tests/phpt/silenced_errors.phpt b/tests/phpt/silenced_errors.phpt new file mode 100644 index 000000000..db54c930b --- /dev/null +++ b/tests/phpt/silenced_errors.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test that the error handler ignores silenced errors by default, but it reports them with the appropriate option enabled. +--FILE-- + static function () { + echo 'Event captured' . PHP_EOL; + } +]); + +echo 'Triggering silenced error' . PHP_EOL; +@$a['missing']; + +Hub::getCurrent() + ->getClient() + ->getOptions() + ->setCaptureSilencedErrors(true); + +echo 'Triggering silenced error' . PHP_EOL; +@$a['missing']; +echo 'End' +?> +--EXPECT-- +Triggering silenced error +Triggering silenced error +Event captured +End From 3be185af4fbf36aca61ba113d02e0e5b3a02d6c3 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 1 Mar 2019 09:53:27 +0100 Subject: [PATCH 0444/1161] Update README and UPGRADE-2.0 files (#783) --- README.md | 2 +- UPGRADE-2.0.md | 65 ++++++++++++++++++++++++++++++++++++++------------ composer.json | 8 ++----- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index b66fdfaa2..4b94e890b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ PSR-7 implementation and HTTP client they want to use. If you just want to get started quickly you should run the following command: ```bash -php composer.phar require sentry/sentry:2.0.0-beta2 php-http/curl-client guzzlehttp/psr7 +php composer.phar require sentry/sentry php-http/curl-client guzzlehttp/psr7 ``` This is basically what our metapackage (`sentry/sdk`) provides. diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index bc7d39319..6f158f65e 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -1,5 +1,36 @@ # Upgrade from 1.10 to 2.0 +Version `2.x` is a complete rewrite of the existing code base. The public API has been trimmed down to a minimum. +The preferred way of using the SDK is through our "Static API" / global functions. + +Here is a simple example to get started: + +```php +\Sentry\init(['dsn' => '___PUBLIC_DSN___' ]); + +\Sentry\configureScope(function (\Sentry\State\Scope $scope): void { + $scope->setTag('page_locale', 'de-at'); + $scope->setUser(['email' => 'john.doe@example.com']); + $scope->setLevel(\Sentry\Severity::warning()); + $scope->setExtra('character_name', 'Mighty Fighter'); +}); + +// The following capture call will contain the data from the previous configured Scope +try { + thisFunctionThrows(); // -> throw new \Exception('foo bar'); +} catch (\Exception $exception) { + \Sentry\captureException($exception); +} + +\Sentry\addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting', 'Message')); +``` + +The call to `\Sentry\init()` sets up global exception/error handlers and any uncaught error will be sent to Sentry. +Version `>= 2.0` conforms to the [Unified SDK API](https://docs.sentry.io/development/sdk-dev/unified-api/). +It has a fundamentally different concept, it's no longer recommended to just use a `Client` unless you really know what you are doing. + +Please visit [our docs](https://docs.sentry.io/error-reporting/quickstart/?platform=php) to get a full overview. + ### Client options - The `exclude` option has been removed. @@ -79,6 +110,8 @@ - The `Raven_Autoloader` class has been removed. To install and use the library you are required to use [Composer](https://getcomposer.org/). +- The `Raven_Util` class has been removed. + - The `Raven_Compat` class has been removed. - The `Raven_Util` class has been removed. @@ -91,7 +124,7 @@ ### Client -- The constructor of the `Raven_Client` class has changed its signature and +- The constructor of the `Client` (before `Raven_Client`) class has changed its signature and now requires to be passed a configuration object, an instance of a transport and an event factory. @@ -112,6 +145,8 @@ // ... } ``` + + The suggested way to create your own instance of the client is to use the provided builder (`ClientBuilder`) that will take care of instantiating a few dependencies like the PSR-7 factories and the HTTP client. - The method `Raven_Client::close_all_children_link` has been removed and there @@ -129,7 +164,7 @@ After: ```php - use Sentry\Hub; + use Sentry\State\Hub; $options = Hub::getCurrent()->getClient()->getOptions(); @@ -151,7 +186,7 @@ After: ```php - use Sentry\Hub; + use Sentry\State\Hub; $options = Hub::getCurrent()->getClient()->getOptions(); @@ -175,7 +210,7 @@ After: ```php - use Sentry\Hub; + use Sentry\State\Hub; $options = Hub::getCurrent()->getClient()->getOptions(); @@ -197,7 +232,7 @@ After: ```php - use Sentry\Hub; + use Sentry\State\Hub; $options = Hub::getCurrent()->getClient()->getOptions(); @@ -219,7 +254,7 @@ After: ```php - use Sentry\Hub; + use Sentry\State\Hub; $options = Hub::getCurrent()->getClient()->getOptions(); @@ -241,7 +276,7 @@ After: ```php - use Sentry\Hub; + use Sentry\State\Hub; $options = Hub::getCurrent()->getClient()->getOptions(); @@ -260,7 +295,7 @@ After: ```php - use Sentry\Hub; + use Sentry\State\Hub; $options = Hub::getCurrent()->getClient()->getOptions(); @@ -294,7 +329,7 @@ - The `Raven_Client::getLastEventID` method has been removed. The ID of the last event that was captured is now returned by each of the `Client::capture*` - methods. + methods. You can also use `Hub::getCurrent()->getLastEventId()`. - The `Raven_Client::parseDSN` method has been removed. @@ -488,8 +523,8 @@ After: ```php - use Sentry\Hub; - use Sentry\Scope; + use Sentry\State\Hub; + use Sentry\State\Scope; Hub::getCurrent()->configureScope(function (Scope $scope): void { $scope->setUser(['email' => 'foo@example.com']); @@ -508,8 +543,8 @@ After: ```php - use Sentry\Hub; - use Sentry\Scope; + use Sentry\State\Hub; + use Sentry\State\Scope; Hub::getCurrent()->configureScope(function (Scope $scope): void { $scope->setTag('tag_name', 'tag_value'); @@ -528,8 +563,8 @@ After: ```php - use Sentry\Hub; - use Sentry\Scope; + use Sentry\State\Hub; + use Sentry\State\Scope; Hub::getCurrent()->configureScope(function (Scope $scope): void { $scope->setExtra('extra_key', 'extra_value'); diff --git a/composer.json b/composer.json index d22ff95e2..f6872b8f2 100644 --- a/composer.json +++ b/composer.json @@ -7,12 +7,8 @@ "license": "BSD-3-Clause", "authors": [ { - "name": "David Cramer", - "email": "dcramer@gmail.com" - }, - { - "name": "Daniel Griesser", - "email": "daniel.griesser.86@gmail.com" + "name": "Sentry", + "email": "accounts@sentry.io" } ], "require": { From 6469ad72d0085d561aa885af773946ce3549ac1d Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 1 Mar 2019 09:56:33 +0100 Subject: [PATCH 0445/1161] meta: Changelog 2.0.1 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2ae67fb6..238328777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # CHANGELOG ## Unreleased + +## 2.0.1 (2019-02-28) + - Do no longer report silenced errors by default (#785) - New option `capture_silenced_error` to enable reporting of silenced errors, disabled by default (#785) From 99ab0ea727d4a226eb76b11484b62004f50aefb2 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 1 Mar 2019 10:00:23 +0100 Subject: [PATCH 0446/1161] fix: Date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 238328777..74d3c5b23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 2.0.1 (2019-02-28) +## 2.0.1 (2019-03-01) - Do no longer report silenced errors by default (#785) - New option `capture_silenced_error` to enable reporting of silenced errors, disabled by default (#785) From 33885046149f7347da9c576203982fefc3aaf11e Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 6 Mar 2019 08:39:05 -0800 Subject: [PATCH 0447/1161] fix: Mark Sentry internal frames for attach_stacktrace to in_app false (#786) * fix: Mark Sentry internal calls for attach_stacktrace to false * ref: CR * meta: Changelog --- CHANGELOG.md | 2 ++ src/Stacktrace.php | 5 +++++ tests/ClientTest.php | 7 ++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d3c5b23..37631e1f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (786) + ## 2.0.1 (2019-03-01) - Do no longer report silenced errors by default (#785) diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 1e2bc8251..cf0e68e46 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -138,6 +138,11 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void $frame->setPostContext($sourceCodeExcerpt['post_context']); } + // In case it's an Sentry internal frame, we mark it as in_app false + if (null !== $functionName && 0 === strpos($functionName, 'Sentry\\')) { + $frame->setIsInApp(false); + } + if (null !== $this->options->getProjectRoot()) { $excludedAppPaths = $this->options->getInAppExcludedPaths(); $absoluteFilePath = @realpath($file) ?: $file; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 07290a129..f5d35766f 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -423,7 +423,12 @@ public function testAttachStacktrace(): void $result = $event->getStacktrace(); $this->assertInstanceOf(Stacktrace::class, $result); - $this->assertNotEmpty($result->getFrames()); + + $frames = $result->getFrames(); + + $this->assertNotEmpty($frames); + $this->assertTrue($frames[0]->isInApp()); + $this->assertFalse($frames[\count($frames) - 1]->isInApp()); return true; })); From 96af39d8993d0ad5cbbd27d16e5b61ca9d5b8ca4 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 13 Mar 2019 11:57:08 +0100 Subject: [PATCH 0448/1161] fix: Canonical for .craft.yml --- .craft.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.craft.yml b/.craft.yml index 95ce3501e..4e96626e6 100644 --- a/.craft.yml +++ b/.craft.yml @@ -9,4 +9,4 @@ targets: - name: registry type: sdk config: - canonical: 'composer:@sentry/sentry' + canonical: 'composer:sentry/sentry' From f5e42d96311427b376de721f8ddb5bba6bf35e50 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 26 Mar 2019 18:15:01 +0100 Subject: [PATCH 0449/1161] Fix documentation of error_types option (#794) --- UPGRADE-2.0.md | 2 -- src/Options.php | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 6f158f65e..70e21e7dd 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -53,8 +53,6 @@ Please visit [our docs](https://docs.sentry.io/error-reporting/quickstart/?platf - The `ignore_server_port` option has been removed. -- The `error_types` option has been removed. - - The `trust_x_forwarded_proto` option has been removed. - The `mb_detect_order` option has been removed. diff --git a/src/Options.php b/src/Options.php index ee3010c1e..df3951d9e 100644 --- a/src/Options.php +++ b/src/Options.php @@ -462,7 +462,7 @@ public function setTags(array $tags): void } /** - * Gets a bit mask for error_reporting used in {@link ErrorHandler::handleError}. + * Gets a bit mask for error_reporting used in {@link ErrorListenerIntegration} to filter which errors to report. * * @return int */ @@ -472,7 +472,7 @@ public function getErrorTypes(): int } /** - * Sets a bit mask for error_reporting used in {@link ErrorHandler::handleError}. + * Sets a bit mask for error_reporting used in {@link ErrorListenerIntegration} to filter which errors to report. * * @param int $errorTypes The bit mask */ From 1f579865ff90b4d9b39d6a550defab3e6d146d4b Mon Sep 17 00:00:00 2001 From: Alessandro Minoccheri Date: Thu, 28 Mar 2019 09:17:26 +0100 Subject: [PATCH 0450/1161] improve test coverage of the Breadcrumb class (#791) --- tests/BreadcrumbTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/BreadcrumbTest.php b/tests/BreadcrumbTest.php index cb00e282c..11f7b6488 100644 --- a/tests/BreadcrumbTest.php +++ b/tests/BreadcrumbTest.php @@ -102,4 +102,25 @@ public function testWithoutMetadata(): void $this->assertSame(['foo' => 'bar'], $breadcrumb->getMetadata()); $this->assertArrayNotHasKey('foo', $newBreadcrumb->getMetadata()); } + + public function testJsonSerialize(): void + { + $type = Breadcrumb::TYPE_USER; + $level = Breadcrumb::LEVEL_INFO; + $category = 'foo'; + $data = ['baz' => 'bar']; + $message = 'message'; + $breadcrumb = new Breadcrumb($level, $type, $category, $message, $data); + + $expected = [ + 'type' => $type, + 'category' => $category, + 'message' => $message, + 'level' => $level, + 'timestamp' => microtime(true), + 'data' => $data, + ]; + + $this->assertEquals($expected, $breadcrumb->jsonSerialize()); + } } From 27139701b456ac9a0348b1746ce274ca69b321b5 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 3 Apr 2019 12:09:22 +0200 Subject: [PATCH 0451/1161] Remap the severity of E_RECOVERABLE_ERROR to error (#792) --- CHANGELOG.md | 3 +- src/Severity.php | 14 +++++++- tests/SeverityTest.php | 77 ++++++++++++++++++++++++++++-------------- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37631e1f1..b753b6882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## Unreleased -- Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (786) +- Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (#786) +- Increase default severity of `E_RECOVERABLE_ERROR` to `Severity::ERROR`, instead of warning (#792) ## 2.0.1 (2019-03-01) diff --git a/src/Severity.php b/src/Severity.php index 730ae9d5c..cfb204cc6 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -14,31 +14,43 @@ final class Severity { /** * This constant represents the "debug" severity level. + * + * @internal */ public const DEBUG = 'debug'; /** * This constant represents the "info" severity level. + * + * @internal */ public const INFO = 'info'; /** * This constant represents the "warning" severity level. + * + * @internal */ public const WARNING = 'warning'; /** * This constant represents the "error" severity level. + * + * @internal */ public const ERROR = 'error'; /** * This constant represents the "fatal" severity level. + * + * @internal */ public const FATAL = 'fatal'; /** * This constant contains the list of allowed enum values. + * + * @internal */ public const ALLOWED_SEVERITIES = [ self::DEBUG, @@ -81,7 +93,6 @@ public static function fromError(int $severity): self case E_USER_DEPRECATED: case E_WARNING: case E_USER_WARNING: - case E_RECOVERABLE_ERROR: return self::warning(); case E_ERROR: case E_PARSE: @@ -90,6 +101,7 @@ public static function fromError(int $severity): self case E_COMPILE_ERROR: case E_COMPILE_WARNING: return self::fatal(); + case E_RECOVERABLE_ERROR: case E_USER_ERROR: return self::error(); case E_NOTICE: diff --git a/tests/SeverityTest.php b/tests/SeverityTest.php index f1fc3949d..c6c000a4d 100644 --- a/tests/SeverityTest.php +++ b/tests/SeverityTest.php @@ -18,39 +18,31 @@ public function testConstructorThrowsOnInvalidValue(): void new Severity('foo'); } - public function testDebug(): void - { - $severity = Severity::debug(); - - $this->assertSame(Severity::DEBUG, (string) $severity); - } - - public function testInfo(): void - { - $severity = Severity::info(); - - $this->assertSame(Severity::INFO, (string) $severity); - } - - public function testWarning(): void + /** + * @dataProvider constantsDataProvider + */ + public function testConstructor(Severity $severity, string $expectedStringRepresentation): void { - $severity = Severity::warning(); - - $this->assertSame(Severity::WARNING, (string) $severity); + $this->assertTrue($severity->isEqualTo(new Severity($expectedStringRepresentation))); } - public function testError(): void + /** + * @dataProvider constantsDataProvider + */ + public function testToString(Severity $severity, string $expectedStringRepresentation): void { - $severity = Severity::error(); - - $this->assertSame(Severity::ERROR, (string) $severity); + $this->assertSame($expectedStringRepresentation, (string) $severity); } - public function testFatal(): void + public function constantsDataProvider(): array { - $severity = Severity::fatal(); - - $this->assertSame(Severity::FATAL, (string) $severity); + return [ + [Severity::debug(), 'debug'], + [Severity::info(), 'info'], + [Severity::warning(), 'warning'], + [Severity::error(), 'error'], + [Severity::fatal(), 'fatal'], + ]; } public function testIsEqualTo(): void @@ -62,4 +54,37 @@ public function testIsEqualTo(): void $this->assertTrue($severity1->isEqualTo($severity2)); $this->assertFalse($severity1->isEqualTo($severity3)); } + + /** + * @dataProvider levelsDataProvider + */ + public function testFromError(int $errorLevel, string $expectedSeverity): void + { + $this->assertSame($expectedSeverity, (string) Severity::fromError($errorLevel)); + } + + public function levelsDataProvider(): array + { + return [ + // Warning + [E_DEPRECATED, 'warning'], + [E_USER_DEPRECATED, 'warning'], + [E_WARNING, 'warning'], + [E_USER_WARNING, 'warning'], + // Fatal + [E_ERROR, 'fatal'], + [E_PARSE, 'fatal'], + [E_CORE_ERROR, 'fatal'], + [E_CORE_WARNING, 'fatal'], + [E_COMPILE_ERROR, 'fatal'], + [E_COMPILE_WARNING, 'fatal'], + // Error + [E_RECOVERABLE_ERROR, 'error'], + [E_USER_ERROR, 'error'], + // Info + [E_NOTICE, 'info'], + [E_USER_NOTICE, 'info'], + [E_STRICT, 'info'], + ]; + } } From bd866d0137288d9035339990ff03c9c3612bf564 Mon Sep 17 00:00:00 2001 From: "Tonin R. Bolzan" Date: Tue, 23 Apr 2019 04:58:11 -0300 Subject: [PATCH 0452/1161] Fix the default value of the $exceptions property of the Event class (#806) --- src/Event.php | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/Event.php b/src/Event.php index de3acef59..0e0a91593 100644 --- a/src/Event.php +++ b/src/Event.php @@ -124,7 +124,7 @@ final class Event implements \JsonSerializable /** * @var array The exceptions */ - private $exceptions; + private $exceptions = []; /** * @var Stacktrace|null The stacktrace that generated this event @@ -640,23 +640,19 @@ public function toArray(): array $data['breadcrumbs']['values'] = $this->breadcrumbs; } - if (null !== $this->exceptions) { - $reversedException = array_reverse($this->exceptions); + foreach (array_reverse($this->exceptions) as $exception) { + $exceptionData = [ + 'type' => $exception['type'], + 'value' => $exception['value'], + ]; - foreach ($reversedException as $exception) { - $exceptionData = [ - 'type' => $exception['type'], - 'value' => $exception['value'], + if (isset($exception['stacktrace'])) { + $exceptionData['stacktrace'] = [ + 'frames' => $exception['stacktrace']->toArray(), ]; - - if (isset($exception['stacktrace'])) { - $exceptionData['stacktrace'] = [ - 'frames' => $exception['stacktrace']->toArray(), - ]; - } - - $data['exception']['values'][] = $exceptionData; } + + $data['exception']['values'][] = $exceptionData; } if (null !== $this->stacktrace) { From 53bcc818bd170041a84cbee81f91737776aa512e Mon Sep 17 00:00:00 2001 From: Harrison Heck Date: Mon, 29 Apr 2019 12:25:39 -0400 Subject: [PATCH 0453/1161] feat: Add support for SENTRY_ENVRIONMENT and SENTRY_RELEASE (#810) * feat: support SENTRY_ENVRIONMENT and SENTRY_RELEASE This fixes #804. * test: add tests for SENTRY_DSN, SENTRY_RELEASE, SENTRY_ENVIRONMENT --- src/Options.php | 4 ++-- tests/OptionsTest.php | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Options.php b/src/Options.php index df3951d9e..1c83d0a86 100644 --- a/src/Options.php +++ b/src/Options.php @@ -684,10 +684,10 @@ private function configureOptions(OptionsResolver $resolver): void 'attach_stacktrace' => false, 'context_lines' => 5, 'enable_compression' => true, - 'environment' => null, + 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, 'project_root' => null, 'logger' => 'php', - 'release' => null, + 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), 'before_send' => function (Event $event): ?Event { diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index dde79cce7..f9242e4c7 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -272,4 +272,33 @@ public function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array [false, '1'], ]; } + + public function testDsnOptionSupportsEnvironmentVariable(): void + { + $_SERVER['SENTRY_DSN'] = 'http://public@example.com/1'; + $options = new Options(); + unset($_SERVER['SENTRY_DSN']); + + $this->assertSame('http://example.com', $options->getDsn()); + $this->assertSame('public', $options->getPublicKey()); + $this->assertSame('1', $options->getProjectId()); + } + + public function testEnvironmentOptionSupportsEnvironmentVariable(): void + { + $_SERVER['SENTRY_ENVIRONMENT'] = 'test_environment'; + $options = new Options(); + unset($_SERVER['SENTRY_ENVIRONMENT']); + + $this->assertSame('test_environment', $options->getEnvironment()); + } + + public function testReleaseOptionSupportsEnvironmentVariable(): void + { + $_SERVER['SENTRY_RELEASE'] = '0.0.1'; + $options = new Options(); + unset($_SERVER['SENTRY_RELEASE']); + + $this->assertSame('0.0.1', $options->getRelease()); + } } From 5f348c02f186def464fc49345200366a70f556d7 Mon Sep 17 00:00:00 2001 From: peter-hoa <49166958+peter-hoa@users.noreply.github.com> Date: Tue, 30 Apr 2019 06:02:16 -0500 Subject: [PATCH 0454/1161] feat: Add Breadcrumb::fromArray (#798) --- src/Breadcrumb.php | 21 ++++++++++++++++++ tests/BreadcrumbTest.php | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index e8d13889e..292e3b7d1 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -13,6 +13,11 @@ */ final class Breadcrumb implements \JsonSerializable { + /** + * This constant defines the default breadcrumb type. + */ + public const TYPE_DEFAULT = 'default'; + /** * This constant defines the http breadcrumb type. */ @@ -357,6 +362,22 @@ public function toArray(): array ]; } + /** + * Helper method to create an instance of this class from an array of data. + * + * @param array $data Data used to populate the breadcrumb + */ + public static function fromArray(array $data): self + { + return new self( + $data['level'], + $data['type'] ?? self::TYPE_DEFAULT, + $data['category'], + $data['message'] ?? null, + $data['data'] ?? [] + ); + } + /** * {@inheritdoc} */ diff --git a/tests/BreadcrumbTest.php b/tests/BreadcrumbTest.php index 11f7b6488..df69a53c2 100644 --- a/tests/BreadcrumbTest.php +++ b/tests/BreadcrumbTest.php @@ -43,6 +43,52 @@ public function testConstructor(): void $this->assertEquals(microtime(true), $breadcrumb->getTimestamp()); } + /** + * @dataProvider fromArrayDataProvider + */ + public function testFromArray(array $requestData, array $expectedResult): void + { + $expectedResult['timestamp'] = microtime(true); + $breadcrumb = Breadcrumb::fromArray($requestData); + + $this->assertEquals($expectedResult, $breadcrumb->toArray()); + } + + public function fromArrayDataProvider(): array + { + return [ + [ + [ + 'level' => Breadcrumb::LEVEL_INFO, + 'type' => Breadcrumb::TYPE_USER, + 'category' => 'foo', + 'message' => 'foo bar', + 'data' => ['baz'], + ], + [ + 'level' => Breadcrumb::LEVEL_INFO, + 'type' => Breadcrumb::TYPE_USER, + 'category' => 'foo', + 'message' => 'foo bar', + 'data' => ['baz'], + ], + ], + [ + [ + 'level' => Breadcrumb::LEVEL_INFO, + 'category' => 'foo', + ], + [ + 'level' => Breadcrumb::LEVEL_INFO, + 'type' => Breadcrumb::TYPE_DEFAULT, + 'category' => 'foo', + 'message' => null, + 'data' => [], + ], + ], + ]; + } + public function testWithCategory(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); From b7b4157069c1481556dcb36405adc3f98d150598 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 30 Apr 2019 13:05:25 +0200 Subject: [PATCH 0455/1161] meta: Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b753b6882..99858bc7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ - Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (#786) - Increase default severity of `E_RECOVERABLE_ERROR` to `Severity::ERROR`, instead of warning (#792) +- feat: Add Breadcrumb::fromArray (#798) +- feat: Add support for SENTRY_ENVRIONMENT and SENTRY_RELEASE (#810) +- fix: The default value of the $exceptions property of the Event class (#806) ## 2.0.1 (2019-03-01) From 59a21135cc0ecb40d81dc9e0ca3beb127b8f83c8 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 2 May 2019 00:28:11 +0200 Subject: [PATCH 0456/1161] Add a Monolog handler (#808) --- CHANGELOG.md | 10 +- composer.json | 2 +- src/EventFactory.php | 4 +- src/Monolog/Handler.php | 103 ++++++++++++ tests/Monolog/HandlerTest.php | 285 ++++++++++++++++++++++++++++++++++ 5 files changed, 397 insertions(+), 7 deletions(-) create mode 100644 src/Monolog/Handler.php create mode 100644 tests/Monolog/HandlerTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 99858bc7e..d978fa3f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,12 @@ ## Unreleased - Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (#786) -- Increase default severity of `E_RECOVERABLE_ERROR` to `Severity::ERROR`, instead of warning (#792) -- feat: Add Breadcrumb::fromArray (#798) -- feat: Add support for SENTRY_ENVRIONMENT and SENTRY_RELEASE (#810) -- fix: The default value of the $exceptions property of the Event class (#806) +- Increase default severity of `E_RECOVERABLE_ERROR` to `Severity::ERROR`, instead + of `Severity::WARNING` (#792) +- Add a static factory method to create a breadcrumb from an array of data (#798) +- Add support for `SENTRY_ENVRIONMENT` and `SENTRY_RELEASE` environment variables (#810) +- Fix the default value of the `$exceptions` property of the Event class (#806) +- Add a Monolog handler (#808) ## 2.0.1 (2019-03-01) diff --git a/composer.json b/composer.json index f6872b8f2..523ee8f58 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", - "monolog/monolog": "^1.3", + "monolog/monolog": "^1.3|^2.0", "php-http/mock-client": "^1.0", "phpstan/phpstan-phpunit": "^0.10", "phpstan/phpstan": "^0.10.3", diff --git a/src/EventFactory.php b/src/EventFactory.php index d59d5317b..82616f772 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -80,8 +80,8 @@ public function create(array $payload): Event { try { $event = new Event(); - } catch (\Throwable $error) { - throw new EventCreationException($error); + } catch (\Throwable $exception) { + throw new EventCreationException($exception); } $event->setSdkIdentifier($this->sdkIdentifier); diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php new file mode 100644 index 000000000..40c646a35 --- /dev/null +++ b/src/Monolog/Handler.php @@ -0,0 +1,103 @@ + + */ +final class Handler extends AbstractProcessingHandler +{ + /** + * @var HubInterface + */ + private $hub; + + /** + * Constructor. + * + * @param HubInterface $hub The hub to which errors are reported + * @param int $level The minimum logging level at which this + * handler will be triggered + * @param bool $bubble Whether the messages that are handled can + * bubble up the stack or not + */ + public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true) + { + $this->hub = $hub; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record): void + { + $payload = [ + 'level' => $this->getSeverityFromLevel($record['level']), + 'message' => $record['message'], + ]; + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { + $payload['exception'] = $record['context']['exception']; + } + + $this->hub->withScope(function (Scope $scope) use ($record, $payload): void { + $scope->setExtra('monolog.channel', $record['channel']); + $scope->setExtra('monolog.level', $record['level_name']); + + if (isset($record['context']['extra']) && \is_array($record['context']['extra'])) { + foreach ($record['context']['extra'] as $key => $value) { + $scope->setExtra($key, $value); + } + } + + if (isset($record['context']['tags']) && \is_array($record['context']['tags'])) { + foreach ($record['context']['tags'] as $key => $value) { + $scope->setTag($key, $value); + } + } + + $this->hub->captureEvent($payload); + }); + } + + /** + * Translates the Monolog level into the Sentry severity. + * + * @param int $level The Monolog log level + * + * @return Severity + */ + private function getSeverityFromLevel(int $level): Severity + { + switch ($level) { + case Logger::DEBUG: + return Severity::debug(); + case Logger::INFO: + case Logger::NOTICE: + return Severity::info(); + case Logger::WARNING: + return Severity::warning(); + case Logger::ERROR: + return Severity::error(); + case Logger::CRITICAL: + case Logger::ALERT: + case Logger::EMERGENCY: + return Severity::fatal(); + default: + return Severity::info(); + } + } +} diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php new file mode 100644 index 000000000..c065f9211 --- /dev/null +++ b/tests/Monolog/HandlerTest.php @@ -0,0 +1,285 @@ +client = $this->createMock(ClientInterface::class); + } + + /** + * @dataProvider handleDataProvider + */ + public function testHandle(array $record, array $expectedPayload, array $expectedExtra, array $expectedTags): void + { + $this->client->expects($this->once()) + ->method('captureEvent') + ->with($expectedPayload, $this->callback(static function (Scope $scope) use ($expectedExtra, $expectedTags): bool { + if ($expectedExtra !== $scope->getExtra()) { + return false; + } + + if ($expectedTags !== $scope->getTags()) { + return false; + } + + return true; + })); + + $handler = new Handler(new Hub($this->client)); + $handler->handle($record); + } + + public function handleDataProvider(): \Generator + { + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::DEBUG, + 'level_name' => Logger::getLevelName(Logger::DEBUG), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + ], + [ + 'level' => Severity::debug(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::DEBUG), + ], + [], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::INFO, + 'level_name' => Logger::getLevelName(Logger::INFO), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + ], + [ + 'level' => Severity::info(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::INFO), + ], + [], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::NOTICE, + 'level_name' => Logger::getLevelName(Logger::NOTICE), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + ], + [ + 'level' => Severity::info(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::NOTICE), + ], + [], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + ], + [ + 'level' => Severity::warning(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::WARNING), + ], + [], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::ERROR, + 'level_name' => Logger::getLevelName(Logger::ERROR), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + ], + [ + 'level' => Severity::error(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::ERROR), + ], + [], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::CRITICAL, + 'level_name' => Logger::getLevelName(Logger::CRITICAL), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + ], + [ + 'level' => Severity::fatal(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::CRITICAL), + ], + [], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::ALERT, + 'level_name' => Logger::getLevelName(Logger::ALERT), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + ], + [ + 'level' => Severity::fatal(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::ALERT), + ], + [], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::EMERGENCY, + 'level_name' => Logger::getLevelName(Logger::EMERGENCY), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + ], + [ + 'level' => Severity::fatal(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::EMERGENCY), + ], + [], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'context' => [ + 'extra' => [ + 'foo.extra' => 'foo extra value', + 'bar.extra' => 'bar extra value', + ], + 'tags' => [ + 'foo.tag' => 'foo tag value', + 'bar.tag' => 'bar tag value', + ], + ], + 'channel' => 'channel.foo', + 'datetime' => new \DateTimeImmutable(), + 'extra' => [], + ], + [ + 'level' => Severity::warning(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::WARNING), + 'foo.extra' => 'foo extra value', + 'bar.extra' => 'bar extra value', + ], + [ + 'foo.tag' => 'foo tag value', + 'bar.tag' => 'bar tag value', + ], + ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'context' => [ + 'exception' => new \Exception('exception message'), + 'extra' => [ + 'foo.extra' => 'foo extra value', + 'bar.extra' => 'bar extra value', + ], + 'tags' => [ + 'foo.tag' => 'foo tag value', + 'bar.tag' => 'bar tag value', + ], + ], + 'channel' => 'channel.foo', + 'datetime' => new \DateTimeImmutable(), + 'extra' => [], + ], + [ + 'level' => Severity::warning(), + 'message' => 'foo bar', + 'exception' => new \Exception('exception message'), + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::WARNING), + 'foo.extra' => 'foo extra value', + 'bar.extra' => 'bar extra value', + ], + [ + 'foo.tag' => 'foo tag value', + 'bar.tag' => 'bar tag value', + ], + ]; + } +} From ce0cef9b7281ed8035f65f220134c57503d3c2cd Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 3 May 2019 10:14:24 +0200 Subject: [PATCH 0457/1161] Make it possible to register fatal error listeners separately from the error listeners (#788) --- CHANGELOG.md | 5 +- phpunit.xml.dist | 2 +- src/ClientBuilder.php | 4 +- src/ErrorHandler.php | 230 +++++++++++-- src/Exception/FatalErrorException.php | 14 + src/Integration/ErrorListenerIntegration.php | 33 +- .../ExceptionListenerIntegration.php | 5 +- .../FatalErrorListenerIntegration.php | 48 +++ tests/ClientBuilderTest.php | 3 + tests/ClientTest.php | 39 +-- tests/ErrorHandlerTest.php | 312 ------------------ tests/Fixtures/classes/CarelessException.php | 13 - tests/Fixtures/classes/StubErrorListener.php | 40 --- tests/bootstrap.php | 4 - tests/phpt/error_handler_called.phpt | 32 -- .../error_handler_can_be_registered_once.phpt | 62 ++++ tests/phpt/error_handler_captures_error.phpt | 46 +++ .../error_handler_captures_exception.phpt | 46 +++ ...xception_thrown_from_previous_handler.phpt | 39 +++ .../error_handler_captures_fatal_error.phpt | 62 ++++ ...er_captures_out_of_memory_fatal_error.phpt | 45 +++ ...dler_captures_rethrown_exception_once.phpt | 51 +++ ...handler_only_error_handler_registered.phpt | 33 ++ ...ler_only_exception_handler_registered.phpt | 33 ++ ...rror_handler_respects_error_reporting.phpt | 51 +++ ...s_previous_error_handler_return_value.phpt | 52 +++ ...er_throws_on_invalid_reserved_memory.phpt} | 14 +- ...tegration_respects_error_types_option.phpt | 54 +++ ...tion_skips_fatal_errors_if_configured.phpt | 51 +++ tests/phpt/exception_rethrown.phpt | 32 -- tests/phpt/exception_thrown.phpt | 32 -- tests/phpt/fatal_error.phpt | 32 -- ...tegration_respects_error_types_option.phpt | 58 ++++ .../phpt/fatal_error_not_captured_twice.phpt | 39 --- tests/phpt/out_of_memory.phpt | 40 --- tests/phpt/send_only_all_except_notice.phpt | 56 ---- tests/phpt/silenced_errors.phpt | 41 --- tests/phpt/spool_drain.phpt | 10 +- 38 files changed, 996 insertions(+), 767 deletions(-) create mode 100644 src/Exception/FatalErrorException.php create mode 100644 src/Integration/FatalErrorListenerIntegration.php delete mode 100644 tests/ErrorHandlerTest.php delete mode 100644 tests/Fixtures/classes/CarelessException.php delete mode 100644 tests/Fixtures/classes/StubErrorListener.php delete mode 100644 tests/phpt/error_handler_called.phpt create mode 100644 tests/phpt/error_handler_can_be_registered_once.phpt create mode 100644 tests/phpt/error_handler_captures_error.phpt create mode 100644 tests/phpt/error_handler_captures_exception.phpt create mode 100644 tests/phpt/error_handler_captures_exception_thrown_from_previous_handler.phpt create mode 100644 tests/phpt/error_handler_captures_fatal_error.phpt create mode 100644 tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt create mode 100644 tests/phpt/error_handler_captures_rethrown_exception_once.phpt create mode 100644 tests/phpt/error_handler_only_error_handler_registered.phpt create mode 100644 tests/phpt/error_handler_only_exception_handler_registered.phpt create mode 100644 tests/phpt/error_handler_respects_error_reporting.phpt create mode 100644 tests/phpt/error_handler_returns_previous_error_handler_return_value.phpt rename tests/phpt/{error_handler_refuses_negative_value.phpt => error_handler_throws_on_invalid_reserved_memory.phpt} (53%) create mode 100644 tests/phpt/error_listener_integration_respects_error_types_option.phpt create mode 100644 tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt delete mode 100644 tests/phpt/exception_rethrown.phpt delete mode 100644 tests/phpt/exception_thrown.phpt delete mode 100644 tests/phpt/fatal_error.phpt create mode 100644 tests/phpt/fatal_error_integration_respects_error_types_option.phpt delete mode 100644 tests/phpt/fatal_error_not_captured_twice.phpt delete mode 100644 tests/phpt/out_of_memory.phpt delete mode 100644 tests/phpt/send_only_all_except_notice.phpt delete mode 100644 tests/phpt/silenced_errors.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index d978fa3f7..6837bd497 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ ## Unreleased - Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (#786) -- Increase default severity of `E_RECOVERABLE_ERROR` to `Severity::ERROR`, instead - of `Severity::WARNING` (#792) +- Increase default severity of `E_RECOVERABLE_ERROR` to `Severity::ERROR`, instead of warning (#792) +- Make it possible to register fatal error listeners separately from the error listeners + and change the type of the reported exception to `\Sentry\Exception\FatalErrorException` (#788) - Add a static factory method to create a breadcrumb from an array of data (#798) - Add support for `SENTRY_ENVRIONMENT` and `SENTRY_RELEASE` environment variables (#810) - Fix the default value of the `$exceptions` property of the Event class (#806) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bcc751a0c..de44dc74d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,7 +11,7 @@
- + tests tests/phpt diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index eb440736a..1ebc94216 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -24,6 +24,7 @@ use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; +use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Integration\RequestIntegration; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; @@ -102,7 +103,8 @@ public function __construct(Options $options = null) if ($this->options->hasDefaultIntegrations()) { $this->options->setIntegrations(array_merge([ new ExceptionListenerIntegration(), - new ErrorListenerIntegration($this->options), + new ErrorListenerIntegration($this->options, false), + new FatalErrorListenerIntegration($this->options), new RequestIntegration($this->options), ], $this->options->getIntegrations())); } diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 053e45e2d..406d20445 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -4,6 +4,7 @@ namespace Sentry; +use Sentry\Exception\FatalErrorException; use Sentry\Exception\SilencedErrorException; /** @@ -18,8 +19,10 @@ final class ErrorHandler { /** * The default amount of bytes of memory to reserve for the fatal error handler. + * + * @internal */ - private const DEFAULT_RESERVED_MEMORY_SIZE = 10240; + public const DEFAULT_RESERVED_MEMORY_SIZE = 10240; /** * @var self The current registered handler (this class is a singleton) @@ -31,6 +34,11 @@ final class ErrorHandler */ private $errorListeners = []; + /** + * @var callable[] List of listeners that will act of each captured fatal error + */ + private $fatalErrorListeners = []; + /** * @var callable[] List of listeners that will act on each captured exception */ @@ -52,6 +60,21 @@ final class ErrorHandler */ private $previousExceptionHandler; + /** + * @var bool Whether the error handler has been registered + */ + private $isErrorHandlerRegistered = false; + + /** + * @var bool Whether the exception handler has been registered + */ + private $isExceptionHandlerRegistered = false; + + /** + * @var bool Whether the fatal error handler has been registered + */ + private $isFatalErrorHandlerRegistered = false; + /** * @var string|null A portion of pre-allocated memory data that will be reclaimed * in case a fatal error occurs to handle it @@ -82,50 +105,130 @@ final class ErrorHandler /** * Constructor. * - * @param int $reservedMemorySize The amount of memory to reserve for the fatal error handler + * @throws \ReflectionException If hooking into the \Exception class to + * make the `trace` property accessible fails */ - private function __construct(int $reservedMemorySize) + private function __construct() { - if ($reservedMemorySize <= 0) { - throw new \InvalidArgumentException('The $reservedMemorySize argument must be greater than 0.'); - } - $this->exceptionReflection = new \ReflectionProperty(\Exception::class, 'trace'); $this->exceptionReflection->setAccessible(true); + } - self::$reservedMemory = str_repeat('x', $reservedMemorySize); + /** + * Gets the current registered error handler; if none is present, it will + * register it. Subsequent calls will not change the reserved memory size. + * + * @param int $reservedMemorySize The amount of memory to reserve for the + * fatal error handler + * @param bool $triggerDeprecation Whether to trigger the deprecation about + * the usage of this method. This is used + * to avoid errors when this method is called + * from other methods of this class until + * their implementation and behavior of + * registering all handlers can be changed + * + * @return self + * + * @deprecated since version 2.1, to be removed in 3.0. + */ + public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE, bool $triggerDeprecation = true): self + { + if ($triggerDeprecation) { + @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Please use the registerOnceErrorHandler(), registerOnceFatalErrorHandler() or registerOnceExceptionHandler() methods instead.', __METHOD__), E_USER_DEPRECATED); + } + + self::registerOnceErrorHandler(); + self::registerOnceFatalErrorHandler($reservedMemorySize); + self::registerOnceExceptionHandler(); + + return self::$handlerInstance; + } + + /** + * Registers the error handler once and returns its instance. + * + * @return self + */ + public static function registerOnceErrorHandler(): self + { + if (null === self::$handlerInstance) { + self::$handlerInstance = new self(); + } + + if (self::$handlerInstance->isErrorHandlerRegistered) { + return self::$handlerInstance; + } - register_shutdown_function([$this, 'handleFatalError']); + $errorHandlerCallback = \Closure::fromCallable([self::$handlerInstance, 'handleError']); - $this->previousErrorHandler = set_error_handler([$this, 'handleError']); + self::$handlerInstance->isErrorHandlerRegistered = true; + self::$handlerInstance->previousErrorHandler = set_error_handler($errorHandlerCallback); - if (null === $this->previousErrorHandler) { + if (null === self::$handlerInstance->previousErrorHandler) { restore_error_handler(); // Specifying the error types caught by the error handler with the // first call to the set_error_handler method would cause the PHP // bug https://bugs.php.net/63206 if the handler is not the first // one in the chain of handlers - set_error_handler([$this, 'handleError'], E_ALL); + set_error_handler($errorHandlerCallback, E_ALL); } - $this->previousExceptionHandler = set_exception_handler([$this, 'handleException']); + return self::$handlerInstance; } /** - * Gets the current registered error handler; if none is present, it will register it. - * Subsequent calls will not change the reserved memory size. + * Registers the fatal error handler and reserves a certain amount of memory + * that will be reclaimed to handle the errors (to prevent out of memory + * issues while handling them) and returns its instance. * - * @param int $reservedMemorySize The requested amount of memory to reserve + * @param int $reservedMemorySize The amount of memory to reserve for the fatal + * error handler expressed in bytes + * + * @return self + */ + public static function registerOnceFatalErrorHandler(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE): self + { + if ($reservedMemorySize <= 0) { + throw new \InvalidArgumentException('The $reservedMemorySize argument must be greater than 0.'); + } + + if (null === self::$handlerInstance) { + self::$handlerInstance = new self(); + } + + if (self::$handlerInstance->isFatalErrorHandlerRegistered) { + return self::$handlerInstance; + } + + self::$handlerInstance->isFatalErrorHandlerRegistered = true; + self::$reservedMemory = str_repeat('x', $reservedMemorySize); + + register_shutdown_function(\Closure::fromCallable([self::$handlerInstance, 'handleFatalError'])); + + return self::$handlerInstance; + } + + /** + * Registers the exception handler, effectively replacing the current one + * and returns its instance. The previous one will be saved anyway and + * called when appropriate. * - * @return self The ErrorHandler singleton + * @return self */ - public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE): self + public static function registerOnceExceptionHandler(): self { if (null === self::$handlerInstance) { - self::$handlerInstance = new self($reservedMemorySize); + self::$handlerInstance = new self(); + } + + if (self::$handlerInstance->isExceptionHandlerRegistered) { + return self::$handlerInstance; } + self::$handlerInstance->isExceptionHandlerRegistered = true; + self::$handlerInstance->previousExceptionHandler = set_exception_handler(\Closure::fromCallable([self::$handlerInstance, 'handleException'])); + return self::$handlerInstance; } @@ -137,13 +240,36 @@ public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESE * @param callable $listener A callable that will act as a listener; * this callable will receive a single * \ErrorException argument + * + * @deprecated since version 2.1, to be removed in 3.0 */ public static function addErrorListener(callable $listener): void { - $handler = self::registerOnce(); + @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addErrorHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); + + $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); $handler->errorListeners[] = $listener; } + /** + * Adds a listener to the current error handler to be called upon each + * invoked captured fatal error; if no handler is registered, this method + * will instantiate and register it. + * + * @param callable $listener A callable that will act as a listener; + * this callable will receive a single + * \ErrorException argument + * + * @deprecated since version 2.1, to be removed in 3.0 + */ + public static function addFatalErrorListener(callable $listener): void + { + @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addFatalErrorHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); + + $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); + $handler->fatalErrorListeners[] = $listener; + } + /** * Adds a listener to the current error handler to be called upon each * invoked captured exception; if no handler is registered, this method @@ -152,13 +278,56 @@ public static function addErrorListener(callable $listener): void * @param callable $listener A callable that will act as a listener; * this callable will receive a single * \Throwable argument + * + * @deprecated since version 2.1, to be removed in 3.0 */ public static function addExceptionListener(callable $listener): void { - $handler = self::registerOnce(); + @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addExceptionHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); + + $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); $handler->exceptionListeners[] = $listener; } + /** + * Adds a listener to the current error handler that will be called every + * time an error is captured. + * + * @param callable $listener A callable that will act as a listener + * and that must accept a single argument + * of type \ErrorException + */ + public function addErrorHandlerListener(callable $listener): void + { + $this->errorListeners[] = $listener; + } + + /** + * Adds a listener to the current error handler that will be called every + * time a fatal error handler is captured. + * + * @param callable $listener A callable that will act as a listener + * and that must accept a single argument + * of type \Sentry\Exception\FatalErrorException + */ + public function addFatalErrorHandlerListener(callable $listener): void + { + $this->fatalErrorListeners[] = $listener; + } + + /** + * Adds a listener to the current error handler that will be called every + * time an exception is captured. + * + * @param callable $listener A callable that will act as a listener + * and that must accept a single argument + * of type \Throwable + */ + public function addExceptionHandlerListener(callable $listener): void + { + $this->exceptionListeners[] = $listener; + } + /** * Handles errors by capturing them through the Raven client according to * the configured bit field. @@ -173,10 +342,8 @@ public static function addExceptionListener(callable $listener): void * handler will be called * * @throws \Throwable - * - * @internal */ - public function handleError(int $level, string $message, string $file, int $line): bool + private function handleError(int $level, string $message, string $file, int $line): bool { if (0 === error_reporting()) { $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); @@ -184,7 +351,7 @@ public function handleError(int $level, string $message, string $file, int $line $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); } - $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $file, $line); + $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $errorAsException->getFile(), $errorAsException->getLine()); $this->exceptionReflection->setValue($errorAsException, $backtrace); @@ -202,10 +369,8 @@ public function handleError(int $level, string $message, string $file, int $line * method is used as callback of a shutdown function. * * @param array|null $error The error details as returned by error_get_last() - * - * @internal */ - public function handleFatalError(array $error = null): void + private function handleFatalError(array $error = null): void { // If there is not enough memory that can be used to handle the error // do nothing @@ -221,9 +386,12 @@ public function handleFatalError(array $error = null): void } if (!empty($error) && $error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)) { - $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); + $errorAsException = new FatalErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); + + $this->exceptionReflection->setValue($errorAsException, []); $this->invokeListeners($this->errorListeners, $errorAsException); + $this->invokeListeners($this->fatalErrorListeners, $errorAsException); } } @@ -234,10 +402,8 @@ public function handleFatalError(array $error = null): void * @param \Throwable $exception The exception to handle * * @throws \Throwable - * - * @internal This method is public only because it's used with set_exception_handler */ - public function handleException(\Throwable $exception): void + private function handleException(\Throwable $exception): void { $this->invokeListeners($this->exceptionListeners, $exception); diff --git a/src/Exception/FatalErrorException.php b/src/Exception/FatalErrorException.php new file mode 100644 index 000000000..3cd50d709 --- /dev/null +++ b/src/Exception/FatalErrorException.php @@ -0,0 +1,14 @@ + + */ +final class FatalErrorException extends \ErrorException +{ +} diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index 50b79bb50..e1efeb445 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -5,6 +5,7 @@ namespace Sentry\Integration; use Sentry\ErrorHandler; +use Sentry\Exception\FatalErrorException; use Sentry\Exception\SilencedErrorException; use Sentry\Options; use Sentry\State\Hub; @@ -21,13 +22,24 @@ final class ErrorListenerIntegration implements IntegrationInterface private $options; /** - * ErrorListenerIntegration constructor. + * @var bool Whether to handle fatal errors or not + */ + private $handleFatalErrors; + + /** + * Constructor. * - * @param Options $options The options to be used with this integration + * @param Options $options The options to be used with this integration + * @param bool $handleFatalErrors Whether to handle fatal errors or not */ - public function __construct(Options $options) + public function __construct(Options $options, bool $handleFatalErrors = true) { $this->options = $options; + $this->handleFatalErrors = $handleFatalErrors; + + if ($handleFatalErrors) { + @trigger_error(sprintf('Handling fatal errors with the "%s" class is deprecated since version 2.1. Use the "%s" integration instead.', self::class, FatalErrorListenerIntegration::class), E_USER_DEPRECATED); + } } /** @@ -35,14 +47,21 @@ public function __construct(Options $options) */ public function setupOnce(): void { - ErrorHandler::addErrorListener(function (\ErrorException $error): void { - if ($error instanceof SilencedErrorException && !$this->options->shouldCaptureSilencedErrors()) { + $errorHandler = ErrorHandler::registerOnce(ErrorHandler::DEFAULT_RESERVED_MEMORY_SIZE, false); + $errorHandler->addErrorHandlerListener(function (\ErrorException $exception): void { + if (!$this->handleFatalErrors && $exception instanceof FatalErrorException) { return; } - if ($this->options->getErrorTypes() & $error->getSeverity()) { - Hub::getCurrent()->captureException($error); + if ($exception instanceof SilencedErrorException && !$this->options->shouldCaptureSilencedErrors()) { + return; } + + if (!($this->options->getErrorTypes() & $exception->getSeverity())) { + return; + } + + Hub::getCurrent()->captureException($exception); }); } } diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index f0fb76a44..119b19459 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -18,8 +18,9 @@ final class ExceptionListenerIntegration implements IntegrationInterface */ public function setupOnce(): void { - ErrorHandler::addExceptionListener(static function (\Throwable $throwable): void { - Hub::getCurrent()->captureException($throwable); + $errorHandler = ErrorHandler::registerOnce(ErrorHandler::DEFAULT_RESERVED_MEMORY_SIZE, false); + $errorHandler->addExceptionHandlerListener(static function (\Throwable $exception): void { + Hub::getCurrent()->captureException($exception); }); } } diff --git a/src/Integration/FatalErrorListenerIntegration.php b/src/Integration/FatalErrorListenerIntegration.php new file mode 100644 index 000000000..85487696f --- /dev/null +++ b/src/Integration/FatalErrorListenerIntegration.php @@ -0,0 +1,48 @@ + + */ +final class FatalErrorListenerIntegration implements IntegrationInterface +{ + /** + * @var Options The options, to know which error level to use + */ + private $options; + + /** + * Constructor. + * + * @param Options $options The options to be used with this integration + */ + public function __construct(Options $options) + { + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function setupOnce(): void + { + $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); + $errorHandler->addFatalErrorHandlerListener(function (FatalErrorException $exception): void { + if (!($this->options->getErrorTypes() & $exception->getSeverity())) { + return; + } + + Hub::getCurrent()->captureException($exception); + }); + } +} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 984b0ca14..6292d50b2 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -19,6 +19,7 @@ use Sentry\Event; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; +use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; use Sentry\Options; @@ -193,6 +194,7 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array [], [ ErrorListenerIntegration::class, + FatalErrorListenerIntegration::class, ExceptionListenerIntegration::class, RequestIntegration::class, ], @@ -202,6 +204,7 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array [new StubIntegration()], [ ErrorListenerIntegration::class, + FatalErrorListenerIntegration::class, ExceptionListenerIntegration::class, RequestIntegration::class, StubIntegration::class, diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f5d35766f..7565a7b07 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -17,8 +17,6 @@ use Sentry\Serializer\SerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; -use Sentry\State\Hub; -use Sentry\Tests\Fixtures\classes\CarelessException; use Sentry\Transport\TransportInterface; class ClientTest extends TestCase @@ -257,32 +255,6 @@ public function sampleRateAbsoluteDataProvider(): array ]; } - public function testHandlingExceptionThrowingAnException(): void - { - /** @var TransportInterface|MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function ($event) { - /* @var Event $event*/ - // Make sure the exception is of the careless exception and not the exception thrown inside - // the __set method of that exception caused by setting the event_id on the exception instance - $this->assertSame(CarelessException::class, $event->getExceptions()[0]['type']); - - return true; - })); - - $client = new Client(new Options(), $transport, $this->createEventFactory()); - - $hub = Hub::getCurrent(); - $hub->bindClient($client); - - $method = new \ReflectionMethod($hub, 'getScope'); - $method->setAccessible(true); - - $client->captureException($this->createCarelessExceptionWithStacktrace(), $method->invoke($hub)); - } - /** * @dataProvider convertExceptionDataProvider */ @@ -464,7 +436,7 @@ public function testAttachStacktraceShouldNotWorkWithCaptureEvent(): void */ private function clearLastError(): void { - $handler = function () { + $handler = static function () { return false; }; @@ -473,15 +445,6 @@ private function clearLastError(): void restore_error_handler(); } - private function createCarelessExceptionWithStacktrace(): CarelessException - { - try { - throw new CarelessException('Foo bar'); - } catch (CarelessException $ex) { - return $ex; - } - } - private function createEventFactory(): EventFactory { return new EventFactory( diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php deleted file mode 100644 index 81fe1ae6e..000000000 --- a/tests/ErrorHandlerTest.php +++ /dev/null @@ -1,312 +0,0 @@ -assertSame(ErrorHandler::registerOnce(), ErrorHandler::registerOnce()); - } - - public function testHandleError(): void - { - $listener = new StubErrorListener(); - $errorLine = null; - - try { - ErrorHandler::addErrorListener($listener); - - $errorHandler = ErrorHandler::registerOnce(); - - $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousErrorHandler'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($errorHandler, null); - $reflectionProperty->setAccessible(false); - - $errorLine = __LINE__ + 2; - - $this->assertFalse($errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, __LINE__)); - } finally { - restore_error_handler(); - restore_exception_handler(); - - $exception = $listener->getError(); - - $this->assertInstanceOf(\ErrorException::class, $exception); - $this->assertEquals(__FILE__, $exception->getFile()); - $this->assertEquals($errorLine, $exception->getLine()); - $this->assertEquals('User Notice: foo bar', $exception->getMessage()); - $this->assertEquals(E_USER_NOTICE, $exception->getSeverity()); - - $backtrace = $exception->getTrace(); - - $this->assertGreaterThanOrEqual(2, $backtrace); - $this->assertEquals('testHandleError', $backtrace[0]['function']); - $this->assertEquals(self::class, $backtrace[0]['class']); - $this->assertEquals('->', $backtrace[0]['type']); - } - } - - /** - * @dataProvider handleErrorWithPreviousErrorHandlerDataProvider - */ - public function testHandleErrorWithPreviousErrorHandler($previousErrorHandlerErrorReturnValue, bool $expectedHandleErrorReturnValue): void - { - $previousErrorHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); - $previousErrorHandler->expects($this->once()) - ->method('__invoke') - ->with(E_USER_NOTICE, 'foo bar', __FILE__, 123) - ->willReturn($previousErrorHandlerErrorReturnValue); - - $listener = new StubErrorListener(); - - try { - ErrorHandler::addErrorListener($listener); - - $errorHandler = ErrorHandler::registerOnce(); - - $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousErrorHandler'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($errorHandler, $previousErrorHandler); - $reflectionProperty->setAccessible(false); - - $this->assertEquals($expectedHandleErrorReturnValue, $errorHandler->handleError(E_USER_NOTICE, 'foo bar', __FILE__, 123)); - } finally { - restore_error_handler(); - restore_exception_handler(); - - $exception = $listener->getError(); - - $this->assertInstanceOf(\ErrorException::class, $exception); - $this->assertEquals(__FILE__, $exception->getFile()); - $this->assertEquals(123, $exception->getLine()); - $this->assertEquals(E_USER_NOTICE, $exception->getSeverity()); - $this->assertEquals('User Notice: foo bar', $exception->getMessage()); - - $backtrace = $exception->getTrace(); - - $this->assertGreaterThanOrEqual(2, $backtrace); - $this->assertEquals('handleError', $backtrace[0]['function']); - $this->assertEquals(ErrorHandler::class, $backtrace[0]['class']); - $this->assertEquals('->', $backtrace[0]['type']); - $this->assertEquals('testHandleErrorWithPreviousErrorHandler', $backtrace[1]['function']); - $this->assertEquals(self::class, $backtrace[1]['class']); - $this->assertEquals('->', $backtrace[1]['type']); - } - } - - public function handleErrorWithPreviousErrorHandlerDataProvider(): array - { - return [ - [false, false], - [true, true], - [0, true], // check that we're using strict comparison instead of shallow - [1, true], // check that we're using strict comparison instead of shallow - ['0', true], // check that we're using strict comparison instead of shallow - ['1', true], // check that we're using strict comparison instead of shallow - ]; - } - - public function testHandleFatalError(): void - { - $listener = new StubErrorListener(); - - try { - ErrorHandler::addErrorListener($listener); - - $errorHandler = ErrorHandler::registerOnce(); - - $errorHandler->handleFatalError([ - 'type' => E_PARSE, - 'message' => 'foo bar', - 'file' => __FILE__, - 'line' => 123, - ]); - } finally { - restore_error_handler(); - restore_exception_handler(); - - $exception = $listener->getError(); - - $this->assertInstanceOf(\ErrorException::class, $exception); - $this->assertEquals(__FILE__, $exception->getFile()); - $this->assertEquals(123, $exception->getLine()); - $this->assertEquals(E_PARSE, $exception->getSeverity()); - $this->assertEquals('Parse Error: foo bar', $exception->getMessage()); - } - } - - public function testHandleFatalErrorWithNonFatalErrorDoesNothing(): void - { - $listener = new StubErrorListener(); - - try { - ErrorHandler::addErrorListener($listener); - - $errorHandler = ErrorHandler::registerOnce(); - - $errorHandler->handleFatalError([ - 'type' => E_USER_NOTICE, - 'message' => 'foo bar', - 'file' => __FILE__, - 'line' => __LINE__, - ]); - } finally { - restore_error_handler(); - restore_exception_handler(); - - $this->assertNull($listener->getError()); - } - } - - public function testHandleException(): void - { - $listenerCalled = false; - $exception = new \Exception('foo bar'); - $listener = function (\Throwable $throwable) use ($exception, &$listenerCalled): void { - $listenerCalled = true; - - $this->assertSame($exception, $throwable); - }; - - try { - ErrorHandler::addExceptionListener($listener); - - $errorHandler = ErrorHandler::registerOnce(); - - try { - $errorHandler->handleException($exception); - - $this->fail('Exception expected'); - } catch (\Exception $caughtException) { - $this->assertSame($exception, $caughtException); - } - } finally { - restore_error_handler(); - restore_exception_handler(); - - $this->assertTrue($listenerCalled, 'Listener was not called'); - } - } - - public function testHandleExceptionWithPreviousExceptionHandler(): void - { - $listenerCalled = false; - $exception = new \Exception('foo bar'); - - $listener = function (\Throwable $throwable) use ($exception, &$listenerCalled): void { - $listenerCalled = true; - - $this->assertSame($exception, $throwable); - }; - - $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); - $previousExceptionHandler->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($exception)); - - try { - ErrorHandler::addExceptionListener($listener); - - $errorHandler = ErrorHandler::registerOnce(); - - $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousExceptionHandler'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); - $reflectionProperty->setAccessible(false); - - try { - $errorHandler->handleException($exception); - - $this->fail('Exception expected'); - } catch (\Exception $caughtException) { - $this->assertSame($exception, $caughtException); - } - } finally { - restore_error_handler(); - restore_exception_handler(); - - $this->assertTrue($listenerCalled, 'Listener was not called'); - } - } - - public function testHandleExceptionWithThrowingPreviousExceptionHandler(): void - { - $listenerCalled = 0; - $exception1 = new \Exception('foo bar'); - $exception2 = new \Exception('bar foo'); - $captured1 = $captured2 = null; - - $listener = function (\Throwable $throwable) use (&$captured1, &$captured2, &$listenerCalled): void { - if (0 === $listenerCalled) { - $captured1 = $throwable; - } elseif (1 === $listenerCalled) { - $captured2 = $throwable; - } - - ++$listenerCalled; - }; - - $previousExceptionHandler = $this->createPartialMock(\stdClass::class, ['__invoke']); - $previousExceptionHandler->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($exception1)) - ->will($this->throwException($exception2)); - - try { - ErrorHandler::addExceptionListener($listener); - - $errorHandler = ErrorHandler::registerOnce(); - - $reflectionProperty = new \ReflectionProperty(ErrorHandler::class, 'previousExceptionHandler'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($errorHandler, $previousExceptionHandler); - $reflectionProperty->setAccessible(false); - - try { - $errorHandler->handleException($exception1); - - $this->fail('Exception expected'); - } catch (\Exception $caughtException) { - $this->assertSame($exception2, $caughtException); - } - } finally { - restore_error_handler(); - restore_exception_handler(); - - $this->assertSame(2, $listenerCalled); - $this->assertSame($exception1, $captured1); - $this->assertSame($exception2, $captured2); - } - } - - public function testListenerThatThrowsExceptionShouldBeIgnored(): void - { - $exception = new \Exception(); - $exceptionRethrown = false; - $listener = function (\Throwable $throwable): void { - throw new \RuntimeException('This exception should not bubble up'); - }; - - try { - ErrorHandler::addExceptionListener($listener); - ErrorHandler::registerOnce()->handleException($exception); - } catch (\Throwable $rethrownException) { - $this->assertSame($exception, $rethrownException); - - $exceptionRethrown = true; - } finally { - $this->assertTrue($exceptionRethrown, 'Handler did not rethrow the exception'); - - restore_error_handler(); - restore_exception_handler(); - } - } -} diff --git a/tests/Fixtures/classes/CarelessException.php b/tests/Fixtures/classes/CarelessException.php deleted file mode 100644 index 40d1cdb50..000000000 --- a/tests/Fixtures/classes/CarelessException.php +++ /dev/null @@ -1,13 +0,0 @@ -callable = $callable; - } - - public function __invoke(\ErrorException $error): void - { - $this->error = $error; - - if ($this->callable) { - call_user_func($this->callable, $error); - } - } - - public function someCallable(\ErrorException $error): void - { - $this->__invoke($error); - } - - public function getError(): ?\ErrorException - { - return $this->error; - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 166f1c4ba..b3985f2b4 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -5,10 +5,6 @@ use Http\Discovery\ClassDiscovery; use Http\Discovery\Strategy\MockClientStrategy; -error_reporting(E_ALL | E_STRICT); - -session_start(); - require_once __DIR__ . '/../vendor/autoload.php'; ClassDiscovery::appendStrategy(MockClientStrategy::class); diff --git a/tests/phpt/error_handler_called.phpt b/tests/phpt/error_handler_called.phpt deleted file mode 100644 index a9070611b..000000000 --- a/tests/phpt/error_handler_called.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Test that the error handler is called regardless of the current `error_reporting` setting ---FILE-- - ---EXPECTF-- -Triggering error -Callback invoked -End diff --git a/tests/phpt/error_handler_can_be_registered_once.phpt b/tests/phpt/error_handler_can_be_registered_once.phpt new file mode 100644 index 000000000..e949b5e8e --- /dev/null +++ b/tests/phpt/error_handler_can_be_registered_once.phpt @@ -0,0 +1,62 @@ +--TEST-- +Test that the error handler is registered only once regardless of how many times it's instantiated +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/tests/phpt/error_handler_captures_error.phpt b/tests/phpt/error_handler_captures_error.phpt new file mode 100644 index 000000000..0d21571c7 --- /dev/null +++ b/tests/phpt/error_handler_captures_error.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test catching errors +--INI-- +error_reporting=0 +--FILE-- +addErrorHandlerListener(static function (): void { + echo 'Error listener called' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called (it should not have been)'; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { + echo 'Exception listener called (it should not have been)'; +}); + +echo 'Triggering error' . PHP_EOL; + +trigger_error('Error thrown', E_USER_WARNING); + +echo 'End of script reached'; +?> +--EXPECT-- +Triggering error +Error listener called +End of script reached diff --git a/tests/phpt/error_handler_captures_exception.phpt b/tests/phpt/error_handler_captures_exception.phpt new file mode 100644 index 000000000..5365d6b84 --- /dev/null +++ b/tests/phpt/error_handler_captures_exception.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test catching exceptions +--FILE-- +addErrorHandlerListener(static function (): void { + echo 'Error listener called (it should not have been)' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called (it should not have been)'; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { + echo 'Exception listener called' . PHP_EOL; +}); + +throw new \Exception('foo bar'); +?> +--EXPECTF-- +Exception listener called +Custom exception handler called +Fatal error: Uncaught Exception: foo bar in %s:%d +Stack trace: +%a diff --git a/tests/phpt/error_handler_captures_exception_thrown_from_previous_handler.phpt b/tests/phpt/error_handler_captures_exception_thrown_from_previous_handler.phpt new file mode 100644 index 000000000..602d4b834 --- /dev/null +++ b/tests/phpt/error_handler_captures_exception_thrown_from_previous_handler.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test catching exceptions +--FILE-- + +--EXPECTF-- +Exception listener called +Custom exception handler called +Exception listener called + +Fatal error: Uncaught Exception: foo bar baz in %s:%d +Stack trace: +%a diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt new file mode 100644 index 000000000..5d2ff8e6f --- /dev/null +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -0,0 +1,62 @@ +--TEST-- +Test catching fatal errors +--FILE-- +setTransport($transport) + ->getClient(); + +Hub::getCurrent()->bindClient($client); + +$errorHandler = ErrorHandler::registerOnceErrorHandler(); +$errorHandler->addErrorHandlerListener(static function (): void { + echo 'Error listener called' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { + echo 'Exception listener called (it should not have been)' . PHP_EOL; +}); + +class TestClass implements \Serializable +{ +} +?> +--EXPECTF-- +Fatal error: Class Sentry\Tests\TestClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d +Error listener called +Transport called +Fatal error listener called diff --git a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt new file mode 100644 index 000000000..961faa654 --- /dev/null +++ b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test catching out of memory fatal error +--FILE-- +addErrorHandlerListener(static function (): void { + echo 'Error listener called' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { + echo 'Exception listener called' . PHP_EOL; +}); + +$foo = str_repeat('x', 1024 * 1024 * 30); +?> +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d +Error listener called +Fatal error listener called diff --git a/tests/phpt/error_handler_captures_rethrown_exception_once.phpt b/tests/phpt/error_handler_captures_rethrown_exception_once.phpt new file mode 100644 index 000000000..1a8d02099 --- /dev/null +++ b/tests/phpt/error_handler_captures_rethrown_exception_once.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test that a thrown exception is captured only once if rethrown from a previous exception handler +--FILE-- +setTransport($transport) + ->getClient(); + +Hub::getCurrent()->bindClient($client); + +throw new \Exception('foo bar'); +?> +--EXPECTF-- +Transport called +Custom exception handler called +Fatal error: Uncaught Exception: foo bar in %s:%d +Stack trace: +%a diff --git a/tests/phpt/error_handler_only_error_handler_registered.phpt b/tests/phpt/error_handler_only_error_handler_registered.phpt new file mode 100644 index 000000000..2975e228b --- /dev/null +++ b/tests/phpt/error_handler_only_error_handler_registered.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test that only the error handler is registered when configured to do so +--FILE-- + +--EXPECT-- +bool(true) +bool(false) diff --git a/tests/phpt/error_handler_only_exception_handler_registered.phpt b/tests/phpt/error_handler_only_exception_handler_registered.phpt new file mode 100644 index 000000000..5ed972abb --- /dev/null +++ b/tests/phpt/error_handler_only_exception_handler_registered.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test that only the error handler is registered when configured to do so +--FILE-- + +--EXPECT-- +bool(false) +bool(true) diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt new file mode 100644 index 000000000..ac7300300 --- /dev/null +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test that the error handler ignores silenced errors by default, but it reports them with the appropriate option enabled. +--FILE-- + true]) + ->setTransport($transport) + ->getClient(); + +Hub::getCurrent()->bindClient($client); + +echo 'Triggering silenced error' . PHP_EOL; + +@$a['missing']; + +$client->getOptions()->setCaptureSilencedErrors(false); + +echo 'Triggering silenced error' . PHP_EOL; + +@$a['missing']; +?> +--EXPECT-- +Triggering silenced error +Transport called +Triggering silenced error diff --git a/tests/phpt/error_handler_returns_previous_error_handler_return_value.phpt b/tests/phpt/error_handler_returns_previous_error_handler_return_value.phpt new file mode 100644 index 000000000..54b905596 --- /dev/null +++ b/tests/phpt/error_handler_returns_previous_error_handler_return_value.phpt @@ -0,0 +1,52 @@ +--TEST-- +Test that the value returned by a previous error handler is used to decide whether to invoke the native PHP handler +--FILE-- + +--EXPECTF-- +Triggering error (shouldn't be displayed) +Triggering error (should be displayed) + +Warning: Error thrown in %s on line %d +Triggering error (shouldn't be displayed) +End of script reached diff --git a/tests/phpt/error_handler_refuses_negative_value.phpt b/tests/phpt/error_handler_throws_on_invalid_reserved_memory.phpt similarity index 53% rename from tests/phpt/error_handler_refuses_negative_value.phpt rename to tests/phpt/error_handler_throws_on_invalid_reserved_memory.phpt index c0d3e50b3..3d8f4ddfb 100644 --- a/tests/phpt/error_handler_refuses_negative_value.phpt +++ b/tests/phpt/error_handler_throws_on_invalid_reserved_memory.phpt @@ -3,10 +3,11 @@ Test that the error handler throws an error when trying to reserve a negative am --FILE-- getMessage(); -} +ErrorHandler::registerOnceFatalErrorHandler(-1); ?> --EXPECTF-- -Exception caught: The $reservedMemorySize argument must be greater than 0. +Fatal error: Uncaught InvalidArgumentException: The $reservedMemorySize argument must be greater than 0. in %s:%d +Stack trace: +%a diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt new file mode 100644 index 000000000..14d2f0fb2 --- /dev/null +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -0,0 +1,54 @@ +--TEST-- +Test that the ErrorListenerIntegration integration captures only the errors allowed by the `error_types` options +--FILE-- +setErrorTypes(E_ALL & ~E_USER_WARNING); +$options->setDefaultIntegrations(false); +$options->setIntegrations([ + new ErrorListenerIntegration($options), +]); + +$client = (new ClientBuilder($options)) + ->setTransport($transport) + ->getClient(); + +Hub::getCurrent()->bindClient($client); + +trigger_error('Error thrown', E_USER_NOTICE); +trigger_error('Error thrown', E_USER_WARNING); +?> +--EXPECTF-- +Transport called +Notice: Error thrown in %s on line %d + +Warning: Error thrown in %s on line %d diff --git a/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt b/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt new file mode 100644 index 000000000..78dfb56b6 --- /dev/null +++ b/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test that the ErrorListenerIntegration integration ignores the fatal errors if configured to do so +--FILE-- +setDefaultIntegrations(false); +$options->setIntegrations([ + new ErrorListenerIntegration($options, false), +]); + +$client = (new ClientBuilder($options)) + ->setTransport($transport) + ->getClient(); + +Hub::getCurrent()->bindClient($client); + +class FooClass implements \Serializable +{ +} +?> +--EXPECTF-- +Fatal error: Class Sentry\Tests\FooClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d diff --git a/tests/phpt/exception_rethrown.phpt b/tests/phpt/exception_rethrown.phpt deleted file mode 100644 index 8cbf8f466..000000000 --- a/tests/phpt/exception_rethrown.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Test rethrowing in custom exception handler ---FILE-- - ---EXPECTREGEX-- -custom exception handler called -Fatal error: (Uncaught Exception: foo bar|Uncaught exception 'Exception' with message 'foo bar') in [^\r\n]+:\d+ -Stack trace: -.+ diff --git a/tests/phpt/exception_thrown.phpt b/tests/phpt/exception_thrown.phpt deleted file mode 100644 index 06256a385..000000000 --- a/tests/phpt/exception_thrown.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Test throwing exception in custom exception handler ---FILE-- - ---EXPECTREGEX-- -custom exception handler called -Fatal error: (Uncaught Exception: bar foo|Uncaught exception 'Exception' with message 'bar foo') in [^\r\n]+:\d+ -Stack trace: -.+ diff --git a/tests/phpt/fatal_error.phpt b/tests/phpt/fatal_error.phpt deleted file mode 100644 index 4f8539179..000000000 --- a/tests/phpt/fatal_error.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Test catching fatal errors ---FILE-- - ---EXPECTF-- -Fatal error: Class Sentry\Tests\TestClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d -Listener called diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt new file mode 100644 index 000000000..de474d15c --- /dev/null +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -0,0 +1,58 @@ +--TEST-- +Test that the FatalErrorListenerIntegration integration captures only the errors allowed by the `error_types` option +--FILE-- +setDefaultIntegrations(false); +$options->setIntegrations([ + new FatalErrorListenerIntegration($options), +]); + +$client = (new ClientBuilder($options)) + ->setTransport($transport) + ->getClient(); + +Hub::getCurrent()->bindClient($client); + +class FooClass implements \Serializable +{ +} + +$options->setErrorTypes(E_ALL & ~E_ERROR); + +class BarClass implements \Serializable +{ +} +?> +--EXPECTF-- +Fatal error: Class Sentry\Tests\FooClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d +Transport called diff --git a/tests/phpt/fatal_error_not_captured_twice.phpt b/tests/phpt/fatal_error_not_captured_twice.phpt deleted file mode 100644 index 53be00ff2..000000000 --- a/tests/phpt/fatal_error_not_captured_twice.phpt +++ /dev/null @@ -1,39 +0,0 @@ ---TEST-- -Test catching fatal errors does not capture twice ---FILE-- -setTransport($transport); - -Hub::getCurrent()->bindClient($builder->getClient()); - -register_shutdown_function('register_shutdown_function', function () use ($spool) { - Assert::assertAttributeCount(1, 'events', $spool); - - echo 'Shutdown function called'; -}); - -\Foo\Bar::baz(); -?> ---EXPECTREGEX-- -Fatal error: (?:Class 'Foo\\Bar' not found in [^\r\n]+ on line \d+|Uncaught Error: Class 'Foo\\Bar' not found in [^\r\n]+:\d+) -(?:Stack trace:[\s\S]+)?Shutdown function called diff --git a/tests/phpt/out_of_memory.phpt b/tests/phpt/out_of_memory.phpt deleted file mode 100644 index ded7b8718..000000000 --- a/tests/phpt/out_of_memory.phpt +++ /dev/null @@ -1,40 +0,0 @@ ---TEST-- -Test catching out of memory fatal error ---FILE-- - function (Event $event): ?Event { - Assert::assertArrayHasKey(0, $event->getExceptions()); - $error = $event->getExceptions()[0]; - Assert::assertContains('Allowed memory size', $error['value']); - Assert::assertTrue($event->getLevel()->isEqualTo(Severity::fatal())); - - echo 'Sending event'; - - return null; - }, -]); - -$foo = str_repeat('x', 1024 * 1024 * 30); -?> ---EXPECTF-- -Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d -Sending event diff --git a/tests/phpt/send_only_all_except_notice.phpt b/tests/phpt/send_only_all_except_notice.phpt deleted file mode 100644 index 31795a01e..000000000 --- a/tests/phpt/send_only_all_except_notice.phpt +++ /dev/null @@ -1,56 +0,0 @@ ---TEST-- -Test catching fatal errors ---FILE-- - E_ALL & ~E_USER_NOTICE -]; - -init($options); - -error_reporting(E_ALL); - -$stubTransport = new StubTransport(); -$client = ClientBuilder::create($options) - ->setTransport($stubTransport) - ->getClient(); - -$hub = Hub::getCurrent(); -$hub->bindClient($client); - -trigger_error('Cannot divide by zero', E_USER_NOTICE); - -Assert::assertEmpty($stubTransport->getEvents()); - -trigger_error('Cannot divide by zero', E_USER_WARNING); - -Assert::assertCount(1, $stubTransport->getEvents()); - -$event = $stubTransport->getLastSent(); - -Assert::assertSame($event->getId(), $hub->getLastEventId()); - -echo 'All assertions executed'; -?> ---EXPECTREGEX-- -Notice: Cannot divide by zero in .* on line \d+ - -Warning: Cannot divide by zero in .* on line \d+ -All assertions executed diff --git a/tests/phpt/silenced_errors.phpt b/tests/phpt/silenced_errors.phpt deleted file mode 100644 index db54c930b..000000000 --- a/tests/phpt/silenced_errors.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -Test that the error handler ignores silenced errors by default, but it reports them with the appropriate option enabled. ---FILE-- - static function () { - echo 'Event captured' . PHP_EOL; - } -]); - -echo 'Triggering silenced error' . PHP_EOL; -@$a['missing']; - -Hub::getCurrent() - ->getClient() - ->getOptions() - ->setCaptureSilencedErrors(true); - -echo 'Triggering silenced error' . PHP_EOL; -@$a['missing']; -echo 'End' -?> ---EXPECT-- -Triggering silenced error -Triggering silenced error -Event captured -End diff --git a/tests/phpt/spool_drain.phpt b/tests/phpt/spool_drain.phpt index f021871f3..6ff4a1f4d 100644 --- a/tests/phpt/spool_drain.phpt +++ b/tests/phpt/spool_drain.phpt @@ -3,6 +3,8 @@ Test emptying spool transport --FILE-- ---EXPECTREGEX-- -Fatal error: (?:Class 'Foo\\Bar' not found in [^\r\n]+ on line \d+|Uncaught Error: Class 'Foo\\Bar' not found in [^\r\n]+:\d+) -(?:Stack trace:[\s\S]+)?Shutdown function called +--EXPECTF-- +Fatal error: Uncaught Error: Class '%s' not found in %s:%d +Stack trace: +%a +Shutdown function called From b837f7e901c87a5fde0efa9e196f8d39b57da56e Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 3 May 2019 10:18:20 +0200 Subject: [PATCH 0458/1161] Capture the body of the request and do a best effort to decode it (#807) --- CHANGELOG.md | 1 + src/Exception/JsonException.php | 9 + src/Integration/RequestIntegration.php | 95 +++- src/Options.php | 39 ++ src/Util/JSON.php | 26 +- tests/Integration/RequestIntegrationTest.php | 487 ++++++++++++++----- tests/OptionsTest.php | 1 + tests/Util/JSONTest.php | 47 +- 8 files changed, 576 insertions(+), 129 deletions(-) create mode 100644 src/Exception/JsonException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6837bd497..e94e16904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Add support for `SENTRY_ENVRIONMENT` and `SENTRY_RELEASE` environment variables (#810) - Fix the default value of the `$exceptions` property of the Event class (#806) - Add a Monolog handler (#808) +- Allow capturing the body of an HTTP request (#807) ## 2.0.1 (2019-03-01) diff --git a/src/Exception/JsonException.php b/src/Exception/JsonException.php new file mode 100644 index 000000000..896ea3e67 --- /dev/null +++ b/src/Exception/JsonException.php @@ -0,0 +1,9 @@ +removePiiFromHeaders($request->getHeaders()); } + $requestBody = $self->captureRequestBody($request); + + if (!empty($requestBody)) { + $requestData['data'] = $requestBody; + } + $event->setRequest($requestData); } @@ -109,8 +132,68 @@ private function removePiiFromHeaders(array $headers): array { $keysToRemove = ['authorization', 'cookie', 'set-cookie', 'remote_addr']; - return array_filter($headers, function ($key) use ($keysToRemove) { - return !\in_array(strtolower($key), $keysToRemove, true); - }, ARRAY_FILTER_USE_KEY); + return array_filter( + $headers, + static function (string $key) use ($keysToRemove): bool { + return !\in_array(strtolower($key), $keysToRemove, true); + }, + ARRAY_FILTER_USE_KEY + ); + } + + /** + * Gets the decoded body of the request, if available. If the Content-Type + * header contains "application/json" then the content is decoded and if + * the parsing fails then the raw data is returned. If there are submitted + * fields or files, all of their information are parsed and returned. + * + * @param ServerRequestInterface $serverRequest The server request + * + * @return mixed + */ + private function captureRequestBody(ServerRequestInterface $serverRequest) + { + $maxRequestBodySize = $this->options->getMaxRequestBodySize(); + $requestBody = $serverRequest->getBody(); + + if ( + 'none' === $maxRequestBodySize || + ('small' === $maxRequestBodySize && $requestBody->getSize() > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) || + ('medium' === $maxRequestBodySize && $requestBody->getSize() > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) + ) { + return null; + } + + $requestData = $serverRequest->getParsedBody(); + $requestData = \is_array($requestData) ? $requestData : []; + + foreach ($serverRequest->getUploadedFiles() as $fieldName => $uploadedFiles) { + if (!\is_array($uploadedFiles)) { + $uploadedFiles = [$uploadedFiles]; + } + + /** @var UploadedFileInterface $uploadedFile */ + foreach ($uploadedFiles as $uploadedFile) { + $requestData[$fieldName][] = [ + 'client_filename' => $uploadedFile->getClientFilename(), + 'client_media_type' => $uploadedFile->getClientMediaType(), + 'size' => $uploadedFile->getSize(), + ]; + } + } + + if (!empty($requestData)) { + return $requestData; + } + + if ('application/json' === $serverRequest->getHeaderLine('Content-Type')) { + try { + return JSON::decode($requestBody->getContents()); + } catch (JsonException $exception) { + // Fallback to returning the raw data from the request body + } + } + + return $requestBody->getContents(); } } diff --git a/src/Options.php b/src/Options.php index 1c83d0a86..d0c88a7d0 100644 --- a/src/Options.php +++ b/src/Options.php @@ -665,6 +665,42 @@ public function setCaptureSilencedErrors(bool $shouldCapture): void $this->options = $this->resolver->resolve($options); } + /** + * Gets the limit up to which integrations should capture the HTTP request + * body. + * + * @return string + */ + public function getMaxRequestBodySize(): string + { + return $this->options['max_request_body_size']; + } + + /** + * Sets the limit up to which integrations should capture the HTTP request + * body. + * + * @param string $maxRequestBodySize The limit up to which request body are + * captured. It can be set to one of the + * following values: + * + * - never: request bodies are never sent + * - small: only small request bodies will + * be captured where the cutoff for small + * depends on the SDK (typically 4KB) + * - medium: medium-sized requests and small + * requests will be captured. (typically 10KB) + * - always: the SDK will always capture the + * request body for as long as sentry can + * make sense of it + */ + public function setMaxRequestBodySize(string $maxRequestBodySize): void + { + $options = array_merge($this->options, ['max_request_body_size' => $maxRequestBodySize]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -705,6 +741,7 @@ private function configureOptions(OptionsResolver $resolver): void 'max_value_length' => 1024, 'http_proxy' => null, 'capture_silenced_errors' => false, + 'max_request_body_size' => 'medium', ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -732,7 +769,9 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('max_value_length', 'int'); $resolver->setAllowedTypes('http_proxy', ['null', 'string']); $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); + $resolver->setAllowedTypes('max_request_body_size', 'string'); + $resolver->setAllowedValues('max_request_body_size', ['none', 'small', 'medium', 'always']); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); $resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions'])); diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 3dd1c66f0..454cce129 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -4,6 +4,8 @@ namespace Sentry\Util; +use Sentry\Exception\JsonException; + /** * This class provides some utility methods to encode/decode JSON data. * @@ -18,16 +20,36 @@ final class JSON * * @return string * - * @throws \InvalidArgumentException If the encoding failed + * @throws JsonException If the encoding failed */ public static function encode($data): string { $encodedData = json_encode($data, JSON_UNESCAPED_UNICODE); if (JSON_ERROR_NONE !== json_last_error() || false === $encodedData) { - throw new \InvalidArgumentException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); + throw new JsonException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); } return $encodedData; } + + /** + * Decodes the given data from JSON. + * + * @param string $data The data to decode + * + * @return mixed + * + * @throws JsonException If the decoding failed + */ + public static function decode(string $data) + { + $decodedData = json_decode($data, true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new JsonException(sprintf('Could not decode value from JSON format. Error was: "%s".', json_last_error_msg())); + } + + return $decodedData; + } } diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index eb1279943..7d97c4fc2 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -4,18 +4,21 @@ namespace Sentry\Tests\Integration; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; use Sentry\Event; use Sentry\Integration\RequestIntegration; use Sentry\Options; use Zend\Diactoros\ServerRequest; +use Zend\Diactoros\UploadedFile; use Zend\Diactoros\Uri; final class RequestIntegrationTest extends TestCase { /** - * @dataProvider invokeUserContextPiiDataProvider + * @dataProvider applyToEventWithRequestHavingIpAddressDataProvider */ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $expectedValue): void { @@ -34,7 +37,7 @@ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $this->assertEquals($expectedValue, $event->getUserContext()->toArray()); } - public function invokeUserContextPiiDataProvider(): array + public function applyToEventWithRequestHavingIpAddressDataProvider(): array { return [ [ @@ -49,162 +52,410 @@ public function invokeUserContextPiiDataProvider(): array } /** - * @dataProvider invokeDataProvider + * @dataProvider applyToEventDataProvider */ - public function testInvoke(bool $shouldSendPii, array $requestData, array $expectedResult): void + public function testApplyToEvent(array $options, ServerRequestInterface $request, array $expectedResult): void { $event = new Event(); - - $request = new ServerRequest(); - $request = $request->withCookieParams($requestData['cookies']); - $request = $request->withUri(new Uri($requestData['uri'])); - $request = $request->withMethod($requestData['method']); - - foreach ($requestData['headers'] as $name => $value) { - $request = $request->withHeader($name, $value); - } - - $this->assertInstanceOf(ServerRequestInterface::class, $request); - - $integration = new RequestIntegration(new Options(['send_default_pii' => $shouldSendPii])); + $integration = new RequestIntegration(new Options($options)); RequestIntegration::applyToEvent($integration, $event, $request); $this->assertEquals($expectedResult, $event->getRequest()); } - public function invokeDataProvider(): array + public function applyToEventDataProvider(): \Generator { - return [ + yield [ [ - true, - [ - 'uri' => 'http://www.example.com/foo', - 'method' => 'GET', - 'cookies' => [ - 'foo' => 'bar', - ], - 'headers' => [], + 'send_default_pii' => true, + ], + (new ServerRequest()) + ->withCookieParams(['foo' => 'bar']) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('GET'), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'GET', + 'cookies' => [ + 'foo' => 'bar', ], - [ - 'url' => 'http://www.example.com/foo', - 'method' => 'GET', - 'cookies' => [ - 'foo' => 'bar', - ], - 'headers' => [ - 'Host' => ['www.example.com'], - ], + 'headers' => [ + 'Host' => ['www.example.com'], ], ], + ]; + + yield [ + [ + 'send_default_pii' => false, + ], + (new ServerRequest()) + ->withCookieParams(['foo' => 'bar']) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('GET'), [ - false, - [ - 'uri' => 'http://www.example.com/foo', - 'method' => 'GET', - 'cookies' => [ - 'foo' => 'bar', - ], - 'headers' => [], + 'url' => 'http://www.example.com/foo', + 'method' => 'GET', + 'headers' => [ + 'Host' => ['www.example.com'], ], - [ - 'url' => 'http://www.example.com/foo', - 'method' => 'GET', - 'headers' => [ - 'Host' => ['www.example.com'], - ], + ], + ]; + + yield [ + [ + 'send_default_pii' => true, + ], + (new ServerRequest()) + ->withUri(new Uri('http://www.example.com:1234/foo')) + ->withMethod('GET'), + [ + 'url' => 'http://www.example.com:1234/foo', + 'method' => 'GET', + 'cookies' => [], + 'headers' => [ + 'Host' => ['www.example.com:1234'], ], ], + ]; + + yield [ [ - true, - [ - 'uri' => 'http://www.example.com:123/foo', - 'method' => 'GET', - 'cookies' => [], - 'headers' => [], - ], - [ - 'url' => 'http://www.example.com:123/foo', - 'method' => 'GET', - 'cookies' => [], - 'headers' => [ - 'Host' => ['www.example.com:123'], - ], + 'send_default_pii' => false, + ], + (new ServerRequest()) + ->withUri(new Uri('http://www.example.com:1234/foo')) + ->withMethod('GET'), + [ + 'url' => 'http://www.example.com:1234/foo', + 'method' => 'GET', + 'headers' => [ + 'Host' => ['www.example.com:1234'], ], ], + ]; + yield [ [ - false, - [ - 'uri' => 'http://www.example.com:123/foo', - 'method' => 'GET', - 'cookies' => [], - 'headers' => [], - ], - [ - 'url' => 'http://www.example.com:123/foo', - 'method' => 'GET', - 'headers' => [ - 'Host' => ['www.example.com:123'], - ], + 'send_default_pii' => true, + ], + (new ServerRequest()) + ->withUri(new Uri('http://www.example.com/foo?foo=bar&bar=baz')) + ->withMethod('GET') + ->withHeader('Host', 'www.example.com') + ->withHeader('REMOTE_ADDR', '127.0.0.1') + ->withHeader('Authorization', 'foo') + ->withHeader('Cookie', 'bar') + ->withHeader('Set-Cookie', 'baz'), + [ + 'url' => 'http://www.example.com/foo?foo=bar&bar=baz', + 'method' => 'GET', + 'query_string' => 'foo=bar&bar=baz', + 'cookies' => [], + 'headers' => [ + 'Host' => ['www.example.com'], + 'REMOTE_ADDR' => ['127.0.0.1'], + 'Authorization' => ['foo'], + 'Cookie' => ['bar'], + 'Set-Cookie' => ['baz'], + ], + 'env' => [ + 'REMOTE_ADDR' => '127.0.0.1', ], ], + ]; + yield [ [ - true, - [ - 'uri' => 'http://www.example.com/foo?foo=bar&bar=baz', - 'method' => 'GET', - 'cookies' => [], - 'headers' => [ - 'Host' => ['www.example.com'], - 'REMOTE_ADDR' => ['127.0.0.1'], - 'Authorization' => 'x', - 'Cookie' => 'y', - 'Set-Cookie' => 'z', + 'send_default_pii' => false, + ], + (new ServerRequest()) + ->withUri(new Uri('http://www.example.com/foo?foo=bar&bar=baz')) + ->withMethod('GET') + ->withHeader('Host', 'www.example.com') + ->withHeader('REMOTE_ADDR', '127.0.0.1') + ->withHeader('Authorization', 'foo') + ->withHeader('Cookie', 'bar') + ->withHeader('Set-Cookie', 'baz'), + [ + 'url' => 'http://www.example.com/foo?foo=bar&bar=baz', + 'method' => 'GET', + 'query_string' => 'foo=bar&bar=baz', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + ], + ]; + + yield [ + [ + 'max_request_body_size' => 'none', + ], + (new ServerRequest()) + ->withParsedBody([ + 'foo' => 'foo value', + 'bar' => 'bar value', + ]) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST') + ->withBody($this->getStreamMock(1)), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + ], + ]; + + yield [ + [ + 'max_request_body_size' => 'small', + ], + (new ServerRequest()) + ->withParsedBody([ + 'foo' => 'foo value', + 'bar' => 'bar value', + ]) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST') + ->withBody($this->getStreamMock(10 ** 3)), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + 'data' => [ + 'foo' => 'foo value', + 'bar' => 'bar value', + ], + ], + ]; + + yield [ + [ + 'max_request_body_size' => 'small', + ], + (new ServerRequest()) + ->withParsedBody([ + 'foo' => 'foo value', + 'bar' => 'bar value', + ]) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST') + ->withBody($this->getStreamMock(10 ** 3 + 1)), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + ], + ]; + + yield [ + [ + 'max_request_body_size' => 'medium', + ], + (new ServerRequest()) + ->withParsedBody([ + 'foo' => 'foo value', + 'bar' => 'bar value', + ]) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST') + ->withBody($this->getStreamMock(10 ** 4)), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + 'data' => [ + 'foo' => 'foo value', + 'bar' => 'bar value', + ], + ], + ]; + + yield [ + [ + 'max_request_body_size' => 'medium', + ], + (new ServerRequest()) + ->withParsedBody([ + 'foo' => 'foo value', + 'bar' => 'bar value', + ]) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST') + ->withBody($this->getStreamMock(10 ** 4 + 1)), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + ], + ]; + + yield [ + [ + 'max_request_body_size' => 'always', + ], + (new ServerRequest()) + ->withUploadedFiles([ + 'foo' => new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), + ]) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST'), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + 'data' => [ + 'foo' => [ + [ + 'client_filename' => 'foo.ext', + 'client_media_type' => 'application/text', + 'size' => 123, + ], ], ], - [ - 'url' => 'http://www.example.com/foo?foo=bar&bar=baz', - 'method' => 'GET', - 'query_string' => 'foo=bar&bar=baz', - 'cookies' => [], - 'headers' => [ - 'Host' => ['www.example.com'], - 'REMOTE_ADDR' => ['127.0.0.1'], - 'Authorization' => ['x'], - 'Cookie' => ['y'], - 'Set-Cookie' => ['z'], + ], + ]; + + yield [ + [ + 'max_request_body_size' => 'always', + ], + (new ServerRequest()) + ->withUploadedFiles([ + 'foo' => [ + new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), + new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), ], - 'env' => [ - 'REMOTE_ADDR' => '127.0.0.1', + ]) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST'), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + ], + 'data' => [ + 'foo' => [ + [ + 'client_filename' => 'foo.ext', + 'client_media_type' => 'application/text', + 'size' => 123, + ], + [ + 'client_filename' => 'bar.ext', + 'client_media_type' => 'application/octet-stream', + 'size' => 321, + ], ], ], ], + ]; + yield [ [ - false, - [ - 'uri' => 'http://www.example.com/foo?foo=bar&bar=baz', - 'method' => 'GET', - 'cookies' => [], - 'headers' => [ - 'Host' => ['www.example.com'], - 'REMOTE_ADDR' => ['127.0.0.1'], - 'Authorization' => 'x', - 'Cookie' => 'y', - 'Set-Cookie' => 'z', + 'max_request_body_size' => 'always', + ], + (new ServerRequest()) + ->withUploadedFiles([ + 'foo' => [ + new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), + new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), ], + ]) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST'), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], ], - [ - 'url' => 'http://www.example.com/foo?foo=bar&bar=baz', - 'method' => 'GET', - 'query_string' => 'foo=bar&bar=baz', - 'headers' => [ - 'Host' => ['www.example.com'], + 'data' => [ + 'foo' => [ + [ + 'client_filename' => 'foo.ext', + 'client_media_type' => 'application/text', + 'size' => 123, + ], + [ + 'client_filename' => 'bar.ext', + 'client_media_type' => 'application/octet-stream', + 'size' => 321, + ], ], ], ], ]; + + yield [ + [ + 'max_request_body_size' => 'always', + ], + (new ServerRequest()) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST') + ->withHeader('Content-Type', 'application/json') + ->withBody($this->getStreamMock(13, '{"foo":"bar"}')), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + 'Content-Type' => ['application/json'], + ], + 'data' => [ + 'foo' => 'bar', + ], + ], + ]; + + yield [ + [ + 'max_request_body_size' => 'always', + ], + (new ServerRequest()) + ->withUri(new Uri('http://www.example.com/foo')) + ->withMethod('POST') + ->withHeader('Content-Type', 'application/json') + ->withBody($this->getStreamMock(1, '{')), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + 'Content-Type' => ['application/json'], + ], + 'data' => '{', + ], + ]; + } + + private function getStreamMock(int $size, string $content = ''): StreamInterface + { + /** @var MockObject|StreamInterface $stream */ + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->any()) + ->method('getSize') + ->willReturn($size); + + $stream->expects($this->any()) + ->method('getContents') + ->willReturn($content); + + return $stream; } } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index f9242e4c7..ae50b4753 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -60,6 +60,7 @@ public function optionsDataProvider(): array ['max_value_length', 50, 'getMaxValueLength', 'setMaxValueLength'], ['http_proxy', '127.0.0.1', 'getHttpProxy', 'setHttpProxy'], ['capture_silenced_errors', true, 'shouldCaptureSilencedErrors', 'setCaptureSilencedErrors'], + ['max_request_body_size', 'small', 'getMaxRequestBodySize', 'setMaxRequestBodySize'], ]; } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index e9857fe83..4b9f7caaf 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -14,9 +14,9 @@ final class JSONTest extends TestCase /** * @dataProvider encodeDataProvider */ - public function testEncode($value, $expectedResult): void + public function testEncode($value, string $expectedResult): void { - $this->assertEquals($expectedResult, JSON::encode($value)); + $this->assertSame($expectedResult, JSON::encode($value)); } public function encodeDataProvider(): array @@ -58,7 +58,7 @@ public function encodeDataProvider(): array } /** - * @expectedException \InvalidArgumentException + * @expectedException \Sentry\Exception\JsonException * @expectedExceptionMessage Could not encode value into JSON format. Error was: "Type is not supported". */ public function testEncodeThrowsIfValueIsResource(): void @@ -71,4 +71,45 @@ public function testEncodeThrowsIfValueIsResource(): void JSON::encode($resource); } + + /** + * @dataProvider decodeDataProvider + */ + public function testDecode(string $value, $expectedResult): void + { + $this->assertSame($expectedResult, JSON::decode($value)); + } + + public function decodeDataProvider(): array + { + return [ + [ + '{"key":"value"}', + [ + 'key' => 'value', + ], + ], + [ + '"string"', + 'string', + ], + [ + '123.45', + 123.45, + ], + [ + 'null', + null, + ], + ]; + } + + /** + * @expectedException \Sentry\Exception\JsonException + * @expectedExceptionMessage Could not decode value from JSON format. Error was: "Syntax error". + */ + public function testDecodeThrowsIfValueIsNotValidJson(): void + { + JSON::decode('foo'); + } } From ca2ebc096b4839417c0b17d629be4c7ab784cbcd Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Mon, 13 May 2019 16:24:09 +0200 Subject: [PATCH 0459/1161] Fix PHP syntax in the code samples of the UPGRADE.md file (#817) --- UPGRADE-2.0.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 70e21e7dd..f2b285914 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -526,7 +526,7 @@ Please visit [our docs](https://docs.sentry.io/error-reporting/quickstart/?platf Hub::getCurrent()->configureScope(function (Scope $scope): void { $scope->setUser(['email' => 'foo@example.com']); - }) + }); ``` - The `Raven_Client::tags_context` method has been removed. You can set this @@ -546,7 +546,7 @@ Please visit [our docs](https://docs.sentry.io/error-reporting/quickstart/?platf Hub::getCurrent()->configureScope(function (Scope $scope): void { $scope->setTag('tag_name', 'tag_value'); - }) + }); ``` - The `Raven_Client::extra_context` method has been removed. You can set this @@ -566,7 +566,7 @@ Please visit [our docs](https://docs.sentry.io/error-reporting/quickstart/?platf Hub::getCurrent()->configureScope(function (Scope $scope): void { $scope->setExtra('extra_key', 'extra_value'); - }) + }); ``` - The method `Raven_Client::install` has been removed. The error handler is From 92c773f374d2999e2dd3bb6de46787c371afaedf Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Sat, 18 May 2019 14:09:39 +0200 Subject: [PATCH 0460/1161] Implement option to allow customizaton of how objects are serialized (#809) --- CHANGELOG.md | 1 + src/Options.php | 46 ++++++++++++++++ src/Serializer/AbstractSerializer.php | 49 ++++++++++++++++- src/Serializer/SerializableInterface.php | 19 +++++++ tests/Serializer/SerializerTest.php | 70 ++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/Serializer/SerializableInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e94e16904..9c8835cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and change the type of the reported exception to `\Sentry\Exception\FatalErrorException` (#788) - Add a static factory method to create a breadcrumb from an array of data (#798) - Add support for `SENTRY_ENVRIONMENT` and `SENTRY_RELEASE` environment variables (#810) +- Add the `class_serializers` option to make it possible to customize how objects are serialized in the event payload (#809) - Fix the default value of the `$exceptions` property of the Event class (#806) - Add a Monolog handler (#808) - Allow capturing the body of an HTTP request (#807) diff --git a/src/Options.php b/src/Options.php index d0c88a7d0..2c9acf432 100644 --- a/src/Options.php +++ b/src/Options.php @@ -701,6 +701,31 @@ public function setMaxRequestBodySize(string $maxRequestBodySize): void $this->options = $this->resolver->resolve($options); } + /** + * Gets the callbacks used to customize how objects are serialized in the payload + * of the event. + * + * @return array + */ + public function getClassSerializers(): array + { + return $this->options['class_serializers']; + } + + /** + * Sets a list of callables that will be called to customize how objects are + * serialized in the event's payload. The list must be a map of FQCN/callable + * pairs. + * + * @param array $serializers The list of serializer callbacks + */ + public function setClassSerializers(array $serializers): void + { + $options = array_merge($this->options, ['class_serializers' => $serializers]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -742,6 +767,7 @@ private function configureOptions(OptionsResolver $resolver): void 'http_proxy' => null, 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', + 'class_serializers' => [], ]); $resolver->setAllowedTypes('send_attempts', 'int'); @@ -770,11 +796,13 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('http_proxy', ['null', 'string']); $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); + $resolver->setAllowedTypes('class_serializers', 'array'); $resolver->setAllowedValues('max_request_body_size', ['none', 'small', 'medium', 'always']); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); $resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions'])); + $resolver->setAllowedValues('class_serializers', \Closure::fromCallable([$this, 'validateClassSerializersOption'])); $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); $resolver->setNormalizer('project_root', function (SymfonyOptions $options, $value) { @@ -940,4 +968,22 @@ private function validateMaxBreadcrumbsOptions(int $value): bool { return $value >= 0 && $value <= self::DEFAULT_MAX_BREADCRUMBS; } + + /** + * Validates that the values passed to the `class_serializers` option are valid. + * + * @param array $serializers The value to validate + * + * @return bool + */ + private function validateClassSerializersOption(array $serializers): bool + { + foreach ($serializers as $class => $serializer) { + if (!\is_string($class) || !\is_callable($serializer)) { + return false; + } + } + + return true; + } } diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index dfa4f49ce..bbbac1f6e 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -122,14 +122,61 @@ protected function serializeRecursively($value, int $_depth = 0) } if (\is_object($value)) { + $classSerializers = $this->resolveClassSerializers($value); + + // Try each serializer until there is none left or the serializer returned data + foreach ($classSerializers as $classSerializer) { + try { + $serializedObjectData = $classSerializer($value); + + if (\is_array($serializedObjectData)) { + return [ + 'class' => \get_class($value), + 'data' => $this->serializeRecursively($serializedObjectData, $_depth + 1), + ]; + } + } catch (\Throwable $e) { + // Ignore any exceptions generated by a class serializer + } + } + if ($this->serializeAllObjects || ('stdClass' === \get_class($value))) { - return $this->serializeObject($value, $_depth, []); + return $this->serializeObject($value, $_depth); } } return $this->serializeValue($value); } + /** + * Find class serializers for a object. + * + * Registered serializers with the `class_serializers` option take precedence over + * objects implementing the `SerializableInterface`. + * + * @param object $object + * + * @return array + */ + protected function resolveClassSerializers($object): array + { + $serializers = []; + + foreach ($this->options->getClassSerializers() as $type => $serializer) { + if ($object instanceof $type) { + $serializers[] = $serializer; + } + } + + if ($object instanceof SerializableInterface) { + $serializers[] = static function (SerializableInterface $object): ?array { + return $object->toSentry(); + }; + } + + return $serializers; + } + /** * @param object $object * @param int $_depth diff --git a/src/Serializer/SerializableInterface.php b/src/Serializer/SerializableInterface.php new file mode 100644 index 000000000..e6d756174 --- /dev/null +++ b/src/Serializer/SerializableInterface.php @@ -0,0 +1,19 @@ +assertNull($result); } + public function testRegisteredObjectSerializers(): void + { + $object = new class() { + public function getPurpose(): string + { + return 'To be tested!'; + } + }; + + $objectClass = \get_class($object); + + $serializer = $this->createSerializer(new Options([ + 'class_serializers' => [ + $objectClass => static function ($object): array { + return [ + 'purpose' => $object->getPurpose(), + ]; + }, + ], + ])); + + $this->assertEquals([ + 'class' => $objectClass, + 'data' => [ + 'purpose' => 'To be tested!', + ], + ], $this->invokeSerialization($serializer, $object)); + } + + public function testSerializableObject(): void + { + $serializer = $this->createSerializer(); + + $serializedValue = [ + 'testing' => 'value', + ]; + + $object = $this->createMock(SerializableInterface::class); + $object->method('toSentry') + ->willReturn($serializedValue); + + $this->assertEquals([ + 'class' => \get_class($object), + 'data' => $serializedValue, + ], $this->invokeSerialization($serializer, $object)); + } + + public function testSerializableThatReturnsNull(): void + { + $serializer = $this->createSerializer(); + + $object = $this->createMock(SerializableInterface::class); + $object->method('toSentry') + ->willReturn(null); + + $this->assertEquals('Object ' . \get_class($object), $this->invokeSerialization($serializer, $object)); + } + + public function testSerializableObjectThatThrowsAnException(): void + { + $serializer = $this->createSerializer(); + + $object = $this->createMock(SerializableInterface::class); + $object->method('toSentry') + ->willThrowException(new \Exception('Doesn\'t matter what the exception is.')); + + $this->assertEquals('Object ' . \get_class($object), $this->invokeSerialization($serializer, $object)); + } + public function testLongStringWithOverwrittenMessageLength(): void { $serializer = $this->createSerializer(new Options(['max_value_length' => 500])); From 1b94087ec8b34c85952d88df1f3bf922c0584a50 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 21 May 2019 15:43:18 +0200 Subject: [PATCH 0461/1161] Catch errors thrown while trying to serialize the data of the event payload (#818) --- CHANGELOG.md | 1 + phpstan.neon | 1 + src/Serializer/AbstractSerializer.php | 62 +++++++++++++++----------- tests/Fixtures/code/BrokenClass.php | 11 +++++ tests/phpt/serialize_broken_class.phpt | 38 ++++++++++++++++ 5 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 tests/Fixtures/code/BrokenClass.php create mode 100644 tests/phpt/serialize_broken_class.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c8835cd7..d82a4ea3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Fix the default value of the `$exceptions` property of the Event class (#806) - Add a Monolog handler (#808) - Allow capturing the body of an HTTP request (#807) +- Capture exceptions during serialization, to avoid hard failures (#818) ## 2.0.1 (2019-03-01) diff --git a/phpstan.neon b/phpstan.neon index e6714eab8..7c8caba84 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,5 +11,6 @@ parameters: - '/Http\\Client\\Curl\\Client/' excludes_analyse: - tests/resources + - tests/Fixtures includes: - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index bbbac1f6e..db56724f6 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -103,49 +103,57 @@ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDete */ protected function serializeRecursively($value, int $_depth = 0) { - if ($_depth >= $this->maxDepth) { - return $this->serializeValue($value); - } + try { + if ($_depth >= $this->maxDepth) { + return $this->serializeValue($value); + } - if (\is_callable($value)) { - return $this->serializeCallable($value); - } + if (\is_callable($value)) { + return $this->serializeCallable($value); + } - if (\is_array($value)) { - $serializedArray = []; + if (\is_array($value)) { + $serializedArray = []; - foreach ($value as $k => $v) { - $serializedArray[$k] = $this->serializeRecursively($v, $_depth + 1); - } + foreach ($value as $k => $v) { + $serializedArray[$k] = $this->serializeRecursively($v, $_depth + 1); + } - return $serializedArray; - } + return $serializedArray; + } - if (\is_object($value)) { - $classSerializers = $this->resolveClassSerializers($value); + if (\is_object($value)) { + $classSerializers = $this->resolveClassSerializers($value); - // Try each serializer until there is none left or the serializer returned data - foreach ($classSerializers as $classSerializer) { - try { - $serializedObjectData = $classSerializer($value); + // Try each serializer until there is none left or the serializer returned data + foreach ($classSerializers as $classSerializer) { + try { + $serializedObjectData = $classSerializer($value); - if (\is_array($serializedObjectData)) { - return [ + if (\is_array($serializedObjectData)) { + return [ 'class' => \get_class($value), 'data' => $this->serializeRecursively($serializedObjectData, $_depth + 1), ]; + } + } catch (\Throwable $e) { + // Ignore any exceptions generated by a class serializer } - } catch (\Throwable $e) { - // Ignore any exceptions generated by a class serializer + } + + if ($this->serializeAllObjects || ('stdClass' === \get_class($value))) { + return $this->serializeObject($value, $_depth); } } - if ($this->serializeAllObjects || ('stdClass' === \get_class($value))) { - return $this->serializeObject($value, $_depth); + return $this->serializeValue($value); + } catch (\Throwable $error) { + if (\is_string($value)) { + return $value . ' {serialization error}'; } - } - return $this->serializeValue($value); + return '{serialization error}'; + } } /** diff --git a/tests/Fixtures/code/BrokenClass.php b/tests/Fixtures/code/BrokenClass.php new file mode 100644 index 000000000..8351bedbf --- /dev/null +++ b/tests/Fixtures/code/BrokenClass.php @@ -0,0 +1,11 @@ +serialize($value); +} + +testSerialization(BrokenClass::class . '::brokenMethod'); +echo PHP_EOL; +testSerialization([BrokenClass::class, 'brokenMethod']); + +?> +--EXPECT-- +Sentry\Tests\Fixtures\code\BrokenClass::brokenMethod {serialization error} +{serialization error} From 1f1704806820c16dcefcbf2e80b920024fda2234 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 22 May 2019 09:44:47 +0200 Subject: [PATCH 0462/1161] meta: Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d82a4ea3c..a2deca6c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## Unreleased +## 2.1.0 (2019-05-22) - Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (#786) - Increase default severity of `E_RECOVERABLE_ERROR` to `Severity::ERROR`, instead of warning (#792) From d407aa1000ece8fd265ef536aef90b0bd7a7ded5 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 27 May 2019 19:22:22 +0200 Subject: [PATCH 0463/1161] Apply the excluded_exceptions option only to the outer-most captured exception (#822) --- CHANGELOG.md | 5 ++++ src/Client.php | 4 +++ src/EventFactory.php | 4 --- tests/ClientTest.php | 70 ++++++++++++++++++++++++++++---------------- 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2deca6c3..361d91c5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## Unreleased + +- Fix the behavior of the `excluded_exceptions` option: now it's used to skip capture of exceptions, not to purge the +`exception` data of the event, which resulted in broken or empty chains of exceptions in reported events (#822) + ## 2.1.0 (2019-05-22) - Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (#786) diff --git a/src/Client.php b/src/Client.php index 56110a40b..bcb3ac9ab 100644 --- a/src/Client.php +++ b/src/Client.php @@ -91,6 +91,10 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope */ public function captureException(\Throwable $exception, ?Scope $scope = null): ?string { + if ($this->options->isExcludedException($exception)) { + return null; + } + $payload['exception'] = $exception; return $this->captureEvent($payload, $scope); diff --git a/src/EventFactory.php b/src/EventFactory.php index 82616f772..f2cc17746 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -145,10 +145,6 @@ private function addThrowableToEvent(Event $event, \Throwable $exception): void $currentException = $exception; do { - if ($this->options->isExcludedException($currentException)) { - continue; - } - $data = [ 'type' => \get_class($currentException), 'value' => $this->serializer->serialize($currentException->getMessage()), diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 7565a7b07..8c11b8b79 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -107,6 +107,49 @@ public function testCaptureException(): void $client->captureException($exception); } + /** + * @dataProvider captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesDataProvider + */ + public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches(bool $shouldCapture, string $excluded, \Throwable $thrown): void + { + $transport = $this->createMock(TransportInterface::class); + + $transport->expects($shouldCapture ? $this->once() : $this->never()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertNotEmpty($event->getExceptions()); + + return true; + })); + + $client = ClientBuilder::create(['excluded_exceptions' => [$excluded]]) + ->setTransport($transport) + ->getClient(); + + $client->captureException($thrown); + } + + public function captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesDataProvider(): array + { + return [ + [ + true, + \Exception::class, + new \Error(), + ], + [ + false, + \Exception::class, + new \LogicException(), + ], + [ + false, + \Throwable::class, + new \Error(), + ], + ]; + } + public function testCapture(): void { /** @var TransportInterface|MockObject $transport */ @@ -258,7 +301,7 @@ public function sampleRateAbsoluteDataProvider(): array /** * @dataProvider convertExceptionDataProvider */ - public function testConvertException(\Exception $exception, array $clientConfig, array $expectedResult): void + public function testConvertException(\Exception $exception, array $expectedResult): void { /** @var TransportInterface|MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -277,7 +320,7 @@ public function testConvertException(\Exception $exception, array $clientConfig, return true; })); - $client = ClientBuilder::create($clientConfig) + $client = ClientBuilder::create() ->setTransport($transport) ->getClient(); @@ -289,7 +332,6 @@ public function convertExceptionDataProvider(): array return [ [ new \RuntimeException('foo'), - [], [ 'level' => Severity::ERROR, 'exception' => [ @@ -304,7 +346,6 @@ public function convertExceptionDataProvider(): array ], [ new \ErrorException('foo', 0, E_USER_WARNING), - [], [ 'level' => Severity::WARNING, 'exception' => [ @@ -317,27 +358,6 @@ public function convertExceptionDataProvider(): array ], ], ], - [ - new \BadMethodCallException('baz', 0, new \BadFunctionCallException('bar', 0, new \LogicException('foo', 0))), - [ - 'excluded_exceptions' => [\BadMethodCallException::class], - ], - [ - 'level' => Severity::ERROR, - 'exception' => [ - 'values' => [ - [ - 'type' => \LogicException::class, - 'value' => 'foo', - ], - [ - 'type' => \BadFunctionCallException::class, - 'value' => 'bar', - ], - ], - ], - ], - ], ]; } From 2b74cde5d05ade32ebc39bdb0d4d86509d497cdc Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 5 Jun 2019 16:12:45 +0200 Subject: [PATCH 0464/1161] Fix exception, open_basedir restriction in effect (#824) --- phpunit.xml.dist | 2 +- src/Stacktrace.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index de44dc74d..7c9f919fa 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,7 +7,7 @@ bootstrap="tests/bootstrap.php" > - + diff --git a/src/Stacktrace.php b/src/Stacktrace.php index cf0e68e46..6e0c9c9e3 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -224,7 +224,7 @@ public function jsonSerialize() */ protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array { - if (!is_file($path) || !is_readable($path)) { + if (@!is_readable($path) || !is_file($path)) { return []; } From e2d99f1cf0158f5758b46e5e461052703a752d09 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 10 Jun 2019 11:35:33 +0200 Subject: [PATCH 0465/1161] Fix uploaded files handling in the RequestIntegration integration (#827) --- CHANGELOG.md | 1 + src/Integration/RequestIntegration.php | 49 +++++++++++++------- tests/Integration/RequestIntegrationTest.php | 34 +++++++------- 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 361d91c5d..3a90a136c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix the behavior of the `excluded_exceptions` option: now it's used to skip capture of exceptions, not to purge the `exception` data of the event, which resulted in broken or empty chains of exceptions in reported events (#822) +- Fix handling of uploaded files in the `RequestIntegration`, to respect the PSR-7 spec fully (#827) ## 2.1.0 (2019-05-22) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 3747d7381..c2efc480a 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -165,22 +165,10 @@ private function captureRequestBody(ServerRequestInterface $serverRequest) } $requestData = $serverRequest->getParsedBody(); - $requestData = \is_array($requestData) ? $requestData : []; - - foreach ($serverRequest->getUploadedFiles() as $fieldName => $uploadedFiles) { - if (!\is_array($uploadedFiles)) { - $uploadedFiles = [$uploadedFiles]; - } - - /** @var UploadedFileInterface $uploadedFile */ - foreach ($uploadedFiles as $uploadedFile) { - $requestData[$fieldName][] = [ - 'client_filename' => $uploadedFile->getClientFilename(), - 'client_media_type' => $uploadedFile->getClientMediaType(), - 'size' => $uploadedFile->getSize(), - ]; - } - } + $requestData = array_merge( + $this->parseUploadedFiles($serverRequest->getUploadedFiles()), + \is_array($requestData) ? $requestData : [] + ); if (!empty($requestData)) { return $requestData; @@ -196,4 +184,33 @@ private function captureRequestBody(ServerRequestInterface $serverRequest) return $requestBody->getContents(); } + + /** + * Create an array with the same structure as $uploadedFiles, but replacing + * each UploadedFileInterface with an array of info. + * + * @param array $uploadedFiles The uploaded files info from a PSR-7 server request + * + * @return array + */ + private function parseUploadedFiles(array $uploadedFiles): array + { + $result = []; + + foreach ($uploadedFiles as $key => $item) { + if ($item instanceof UploadedFileInterface) { + $result[$key] = [ + 'client_filename' => $item->getClientFilename(), + 'client_media_type' => $item->getClientMediaType(), + 'size' => $item->getSize(), + ]; + } elseif (\is_array($item)) { + $result[$key] = $this->parseUploadedFiles($item); + } else { + throw new \UnexpectedValueException(sprintf('Expected either an object implementing the "%s" interface or an array. Got: "%s".', UploadedFileInterface::class, \is_object($item) ? \get_class($item) : \gettype($item))); + } + } + + return $result; + } } diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 7d97c4fc2..95775585f 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -319,11 +319,9 @@ public function applyToEventDataProvider(): \Generator ], 'data' => [ 'foo' => [ - [ - 'client_filename' => 'foo.ext', - 'client_media_type' => 'application/text', - 'size' => 123, - ], + 'client_filename' => 'foo.ext', + 'client_media_type' => 'application/text', + 'size' => 123, ], ], ], @@ -372,8 +370,10 @@ public function applyToEventDataProvider(): \Generator (new ServerRequest()) ->withUploadedFiles([ 'foo' => [ - new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), - new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), + 'bar' => [ + new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), + new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), + ], ], ]) ->withUri(new Uri('http://www.example.com/foo')) @@ -386,15 +386,17 @@ public function applyToEventDataProvider(): \Generator ], 'data' => [ 'foo' => [ - [ - 'client_filename' => 'foo.ext', - 'client_media_type' => 'application/text', - 'size' => 123, - ], - [ - 'client_filename' => 'bar.ext', - 'client_media_type' => 'application/octet-stream', - 'size' => 321, + 'bar' => [ + [ + 'client_filename' => 'foo.ext', + 'client_media_type' => 'application/text', + 'size' => 123, + ], + [ + 'client_filename' => 'bar.ext', + 'client_media_type' => 'application/octet-stream', + 'size' => 321, + ], ], ], ], From ea3de623e4d1c6422fc62275a1f741c85ee7f1ee Mon Sep 17 00:00:00 2001 From: alemax-xyz Date: Wed, 12 Jun 2019 10:32:01 -0400 Subject: [PATCH 0466/1161] Fix getting the client IP address from the server params instead of the HTTP headers (#831) --- CHANGELOG.md | 1 + src/Integration/RequestIntegration.php | 10 ++++++---- tests/Integration/RequestIntegrationTest.php | 10 +++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a90a136c..4c82a3609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix the behavior of the `excluded_exceptions` option: now it's used to skip capture of exceptions, not to purge the `exception` data of the event, which resulted in broken or empty chains of exceptions in reported events (#822) - Fix handling of uploaded files in the `RequestIntegration`, to respect the PSR-7 spec fully (#827) +- Fix use of `REMOTE_ADDR` server variable rather than HTTP header ## 2.1.0 (2019-05-22) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index c2efc480a..67897a48b 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -96,8 +96,10 @@ public static function applyToEvent(self $self, Event $event, ?ServerRequestInte } if ($self->options->shouldSendDefaultPii()) { - if ($request->hasHeader('REMOTE_ADDR')) { - $requestData['env']['REMOTE_ADDR'] = $request->getHeaderLine('REMOTE_ADDR'); + $serverParams = $request->getServerParams(); + + if (isset($serverParams['REMOTE_ADDR'])) { + $requestData['env']['REMOTE_ADDR'] = $serverParams['REMOTE_ADDR']; } $requestData['cookies'] = $request->getCookieParams(); @@ -105,8 +107,8 @@ public static function applyToEvent(self $self, Event $event, ?ServerRequestInte $userContext = $event->getUserContext(); - if (null === $userContext->getIpAddress() && $request->hasHeader('REMOTE_ADDR')) { - $userContext->setIpAddress($request->getHeaderLine('REMOTE_ADDR')); + if (null === $userContext->getIpAddress() && isset($serverParams['REMOTE_ADDR'])) { + $userContext->setIpAddress($serverParams['REMOTE_ADDR']); } } else { $requestData['headers'] = $self->removePiiFromHeaders($request->getHeaders()); diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 95775585f..f462827b1 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -25,8 +25,7 @@ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $event = new Event(); $event->getUserContext()->setData(['foo' => 'bar']); - $request = new ServerRequest(); - $request = $request->withHeader('REMOTE_ADDR', '127.0.0.1'); + $request = new ServerRequest(['REMOTE_ADDR' => '127.0.0.1']); $this->assertInstanceOf(ServerRequestInterface::class, $request); @@ -140,11 +139,10 @@ public function applyToEventDataProvider(): \Generator [ 'send_default_pii' => true, ], - (new ServerRequest()) + (new ServerRequest(['REMOTE_ADDR' => '127.0.0.1'])) ->withUri(new Uri('http://www.example.com/foo?foo=bar&bar=baz')) ->withMethod('GET') ->withHeader('Host', 'www.example.com') - ->withHeader('REMOTE_ADDR', '127.0.0.1') ->withHeader('Authorization', 'foo') ->withHeader('Cookie', 'bar') ->withHeader('Set-Cookie', 'baz'), @@ -155,7 +153,6 @@ public function applyToEventDataProvider(): \Generator 'cookies' => [], 'headers' => [ 'Host' => ['www.example.com'], - 'REMOTE_ADDR' => ['127.0.0.1'], 'Authorization' => ['foo'], 'Cookie' => ['bar'], 'Set-Cookie' => ['baz'], @@ -170,11 +167,10 @@ public function applyToEventDataProvider(): \Generator [ 'send_default_pii' => false, ], - (new ServerRequest()) + (new ServerRequest(['REMOTE_ADDR' => '127.0.0.1'])) ->withUri(new Uri('http://www.example.com/foo?foo=bar&bar=baz')) ->withMethod('GET') ->withHeader('Host', 'www.example.com') - ->withHeader('REMOTE_ADDR', '127.0.0.1') ->withHeader('Authorization', 'foo') ->withHeader('Cookie', 'bar') ->withHeader('Set-Cookie', 'baz'), From 8e27e6c5fcf6f01fc2e5235dd14cc0b2b347d793 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 13 Jun 2019 13:27:23 +0200 Subject: [PATCH 0467/1161] meta: 2.1.1 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c82a3609..ce43cc22b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,13 @@ ## Unreleased +## 2.1.1 (2019-06-13) + - Fix the behavior of the `excluded_exceptions` option: now it's used to skip capture of exceptions, not to purge the `exception` data of the event, which resulted in broken or empty chains of exceptions in reported events (#822) - Fix handling of uploaded files in the `RequestIntegration`, to respect the PSR-7 spec fully (#827) - Fix use of `REMOTE_ADDR` server variable rather than HTTP header +- Fix exception, open_basedir restriction in effect (#824) ## 2.1.0 (2019-05-22) From 50f58938ef857c398e188b79c2fc5036f1adbeb2 Mon Sep 17 00:00:00 2001 From: Floran Brutel Date: Thu, 13 Jun 2019 14:12:40 +0200 Subject: [PATCH 0468/1161] Update branch alias for dev-master (#832) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 523ee8f58..6a0a8c6b0 100644 --- a/composer.json +++ b/composer.json @@ -69,7 +69,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.1.x-dev" } } } From e85c7480e41e6f417d4207a4f68dd5a09b429a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pontus=20M=C3=A5rdn=C3=A4s?= Date: Fri, 14 Jun 2019 12:50:36 +0200 Subject: [PATCH 0469/1161] Fix exception thrown in Monolog handler when extra data has numeric array keys (#834) --- CHANGELOG.md | 2 ++ src/Monolog/Handler.php | 2 +- tests/Monolog/HandlerTest.php | 25 +++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce43cc22b..ce2a8210d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). + ## 2.1.1 (2019-06-13) - Fix the behavior of the `excluded_exceptions` option: now it's used to skip capture of exceptions, not to purge the diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 40c646a35..d1a7ea1b3 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -59,7 +59,7 @@ protected function write(array $record): void if (isset($record['context']['extra']) && \is_array($record['context']['extra'])) { foreach ($record['context']['extra'] as $key => $value) { - $scope->setExtra($key, $value); + $scope->setExtra((string) $key, $value); } } diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index c065f9211..8b544b333 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -281,5 +281,30 @@ public function handleDataProvider(): \Generator 'bar.tag' => 'bar tag value', ], ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::INFO, + 'level_name' => Logger::getLevelName(Logger::INFO), + 'channel' => 'channel.foo', + 'context' => [ + 'extra' => [ + 1 => 'numeric key', + ], + ], + 'extra' => [], + ], + [ + 'level' => Severity::info(), + 'message' => 'foo bar', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::INFO), + '1' => 'numeric key', + ], + [], + ]; } } From 42f6f464ecb5a89b44a46ab293672fac9191dec2 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 1 Jul 2019 17:36:35 +0200 Subject: [PATCH 0470/1161] Start development of version 2.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6a0a8c6b0..2ef7d271e 100644 --- a/composer.json +++ b/composer.json @@ -69,7 +69,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-develop": "2.2-dev" } } } From 7ca3cff86b8b1a2b7d89595122099c7dd6a4e434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Mike=C5=A1?= Date: Mon, 1 Jul 2019 17:41:33 +0200 Subject: [PATCH 0471/1161] Support setting the transaction name in the Monolog handler (#843) --- src/Monolog/Handler.php | 4 ++++ tests/Monolog/HandlerTest.php | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index d1a7ea1b3..2030ad6c2 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -53,6 +53,10 @@ protected function write(array $record): void $payload['exception'] = $record['context']['exception']; } + if (isset($record['extra']['transaction'])) { + $payload['transaction'] = $record['extra']['transaction']; + } + $this->hub->withScope(function (Scope $scope) use ($record, $payload): void { $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index 8b544b333..a73c38326 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -306,5 +306,28 @@ public function handleDataProvider(): \Generator ], [], ]; + + yield [ + [ + 'message' => 'foo bar', + 'level' => Logger::DEBUG, + 'level_name' => Logger::getLevelName(Logger::DEBUG), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [ + 'transaction' => 'Foo transaction', + ], + ], + [ + 'level' => Severity::debug(), + 'message' => 'foo bar', + 'transaction' => 'Foo transaction', + ], + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::DEBUG), + ], + [], + ]; } } From 934526d1b31600178662e99f2b4eeaade432e5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Mike=C5=A1?= Date: Mon, 1 Jul 2019 17:50:37 +0200 Subject: [PATCH 0472/1161] Support setting the logger in the Monolog handler (#840) --- src/Monolog/Handler.php | 1 + tests/Monolog/HandlerTest.php | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 2030ad6c2..b5badfc74 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -47,6 +47,7 @@ protected function write(array $record): void $payload = [ 'level' => $this->getSeverityFromLevel($record['level']), 'message' => $record['message'], + 'logger' => 'monolog.' . $record['channel'], ]; if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index a73c38326..430daaa2d 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -62,6 +62,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::debug(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -82,6 +83,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::info(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -102,6 +104,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::info(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -122,6 +125,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::warning(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -142,6 +146,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::error(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -162,6 +167,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::fatal(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -182,6 +188,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::fatal(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -202,6 +209,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::fatal(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -232,6 +240,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::warning(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -269,6 +278,7 @@ public function handleDataProvider(): \Generator 'level' => Severity::warning(), 'message' => 'foo bar', 'exception' => new \Exception('exception message'), + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', @@ -298,6 +308,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::info(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', ], [ 'monolog.channel' => 'channel.foo', From 3e3bc54a1519c52ce836eb148c03a91a0ba56134 Mon Sep 17 00:00:00 2001 From: Benoit GALATI Date: Thu, 11 Jul 2019 14:40:06 +0200 Subject: [PATCH 0473/1161] Change the typehints of getCurrentHub and setCurrentHub methods of the HubInterface interface (#849) --- CHANGELOG.md | 1 + src/State/HubInterface.php | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2a8210d..8306df2d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). +- Changed type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) ## 2.1.1 (2019-06-13) diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index af13d62e6..babeeb8a4 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -122,18 +122,18 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool; /** * Returns the current global Hub. * - * @return self + * @return HubInterface */ - public static function getCurrent(): self; + public static function getCurrent(): HubInterface; /** * Sets the Hub as the current. * - * @param self $hub The Hub that will become the current one + * @param HubInterface $hub The Hub that will become the current one * - * @return self + * @return HubInterface */ - public static function setCurrent(self $hub): self; + public static function setCurrent(HubInterface $hub): HubInterface; /** * Gets the integration whose FQCN matches the given one if it's available on the current client. From c0f981be471a973f74b8e149cd87db1109b91351 Mon Sep 17 00:00:00 2001 From: HazA Date: Thu, 18 Jul 2019 20:10:10 +0200 Subject: [PATCH 0474/1161] meta: Add develop branch --- .appveyor.yml | 1 + .travis.yml | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index cb495dc34..3aa9a21f2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,6 +6,7 @@ skip_branch_with_pr: true branches: only: - master + - develop environment: matrix: diff --git a/.travis.yml b/.travis.yml index e335ff246..9ff1927a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: php branches: - only: - - master - - /^release\/.+$/ + only: + - master + - develop + - /^release\/.+$/ php: - 7.1 From 2052a04dd4821cd8503dca18f0360d55f7e48e58 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 19 Jul 2019 15:00:07 +0200 Subject: [PATCH 0475/1161] Add functions to clear breadcrumbs and bulk set tags and extras in the scope (#852) --- .php_cs | 1 + CHANGELOG.md | 1 + src/State/Hub.php | 1 + src/State/Scope.php | 72 +++------ tests/Monolog/HandlerTest.php | 34 ++-- tests/SdkTest.php | 20 ++- tests/State/HubTest.php | 295 ++++++++++++++++++++++++---------- tests/State/ScopeTest.php | 155 +++++++++++++++--- 8 files changed, 394 insertions(+), 185 deletions(-) diff --git a/.php_cs b/.php_cs index 2ed598fb7..04238d23c 100644 --- a/.php_cs +++ b/.php_cs @@ -13,6 +13,7 @@ return PhpCsFixer\Config::create() 'psr4' => true, 'random_api_migration' => true, 'yoda_style' => true, + 'self_accessor' => false, 'phpdoc_no_useless_inheritdoc' => false, 'phpdoc_align' => [ 'tags' => ['param', 'return', 'throws', 'type', 'var'], diff --git a/CHANGELOG.md b/CHANGELOG.md index 8306df2d2..2eea33be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). - Changed type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) +- Add the `setTags`, `setExtras` and `clearBreadcrumbs` methods to the `Scope` class (#852) ## 2.1.1 (2019-06-13) diff --git a/src/State/Hub.php b/src/State/Hub.php index a37cb5f3a..f11c7279b 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -227,6 +227,7 @@ public static function setCurrent(HubInterface $hub): HubInterface public function getIntegration(string $className): ?IntegrationInterface { $client = $this->getClient(); + if (null !== $client) { return $client->getIntegration($className); } diff --git a/src/State/Scope.php b/src/State/Scope.php index d0e58b71e..dcbf6b112 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -85,15 +85,17 @@ public function setTag(string $key, string $value): self } /** - * Gets the tags contained in the tags context. + * Merges the given tags into the current tags context. * - * @return array + * @param array $tags The tags to merge into the current context * - * @internal + * @return $this */ - public function getTags(): array + public function setTags(array $tags): self { - return $this->tags->toArray(); + $this->tags->merge($tags); + + return $this; } /** @@ -112,15 +114,17 @@ public function setExtra(string $key, $value): self } /** - * Gets the information contained in the extra context. + * Merges the given data into the current extras context. * - * @return array + * @param array $extras Data to merge into the current context * - * @internal + * @return $this */ - public function getExtra(): array + public function setExtras(array $extras): self { - return $this->extra->toArray(); + $this->extra->merge($extras); + + return $this; } /** @@ -137,18 +141,6 @@ public function setUser(array $data): self return $this; } - /** - * Gets the information contained in the user context. - * - * @return array - * - * @internal - */ - public function getUser(): array - { - return $this->user->toArray(); - } - /** * Sets the list of strings used to dictate the deduplication of this event. * @@ -163,18 +155,6 @@ public function setFingerprint(array $fingerprint): self return $this; } - /** - * Gets the list of strings used to dictate the deduplication of this event. - * - * @return string[] - * - * @internal - */ - public function getFingerprint(): array - { - return $this->fingerprint; - } - /** * Sets the severity to apply to all events captured in this scope. * @@ -189,18 +169,6 @@ public function setLevel(?Severity $level): self return $this; } - /** - * Gets the severity to apply to all events captured in this scope. - * - * @return Severity|null - * - * @internal - */ - public function getLevel(): ?Severity - { - return $this->level; - } - /** * Add the given breadcrumb to the scope. * @@ -218,15 +186,15 @@ public function addBreadcrumb(Breadcrumb $breadcrumb, int $maxBreadcrumbs = 100) } /** - * Gets the breadcrumbs. - * - * @return Breadcrumb[] + * Clears all the breadcrumbs. * - * @internal + * @return $this */ - public function getBreadcrumbs(): array + public function clearBreadcrumbs(): self { - return $this->breadcrumbs; + $this->breadcrumbs = []; + + return $this; } /** diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index 430daaa2d..2bd4715bb 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; +use Sentry\Event; use Sentry\Monolog\Handler; use Sentry\Severity; use Sentry\State\Hub; @@ -15,36 +16,28 @@ final class HandlerTest extends TestCase { - /** - * @var MockObject|ClientInterface - */ - private $client; - - protected function setUp(): void - { - $this->client = $this->createMock(ClientInterface::class); - } - /** * @dataProvider handleDataProvider */ public function testHandle(array $record, array $expectedPayload, array $expectedExtra, array $expectedTags): void { - $this->client->expects($this->once()) + $scope = new Scope(); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) ->method('captureEvent') - ->with($expectedPayload, $this->callback(static function (Scope $scope) use ($expectedExtra, $expectedTags): bool { - if ($expectedExtra !== $scope->getExtra()) { - return false; - } + ->with($expectedPayload, $this->callback(function (Scope $scopeArg) use ($expectedExtra, $expectedTags): bool { + $event = $scopeArg->applyToEvent(new Event(), []); - if ($expectedTags !== $scope->getTags()) { - return false; - } + $this->assertNotNull($event); + $this->assertSame($expectedExtra, $event->getExtraContext()->toArray()); + $this->assertSame($expectedTags, $event->getTagsContext()->toArray()); return true; })); - $handler = new Handler(new Hub($this->client)); + $handler = new Handler(new Hub($client, $scope)); $handler->handle($record); } @@ -313,7 +306,7 @@ public function handleDataProvider(): \Generator [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::INFO), - '1' => 'numeric key', + '0' => 'numeric key', ], [], ]; @@ -332,6 +325,7 @@ public function handleDataProvider(): \Generator [ 'level' => Severity::debug(), 'message' => 'foo bar', + 'logger' => 'monolog.channel.foo', 'transaction' => 'Foo transaction', ], [ diff --git a/tests/SdkTest.php b/tests/SdkTest.php index 6d0f55e37..fabc1c8d1 100644 --- a/tests/SdkTest.php +++ b/tests/SdkTest.php @@ -14,8 +14,11 @@ use function Sentry\captureMessage; use Sentry\ClientInterface; use function Sentry\configureScope; +use Sentry\Event; use function Sentry\init; +use Sentry\Options; use Sentry\State\Hub; +use Sentry\State\Scope; use function Sentry\withScope; class SdkTest extends TestCase @@ -92,14 +95,21 @@ public function testAddBreadcrumb(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - addBreadcrumb($breadcrumb); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options()); - $hub = Hub::getCurrent(); + Hub::getCurrent()->bindClient($client); - $method = new \ReflectionMethod($hub, 'getScope'); - $method->setAccessible(true); + addBreadcrumb($breadcrumb); + configureScope(function (Scope $scope) use ($breadcrumb): void { + $event = $scope->applyToEvent(new Event(), []); - $this->assertSame([$breadcrumb], $method->invoke($hub)->getBreadcrumbs()); + $this->assertNotNull($event); + $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); + }); } public function testWithScope(): void diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index a95e73caf..f6b1c82cc 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -7,25 +7,19 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; -use Sentry\ClientBuilder; use Sentry\ClientInterface; +use Sentry\Event; +use Sentry\Integration\IntegrationInterface; +use Sentry\Options; use Sentry\Severity; use Sentry\State\Hub; -use Sentry\State\HubInterface; use Sentry\State\Scope; final class HubTest extends TestCase { - public function testConstructorCreatesScopeAutomatically(): void - { - $hub = new Hub(null, null); - - $this->assertNotNull($this->getScope($hub)); - } - public function testGetClient(): void { - /** @var ClientInterface|MockObject $client */ + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $hub = new Hub($client); @@ -34,15 +28,22 @@ public function testGetClient(): void public function testGetScope(): void { + $callbackInvoked = false; $scope = new Scope(); $hub = new Hub($this->createMock(ClientInterface::class), $scope); - $this->assertSame($scope, $this->getScope($hub)); + $hub->configureScope(function (Scope $scopeArg) use (&$callbackInvoked, $scope) { + $this->assertSame($scope, $scopeArg); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); } public function testGetLastEventId(): void { - /** @var ClientInterface|MockObject $client */ + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') @@ -56,40 +57,73 @@ public function testGetLastEventId(): void public function testPushScope(): void { - $hub = new Hub($this->createMock(ClientInterface::class)); + /** @var ClientInterface&MockObject $client1 */ + $client1 = $this->createMock(ClientInterface::class); + $scope1 = new Scope(); + $hub = new Hub($client1, $scope1); - $scope1 = $this->getScope($hub); - $client1 = $hub->getClient(); + $this->assertSame($client1, $hub->getClient()); $scope2 = $hub->pushScope(); - $client2 = $hub->getClient(); - $this->assertNotSame($scope1, $scope2); - $this->assertSame($scope2, $this->getScope($hub)); - $this->assertSame($client1, $client2); $this->assertSame($client1, $hub->getClient()); + $this->assertNotSame($scope1, $scope2); + + $hub->configureScope(function (Scope $scopeArg) use (&$callbackInvoked, $scope2): void { + $this->assertSame($scope2, $scopeArg); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); } public function testPopScope(): void { - $hub = new Hub($this->createMock(ClientInterface::class)); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $scope1 = new Scope(); + $hub = new Hub($client, $scope1); - $scope1 = $this->getScope($hub); - $client = $hub->getClient(); + $this->assertFalse($hub->popScope()); $scope2 = $hub->pushScope(); - $this->assertSame($scope2, $this->getScope($hub)); + $callbackInvoked = false; + + $hub->configureScope(function (Scope $scopeArg) use ($scope2, &$callbackInvoked): void { + $this->assertSame($scope2, $scopeArg); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); $this->assertSame($client, $hub->getClient()); $this->assertTrue($hub->popScope()); - $this->assertSame($scope1, $this->getScope($hub)); + $callbackInvoked = false; + + $hub->configureScope(function (Scope $scopeArg) use ($scope1, &$callbackInvoked): void { + $this->assertSame($scope1, $scopeArg); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); $this->assertSame($client, $hub->getClient()); $this->assertFalse($hub->popScope()); - $this->assertSame($scope1, $this->getScope($hub)); + $callbackInvoked = false; + + $hub->configureScope(function (Scope $scopeArg) use ($scope1, &$callbackInvoked): void { + $this->assertSame($scope1, $scopeArg); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); $this->assertSame($client, $hub->getClient()); } @@ -97,9 +131,6 @@ public function testWithScope(): void { $scope = new Scope(); $hub = new Hub($this->createMock(ClientInterface::class), $scope); - - $this->assertSame($scope, $this->getScope($hub)); - $callbackInvoked = false; try { @@ -117,16 +148,22 @@ public function testWithScope(): void } $this->assertTrue($callbackInvoked); - $this->assertSame($scope, $this->getScope($hub)); + + $callbackInvoked = false; + + $hub->configureScope(function (Scope $scopeArg) use (&$callbackInvoked, $scope): void { + $this->assertSame($scope, $scopeArg); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); } public function testConfigureScope(): void { - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - - $hub = new Hub($client); - $scope = $hub->pushScope(); + $scope = new Scope(); + $hub = new Hub(null, $scope); $hub->configureScope(function (Scope $scopeArg) use ($scope, &$callbackInvoked): void { $this->assertSame($scope, $scopeArg); @@ -135,15 +172,14 @@ public function testConfigureScope(): void }); $this->assertTrue($callbackInvoked); - $this->assertSame($scope, $this->getScope($hub)); } public function testBindClient(): void { - /** @var ClientInterface|MockObject $client1 */ + /** @var ClientInterface&MockObject $client1 */ $client1 = $this->createMock(ClientInterface::class); - /** @var ClientInterface|MockObject $client2 */ + /** @var ClientInterface&MockObject $client2 */ $client2 = $this->createMock(ClientInterface::class); $hub = new Hub($client1); @@ -155,88 +191,123 @@ public function testBindClient(): void $this->assertSame($client2, $hub->getClient()); } - public function testCaptureMessageDoesNothingIfClientIsNotBinded() - { - $hub = new Hub(); - - $this->assertNull($hub->captureMessage('foo')); - } - public function testCaptureMessage(): void { - /** @var ClientInterface|MockObject $client */ + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') ->with('foo', Severity::debug()) ->willReturn('2b867534eead412cbdb882fd5d441690'); - $hub = new Hub($client); + $hub = new Hub(); - $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureMessage('foo', Severity::debug())); - } + $this->assertNull($hub->captureMessage('foo')); - public function testCaptureExceptionDoesNothingIfClientIsNotBinded() - { - $hub = new Hub(); + $hub->bindClient($client); - $this->assertNull($hub->captureException(new \RuntimeException())); + $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureMessage('foo', Severity::debug())); } public function testCaptureException(): void { $exception = new \RuntimeException('foo'); - /** @var ClientInterface|MockObject $client */ + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureException') ->with($exception) ->willReturn('2b867534eead412cbdb882fd5d441690'); - $hub = new Hub($client); + $hub = new Hub(); + + $this->assertNull($hub->captureException(new \RuntimeException())); + + $hub->bindClient($client); $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureException($exception)); } - public function testAddBreadcrumbDoesNothingIfClientIsNotBinded(): void + public function testCaptureLastError(): void { - $scope = new Scope(); - $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('captureLastError') + ->willReturn('7565e130d1d14e639442110a6dd1cbab'); - $hub = new Hub(null, $scope); - $hub->addBreadcrumb($breadcrumb); + $hub = new Hub(); + + $this->assertNull($hub->captureLastError()); - $this->assertEmpty($scope->getBreadcrumbs()); + $hub->bindClient($client); + + $this->assertEquals('7565e130d1d14e639442110a6dd1cbab', $hub->captureLastError()); } public function testAddBreadcrumb(): void { - $client = ClientBuilder::create()->getClient(); - $hub = new Hub($client); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options()); + + $callbackInvoked = false; + $hub = new Hub(); $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $hub->addBreadcrumb($breadcrumb); + $hub->configureScope(function (Scope $scope): void { + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertEmpty($event->getBreadcrumbs()); + }); - $this->assertSame([$breadcrumb], $this->getScope($hub)->getBreadcrumbs()); + $hub->bindClient($client); + $hub->addBreadcrumb($breadcrumb); + $hub->configureScope(function (Scope $scope) use (&$callbackInvoked, $breadcrumb): void { + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); } public function testAddBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsZero(): void { - $client = ClientBuilder::create(['max_breadcrumbs' => 0])->getClient(); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['max_breadcrumbs' => 0])); + $hub = new Hub($client); $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); + $hub->configureScope(function (Scope $scope): void { + $event = $scope->applyToEvent(new Event(), []); - $this->assertEmpty($this->getScope($hub)->getBreadcrumbs()); + $this->assertNotNull($event); + $this->assertEmpty($event->getBreadcrumbs()); + }); } public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void { - $client = ClientBuilder::create(['max_breadcrumbs' => 2])->getClient(); - $hub = new Hub($client); - $scope = $this->getScope($hub); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options(['max_breadcrumbs' => 2])); + $hub = new Hub($client); $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'foo'); $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'bar'); $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_ERROR, 'error_reporting', 'baz'); @@ -244,61 +315,113 @@ public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void $hub->addBreadcrumb($breadcrumb1); $hub->addBreadcrumb($breadcrumb2); - $this->assertSame([$breadcrumb1, $breadcrumb2], $scope->getBreadcrumbs()); + $hub->configureScope(function (Scope $scope) use ($breadcrumb1, $breadcrumb2): void { + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([$breadcrumb1, $breadcrumb2], $event->getBreadcrumbs()); + }); $hub->addBreadcrumb($breadcrumb3); - $this->assertSame([$breadcrumb2, $breadcrumb3], $scope->getBreadcrumbs()); + $hub->configureScope(function (Scope $scope) use ($breadcrumb2, $breadcrumb3): void { + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([$breadcrumb2, $breadcrumb3], $event->getBreadcrumbs()); + }); } public function testAddBreadcrumbDoesNothingWhenBeforeBreadcrumbCallbackReturnsNull(): void { - $callback = function (): ?Breadcrumb { - return null; - }; - $client = ClientBuilder::create(['before_breadcrumb' => $callback])->getClient(); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'before_breadcrumb' => static function () { + return null; + }, + ])); + $hub = new Hub($client); $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); + $hub->configureScope(function (Scope $scope): void { + $event = $scope->applyToEvent(new Event(), []); - $this->assertEmpty($this->getScope($hub)->getBreadcrumbs()); + $this->assertNotNull($event); + $this->assertEmpty($event->getBreadcrumbs()); + }); } public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallback(): void { + $callbackInvoked = false; $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - $callback = function () use ($breadcrumb2): ?Breadcrumb { - return $breadcrumb2; - }; - $client = ClientBuilder::create(['before_breadcrumb' => $callback])->getClient(); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'before_breadcrumb' => static function () use ($breadcrumb2): Breadcrumb { + return $breadcrumb2; + }, + ])); + $hub = new Hub($client); $hub->addBreadcrumb($breadcrumb1); + $hub->configureScope(function (Scope $scope) use (&$callbackInvoked, $breadcrumb2): void { + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([$breadcrumb2], $event->getBreadcrumbs()); + + $callbackInvoked = true; + }); - $this->assertSame([$breadcrumb2], $this->getScope($hub)->getBreadcrumbs()); + $this->assertTrue($callbackInvoked); } public function testCaptureEvent(): void { - /** @var ClientInterface|MockObject $client */ + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') ->with(['message' => 'test']) ->willReturn('2b867534eead412cbdb882fd5d441690'); - $hub = new Hub($client); + $hub = new Hub(); + + $this->assertNull($hub->captureEvent([])); + + $hub->bindClient($client); $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureEvent(['message' => 'test'])); } - private function getScope(HubInterface $hub): Scope + public function testGetIntegration(): void { - $method = new \ReflectionMethod($hub, 'getScope'); - $method->setAccessible(true); + /** @var MockObject $integration */ + $integration = $this->createMock(IntegrationInterface::class); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getIntegration') + ->with('Foo\\Bar') + ->willReturn($integration); + + $hub = new Hub(); + + $this->assertNull($hub->getIntegration('Foo\\Bar')); + + $hub->bindClient($client); - return $method->invoke($hub); + $this->assertSame($integration, $hub->getIntegration('Foo\\Bar')); } } diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 33a9908d4..51a9ace52 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -15,62 +15,127 @@ final class ScopeTest extends TestCase public function testSetTag(): void { $scope = new Scope(); + $event = $scope->applyToEvent(new Event(), []); - $this->assertEquals([], $scope->getTags()); + $this->assertNotNull($event); + $this->assertTrue($event->getTagsContext()->isEmpty()); $scope->setTag('foo', 'bar'); $scope->setTag('bar', 'baz'); - $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $scope->getTags()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTagsContext()->toArray()); + } + + public function setTags(): void + { + $scope = new Scope(); + $scope->setTags(['foo' => 'bar']); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar'], $event->getTagsContext()->toArray()); + + $scope->setTags(['bar' => 'baz']); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTagsContext()->toArray()); } public function testSetExtra(): void { $scope = new Scope(); + $event = $scope->applyToEvent(new Event(), []); - $this->assertEquals([], $scope->getExtra()); + $this->assertNotNull($event); + $this->assertTrue($event->getExtraContext()->isEmpty()); $scope->setExtra('foo', 'bar'); $scope->setExtra('bar', 'baz'); - $this->assertEquals(['foo' => 'bar', 'bar' => 'baz'], $scope->getExtra()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtraContext()->toArray()); + } + + public function testSetExtras(): void + { + $scope = new Scope(); + $scope->setExtras(['foo' => 'bar']); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar'], $event->getExtraContext()->toArray()); + + $scope->setExtras(['bar' => 'baz']); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtraContext()->toArray()); } public function testSetUser(): void { $scope = new Scope(); - $this->assertEquals([], $scope->getUser()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([], $event->getUserContext()->toArray()); $scope->setUser(['foo' => 'bar']); - $this->assertEquals(['foo' => 'bar'], $scope->getUser()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar'], $event->getUserContext()->toArray()); $scope->setUser(['bar' => 'baz']); - $this->assertEquals(['bar' => 'baz'], $scope->getUser()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['bar' => 'baz'], $event->getUserContext()->toArray()); } public function testSetFingerprint(): void { $scope = new Scope(); + $event = $scope->applyToEvent(new Event(), []); - $this->assertEmpty($scope->getFingerprint()); + $this->assertNotNull($event); + $this->assertEmpty($event->getFingerprint()); $scope->setFingerprint(['foo', 'bar']); - $this->assertEquals(['foo', 'bar'], $scope->getFingerprint()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo', 'bar'], $event->getFingerprint()); } public function testSetLevel(): void { $scope = new Scope(); + $event = $scope->applyToEvent(new Event(), []); - $this->assertNull($scope->getLevel()); + $this->assertNotNull($event); + $this->assertEquals(Severity::error(), $event->getLevel()); $scope->setLevel(Severity::debug()); - $this->assertEquals(Breadcrumb::LEVEL_DEBUG, $scope->getLevel()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertEquals(Severity::debug(), $event->getLevel()); } public function testAddBreadcrumb(): void @@ -80,16 +145,45 @@ public function testAddBreadcrumb(): void $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - $this->assertEmpty($scope->getBreadcrumbs()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertEmpty($event->getBreadcrumbs()); $scope->addBreadcrumb($breadcrumb1); $scope->addBreadcrumb($breadcrumb2); - $this->assertSame([$breadcrumb1, $breadcrumb2], $scope->getBreadcrumbs()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([$breadcrumb1, $breadcrumb2], $event->getBreadcrumbs()); $scope->addBreadcrumb($breadcrumb3, 2); - $this->assertSame([$breadcrumb2, $breadcrumb3], $scope->getBreadcrumbs()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([$breadcrumb2, $breadcrumb3], $event->getBreadcrumbs()); + } + + public function testClearBreadcrumbs(): void + { + $scope = new Scope(); + + $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); + $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertNotEmpty($event->getBreadcrumbs()); + + $scope->clearBreadcrumbs(); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertEmpty($event->getBreadcrumbs()); } public function testAddEventProcessor(): void @@ -136,19 +230,36 @@ public function testAddEventProcessor(): void public function testClear(): void { $scope = new Scope(); - $scope->setLevel(Severity::error()); - $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + + $scope->setLevel(Severity::info()); + $scope->addBreadcrumb($breadcrumb); $scope->setFingerprint(['foo']); + $scope->setExtras(['foo' => 'bar']); + $scope->setTags(['bar' => 'foo']); + $scope->setUser(['foobar' => 'barfoo']); - $this->assertNotNull($scope->getLevel()); - $this->assertNotEmpty($scope->getBreadcrumbs()); - $this->assertNotEmpty($scope->getFingerprint()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertEquals(Severity::info(), $event->getLevel()); + $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); + $this->assertSame(['foo'], $event->getFingerprint()); + $this->assertSame(['foo' => 'bar'], $event->getExtraContext()->toArray()); + $this->assertSame(['bar' => 'foo'], $event->getTagsContext()->toArray()); + $this->assertSame(['foobar' => 'barfoo'], $event->getUserContext()->toArray()); $scope->clear(); - $this->assertNull($scope->getLevel()); - $this->assertEmpty($scope->getBreadcrumbs()); - $this->assertEmpty($scope->getFingerprint()); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertEquals(Severity::error(), $event->getLevel()); + $this->assertEmpty($event->getBreadcrumbs()); + $this->assertEmpty($event->getFingerprint()); + $this->assertEmpty($event->getExtraContext()->toArray()); + $this->assertEmpty($event->getTagsContext()->toArray()); + $this->assertEmpty($event->getUserContext()->toArray()); } public function testApplyToEvent(): void From c9b15bcad45fe5643f5c10c94b35b8844bb000f0 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 19 Jul 2019 15:27:13 +0200 Subject: [PATCH 0476/1161] Revert "Support setting the transaction name in the Monolog handler (#843)" (#853) This reverts commit 7ca3cff8 --- src/Monolog/Handler.php | 4 ---- tests/Monolog/HandlerTest.php | 24 ------------------------ 2 files changed, 28 deletions(-) diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index b5badfc74..46629d3f8 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -54,10 +54,6 @@ protected function write(array $record): void $payload['exception'] = $record['context']['exception']; } - if (isset($record['extra']['transaction'])) { - $payload['transaction'] = $record['extra']['transaction']; - } - $this->hub->withScope(function (Scope $scope) use ($record, $payload): void { $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index 2bd4715bb..2d8a50bf5 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -310,29 +310,5 @@ public function handleDataProvider(): \Generator ], [], ]; - - yield [ - [ - 'message' => 'foo bar', - 'level' => Logger::DEBUG, - 'level_name' => Logger::getLevelName(Logger::DEBUG), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [ - 'transaction' => 'Foo transaction', - ], - ], - [ - 'level' => Severity::debug(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - 'transaction' => 'Foo transaction', - ], - [ - 'monolog.channel' => 'channel.foo', - 'monolog.level' => Logger::getLevelName(Logger::DEBUG), - ], - [], - ]; } } From 98a07e7a800b6d2fee52157ef6936cc3edda6bdd Mon Sep 17 00:00:00 2001 From: leo108 Date: Tue, 30 Jul 2019 18:38:03 +0800 Subject: [PATCH 0477/1161] Silently convert numeric values to strings when setting a tag (#858) --- CHANGELOG.md | 1 + src/Context/Context.php | 5 +-- src/Context/TagsContext.php | 53 +++++++++++++++++++------------ tests/Context/TagsContextTest.php | 4 +-- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eea33be2..aacbf7ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). - Changed type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) - Add the `setTags`, `setExtras` and `clearBreadcrumbs` methods to the `Scope` class (#852) +- Silently cast numeric values to strings when trying to set the tags instead of throwing (#858) ## 2.1.1 (2019-06-13) diff --git a/src/Context/Context.php b/src/Context/Context.php index 1a784b017..b8e265a9f 100644 --- a/src/Context/Context.php +++ b/src/Context/Context.php @@ -65,9 +65,10 @@ public function merge(array $data, bool $recursive = false): void } /** - * Sets the given data into this object. + * Sets each element of the array to the value of the corresponding key in + * the given input data. * - * @param array $data + * @param array $data The data to set */ public function setData(array $data): void { diff --git a/src/Context/TagsContext.php b/src/Context/TagsContext.php index d36d3103c..8de06c66a 100644 --- a/src/Context/TagsContext.php +++ b/src/Context/TagsContext.php @@ -21,13 +21,7 @@ public function merge(array $data, bool $recursive = false): void throw new \InvalidArgumentException('The tags context does not allow recursive merging of its data.'); } - foreach ($data as $value) { - if (!\is_string($value)) { - throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); - } - } - - parent::merge($data); + parent::merge(self::sanitizeData($data)); } /** @@ -35,13 +29,7 @@ public function merge(array $data, bool $recursive = false): void */ public function setData(array $data): void { - foreach ($data as $value) { - if (!\is_string($value)) { - throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); - } - } - - parent::setData($data); + parent::setData(self::sanitizeData($data)); } /** @@ -49,13 +37,7 @@ public function setData(array $data): void */ public function replaceData(array $data): void { - foreach ($data as $value) { - if (!\is_string($value)) { - throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); - } - } - - parent::replaceData($data); + parent::replaceData(self::sanitizeData($data)); } /** @@ -63,10 +45,39 @@ public function replaceData(array $data): void */ public function offsetSet($offset, $value): void { + if (is_numeric($value)) { + $value = (string) $value; + } + if (!\is_string($value)) { throw new \InvalidArgumentException('The $value argument must be a string.'); } parent::offsetSet($offset, $value); } + + /** + * Sanitizes the given data by converting numeric values to strings. + * + * @param array $data The data to sanitize + * + * @return array + * + * @throws \InvalidArgumentException If any of the values of the input data + * is not a number or a string + */ + private static function sanitizeData(array $data): array + { + foreach ($data as &$value) { + if (is_numeric($value)) { + $value = (string) $value; + } + + if (!\is_string($value)) { + throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); + } + } + + return $data; + } } diff --git a/tests/Context/TagsContextTest.php b/tests/Context/TagsContextTest.php index 95fb9a6e8..d3b756dac 100644 --- a/tests/Context/TagsContextTest.php +++ b/tests/Context/TagsContextTest.php @@ -29,9 +29,9 @@ public function mergeDataProvider() { return [ [ - ['foo' => 'baz', 'baz' => 'foo'], + ['foo' => 'baz', 'baz' => 'foo', 'int' => 1, 'float' => 1.1], false, - ['foo' => 'baz', 'bar' => 'foo', 'baz' => 'foo'], + ['foo' => 'baz', 'bar' => 'foo', 'baz' => 'foo', 'int' => '1', 'float' => '1.1'], null, ], [ From fc4cd9932c07b1c6a5550e6451b337e5517f3b08 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 30 Jul 2019 14:01:03 +0200 Subject: [PATCH 0478/1161] Fix sending of GZIP-compressed requests when the enable_compression option is on (#857) --- .appveyor.yml | 1 - CHANGELOG.md | 1 + composer.json | 2 +- src/ClientBuilder.php | 7 ++- src/HttpClient/Plugin/GzipEncoderPlugin.php | 47 ++++++++++++++++++ tests/ClientBuilderTest.php | 19 ++++--- .../Plugin/GzipEncoderPluginTest.php | 49 +++++++++++++++++++ 7 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 src/HttpClient/Plugin/GzipEncoderPlugin.php create mode 100644 tests/HttpClient/Plugin/GzipEncoderPluginTest.php diff --git a/.appveyor.yml b/.appveyor.yml index cb495dc34..c1c670e75 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -32,7 +32,6 @@ cache: init: - SET PATH=c:\php\%PHP_VERSION%;%PATH% - - SET SYMFONY_DEPRECATIONS_HELPER=strict - SET ANSICON=121x90 (121x90) - SET INSTALL_PHP=1 diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2a8210d..715f0d5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). +- Fix sending of GZIP-compressed requests when the `enable_compression` option is `true` (#857) ## 2.1.1 (2019-06-13) diff --git a/composer.json b/composer.json index 6a0a8c6b0..8f92fc9c3 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpstan/phpstan-phpunit": "^0.10", "phpstan/phpstan": "^0.10.3", "phpunit/phpunit": "^7.0", - "symfony/phpunit-bridge": "^4.1.6" + "symfony/phpunit-bridge": "^4.3" }, "conflict": { "php-http/client-common": "1.8.0", diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 1ebc94216..a9b64dce5 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -22,6 +22,7 @@ use Http\Message\UriFactory; use Jean85\PrettyVersions; use Sentry\HttpClient\Authentication\SentryAuthentication; +use Sentry\HttpClient\Plugin\GzipEncoderPlugin; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -291,13 +292,15 @@ private function createHttpClientInstance(): PluginClient $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->getSdkVersion()])); $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuthentication($this->options, $this->sdkIdentifier, $this->getSdkVersion()))); - $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->options->getSendAttempts()])); - $this->addHttpClientPlugin(new ErrorPlugin()); if ($this->options->isCompressionEnabled()) { + $this->addHttpClientPlugin(new GzipEncoderPlugin()); $this->addHttpClientPlugin(new DecoderPlugin()); } + $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->options->getSendAttempts()])); + $this->addHttpClientPlugin(new ErrorPlugin()); + return new PluginClient($this->httpClient, $this->httpClientPlugins); } diff --git a/src/HttpClient/Plugin/GzipEncoderPlugin.php b/src/HttpClient/Plugin/GzipEncoderPlugin.php new file mode 100644 index 000000000..bf7d49950 --- /dev/null +++ b/src/HttpClient/Plugin/GzipEncoderPlugin.php @@ -0,0 +1,47 @@ + + */ +final class GzipEncoderPlugin implements PluginInterface +{ + /** + * Constructor. + * + * @throws \RuntimeException If the zlib extension is not enabled + */ + public function __construct() + { + if (!\extension_loaded('zlib')) { + throw new \RuntimeException('The "zlib" extension must be enabled to use this plugin.'); + } + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first): PromiseInterface + { + $requestBody = $request->getBody(); + + if ($requestBody->isSeekable()) { + $requestBody->rewind(); + } + + $request = $request->withHeader('Content-Encoding', 'gzip'); + $request = $request->withBody(new GzipEncodeStream($requestBody)); + + return $next($request); + } +} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 6292d50b2..9ccde518e 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -5,6 +5,7 @@ namespace Sentry\Tests; use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin\DecoderPlugin; use Http\Client\Common\PluginClient; use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory; @@ -17,6 +18,7 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\HttpClient\Plugin\GzipEncoderPlugin; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -261,22 +263,27 @@ public function testClientBuilderSetsSdkIdentifierAndVersion(): void */ public function testGetClientTogglesCompressionPluginInHttpClient(bool $enabled): void { - $options = new Options(['dsn' => 'http://public:secret@example.com/sentry/1']); - $options->setEnableCompression($enabled); + $options = new Options([ + 'dsn' => 'http://public:secret@example.com/sentry/1', + 'enable_compression' => $enabled, + ]); + $builder = new ClientBuilder($options); $builder->getClient(); + $encoderPluginFound = false; $decoderPluginFound = false; foreach ($this->getObjectAttribute($builder, 'httpClientPlugins') as $plugin) { - if ($plugin instanceof Plugin\DecoderPlugin) { + if ($plugin instanceof GzipEncoderPlugin) { + $encoderPluginFound = true; + } elseif ($plugin instanceof DecoderPlugin) { $decoderPluginFound = true; - - break; } } - $this->assertEquals($enabled, $decoderPluginFound); + $this->assertSame($enabled, $encoderPluginFound); + $this->assertSame($enabled, $decoderPluginFound); } public function getClientTogglesCompressionPluginInHttpClientDataProvider(): array diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php new file mode 100644 index 000000000..81576ad52 --- /dev/null +++ b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php @@ -0,0 +1,49 @@ +createMock(PromiseInterface::class); + $request = MessageFactoryDiscovery::find()->createRequest( + 'POST', + 'http://www.local.host', + [], + 'foo' + ); + + $this->assertSame('foo', (string) $request->getBody()); + $this->assertSame( + $expectedPromise, + $plugin->handleRequest( + $request, + function (RequestInterface $requestArg) use (&$nextCallableCalled, $expectedPromise): PromiseInterface { + $nextCallableCalled = true; + + $this->assertSame('gzip', $requestArg->getHeaderLine('Content-Encoding')); + $this->assertSame(gzcompress('foo', -1, ZLIB_ENCODING_GZIP), (string) $requestArg->getBody()); + + return $expectedPromise; + }, + static function () {} + ) + ); + + $this->assertTrue($nextCallableCalled); + } +} From 62ee726400b33060e2a60a2a7d494fb0fa38608c Mon Sep 17 00:00:00 2001 From: Alexandru-Daniel Nedelcu Date: Wed, 31 Jul 2019 16:04:39 +0200 Subject: [PATCH 0479/1161] Fix error thrown when function name is missing in the stacktrace frame (#823) --- src/Stacktrace.php | 2 +- .../Fixtures/backtraces/anonymous_frame.json | 2 +- tests/Fixtures/backtraces/exception.json | 2 +- .../Fixtures/frames/missing_function_key.json | 5 +++++ tests/StacktraceTest.php | 21 ++++++++++++------- 5 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 tests/Fixtures/frames/missing_function_key.json diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 6e0c9c9e3..b1b2143be 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -115,7 +115,7 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void $line = (int) $matches[2]; } - if (isset($backtraceFrame['class'])) { + if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { $functionName = sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); } elseif (isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['function']; diff --git a/tests/Fixtures/backtraces/anonymous_frame.json b/tests/Fixtures/backtraces/anonymous_frame.json index 84eaa9898..2f450a5db 100644 --- a/tests/Fixtures/backtraces/anonymous_frame.json +++ b/tests/Fixtures/backtraces/anonymous_frame.json @@ -12,4 +12,4 @@ "function": "call_user_func" } ] -} \ No newline at end of file +} diff --git a/tests/Fixtures/backtraces/exception.json b/tests/Fixtures/backtraces/exception.json index 543157330..f371c73e5 100644 --- a/tests/Fixtures/backtraces/exception.json +++ b/tests/Fixtures/backtraces/exception.json @@ -15,4 +15,4 @@ "function": "crashyFunction" } ] -} \ No newline at end of file +} diff --git a/tests/Fixtures/frames/missing_function_key.json b/tests/Fixtures/frames/missing_function_key.json new file mode 100644 index 000000000..898b752e7 --- /dev/null +++ b/tests/Fixtures/frames/missing_function_key.json @@ -0,0 +1,5 @@ +{ + "file": "path/to/file", + "line": 12, + "class": "TestClass" +} diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 7c5bd2849..08222be4a 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -39,15 +39,17 @@ public function testGetFramesAndToArray(): void { $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); - $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); - $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); + $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'class' => 'TestClass']); + $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function']); + $stacktrace->addFrame('path/to/file', 3, ['file' => 'path/to/file', 'line' => 3, 'function' => 'test_function', 'class' => 'TestClass']); $frames = $stacktrace->getFrames(); - $this->assertCount(2, $frames); + $this->assertCount(3, $frames); $this->assertEquals($frames, $stacktrace->toArray()); - $this->assertFrameEquals($frames[0], 'TestClass::test_function', 'path/to/file', 2); - $this->assertFrameEquals($frames[1], 'test_function', 'path/to/file', 1); + $this->assertFrameEquals($frames[0], 'TestClass::test_function', 'path/to/file', 3); + $this->assertFrameEquals($frames[1], 'test_function', 'path/to/file', 2); + $this->assertFrameEquals($frames[2], null, 'path/to/file', 1); } public function testStacktraceJsonSerialization(): void @@ -56,6 +58,7 @@ public function testStacktraceJsonSerialization(): void $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); + $stacktrace->addFrame('path/to/file', 3, ['file' => 'path/to/file', 'line' => 3, 'class' => 'TestClass']); $frames = json_encode($stacktrace->getFrames()); $serializedStacktrace = json_encode($stacktrace); @@ -72,6 +75,7 @@ public function testAddFrame(): void $this->getJsonFixture('frames/eval.json'), $this->getJsonFixture('frames/runtime_created.json'), $this->getJsonFixture('frames/function.json'), + $this->getJsonFixture('frames/missing_function_key.json'), ]; foreach ($frames as $frame) { @@ -80,10 +84,11 @@ public function testAddFrame(): void $frames = $stacktrace->getFrames(); - $this->assertCount(3, $frames); - $this->assertFrameEquals($frames[0], 'TestClass::test_function', 'path/to/file', 12); - $this->assertFrameEquals($frames[1], 'test_function', 'path/to/file', 12); + $this->assertCount(4, $frames); + $this->assertFrameEquals($frames[0], null, 'path/to/file', 12); + $this->assertFrameEquals($frames[1], 'TestClass::test_function', 'path/to/file', 12); $this->assertFrameEquals($frames[2], 'test_function', 'path/to/file', 12); + $this->assertFrameEquals($frames[3], 'test_function', 'path/to/file', 12); } public function testAddFrameSerializesMethodArguments(): void From 75118eab8b59a2f1b3148c5c0a242fcef92ad523 Mon Sep 17 00:00:00 2001 From: Alfred Bez Date: Thu, 1 Aug 2019 08:15:39 +0200 Subject: [PATCH 0480/1161] Added OXID eShop to the list of 3rd party integrations (#860) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4b94e890b..6dd48bf03 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ The following integrations are available and maintained by members of the Sentry - [Drupal](https://www.drupal.org/project/raven) - [OpenCart](https://github.com/BurdaPraha/oc_sentry) - [TYPO3](https://github.com/networkteam/sentry_client) +- [OXID eShop](https://github.com/OXIDprojects/sentry) - ... feel free to be famous, create a port to your favourite platform! ## Community From 19e629dbc84ea496a865808cbd42f78e2a67420f Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 9 Aug 2019 15:45:52 +0200 Subject: [PATCH 0481/1161] Do not set the transaction attribute of the event when in CLI (#864) --- CHANGELOG.md | 1 + src/EventFactory.php | 10 ++-------- tests/ClientTest.php | 6 +++--- tests/EventFactoryTest.php | 3 +++ 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 715f0d5d3..962acae63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). - Fix sending of GZIP-compressed requests when the `enable_compression` option is `true` (#857) +- Fix error thrown when trying to set the `transaction` attribute of the event in a CLI environment (#862) ## 2.1.1 (2019-06-13) diff --git a/src/EventFactory.php b/src/EventFactory.php index f2cc17746..3aa892510 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -7,7 +7,6 @@ use Sentry\Exception\EventCreationException; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\SerializerInterface; -use Zend\Diactoros\ServerRequestFactory; /** * Factory for the {@see Event} class. @@ -93,13 +92,8 @@ public function create(array $payload): Event if (isset($payload['transaction'])) { $event->setTransaction($payload['transaction']); - } else { - $request = ServerRequestFactory::fromGlobals(); - $serverParams = $request->getServerParams(); - - if (isset($serverParams['PATH_INFO'])) { - $event->setTransaction($serverParams['PATH_INFO']); - } + } elseif (isset($_SERVER['PATH_INFO'])) { + $event->setTransaction($_SERVER['PATH_INFO']); } if (isset($payload['logger'])) { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 8c11b8b79..45de93afe 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -21,6 +21,9 @@ class ClientTest extends TestCase { + /** + * @backupGlobals + */ public function testTransactionEventAttributeIsPopulated(): void { $_SERVER['PATH_INFO'] = '/foo'; @@ -39,9 +42,6 @@ public function testTransactionEventAttributeIsPopulated(): void $client = new Client(new Options(), $transport, $this->createEventFactory()); $client->captureMessage('test'); - - unset($_SERVER['PATH_INFO']); - unset($_SERVER['REQUEST_METHOD']); } public function testTransactionEventAttributeIsNotPopulatedInCli(): void diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index 69525a7e3..8c4d09d0a 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -16,6 +16,9 @@ class EventFactoryTest extends TestCase { + /** + * @backupGlobals + */ public function testCreateEventWithDefaultValues(): void { $options = new Options(); From 78495dd5cd68b1a01a864c1e4019556ed7a3962e Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 21 Aug 2019 11:50:33 +0200 Subject: [PATCH 0482/1161] Skip integrations not bound to the current client and fetch their options from it (#861) --- CHANGELOG.md | 1 + src/ClientBuilder.php | 6 +-- src/Integration/ErrorListenerIntegration.php | 34 ++++++++++---- .../ExceptionListenerIntegration.php | 11 ++++- .../FatalErrorListenerIntegration.php | 26 ++++++++--- src/Integration/ModulesIntegration.php | 8 ++-- src/Integration/RequestIntegration.php | 44 ++++++++++++++----- src/State/Hub.php | 1 + tests/Integration/RequestIntegrationTest.php | 26 +++++++++-- 9 files changed, 120 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 962acae63..e36d53736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). - Fix sending of GZIP-compressed requests when the `enable_compression` option is `true` (#857) - Fix error thrown when trying to set the `transaction` attribute of the event in a CLI environment (#862) +- Fix integrations that were not skipped if the client bound to the current hub was not using them (#861) ## 2.1.1 (2019-06-13) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index a9b64dce5..c53bb458e 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -104,9 +104,9 @@ public function __construct(Options $options = null) if ($this->options->hasDefaultIntegrations()) { $this->options->setIntegrations(array_merge([ new ExceptionListenerIntegration(), - new ErrorListenerIntegration($this->options, false), - new FatalErrorListenerIntegration($this->options), - new RequestIntegration($this->options), + new ErrorListenerIntegration(null, false), + new FatalErrorListenerIntegration(), + new RequestIntegration(), ], $this->options->getIntegrations())); } } diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index e1efeb445..d4ff60e61 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -17,7 +17,7 @@ final class ErrorListenerIntegration implements IntegrationInterface { /** - * @var Options The options, to know which error level to use + * @var Options|null The options, to know which error level to use */ private $options; @@ -29,17 +29,21 @@ final class ErrorListenerIntegration implements IntegrationInterface /** * Constructor. * - * @param Options $options The options to be used with this integration - * @param bool $handleFatalErrors Whether to handle fatal errors or not + * @param Options|null $options The options to be used with this integration + * @param bool $handleFatalErrors Whether to handle fatal errors or not */ - public function __construct(Options $options, bool $handleFatalErrors = true) + public function __construct(?Options $options = null, bool $handleFatalErrors = true) { - $this->options = $options; - $this->handleFatalErrors = $handleFatalErrors; + if (null !== $options) { + @trigger_error(sprintf('Passing the options as argument of the constructor of the "%s" class is deprecated since version 2.1 and will not work in 3.0.', self::class), E_USER_DEPRECATED); + } if ($handleFatalErrors) { @trigger_error(sprintf('Handling fatal errors with the "%s" class is deprecated since version 2.1. Use the "%s" integration instead.', self::class, FatalErrorListenerIntegration::class), E_USER_DEPRECATED); } + + $this->options = $options; + $this->handleFatalErrors = $handleFatalErrors; } /** @@ -53,15 +57,27 @@ public function setupOnce(): void return; } - if ($exception instanceof SilencedErrorException && !$this->options->shouldCaptureSilencedErrors()) { + $currentHub = Hub::getCurrent(); + $integration = $currentHub->getIntegration(self::class); + $client = $currentHub->getClient(); + + // The client bound to the current hub, if any, could not have this + // integration enabled. If this is the case, bail out + if (null === $integration || null === $client) { + return; + } + + $options = $this->options ?? $client->getOptions(); + + if ($exception instanceof SilencedErrorException && !$options->shouldCaptureSilencedErrors()) { return; } - if (!($this->options->getErrorTypes() & $exception->getSeverity())) { + if (!($options->getErrorTypes() & $exception->getSeverity())) { return; } - Hub::getCurrent()->captureException($exception); + $currentHub->captureException($exception); }); } } diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index 119b19459..678b92918 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -20,7 +20,16 @@ public function setupOnce(): void { $errorHandler = ErrorHandler::registerOnce(ErrorHandler::DEFAULT_RESERVED_MEMORY_SIZE, false); $errorHandler->addExceptionHandlerListener(static function (\Throwable $exception): void { - Hub::getCurrent()->captureException($exception); + $currentHub = Hub::getCurrent(); + $integration = $currentHub->getIntegration(self::class); + + // The client bound to the current hub, if any, could not have this + // integration enabled. If this is the case, bail out + if (null === $integration) { + return; + } + + $currentHub->captureException($exception); }); } } diff --git a/src/Integration/FatalErrorListenerIntegration.php b/src/Integration/FatalErrorListenerIntegration.php index 85487696f..b528dd366 100644 --- a/src/Integration/FatalErrorListenerIntegration.php +++ b/src/Integration/FatalErrorListenerIntegration.php @@ -17,17 +17,21 @@ final class FatalErrorListenerIntegration implements IntegrationInterface { /** - * @var Options The options, to know which error level to use + * @var Options|null The options, to know which error level to use */ private $options; /** * Constructor. * - * @param Options $options The options to be used with this integration + * @param Options|null $options The options to be used with this integration */ - public function __construct(Options $options) + public function __construct(?Options $options = null) { + if (null !== $options) { + @trigger_error(sprintf('Passing the options as argument of the constructor of the "%s" class is deprecated since version 2.1 and will not work in 3.0.', self::class), E_USER_DEPRECATED); + } + $this->options = $options; } @@ -38,11 +42,23 @@ public function setupOnce(): void { $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); $errorHandler->addFatalErrorHandlerListener(function (FatalErrorException $exception): void { - if (!($this->options->getErrorTypes() & $exception->getSeverity())) { + $currentHub = Hub::getCurrent(); + $integration = $currentHub->getIntegration(self::class); + $client = $currentHub->getClient(); + + // The client bound to the current hub, if any, could not have this + // integration enabled. If this is the case, bail out + if (null === $integration || null === $client) { + return; + } + + $options = $this->options ?? $client->getOptions(); + + if (!($options->getErrorTypes() & $exception->getSeverity())) { return; } - Hub::getCurrent()->captureException($exception); + $currentHub->captureException($exception); }); } } diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index a49ab7647..03ca3232c 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -27,10 +27,12 @@ final class ModulesIntegration implements IntegrationInterface public function setupOnce(): void { Scope::addGlobalEventProcessor(function (Event $event) { - $self = Hub::getCurrent()->getIntegration(self::class); + $integration = Hub::getCurrent()->getIntegration(self::class); - if ($self instanceof self) { - self::applyToEvent($self, $event); + // The integration could be bound to a client that is not the one + // attached to the current hub. If this is the case, bail out + if ($integration instanceof self) { + self::applyToEvent($integration, $event); } return $event; diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 67897a48b..36bb5a276 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -37,7 +37,7 @@ final class RequestIntegration implements IntegrationInterface private const REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH = 10 ** 4; /** - * @var Options The client options + * @var Options|null The client options */ private $options; @@ -46,8 +46,12 @@ final class RequestIntegration implements IntegrationInterface * * @param Options $options The client options */ - public function __construct(Options $options) + public function __construct(?Options $options = null) { + if (null !== $options) { + @trigger_error(sprintf('Passing the options as argument of the constructor of the "%s" class is deprecated since version 2.1 and will not work in 3.0.', self::class), E_USER_DEPRECATED); + } + $this->options = $options; } @@ -57,13 +61,17 @@ public function __construct(Options $options) public function setupOnce(): void { Scope::addGlobalEventProcessor(function (Event $event): Event { - $self = Hub::getCurrent()->getIntegration(self::class); + $currentHub = Hub::getCurrent(); + $integration = $currentHub->getIntegration(self::class); + $client = $currentHub->getClient(); - if (!$self instanceof self) { + // The client bound to the current hub, if any, could not have this + // integration enabled. If this is the case, bail out + if (null === $integration || null === $client) { return $event; } - self::applyToEvent($self, $event); + $this->processEvent($event, $this->options ?? $client->getOptions()); return $event; }); @@ -77,6 +85,17 @@ public function setupOnce(): void * @param ServerRequestInterface|null $request The Request that will be processed and added to the event */ public static function applyToEvent(self $self, Event $event, ?ServerRequestInterface $request = null): void + { + @trigger_error(sprintf('The "%s" method is deprecated since version 2.1 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + + if (null === $self->options) { + throw new \BadMethodCallException('The options of the integration must be set.'); + } + + $self->processEvent($event, $self->options, $request); + } + + private function processEvent(Event $event, Options $options, ?ServerRequestInterface $request = null): void { if (null === $request) { $request = isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null; @@ -95,7 +114,7 @@ public static function applyToEvent(self $self, Event $event, ?ServerRequestInte $requestData['query_string'] = $request->getUri()->getQuery(); } - if ($self->options->shouldSendDefaultPii()) { + if ($options->shouldSendDefaultPii()) { $serverParams = $request->getServerParams(); if (isset($serverParams['REMOTE_ADDR'])) { @@ -111,10 +130,10 @@ public static function applyToEvent(self $self, Event $event, ?ServerRequestInte $userContext->setIpAddress($serverParams['REMOTE_ADDR']); } } else { - $requestData['headers'] = $self->removePiiFromHeaders($request->getHeaders()); + $requestData['headers'] = $this->removePiiFromHeaders($request->getHeaders()); } - $requestBody = $self->captureRequestBody($request); + $requestBody = $this->captureRequestBody($options, $request); if (!empty($requestBody)) { $requestData['data'] = $requestBody; @@ -126,9 +145,9 @@ public static function applyToEvent(self $self, Event $event, ?ServerRequestInte /** * Removes headers containing potential PII. * - * @param array $headers Array containing request headers + * @param array> $headers Array containing request headers * - * @return array + * @return array> */ private function removePiiFromHeaders(array $headers): array { @@ -149,13 +168,14 @@ static function (string $key) use ($keysToRemove): bool { * the parsing fails then the raw data is returned. If there are submitted * fields or files, all of their information are parsed and returned. * + * @param Options $options The options of the client * @param ServerRequestInterface $serverRequest The server request * * @return mixed */ - private function captureRequestBody(ServerRequestInterface $serverRequest) + private function captureRequestBody(Options $options, ServerRequestInterface $serverRequest) { - $maxRequestBodySize = $this->options->getMaxRequestBodySize(); + $maxRequestBodySize = $options->getMaxRequestBodySize(); $requestBody = $serverRequest->getBody(); if ( diff --git a/src/State/Hub.php b/src/State/Hub.php index a37cb5f3a..f11c7279b 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -227,6 +227,7 @@ public static function setCurrent(HubInterface $hub): HubInterface public function getIntegration(string $className): ?IntegrationInterface { $client = $this->getClient(); + if (null !== $client) { return $client->getIntegration($className); } diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index f462827b1..7eec01f25 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -18,7 +18,21 @@ final class RequestIntegrationTest extends TestCase { /** + * @group legacy + * + * @expectedDeprecation Passing the options as argument of the constructor of the "Sentry\Integration\RequestIntegration" class is deprecated since version 2.1 and will not work in 3.0. + */ + public function testConstructorThrowsDeprecationIfPassingOptions(): void + { + new RequestIntegration(new Options([])); + } + + /** + * @group legacy + * * @dataProvider applyToEventWithRequestHavingIpAddressDataProvider + * + * @expectedDeprecation The "Sentry\Integration\RequestIntegration::applyToEvent" method is deprecated since version 2.1 and will be removed in 3.0. */ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $expectedValue): void { @@ -26,9 +40,6 @@ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $event->getUserContext()->setData(['foo' => 'bar']); $request = new ServerRequest(['REMOTE_ADDR' => '127.0.0.1']); - - $this->assertInstanceOf(ServerRequestInterface::class, $request); - $integration = new RequestIntegration(new Options(['send_default_pii' => $shouldSendPii])); RequestIntegration::applyToEvent($integration, $event, $request); @@ -41,7 +52,10 @@ public function applyToEventWithRequestHavingIpAddressDataProvider(): array return [ [ true, - ['ip_address' => '127.0.0.1', 'foo' => 'bar'], + [ + 'ip_address' => '127.0.0.1', + 'foo' => 'bar', + ], ], [ false, @@ -51,7 +65,11 @@ public function applyToEventWithRequestHavingIpAddressDataProvider(): array } /** + * @group legacy + * * @dataProvider applyToEventDataProvider + * + * @expectedDeprecation The "Sentry\Integration\RequestIntegration::applyToEvent" method is deprecated since version 2.1 and will be removed in 3.0. */ public function testApplyToEvent(array $options, ServerRequestInterface $request, array $expectedResult): void { From 5dfc0edb215fa1af5ecff698131a2fb501ac6dc2 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 21 Aug 2019 14:30:15 +0200 Subject: [PATCH 0483/1161] Implement support for flushable clients (#813) --- CHANGELOG.md | 1 + composer.json | 1 + src/Client.php | 37 ++++-- src/FlushableClientInterface.php | 26 ++++ src/Transport/ClosableTransportInterface.php | 25 ++++ src/Transport/HttpTransport.php | 70 +++++------ tests/ClientBuilderTest.php | 27 +++-- tests/ClientTest.php | 5 +- tests/Transport/HttpTransportTest.php | 120 +++---------------- 9 files changed, 155 insertions(+), 157 deletions(-) create mode 100644 src/FlushableClientInterface.php create mode 100644 src/Transport/ClosableTransportInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index aacbf7ec7..87cfc2321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Changed type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) - Add the `setTags`, `setExtras` and `clearBreadcrumbs` methods to the `Scope` class (#852) - Silently cast numeric values to strings when trying to set the tags instead of throwing (#858) +- Support force sending events on-demand and fix sending of events in long-running processes (#813) ## 2.1.1 (2019-06-13) diff --git a/composer.json b/composer.json index 2ef7d271e..f5a2ce752 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "php": "^7.1", "ext-json": "*", "ext-mbstring": "*", + "guzzlehttp/promises": "^1.3", "jean85/pretty-package-versions": "^1.2", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", diff --git a/src/Client.php b/src/Client.php index bcb3ac9ab..5a125e33a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -4,9 +4,12 @@ namespace Sentry; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\Integration\Handler; use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; +use Sentry\Transport\ClosableTransportInterface; use Sentry\Transport\TransportInterface; /** @@ -14,7 +17,7 @@ * * @author Stefano Arlandini */ -final class Client implements ClientInterface +final class Client implements FlushableClientInterface { /** * The version of the protocol to communicate with the Sentry server. @@ -79,11 +82,13 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope 'level' => $level, ]; - if ($event = $this->prepareEvent($payload, $scope, $this->options->shouldAttachStacktrace())) { - return $this->transport->send($event); + $event = $this->prepareEvent($payload, $scope, $this->options->shouldAttachStacktrace()); + + if (null === $event) { + return null; } - return null; + return $this->transport->send($event); } /** @@ -95,9 +100,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? return null; } - $payload['exception'] = $exception; - - return $this->captureEvent($payload, $scope); + return $this->captureEvent(['exception' => $exception], $scope); } /** @@ -105,11 +108,13 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? */ public function captureEvent(array $payload, ?Scope $scope = null): ?string { - if ($event = $this->prepareEvent($payload, $scope)) { - return $this->transport->send($event); + $event = $this->prepareEvent($payload, $scope); + + if (null === $event) { + return null; } - return null; + return $this->transport->send($event); } /** @@ -136,6 +141,18 @@ public function getIntegration(string $className): ?IntegrationInterface return $this->integrations[$className] ?? null; } + /** + * {@inheritdoc} + */ + public function flush(?int $timeout = null): PromiseInterface + { + if (!$this->transport instanceof ClosableTransportInterface) { + return new FulfilledPromise(true); + } + + return $this->transport->close($timeout); + } + /** * Assembles an event and prepares it to be sent of to Sentry. * diff --git a/src/FlushableClientInterface.php b/src/FlushableClientInterface.php new file mode 100644 index 000000000..d5d2ee442 --- /dev/null +++ b/src/FlushableClientInterface.php @@ -0,0 +1,26 @@ + + */ +interface FlushableClientInterface extends ClientInterface +{ + /** + * Flushes the queue of events pending to be sent. If a timeout is provided + * and the queue takes longer to drain, the promise resolves with `false`. + * + * @param int|null $timeout Maximum time in seconds the client should wait + * + * @return PromiseInterface + */ + public function flush(?int $timeout = null): PromiseInterface; +} diff --git a/src/Transport/ClosableTransportInterface.php b/src/Transport/ClosableTransportInterface.php new file mode 100644 index 000000000..61ee025ed --- /dev/null +++ b/src/Transport/ClosableTransportInterface.php @@ -0,0 +1,25 @@ + + */ +interface ClosableTransportInterface +{ + /** + * Waits until all pending requests have been sent or the timeout expires. + * + * @param int|null $timeout Maximum time in seconds before the sending + * operation is interrupted + * + * @return PromiseInterface + */ + public function close(?int $timeout = null): PromiseInterface; +} diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 739b77351..c4da01013 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -4,15 +4,16 @@ namespace Sentry\Transport; -use Http\Client\HttpAsyncClient; -use Http\Message\RequestFactory; -use Http\Promise\Promise; +use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; +use Http\Message\RequestFactory as RequestFactoryInterface; +use Http\Promise\Promise as PromiseInterface; use Sentry\Event; use Sentry\Options; use Sentry\Util\JSON; /** - * This transport sends the events using an HTTP client. + * This transport sends the events using a syncronous HTTP client that will + * delay sending of the requests until the shutdown of the application. * * @author Stefano Arlandini */ @@ -24,32 +25,47 @@ final class HttpTransport implements TransportInterface private $config; /** - * @var HttpAsyncClient The HTTP client + * @var HttpAsyncClientInterface The HTTP client */ private $httpClient; /** - * @var RequestFactory The PSR-7 request factory + * @var RequestFactoryInterface The PSR-7 request factory */ private $requestFactory; /** - * @var Promise[] The list of pending requests + * @var PromiseInterface[] The list of pending promises */ private $pendingRequests = []; + /** + * @var bool Flag indicating whether the sending of the events should be + * delayed until the shutdown of the application + */ + private $delaySendingUntilShutdown = false; + /** * Constructor. * - * @param Options $config The Raven client configuration - * @param HttpAsyncClient $httpClient The HTTP client - * @param RequestFactory $requestFactory The PSR-7 request factory + * @param Options $config The Raven client configuration + * @param HttpAsyncClientInterface $httpClient The HTTP client + * @param RequestFactoryInterface $requestFactory The PSR-7 request factory + * @param bool $delaySendingUntilShutdown This flag controls whether to delay + * sending of the events until the shutdown + * of the application. This is a legacy feature + * that will stop working in version 3.0. */ - public function __construct(Options $config, HttpAsyncClient $httpClient, RequestFactory $requestFactory) + public function __construct(Options $config, HttpAsyncClientInterface $httpClient, RequestFactoryInterface $requestFactory, bool $delaySendingUntilShutdown = true) { + if ($delaySendingUntilShutdown) { + @trigger_error(sprintf('Delaying the sending of the events using the "%s" class is deprecated since version 2.2 and will not work in 3.0.', __CLASS__), E_USER_DEPRECATED); + } + $this->config = $config; $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; + $this->delaySendingUntilShutdown = $delaySendingUntilShutdown; // By calling the cleanupPendingRequests function from a shutdown function // registered inside another shutdown function we can be confident that it @@ -80,37 +96,23 @@ public function send(Event $event): ?string $promise = $this->httpClient->sendAsyncRequest($request); - // This function is defined in-line so it doesn't show up for type-hinting - $cleanupPromiseCallback = function ($responseOrException) use ($promise) { - $index = array_search($promise, $this->pendingRequests, true); - - if (false !== $index) { - unset($this->pendingRequests[$index]); - } - - return $responseOrException; - }; - - $promise->then($cleanupPromiseCallback, $cleanupPromiseCallback); - - $this->pendingRequests[] = $promise; + if ($this->delaySendingUntilShutdown) { + $this->pendingRequests[] = $promise; + } else { + $promise->wait(false); + } return $event->getId(); } /** - * Cleanups the pending requests by forcing them to be sent. Any error that - * occurs will be ignored. + * Cleanups the pending promises by awaiting for them. Any error that occurs + * will be ignored. */ private function cleanupPendingRequests(): void { - foreach ($this->pendingRequests as $pendingRequest) { - try { - $pendingRequest->wait(); - } catch (\Throwable $exception) { - // Do nothing because an exception thrown from a destructor - // can't be catched in PHP (see http://php.net/manual/en/language.oop5.decon.php#language.oop5.decon.destructor) - } + while ($promise = array_pop($this->pendingRequests)) { + $promise->wait(false); } } } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 6292d50b2..f51ff466d 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -29,13 +29,10 @@ final class ClientBuilderTest extends TestCase { - public function testCreate(): void - { - $clientBuilder = ClientBuilder::create(); - - $this->assertInstanceOf(ClientBuilder::class, $clientBuilder); - } - + /** + * @group legacy + * @expectedDeprecation Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ public function testHttpTransportIsUsedWhenServeIsConfigured(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -65,6 +62,10 @@ public function testSetUriFactory(): void $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); } + /** + * @group legacy + * @expectedDeprecation Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ public function testSetMessageFactory(): void { /** @var MessageFactory|MockObject $messageFactory */ @@ -92,6 +93,10 @@ public function testSetTransport(): void $this->assertAttributeSame($transport, 'transport', $clientBuilder->getClient()); } + /** + * @group legacy + * @expectedDeprecation Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ public function testSetHttpClient(): void { /** @var HttpAsyncClient|MockObject $httpClient */ @@ -141,6 +146,10 @@ public function testRemoveHttpClientPlugin(): void $this->assertSame($plugin2, reset($plugins)); } + /** + * @group legacy + * @expectedDeprecation Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ public function testGetClient(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -258,11 +267,15 @@ public function testClientBuilderSetsSdkIdentifierAndVersion(): void /** * @dataProvider getClientTogglesCompressionPluginInHttpClientDataProvider + * + * @group legacy + * @expectedDeprecation Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. */ public function testGetClientTogglesCompressionPluginInHttpClient(bool $enabled): void { $options = new Options(['dsn' => 'http://public:secret@example.com/sentry/1']); $options->setEnableCompression($enabled); + $builder = new ClientBuilder($options); $builder->getClient(); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 8c11b8b79..642ec39cf 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -4,6 +4,7 @@ namespace Sentry\Tests; +use Http\Discovery\MessageFactoryDiscovery; use Http\Mock\Client as MockClient; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -17,6 +18,7 @@ use Sentry\Serializer\SerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; +use Sentry\Transport\HttpTransport; use Sentry\Transport\TransportInterface; class ClientTest extends TestCase @@ -268,12 +270,11 @@ public function testSendChecksBeforeSendOption(): void public function testSampleRateAbsolute(float $sampleRate): void { $httpClient = new MockClient(); - $options = new Options(['dsn' => 'http://public:secret@example.com/1']); $options->setSampleRate($sampleRate); $client = (new ClientBuilder($options)) - ->setHttpClient($httpClient) + ->setTransport(new HttpTransport($options, $httpClient, MessageFactoryDiscovery::find(), false)) ->getClient(); for ($i = 0; $i < 10; ++$i) { diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 203bbb56d..b42d17ea0 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -4,10 +4,10 @@ namespace Sentry\Tests\Transport; -use Http\Client\Exception\HttpException; use Http\Client\HttpAsyncClient; use Http\Discovery\MessageFactoryDiscovery; -use Http\Promise\Promise; +use Http\Promise\FulfilledPromise; +use Http\Promise\RejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\Event; @@ -16,12 +16,14 @@ final class HttpTransportTest extends TestCase { - public function testDestructor(): void + /** + * @group legacy + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ + public function testSendDelaysExecutionUntilShutdown(): void { - /** @var Promise|MockObject $promise1 */ - $promise = $this->createMock(Promise::class); - $promise->expects($this->once()) - ->method('wait'); + $promise = new FulfilledPromise('foo'); /** @var HttpAsyncClient|MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); @@ -29,43 +31,11 @@ public function testDestructor(): void ->method('sendAsyncRequest') ->willReturn($promise); - $config = new Options(); - $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); - - $transport->send(new Event()); - - // In PHP calling the destructor manually does not destroy the object, - // but for testing we will do it anyway because otherwise we could not - // test the cleanup code of the class if not all references to its - // instance are released - $transport->__destruct(); - } - - public function testCleanupPendingRequests(): void - { - /** @var Promise|MockObject $promise1 */ - $promise1 = $this->createMock(Promise::class); - $promise1->expects($this->once()) - ->method('wait') - ->willThrowException(new \Exception()); - - /** @var Promise|MockObject $promise2 */ - $promise2 = $this->createMock(Promise::class); - $promise2->expects($this->once()) - ->method('wait'); - - /** @var HttpAsyncClient|MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); - $httpClient->expects($this->exactly(2)) - ->method('sendAsyncRequest') - ->willReturnOnConsecutiveCalls($promise1, $promise2); - - $config = new Options(); + $config = new Options(['dsn' => 'http://public@example.com/sentry/1']); $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); $this->assertAttributeEmpty('pendingRequests', $transport); - $transport->send(new Event()); $transport->send(new Event()); $this->assertAttributeNotEmpty('pendingRequests', $transport); @@ -74,14 +44,13 @@ public function testCleanupPendingRequests(): void $reflectionMethod->setAccessible(true); $reflectionMethod->invoke($transport); $reflectionMethod->setAccessible(false); + + $this->assertAttributeEmpty('pendingRequests', $transport); } - public function testSendFailureCleanupPendingRequests(): void + public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoIt(): void { - /** @var HttpException|MockObject $exception */ - $exception = $this->createMock(HttpException::class); - - $promise = new PromiseMock($exception, PromiseMock::REJECTED); + $promise = new RejectedPromise(new \Exception()); /** @var HttpAsyncClient|MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); @@ -89,68 +58,11 @@ public function testSendFailureCleanupPendingRequests(): void ->method('sendAsyncRequest') ->willReturn($promise); - $config = new Options(); - $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); + $config = new Options(['dsn' => 'http://public@example.com/sentry/1']); + $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find(), false); $transport->send(new Event()); - $this->assertAttributeNotEmpty('pendingRequests', $transport); - $this->assertSame($exception, $promise->wait(true)); $this->assertAttributeEmpty('pendingRequests', $transport); } } - -final class PromiseMock implements Promise -{ - private $result; - - private $state; - - private $onFullfilledCallbacks = []; - - private $onRejectedCallbacks = []; - - public function __construct($result, $state = self::FULFILLED) - { - $this->result = $result; - $this->state = $state; - } - - public function then(callable $onFulfilled = null, callable $onRejected = null) - { - if (null !== $onFulfilled) { - $this->onFullfilledCallbacks[] = $onFulfilled; - } - - if (null !== $onRejected) { - $this->onRejectedCallbacks[] = $onRejected; - } - - return $this; - } - - public function getState() - { - return $this->state; - } - - public function wait($unwrap = true) - { - switch ($this->state) { - case self::FULFILLED: - foreach ($this->onFullfilledCallbacks as $onFullfilledCallback) { - $this->result = $onFullfilledCallback($this->result); - } - - break; - case self::REJECTED: - foreach ($this->onRejectedCallbacks as $onRejectedCallback) { - $this->result = $onRejectedCallback($this->result); - } - - break; - } - - return $unwrap ? $this->result : null; - } -} From ac6af863d195a90ee711a83fd2546940c0698654 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 22 Aug 2019 09:19:00 +0200 Subject: [PATCH 0484/1161] meta: Changelog 2.1.2 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e36d53736..163e2bceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.1.2 (2019-08-22) + - Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). - Fix sending of GZIP-compressed requests when the `enable_compression` option is `true` (#857) - Fix error thrown when trying to set the `transaction` attribute of the event in a CLI environment (#862) From 646f6ada8b89a08063e31f54ed6d260bd6879239 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 22 Aug 2019 09:37:30 +0200 Subject: [PATCH 0485/1161] meta: Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 163e2bceb..838276db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Fix sending of GZIP-compressed requests when the `enable_compression` option is `true` (#857) - Fix error thrown when trying to set the `transaction` attribute of the event in a CLI environment (#862) - Fix integrations that were not skipped if the client bound to the current hub was not using them (#861) +- Fix undefined index generated by missing function in class (#823) ## 2.1.1 (2019-06-13) From ece712dc239e3348e1c01e3c2790194951d86645 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 31 Aug 2019 00:23:22 +0100 Subject: [PATCH 0486/1161] Update PHPStan and support Psalm (#846) --- .php_cs | 1 + .travis.yml | 7 +- CHANGELOG.md | 3 +- README.md | 2 +- composer.json | 15 ++- phpstan.neon | 11 +- psalm.xml.dist | 68 ++++++++++++ src/ClientBuilder.php | 55 ++++------ src/ClientBuilderInterface.php | 18 ++-- src/Context/Context.php | 10 ++ src/ErrorHandler.php | 28 ++++- src/Event.php | 100 +++++++++++++----- src/EventFactory.php | 23 ++-- .../MissingProjectIdCredentialException.php | 19 ++++ .../MissingPublicKeyCredentialException.php | 19 ++++ .../Authentication/SentryAuthentication.php | 16 ++- src/Integration/ErrorListenerIntegration.php | 1 + .../ExceptionListenerIntegration.php | 1 + src/Integration/Handler.php | 3 + src/Options.php | 61 ++++++++--- src/Serializer/AbstractSerializer.php | 45 ++++---- src/Serializer/RepresentationSerializer.php | 14 ++- .../RepresentationSerializerInterface.php | 2 +- src/Stacktrace.php | 11 +- src/State/Hub.php | 2 +- src/Transport/HttpTransport.php | 9 +- src/Transport/TransportInterface.php | 3 + tests/ClientBuilderTest.php | 26 ++++- ...issingProjectIdCredentialExceptionTest.php | 18 ++++ ...issingPublicKeyCredentialExceptionTest.php | 18 ++++ .../SentryAuthenticationTest.php | 13 +++ tests/Transport/HttpTransportTest.php | 12 +++ 32 files changed, 491 insertions(+), 143 deletions(-) create mode 100644 psalm.xml.dist create mode 100644 src/Exception/MissingProjectIdCredentialException.php create mode 100644 src/Exception/MissingPublicKeyCredentialException.php create mode 100644 tests/Exception/MissingProjectIdCredentialExceptionTest.php create mode 100644 tests/Exception/MissingPublicKeyCredentialExceptionTest.php diff --git a/.php_cs b/.php_cs index 04238d23c..32bd22813 100644 --- a/.php_cs +++ b/.php_cs @@ -15,6 +15,7 @@ return PhpCsFixer\Config::create() 'yoda_style' => true, 'self_accessor' => false, 'phpdoc_no_useless_inheritdoc' => false, + 'phpdoc_to_comment' => false, 'phpdoc_align' => [ 'tags' => ['param', 'return', 'throws', 'type', 'var'], ], diff --git a/.travis.yml b/.travis.yml index 9ff1927a7..4e886d7ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,9 +36,12 @@ jobs: name: PHP CS Fixer env: dependencies=highest script: composer phpcs - - script: composer phpstan + - name: PHPStan env: dependencies=highest - name: PHPStan + script: composer phpstan + - name: Psalm + env: dependencies=highest + script: composer psalm - stage: Code coverage php: 7.3 env: dependencies=highest diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e0f9dc94..6cf518340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ ## Unreleased -- Changed type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) +- Change type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) - Add the `setTags`, `setExtras` and `clearBreadcrumbs` methods to the `Scope` class (#852) - Silently cast numeric values to strings when trying to set the tags instead of throwing (#858) - Support force sending events on-demand and fix sending of events in long-running processes (#813) +- Update PHPStan and introduce Psalm (#846) ## 2.1.2 (2019-08-22) diff --git a/README.md b/README.md index 6dd48bf03..b1c7bf061 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # Sentry for PHP -[![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master)](http://travis-ci.org/getsentry/sentry-php) +[![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=develop)](http://travis-ci.org/getsentry/sentry-php) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/getsentry/sentry-php?branch=master&svg=true)](https://ci.appveyor.com/project/sentry/sentry-php) [![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) [![Monthly Downloads](https://poser.pugx.org/sentry/sentry/d/monthly)](https://packagist.org/packages/sentry/sentry) diff --git a/composer.json b/composer.json index 471f955db..efcac7dab 100644 --- a/composer.json +++ b/composer.json @@ -31,17 +31,21 @@ "friendsofphp/php-cs-fixer": "^2.13", "monolog/monolog": "^1.3|^2.0", "php-http/mock-client": "^1.0", - "phpstan/phpstan-phpunit": "^0.10", - "phpstan/phpstan": "^0.10.3", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", "phpunit/phpunit": "^7.0", - "symfony/phpunit-bridge": "^4.3" + "symfony/phpunit-bridge": "^4.3", + "vimeo/psalm": "^3.4" }, "conflict": { "php-http/client-common": "1.8.0", "raven/raven": "*" }, "autoload": { - "files": ["src/Sdk.php"], + "files": [ + "src/Sdk.php" + ], "psr-4" : { "Sentry\\" : "src/" } @@ -63,6 +67,9 @@ ], "phpstan": [ "vendor/bin/phpstan analyse" + ], + "psalm": [ + "vendor/bin/psalm --config=psalm.xml.dist" ] }, "config": { diff --git a/phpstan.neon b/phpstan.neon index 7c8caba84..317616aab 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,16 +1,19 @@ parameters: + tipsOfTheDay: false level: 7 paths: - src - tests ignoreErrors: - - '/Method Sentry\\ClientBuilder::\w+\(\) should return \$this\(Sentry\\ClientBuilderInterface\) but returns \$this\(Sentry\\ClientBuilder\)/' - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' - - '/Method Sentry\\Serializer\\RepresentationSerializer::(representationSerialize|serializeValue)\(\) should return [\w|]+ but returns [\w|]+/' - '/Http\\Client\\Curl\\Client/' + - + message: /^Cannot assign offset 'os' to array\|string\.$/ + path: src/Event.php + - + message: "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" + path: src/Transport/HttpTransport.php excludes_analyse: - tests/resources - tests/Fixtures -includes: - - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/psalm.xml.dist b/psalm.xml.dist new file mode 100644 index 000000000..34d01006d --- /dev/null +++ b/psalm.xml.dist @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index c53bb458e..f909ced46 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,7 +4,7 @@ namespace Sentry; -use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin as PluginInterface; use Http\Client\Common\Plugin\AuthenticationPlugin; use Http\Client\Common\Plugin\BaseUriPlugin; use Http\Client\Common\Plugin\DecoderPlugin; @@ -18,8 +18,8 @@ use Http\Discovery\HttpAsyncClientDiscovery; use Http\Discovery\MessageFactoryDiscovery; use Http\Discovery\UriFactoryDiscovery; -use Http\Message\MessageFactory; -use Http\Message\UriFactory; +use Http\Message\MessageFactory as MessageFactoryInterface; +use Http\Message\UriFactory as UriFactoryInterface; use Jean85\PrettyVersions; use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\HttpClient\Plugin\GzipEncoderPlugin; @@ -48,17 +48,17 @@ final class ClientBuilder implements ClientBuilderInterface private $options; /** - * @var UriFactory|null The PSR-7 URI factory + * @var UriFactoryInterface|null The PSR-7 URI factory */ private $uriFactory; /** - * @var MessageFactory|null The PSR-7 message factory + * @var MessageFactoryInterface|null The PSR-7 message factory */ private $messageFactory; /** - * @var TransportInterface The transport + * @var TransportInterface|null The transport */ private $transport; @@ -68,17 +68,17 @@ final class ClientBuilder implements ClientBuilderInterface private $httpClient; /** - * @var Plugin[] The list of Httplug plugins + * @var PluginInterface[] The list of Httplug plugins */ private $httpClientPlugins = []; /** - * @var SerializerInterface The serializer to be injected in the client + * @var SerializerInterface|null The serializer to be injected in the client */ private $serializer; /** - * @var RepresentationSerializerInterface The representation serializer to be injected in the client + * @var RepresentationSerializerInterface|null The representation serializer to be injected in the client */ private $representationSerializer; @@ -100,6 +100,7 @@ final class ClientBuilder implements ClientBuilderInterface public function __construct(Options $options = null) { $this->options = $options ?? new Options(); + $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); if ($this->options->hasDefaultIntegrations()) { $this->options->setIntegrations(array_merge([ @@ -130,7 +131,7 @@ public function getOptions(): Options /** * {@inheritdoc} */ - public function setUriFactory(UriFactory $uriFactory): ClientBuilderInterface + public function setUriFactory(UriFactoryInterface $uriFactory): ClientBuilderInterface { $this->uriFactory = $uriFactory; @@ -140,7 +141,7 @@ public function setUriFactory(UriFactory $uriFactory): ClientBuilderInterface /** * {@inheritdoc} */ - public function setMessageFactory(MessageFactory $messageFactory): ClientBuilderInterface + public function setMessageFactory(MessageFactoryInterface $messageFactory): ClientBuilderInterface { $this->messageFactory = $messageFactory; @@ -170,7 +171,7 @@ public function setHttpClient(HttpAsyncClient $httpClient): ClientBuilderInterfa /** * {@inheritdoc} */ - public function addHttpClientPlugin(Plugin $plugin): ClientBuilderInterface + public function addHttpClientPlugin(PluginInterface $plugin): ClientBuilderInterface { $this->httpClientPlugins[] = $plugin; @@ -223,20 +224,6 @@ public function setSdkIdentifier(string $sdkIdentifier): ClientBuilderInterface return $this; } - /** - * Gets the SDK version to be passed onto {@see Event} and HTTP User-Agent header. - * - * @return string - */ - private function getSdkVersion(): string - { - if (null === $this->sdkVersion) { - $this->setSdkVersionByPackageName('sentry/sentry'); - } - - return $this->sdkVersion; - } - /** * {@inheritdoc} */ @@ -253,9 +240,13 @@ public function setSdkVersion(string $sdkVersion): ClientBuilderInterface * @param string $packageName The package name that will be used to get the version from (i.e. "sentry/sentry") * * @return $this + * + * @deprecated since version 2.2, to be removed in 3.0 */ public function setSdkVersionByPackageName(string $packageName): ClientBuilderInterface { + @trigger_error(sprintf('Method %s() is deprecated since version 2.2 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + $this->sdkVersion = PrettyVersions::getVersion($packageName)->getPrettyVersion(); return $this; @@ -290,8 +281,8 @@ private function createHttpClientInstance(): PluginClient $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->options->getDsn()))); } - $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->getSdkVersion()])); - $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuthentication($this->options, $this->sdkIdentifier, $this->getSdkVersion()))); + $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion])); + $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuthentication($this->options, $this->sdkIdentifier, $this->sdkVersion))); if ($this->options->isCompressionEnabled()) { $this->addHttpClientPlugin(new GzipEncoderPlugin()); @@ -331,17 +322,15 @@ private function createTransportInstance(): TransportInterface throw new \RuntimeException('The `http_proxy` option requires the `php-http/curl-client` package to be installed.'); } + /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->httpClient = new HttpCurlClient(null, null, [ CURLOPT_PROXY => $this->options->getHttpProxy(), ]); } + /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */ $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); - if (null === $this->messageFactory) { - throw new \RuntimeException('The PSR-7 message factory must be set.'); - } - return new HttpTransport($this->options, $this->createHttpClientInstance(), $this->messageFactory); } @@ -355,6 +344,6 @@ private function createEventFactory(): EventFactoryInterface $this->serializer = $this->serializer ?? new Serializer($this->options); $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer($this->options); - return new EventFactory($this->serializer, $this->representationSerializer, $this->options, $this->sdkIdentifier, $this->getSdkVersion()); + return new EventFactory($this->serializer, $this->representationSerializer, $this->options, $this->sdkIdentifier, $this->sdkVersion); } } diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 6bb57b454..5a97eb466 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -4,10 +4,10 @@ namespace Sentry; -use Http\Client\Common\Plugin; +use Http\Client\Common\Plugin as PluginInterface; use Http\Client\HttpAsyncClient; -use Http\Message\MessageFactory; -use Http\Message\UriFactory; +use Http\Message\MessageFactory as MessageFactoryInterface; +use Http\Message\UriFactory as UriFactoryInterface; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\SerializerInterface; use Sentry\Transport\TransportInterface; @@ -38,20 +38,20 @@ public function getOptions(): Options; /** * Sets the factory to use to create URIs. * - * @param UriFactory $uriFactory The factory + * @param UriFactoryInterface $uriFactory The factory * * @return $this */ - public function setUriFactory(UriFactory $uriFactory): self; + public function setUriFactory(UriFactoryInterface $uriFactory): self; /** * Sets the factory to use to create PSR-7 messages. * - * @param MessageFactory $messageFactory The factory + * @param MessageFactoryInterface $messageFactory The factory * * @return $this */ - public function setMessageFactory(MessageFactory $messageFactory): self; + public function setMessageFactory(MessageFactoryInterface $messageFactory): self; /** * Sets the transport that will be used to send events. @@ -74,11 +74,11 @@ public function setHttpClient(HttpAsyncClient $httpClient): self; /** * Adds a new HTTP client plugin to the end of the plugins chain. * - * @param Plugin $plugin The plugin instance + * @param PluginInterface $plugin The plugin instance * * @return $this */ - public function addHttpClientPlugin(Plugin $plugin): self; + public function addHttpClientPlugin(PluginInterface $plugin): self; /** * Removes a HTTP client plugin by its fully qualified class name (FQCN). diff --git a/src/Context/Context.php b/src/Context/Context.php index b8e265a9f..4a045df37 100644 --- a/src/Context/Context.php +++ b/src/Context/Context.php @@ -14,26 +14,36 @@ class Context implements \ArrayAccess, \JsonSerializable, \IteratorAggregate { /** * This constant defines the alias used for the user context. + * + * @deprecated To be removed in 3.0 because unused */ public const CONTEXT_USER = 'user'; /** * This constant defines the alias used for the runtime context. + * + * @deprecated To be removed in 3.0 because unused */ public const CONTEXT_RUNTIME = 'runtime'; /** * This constant defines the alias used for the tags context. + * + * @deprecated To be removed in 3.0 because unused */ public const CONTEXT_TAGS = 'tags'; /** * This constant defines the alias used for the extra context. + * + * @deprecated To be removed in 3.0 because unused */ public const CONTEXT_EXTRA = 'extra'; /** * This constant defines the alias used for the server OS context. + * + * @deprecated To be removed in 3.0 because unused */ public const CONTEXT_SERVER_OS = 'server_os'; diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 406d20445..d077117c3 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -25,22 +25,28 @@ final class ErrorHandler public const DEFAULT_RESERVED_MEMORY_SIZE = 10240; /** - * @var self The current registered handler (this class is a singleton) + * @var self|null The current registered handler (this class is a singleton) */ private static $handlerInstance; /** * @var callable[] List of listeners that will act on each captured error + * + * @psalm-var (callable(\ErrorException): void)[] */ private $errorListeners = []; /** * @var callable[] List of listeners that will act of each captured fatal error + * + * @psalm-var (callable(FatalErrorException): void)[] */ private $fatalErrorListeners = []; /** * @var callable[] List of listeners that will act on each captured exception + * + * @psalm-var (callable(\Throwable): void)[] */ private $exceptionListeners = []; @@ -137,6 +143,10 @@ public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESE @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Please use the registerOnceErrorHandler(), registerOnceFatalErrorHandler() or registerOnceExceptionHandler() methods instead.', __METHOD__), E_USER_DEPRECATED); } + if (null === self::$handlerInstance) { + self::$handlerInstance = new self(); + } + self::registerOnceErrorHandler(); self::registerOnceFatalErrorHandler($reservedMemorySize); self::registerOnceExceptionHandler(); @@ -241,12 +251,15 @@ public static function registerOnceExceptionHandler(): self * this callable will receive a single * \ErrorException argument * + * @psalm-param callable(\ErrorException): void $listener + * * @deprecated since version 2.1, to be removed in 3.0 */ public static function addErrorListener(callable $listener): void { @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addErrorHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); + /** @psalm-suppress DeprecatedMethod */ $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); $handler->errorListeners[] = $listener; } @@ -260,12 +273,15 @@ public static function addErrorListener(callable $listener): void * this callable will receive a single * \ErrorException argument * + * @psalm-param callable(FatalErrorException): void $listener + * * @deprecated since version 2.1, to be removed in 3.0 */ public static function addFatalErrorListener(callable $listener): void { @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addFatalErrorHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); + /** @psalm-suppress DeprecatedMethod */ $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); $handler->fatalErrorListeners[] = $listener; } @@ -279,12 +295,15 @@ public static function addFatalErrorListener(callable $listener): void * this callable will receive a single * \Throwable argument * + * @psalm-param callable(\Throwable): void $listener + * * @deprecated since version 2.1, to be removed in 3.0 */ public static function addExceptionListener(callable $listener): void { @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addExceptionHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); + /** @psalm-suppress DeprecatedMethod */ $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); $handler->exceptionListeners[] = $listener; } @@ -296,6 +315,8 @@ public static function addExceptionListener(callable $listener): void * @param callable $listener A callable that will act as a listener * and that must accept a single argument * of type \ErrorException + * + * @psalm-param callable(\ErrorException): void $listener */ public function addErrorHandlerListener(callable $listener): void { @@ -309,6 +330,8 @@ public function addErrorHandlerListener(callable $listener): void * @param callable $listener A callable that will act as a listener * and that must accept a single argument * of type \Sentry\Exception\FatalErrorException + * + * @psalm-param callable(FatalErrorException): void $listener */ public function addFatalErrorHandlerListener(callable $listener): void { @@ -322,6 +345,8 @@ public function addFatalErrorHandlerListener(callable $listener): void * @param callable $listener A callable that will act as a listener * and that must accept a single argument * of type \Throwable + * + * @psalm-param callable(\Throwable): void $listener */ public function addExceptionHandlerListener(callable $listener): void { @@ -379,7 +404,6 @@ private function handleFatalError(array $error = null): void } self::$reservedMemory = null; - $errorAsException = null; if (null === $error) { $error = error_get_last(); diff --git a/src/Event.php b/src/Event.php index 0e0a91593..17353dfe7 100644 --- a/src/Event.php +++ b/src/Event.php @@ -37,7 +37,7 @@ final class Event implements \JsonSerializable private $level; /** - * @var string The name of the logger which created the record + * @var string|null The name of the logger which created the record */ private $logger; @@ -47,7 +47,7 @@ final class Event implements \JsonSerializable private $transaction; /** - * @var string The name of the server (e.g. the host name) + * @var string|null The name of the server (e.g. the host name) */ private $serverName; @@ -67,7 +67,7 @@ final class Event implements \JsonSerializable private $messageFormatted; /** - * @var array The parameters to use to format the message + * @var mixed[] The parameters to use to format the message */ private $messageParams = []; @@ -77,12 +77,12 @@ final class Event implements \JsonSerializable private $environment; /** - * @var array A list of relevant modules and their versions + * @var array A list of relevant modules and their versions */ private $modules = []; /** - * @var array The request data + * @var array The request data */ private $request = []; @@ -122,7 +122,13 @@ final class Event implements \JsonSerializable private $breadcrumbs = []; /** - * @var array The exceptions + * @var array> The exceptions + * + * @psalm-var array */ private $exceptions = []; @@ -158,6 +164,7 @@ public function __construct() $this->userContext = new UserContext(); $this->extraContext = new Context(); $this->tagsContext = new TagsContext(); + $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); } /** @@ -203,10 +210,6 @@ public function setSdkIdentifier(string $sdkIdentifier): void */ public function getSdkVersion(): string { - if (null === $this->sdkVersion) { - $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); - } - return $this->sdkVersion; } @@ -255,9 +258,9 @@ public function setLevel(Severity $level): void /** * Gets the name of the logger which created the event. * - * @return string + * @return string|null */ - public function getLogger(): string + public function getLogger(): ?string { return $this->logger; } @@ -265,9 +268,9 @@ public function getLogger(): string /** * Sets the name of the logger which created the event. * - * @param string $logger The logger name + * @param string|null $logger The logger name */ - public function setLogger(string $logger): void + public function setLogger(?string $logger): void { $this->logger = $logger; } @@ -297,9 +300,9 @@ public function setTransaction(?string $transaction): void /** * Gets the name of the server. * - * @return string + * @return string|null */ - public function getServerName(): string + public function getServerName(): ?string { return $this->serverName; } @@ -307,9 +310,9 @@ public function getServerName(): string /** * Sets the name of the server. * - * @param string $serverName The server name + * @param string|null $serverName The server name */ - public function setServerName(string $serverName): void + public function setServerName(?string $serverName): void { $this->serverName = $serverName; } @@ -368,10 +371,10 @@ public function getMessageParams(): array * Sets the error message. * * @param string $message The message - * @param array $params The parameters to use to format the message + * @param mixed[] $params The parameters to use to format the message * @param string|null $formatted The formatted message */ - public function setMessage(string $message, array $params = [], string $formatted = null): void + public function setMessage(string $message, array $params = [], ?string $formatted = null): void { $this->message = $message; $this->messageParams = $params; @@ -381,7 +384,7 @@ public function setMessage(string $message, array $params = [], string $formatte /** * Gets a list of relevant modules and their versions. * - * @return array + * @return array */ public function getModules(): array { @@ -391,7 +394,7 @@ public function getModules(): array /** * Sets a list of relevant modules and their versions. * - * @param array $modules + * @param array $modules */ public function setModules(array $modules): void { @@ -411,7 +414,7 @@ public function getRequest(): array /** * Sets the request data. * - * @param array $request The request data + * @param array $request The request data */ public function setRequest(array $request): void { @@ -543,7 +546,13 @@ public function getExceptions(): array /** * Sets the exception. * - * @param array $exceptions The exception + * @param array> $exceptions The exception + * + * @psalm-param array $exceptions */ public function setExceptions(array $exceptions): void { @@ -574,6 +583,49 @@ public function setStacktrace(Stacktrace $stacktrace): void * Gets the event as an array. * * @return array + * + * @psalm-return array{ + * event_id: string, + * timestamp: string, + * level: string, + * platform: string, + * sdk: array{ + * name: string, + * version: string + * }, + * logger?: string, + * transaction?: string, + * server_name?: string, + * release?: string, + * environment?: string, + * fingerprint?: string[], + * modules?: array, + * extra?: mixed[], + * tags?: mixed[], + * user?: mixed[], + * contexts?: array{ + * os?: mixed[], + * runtime?: mixed[] + * }, + * breadcrumbs?: array{ + * values: Breadcrumb[] + * }, + * exception?: array{ + * values: array{ + * type: class-string, + * value: string, + * stacktrace?: array{ + * frames: Frame[] + * } + * }[] + * }, + * request?: array, + * message?: string|array{ + * message: string, + * params: mixed[], + * formatted: string + * } + * } */ public function toArray(): array { diff --git a/src/EventFactory.php b/src/EventFactory.php index 3aa892510..717e0f4a9 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -139,21 +139,18 @@ private function addThrowableToEvent(Event $event, \Throwable $exception): void $currentException = $exception; do { - $data = [ + $exceptions[] = [ 'type' => \get_class($currentException), - 'value' => $this->serializer->serialize($currentException->getMessage()), + 'value' => $currentException->getMessage(), + 'stacktrace' => Stacktrace::createFromBacktrace( + $this->options, + $this->serializer, + $this->representationSerializer, + $currentException->getTrace(), + $currentException->getFile(), + $currentException->getLine() + ), ]; - - $data['stacktrace'] = Stacktrace::createFromBacktrace( - $this->options, - $this->serializer, - $this->representationSerializer, - $currentException->getTrace(), - $currentException->getFile(), - $currentException->getLine() - ); - - $exceptions[] = $data; } while ($currentException = $currentException->getPrevious()); $event->setExceptions($exceptions); diff --git a/src/Exception/MissingProjectIdCredentialException.php b/src/Exception/MissingProjectIdCredentialException.php new file mode 100644 index 000000000..79b6a5b88 --- /dev/null +++ b/src/Exception/MissingProjectIdCredentialException.php @@ -0,0 +1,19 @@ +options->getPublicKey(); + $secretKey = $this->options->getSecretKey(); + + if (null === $publicKey) { + throw new MissingPublicKeyCredentialException(); + } + $data = [ 'sentry_version' => Client::PROTOCOL_VERSION, 'sentry_client' => $this->sdkIdentifier . '/' . $this->sdkVersion, 'sentry_timestamp' => sprintf('%F', microtime(true)), - 'sentry_key' => $this->options->getPublicKey(), + 'sentry_key' => $publicKey, ]; - if ($this->options->getSecretKey()) { - $data['sentry_secret'] = $this->options->getSecretKey(); + if (null !== $secretKey) { + $data['sentry_secret'] = $secretKey; } $headers = []; diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index d4ff60e61..64015f164 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -51,6 +51,7 @@ public function __construct(?Options $options = null, bool $handleFatalErrors = */ public function setupOnce(): void { + /** @psalm-suppress DeprecatedMethod */ $errorHandler = ErrorHandler::registerOnce(ErrorHandler::DEFAULT_RESERVED_MEMORY_SIZE, false); $errorHandler->addErrorHandlerListener(function (\ErrorException $exception): void { if (!$this->handleFatalErrors && $exception instanceof FatalErrorException) { diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index 678b92918..8f7ae1108 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -18,6 +18,7 @@ final class ExceptionListenerIntegration implements IntegrationInterface */ public function setupOnce(): void { + /** @psalm-suppress DeprecatedMethod */ $errorHandler = ErrorHandler::registerOnce(ErrorHandler::DEFAULT_RESERVED_MEMORY_SIZE, false); $errorHandler->addExceptionHandlerListener(static function (\Throwable $exception): void { $currentHub = Hub::getCurrent(); diff --git a/src/Integration/Handler.php b/src/Integration/Handler.php index 5dfb481d6..4871b3304 100644 --- a/src/Integration/Handler.php +++ b/src/Integration/Handler.php @@ -10,6 +10,9 @@ */ final class Handler { + /** + * @var array The registered integrations + */ private static $integrations = []; /** diff --git a/src/Options.php b/src/Options.php index 2c9acf432..7cd88508a 100644 --- a/src/Options.php +++ b/src/Options.php @@ -21,7 +21,7 @@ final class Options public const DEFAULT_MAX_BREADCRUMBS = 100; /** - * @var array The configuration options + * @var array The configuration options */ private $options = []; @@ -36,12 +36,12 @@ final class Options private $projectId; /** - * @var string The public key to authenticate the SDK + * @var string|null The public key to authenticate the SDK */ private $publicKey; /** - * @var string The secret key to authenticate the SDK + * @var string|null The secret key to authenticate the SDK */ private $secretKey; @@ -420,6 +420,8 @@ public function setServerName(string $serverName): void * If `null` is returned it won't be sent. * * @return callable + * + * @psalm-return callable(Event): ?Event */ public function getBeforeSendCallback(): callable { @@ -431,6 +433,8 @@ public function getBeforeSendCallback(): callable * be captured or not. * * @param callable $callback The callable + * + * @psalm-param callable(Event): ?Event $callback */ public function setBeforeSendCallback(callable $callback): void { @@ -442,7 +446,7 @@ public function setBeforeSendCallback(callable $callback): void /** * Gets a list of default tags for events. * - * @return string[] + * @return array */ public function getTags(): array { @@ -452,7 +456,7 @@ public function getTags(): array /** * Sets a list of default tags for events. * - * @param string[] $tags A list of tags + * @param array $tags A list of tags */ public function setTags(array $tags): void { @@ -509,6 +513,8 @@ public function setMaxBreadcrumbs(int $maxBreadcrumbs): void * Gets a callback that will be invoked when adding a breadcrumb. * * @return callable + * + * @psalm-return callable(Breadcrumb): ?Breadcrumb */ public function getBeforeBreadcrumbCallback(): callable { @@ -523,6 +529,8 @@ public function getBeforeBreadcrumbCallback(): callable * cause the breadcrumb to be dropped. * * @param callable $callback The callback + * + * @psalm-param callable(Breadcrumb): ?Breadcrumb $callback */ public function setBeforeBreadcrumbCallback(callable $callback): void { @@ -751,13 +759,13 @@ private function configureOptions(OptionsResolver $resolver): void 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), - 'before_send' => function (Event $event): ?Event { + 'before_send' => static function (Event $event): Event { return $event; }, 'tags' => [], 'error_types' => E_ALL, 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, - 'before_breadcrumb' => function (Breadcrumb $breadcrumb): ?Breadcrumb { + 'before_breadcrumb' => static function (Breadcrumb $breadcrumb): Breadcrumb { return $breadcrumb; }, 'excluded_exceptions' => [], @@ -782,7 +790,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('project_root', ['null', 'string']); $resolver->setAllowedTypes('logger', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); - $resolver->setAllowedTypes('dsn', ['null', 'boolean', 'string']); + $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool']); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('tags', 'array'); @@ -803,9 +811,10 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); $resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions'])); $resolver->setAllowedValues('class_serializers', \Closure::fromCallable([$this, 'validateClassSerializersOption'])); + $resolver->setAllowedValues('tags', \Closure::fromCallable([$this, 'validateTagsOption'])); $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); - $resolver->setNormalizer('project_root', function (SymfonyOptions $options, $value) { + $resolver->setNormalizer('project_root', function (SymfonyOptions $options, ?string $value) { if (null === $value) { return null; } @@ -813,11 +822,11 @@ private function configureOptions(OptionsResolver $resolver): void return $this->normalizeAbsolutePath($value); }); - $resolver->setNormalizer('prefixes', function (SymfonyOptions $options, $value) { + $resolver->setNormalizer('prefixes', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); - $resolver->setNormalizer('in_app_exclude', function (SymfonyOptions $options, $value) { + $resolver->setNormalizer('in_app_exclude', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); } @@ -829,7 +838,7 @@ private function configureOptions(OptionsResolver $resolver): void * * @return string */ - private function normalizeAbsolutePath($value) + private function normalizeAbsolutePath(string $value): string { $path = @realpath($value); @@ -844,12 +853,12 @@ private function normalizeAbsolutePath($value) * Normalizes the DSN option by parsing the host, public and secret keys and * an optional path. * - * @param SymfonyOptions $options The configuration options - * @param mixed $dsn The actual value of the option to normalize + * @param SymfonyOptions$options The configuration options + * @param string|null $dsn The actual value of the option to normalize * * @return string|null */ - private function normalizeDsnOption(SymfonyOptions $options, $dsn): ?string + private function normalizeDsnOption(SymfonyOptions $options, ?string $dsn): ?string { if (empty($dsn)) { return null; @@ -868,6 +877,10 @@ private function normalizeDsnOption(SymfonyOptions $options, $dsn): ?string $parsed = @parse_url($dsn); + if (false === $parsed || !isset($parsed['scheme'], $parsed['host'], $parsed['path'], $parsed['user'])) { + return null; + } + $this->dsn = $parsed['scheme'] . '://' . $parsed['host']; if (isset($parsed['port']) && ((80 !== $parsed['port'] && 'http' === $parsed['scheme']) || (443 !== $parsed['port'] && 'https' === $parsed['scheme']))) { @@ -986,4 +999,22 @@ private function validateClassSerializersOption(array $serializers): bool return true; } + + /** + * Validates that the values passed to the `tags` option are valid. + * + * @param array $tags The value to validate + * + * @return bool + */ + private function validateTagsOption(array $tags): bool + { + foreach ($tags as $tagName => $tagValue) { + if (!\is_string($tagValue)) { + return false; + } + } + + return true; + } } diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index db56724f6..a5e316649 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -67,13 +67,6 @@ abstract class AbstractSerializer */ protected $options; - /** - * Whether the ext-mbstring PHP extension is enabled or not. - * - * @var bool - */ - private $mbStringEnabled; - /** * AbstractSerializer constructor. * @@ -99,7 +92,7 @@ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDete * @param mixed $value * @param int $_depth * - * @return string|bool|float|int|object|array|null + * @return string|bool|float|int|array|null */ protected function serializeRecursively($value, int $_depth = 0) { @@ -132,16 +125,16 @@ protected function serializeRecursively($value, int $_depth = 0) if (\is_array($serializedObjectData)) { return [ - 'class' => \get_class($value), - 'data' => $this->serializeRecursively($serializedObjectData, $_depth + 1), - ]; + 'class' => \get_class($value), + 'data' => $this->serializeRecursively($serializedObjectData, $_depth + 1), + ]; } } catch (\Throwable $e) { // Ignore any exceptions generated by a class serializer } } - if ($this->serializeAllObjects || ('stdClass' === \get_class($value))) { + if ($this->serializeAllObjects || ($value instanceof \stdClass)) { return $this->serializeObject($value, $_depth); } } @@ -212,7 +205,14 @@ protected function serializeObject($object, int $_depth = 0, array $hashes = []) return $serializedObject; } - protected function serializeString($value) + /** + * Serializes the given value to a string. + * + * @param mixed $value The value to serialize + * + * @return string + */ + protected function serializeString($value): string { $value = (string) $value; @@ -284,17 +284,18 @@ protected function serializeCallable(callable $callable): string return '{unserializable callable, reflection error}'; } - $value = $reflection->isClosure() ? 'Lambda ' : 'Callable '; + $callableType = $reflection->isClosure() ? 'Lambda ' : 'Callable '; + $callableReturnType = $reflection->getReturnType(); - if ($reflection->getReturnType()) { - $value .= $reflection->getReturnType() . ' '; + if (null !== $callableReturnType) { + $callableType .= $callableReturnType . ' '; } if ($class) { - $value .= $class->getName() . '::'; + $callableType .= $class->getName() . '::'; } - return $value . $reflection->getName() . ' ' . $this->serializeCallableParameters($reflection); + return $callableType . $reflection->getName() . ' ' . $this->serializeCallableParameters($reflection); } /** @@ -306,7 +307,7 @@ private function serializeCallableParameters(\ReflectionFunctionAbstract $reflec { $params = []; foreach ($reflection->getParameters() as &$param) { - $paramType = $param->hasType() ? $param->getType() : 'mixed'; + $paramType = $param->getType() ?? 'mixed'; if ($param->allowsNull()) { $paramType .= '|null'; @@ -318,11 +319,7 @@ private function serializeCallableParameters(\ReflectionFunctionAbstract $reflec $paramName = '[' . $paramName . ']'; } - if ($paramType) { - $params[] = $paramType . ' ' . $paramName; - } else { - $params[] = $paramName; - } + $params[] = $paramType . ' ' . $paramName; } return '[' . implode('; ', $params) . ']'; diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index 81b88a21f..f8b9e07e2 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -15,7 +15,17 @@ class RepresentationSerializer extends AbstractSerializer implements Representat */ public function representationSerialize($value) { - return $this->serializeRecursively($value); + $value = $this->serializeRecursively($value); + + if (is_numeric($value)) { + return (string) $value; + } + + if (\is_bool($value)) { + return $value ? 'true' : 'false'; + } + + return $value; } /** @@ -47,6 +57,6 @@ protected function serializeValue($value) return (string) $value; } - return parent::serializeValue($value); + return (string) parent::serializeValue($value); } } diff --git a/src/Serializer/RepresentationSerializerInterface.php b/src/Serializer/RepresentationSerializerInterface.php index 6db3132d7..423464a19 100644 --- a/src/Serializer/RepresentationSerializerInterface.php +++ b/src/Serializer/RepresentationSerializerInterface.php @@ -18,7 +18,7 @@ interface RepresentationSerializerInterface * * @param mixed $value * - * @return string|object|array|null + * @return array|string|null */ public function representationSerialize($value); } diff --git a/src/Stacktrace.php b/src/Stacktrace.php index b1b2143be..fae71c531 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -165,8 +165,8 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void foreach ($frameArguments as $argumentName => $argumentValue) { $argumentValue = $this->representationSerializer->representationSerialize($argumentValue); - if (\is_string($argumentValue) || is_numeric($argumentValue)) { - $frameArguments[(string) $argumentName] = mb_substr($argumentValue, 0, $this->options->getMaxValueLength()); + if (is_numeric($argumentValue) || \is_string($argumentValue)) { + $frameArguments[(string) $argumentName] = mb_substr((string) $argumentValue, 0, $this->options->getMaxValueLength()); } else { $frameArguments[(string) $argumentName] = $argumentValue; } @@ -394,6 +394,13 @@ public function getFrameArguments(array $frame): array return $args; } + /** + * Serializes the given argument. + * + * @param mixed $arg The argument to serialize + * + * @return mixed + */ protected function serializeArgument($arg) { $maxValueLength = $this->options->getMaxValueLength(); diff --git a/src/State/Hub.php b/src/State/Hub.php index f11c7279b..a0ee25470 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -25,7 +25,7 @@ final class Hub implements HubInterface private $lastEventId; /** - * @var HubInterface The hub that is set as the current one + * @var HubInterface|null The hub that is set as the current one */ private static $currentHub; diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index c4da01013..28f974d8b 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -8,6 +8,7 @@ use Http\Message\RequestFactory as RequestFactoryInterface; use Http\Promise\Promise as PromiseInterface; use Sentry\Event; +use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; use Sentry\Util\JSON; @@ -87,9 +88,15 @@ public function __destruct() */ public function send(Event $event): ?string { + $projectId = $this->config->getProjectId(); + + if (null === $projectId) { + throw new MissingProjectIdCredentialException(); + } + $request = $this->requestFactory->createRequest( 'POST', - sprintf('/api/%d/store/', $this->config->getProjectId()), + sprintf('/api/%d/store/', $projectId), ['Content-Type' => 'application/json'], JSON::encode($event) ); diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index b6d2a8567..f7e10569e 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -5,6 +5,7 @@ namespace Sentry\Transport; use Sentry\Event; +use Sentry\Exception\MissingProjectIdCredentialException; /** * This interface must be implemented by all classes willing to provide a way @@ -20,6 +21,8 @@ interface TransportInterface * @param Event $event The event * * @return string|null Returns the ID of the event or `null` if it failed to be sent + * + * @throws MissingProjectIdCredentialException If the project ID is missing in the DSN */ public function send(Event $event): ?string; } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 9ccde518e..a550cabe8 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -38,7 +38,12 @@ public function testCreate(): void $this->assertInstanceOf(ClientBuilder::class, $clientBuilder); } - public function testHttpTransportIsUsedWhenServeIsConfigured(): void + /** + * @group legacy + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ + public function testHttpTransportIsUsedWhenServerIsConfigured(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -67,6 +72,11 @@ public function testSetUriFactory(): void $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); } + /** + * @group legacy + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ public function testSetMessageFactory(): void { /** @var MessageFactory|MockObject $messageFactory */ @@ -94,6 +104,11 @@ public function testSetTransport(): void $this->assertAttributeSame($transport, 'transport', $clientBuilder->getClient()); } + /** + * @group legacy + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ public function testSetHttpClient(): void { /** @var HttpAsyncClient|MockObject $httpClient */ @@ -143,6 +158,11 @@ public function testRemoveHttpClientPlugin(): void $this->assertSame($plugin2, reset($plugins)); } + /** + * @group legacy + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ public function testGetClient(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -259,7 +279,11 @@ public function testClientBuilderSetsSdkIdentifierAndVersion(): void } /** + * @group legacy + * * @dataProvider getClientTogglesCompressionPluginInHttpClientDataProvider + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. */ public function testGetClientTogglesCompressionPluginInHttpClient(bool $enabled): void { diff --git a/tests/Exception/MissingProjectIdCredentialExceptionTest.php b/tests/Exception/MissingProjectIdCredentialExceptionTest.php new file mode 100644 index 000000000..5386e6224 --- /dev/null +++ b/tests/Exception/MissingProjectIdCredentialExceptionTest.php @@ -0,0 +1,18 @@ +assertSame('The project ID of the DSN is required to authenticate with the Sentry server.', $exception->getMessage()); + } +} diff --git a/tests/Exception/MissingPublicKeyCredentialExceptionTest.php b/tests/Exception/MissingPublicKeyCredentialExceptionTest.php new file mode 100644 index 000000000..8da72b6b2 --- /dev/null +++ b/tests/Exception/MissingPublicKeyCredentialExceptionTest.php @@ -0,0 +1,18 @@ +assertSame('The public key of the DSN is required to authenticate with the Sentry server.', $exception->getMessage()); + } +} diff --git a/tests/HttpClient/Authentication/SentryAuthenticationTest.php b/tests/HttpClient/Authentication/SentryAuthenticationTest.php index 620d27c47..b99af9395 100644 --- a/tests/HttpClient/Authentication/SentryAuthenticationTest.php +++ b/tests/HttpClient/Authentication/SentryAuthenticationTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; use Sentry\Client; +use Sentry\Exception\MissingPublicKeyCredentialException; use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\Options; @@ -71,4 +72,16 @@ public function testAuthenticateWithNoSecretKey(): void $this->assertSame($newRequest, $authentication->authenticate($request)); } + + public function testAuthenticateWithNoPublicKeyThrowsException(): void + { + $this->expectException(MissingPublicKeyCredentialException::class); + + $authentication = new SentryAuthentication(new Options(), 'sentry.php.test', '2.0.0'); + + /** @var RequestInterface&MockObject $request */ + $request = $this->createMock(RequestInterface::class); + + $authentication->authenticate($request); + } } diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index b42d17ea0..928e451a3 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -11,6 +11,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\Event; +use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; use Sentry\Transport\HttpTransport; @@ -65,4 +66,15 @@ public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoI $this->assertAttributeEmpty('pendingRequests', $transport); } + + public function testSendThrowsOnMissingProjectIdCredential(): void + { + $this->expectException(MissingProjectIdCredentialException::class); + + /** @var HttpAsyncClient&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $transport = new HttpTransport(new Options(), $httpClient, MessageFactoryDiscovery::find(), false); + + $transport->send(new Event()); + } } From e2713a789eb62cdce9c3addbe2a77c6a25d67d5b Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 6 Sep 2019 17:52:25 +0200 Subject: [PATCH 0487/1161] GZIP-encode the request in memory instead of streaming (#877) * Gzip encode the request in memory instead of streaming This is a fix for a PHP bug that was discovered causing the `GzipEncodeStream` to output an empty string when `exit($code)` is used instead of the expected encoded string which causes requests to Sentry to fail because the event data was never sent. * Add changelog entry * Handle compression failure in GzipEncoderPlugin * Throw exception instead of silently not compressing the request * CS * Inject a discovered stream factory * CS * Add deprecation notice to the GzipEncoderPlugin constructor to prevent a BC break --- CHANGELOG.md | 2 ++ src/ClientBuilder.php | 14 +++++++++- src/HttpClient/Plugin/GzipEncoderPlugin.php | 26 ++++++++++++++++--- .../Plugin/GzipEncoderPluginTest.php | 3 ++- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 838276db8..5f34fad36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix GZIP-compressed requests failing when `exit($code)` was used to terminate the application (#877) + ## 2.1.2 (2019-08-22) - Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index c53bb458e..c3952f5d9 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -17,8 +17,10 @@ use Http\Discovery\ClassDiscovery; use Http\Discovery\HttpAsyncClientDiscovery; use Http\Discovery\MessageFactoryDiscovery; +use Http\Discovery\StreamFactoryDiscovery; use Http\Discovery\UriFactoryDiscovery; use Http\Message\MessageFactory; +use Http\Message\StreamFactory as StreamFactoryInterface; use Http\Message\UriFactory; use Jean85\PrettyVersions; use Sentry\HttpClient\Authentication\SentryAuthentication; @@ -52,6 +54,11 @@ final class ClientBuilder implements ClientBuilderInterface */ private $uriFactory; + /** + * @var StreamFactoryInterface|null The PSR-17 stream factory + */ + private $streamFactory; + /** * @var MessageFactory|null The PSR-7 message factory */ @@ -282,6 +289,10 @@ private function createHttpClientInstance(): PluginClient throw new \RuntimeException('The PSR-7 URI factory must be set.'); } + if (null === $this->streamFactory) { + throw new \RuntimeException('The PSR-17 stream factory must be set.'); + } + if (null === $this->httpClient) { throw new \RuntimeException('The PSR-18 HTTP client must be set.'); } @@ -294,7 +305,7 @@ private function createHttpClientInstance(): PluginClient $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuthentication($this->options, $this->sdkIdentifier, $this->getSdkVersion()))); if ($this->options->isCompressionEnabled()) { - $this->addHttpClientPlugin(new GzipEncoderPlugin()); + $this->addHttpClientPlugin(new GzipEncoderPlugin($this->streamFactory)); $this->addHttpClientPlugin(new DecoderPlugin()); } @@ -320,6 +331,7 @@ private function createTransportInstance(): TransportInterface } $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); + $this->streamFactory = $this->streamFactory ?? StreamFactoryDiscovery::find(); $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); if (null !== $this->options->getHttpProxy()) { diff --git a/src/HttpClient/Plugin/GzipEncoderPlugin.php b/src/HttpClient/Plugin/GzipEncoderPlugin.php index bf7d49950..a79680b12 100644 --- a/src/HttpClient/Plugin/GzipEncoderPlugin.php +++ b/src/HttpClient/Plugin/GzipEncoderPlugin.php @@ -5,7 +5,8 @@ namespace Sentry\HttpClient\Plugin; use Http\Client\Common\Plugin as PluginInterface; -use Http\Message\Encoding\GzipEncodeStream; +use Http\Discovery\StreamFactoryDiscovery; +use Http\Message\StreamFactory as StreamFactoryInterface; use Http\Promise\Promise as PromiseInterface; use Psr\Http\Message\RequestInterface; @@ -16,16 +17,29 @@ */ final class GzipEncoderPlugin implements PluginInterface { + /** + * @var StreamFactoryInterface The PSR-17 stream factory + */ + private $streamFactory; + /** * Constructor. * + * @param StreamFactoryInterface|null $streamFactory The stream factory + * * @throws \RuntimeException If the zlib extension is not enabled */ - public function __construct() + public function __construct(?StreamFactoryInterface $streamFactory = null) { if (!\extension_loaded('zlib')) { throw new \RuntimeException('The "zlib" extension must be enabled to use this plugin.'); } + + if (null === $streamFactory) { + @trigger_error(sprintf('A PSR-17 stream factory is needed as argument of the constructor of the "%s" class since version 2.1.3 and will be required in 3.0.', self::class), E_USER_DEPRECATED); + } + + $this->streamFactory = $streamFactory ?? StreamFactoryDiscovery::find(); } /** @@ -39,8 +53,14 @@ public function handleRequest(RequestInterface $request, callable $next, callabl $requestBody->rewind(); } + $encodedBody = gzcompress($requestBody->getContents(), -1, ZLIB_ENCODING_GZIP); + + if (false === $encodedBody) { + throw new \RuntimeException('Failed to GZIP-encode the request body.'); + } + $request = $request->withHeader('Content-Encoding', 'gzip'); - $request = $request->withBody(new GzipEncodeStream($requestBody)); + $request = $request->withBody($this->streamFactory->createStream($encodedBody)); return $next($request); } diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php index 81576ad52..e0a540ca0 100644 --- a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php +++ b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php @@ -5,6 +5,7 @@ namespace Sentry\Tests\HttpClient\Plugin; use Http\Discovery\MessageFactoryDiscovery; +use Http\Discovery\StreamFactoryDiscovery; use Http\Promise\Promise as PromiseInterface; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; @@ -17,7 +18,7 @@ final class GzipEncoderPluginTest extends TestCase */ public function testHandleRequest(): void { - $plugin = new GzipEncoderPlugin(); + $plugin = new GzipEncoderPlugin(StreamFactoryDiscovery::find()); $nextCallableCalled = false; $expectedPromise = $this->createMock(PromiseInterface::class); $request = MessageFactoryDiscovery::find()->createRequest( From 41ef3b66c2a06e3510d6e128eec28acac388f770 Mon Sep 17 00:00:00 2001 From: HazA Date: Fri, 6 Sep 2019 18:10:38 +0200 Subject: [PATCH 0488/1161] meta: Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f34fad36..dc3bf2f1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.1.3 (2019-09-06) + - Fix GZIP-compressed requests failing when `exit($code)` was used to terminate the application (#877) ## 2.1.2 (2019-08-22) From 65edd2d17104e0a1fe5217e4d58de5004e3ee760 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 18 Sep 2019 14:52:59 +0200 Subject: [PATCH 0489/1161] Add an integration to set the transaction attribute of the event (#865) --- CHANGELOG.md | 3 +- composer.json | 2 +- phpstan.neon | 1 + phpunit.xml.dist | 4 + src/ClientBuilder.php | 2 + src/EventFactory.php | 6 -- src/Integration/TransactionIntegration.php | 48 +++++++++ tests/ClientBuilderTest.php | 3 + tests/ClientTest.php | 41 ------- tests/EventFactoryTest.php | 7 -- .../TransactionIntegrationTest.php | 102 ++++++++++++++++++ tests/SentrySdkExtension.php | 22 ++++ 12 files changed, 185 insertions(+), 56 deletions(-) create mode 100644 src/Integration/TransactionIntegration.php create mode 100644 tests/Integration/TransactionIntegrationTest.php create mode 100644 tests/SentrySdkExtension.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cf518340..c953aca4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Silently cast numeric values to strings when trying to set the tags instead of throwing (#858) - Support force sending events on-demand and fix sending of events in long-running processes (#813) - Update PHPStan and introduce Psalm (#846) +- Add an integration to set the transaction attribute of the event (#865) ## 2.1.2 (2019-08-22) @@ -19,7 +20,7 @@ ## 2.1.1 (2019-06-13) - Fix the behavior of the `excluded_exceptions` option: now it's used to skip capture of exceptions, not to purge the -`exception` data of the event, which resulted in broken or empty chains of exceptions in reported events (#822) + `exception` data of the event, which resulted in broken or empty chains of exceptions in reported events (#822) - Fix handling of uploaded files in the `RequestIntegration`, to respect the PSR-7 spec fully (#827) - Fix use of `REMOTE_ADDR` server variable rather than HTTP header - Fix exception, open_basedir restriction in effect (#824) diff --git a/composer.json b/composer.json index efcac7dab..3711dc8ef 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.11", "phpstan/phpstan-phpunit": "^0.11", - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^7.5", "symfony/phpunit-bridge": "^4.3", "vimeo/psalm": "^3.4" }, diff --git a/phpstan.neon b/phpstan.neon index 317616aab..96635fcfe 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,6 +8,7 @@ parameters: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' - '/Http\\Client\\Curl\\Client/' + - '/^Parameter #1 \$object of method ReflectionProperty::setValue\(\) expects object, null given\.$/' # https://github.com/phpstan/phpstan/pull/2340 - message: /^Cannot assign offset 'os' to array\|string\.$/ path: src/Event.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7c9f919fa..8c633d455 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -26,4 +26,8 @@ + + + +
diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index f909ced46..cc69c646e 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -27,6 +27,7 @@ use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Integration\RequestIntegration; +use Sentry\Integration\TransactionIntegration; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\Serializer; @@ -108,6 +109,7 @@ public function __construct(Options $options = null) new ErrorListenerIntegration(null, false), new FatalErrorListenerIntegration(), new RequestIntegration(), + new TransactionIntegration(), ], $this->options->getIntegrations())); } } diff --git a/src/EventFactory.php b/src/EventFactory.php index 717e0f4a9..83035b203 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -90,12 +90,6 @@ public function create(array $payload): Event $event->getTagsContext()->merge($this->options->getTags()); $event->setEnvironment($this->options->getEnvironment()); - if (isset($payload['transaction'])) { - $event->setTransaction($payload['transaction']); - } elseif (isset($_SERVER['PATH_INFO'])) { - $event->setTransaction($_SERVER['PATH_INFO']); - } - if (isset($payload['logger'])) { $event->setLogger($payload['logger']); } diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php new file mode 100644 index 000000000..f3604f396 --- /dev/null +++ b/src/Integration/TransactionIntegration.php @@ -0,0 +1,48 @@ + + */ +final class TransactionIntegration implements IntegrationInterface +{ + /** + * {@inheritdoc} + */ + public function setupOnce(): void + { + Scope::addGlobalEventProcessor(static function (Event $event, array $payload): Event { + $currentHub = Hub::getCurrent(); + $integration = $currentHub->getIntegration(self::class); + + // The client bound to the current hub, if any, could not have this + // integration enabled. If this is the case, bail out + if (null === $integration) { + return $event; + } + + if (null !== $event->getTransaction()) { + return $event; + } + + if (isset($payload['transaction'])) { + $event->setTransaction($payload['transaction']); + } elseif (isset($_SERVER['PATH_INFO'])) { + $event->setTransaction($_SERVER['PATH_INFO']); + } + + return $event; + }); + } +} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index a550cabe8..6a9504b9a 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -24,6 +24,7 @@ use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; +use Sentry\Integration\TransactionIntegration; use Sentry\Options; use Sentry\Transport\HttpTransport; use Sentry\Transport\NullTransport; @@ -219,6 +220,7 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array FatalErrorListenerIntegration::class, ExceptionListenerIntegration::class, RequestIntegration::class, + TransactionIntegration::class, ], ], [ @@ -229,6 +231,7 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array FatalErrorListenerIntegration::class, ExceptionListenerIntegration::class, RequestIntegration::class, + TransactionIntegration::class, StubIntegration::class, ], ], diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 6f3647525..e12e90c9c 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -8,7 +8,6 @@ use Http\Mock\Client as MockClient; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\EventFactory; @@ -23,46 +22,6 @@ class ClientTest extends TestCase { - /** - * @backupGlobals - */ - public function testTransactionEventAttributeIsPopulated(): void - { - $_SERVER['PATH_INFO'] = '/foo'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - /** @var TransportInterface|MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $this->assertEquals('/foo', $event->getTransaction()); - - return true; - })); - - $client = new Client(new Options(), $transport, $this->createEventFactory()); - $client->captureMessage('test'); - } - - public function testTransactionEventAttributeIsNotPopulatedInCli(): void - { - /** @var TransportInterface|MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $this->assertNull($event->getTransaction()); - - return true; - })); - - $client = new Client(new Options(), $transport, $this->createEventFactory()); - $client->captureMessage('test'); - } - public function testCaptureMessage(): void { /** @var TransportInterface|MockObject $transport */ diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index 8c4d09d0a..717e20679 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -27,8 +27,6 @@ public function testCreateEventWithDefaultValues(): void $options->setTags(['test' => 'tag']); $options->setEnvironment('testEnvironment'); - $_SERVER['PATH_INFO'] = 'testPathInfo'; - $eventFactory = new EventFactory( $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class), @@ -45,7 +43,6 @@ public function testCreateEventWithDefaultValues(): void $this->assertSame($options->getRelease(), $event->getRelease()); $this->assertSame($options->getTags(), $event->getTagsContext()->toArray()); $this->assertSame($options->getEnvironment(), $event->getEnvironment()); - $this->assertSame('testPathInfo', $event->getTransaction()); $this->assertNull($event->getStacktrace()); } @@ -70,10 +67,6 @@ public function testCreateWithPayload(array $payload, array $expectedSubset): vo public function createWithPayloadDataProvider() { return [ - [ - ['transaction' => 'testTransaction'], - ['transaction' => 'testTransaction'], - ], [ ['logger' => 'testLogger'], ['logger' => 'testLogger'], diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php new file mode 100644 index 000000000..b05c78f3c --- /dev/null +++ b/tests/Integration/TransactionIntegrationTest.php @@ -0,0 +1,102 @@ +setupOnce(); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getIntegration') + ->willReturn($isIntegrationEnabled ? $integration : null); + + Hub::getCurrent()->bindClient($client); + + withScope(function (Scope $scope) use ($event, $payload, $expectedTransaction): void { + $event = $scope->applyToEvent($event, $payload); + + $this->assertNotNull($event); + $this->assertSame($event->getTransaction(), $expectedTransaction); + }); + } + + public function setupOnceDataProvider(): \Generator + { + yield [ + new Event(), + true, + [], + [], + null, + ]; + + yield [ + new Event(), + true, + ['transaction' => '/foo/bar'], + [], + '/foo/bar', + ]; + + yield [ + new Event(), + true, + [], + ['PATH_INFO' => '/foo/bar'], + '/foo/bar', + ]; + + $event = new Event(); + $event->setTransaction('/foo/bar'); + + yield [ + $event, + true, + [], + [], + '/foo/bar', + ]; + + $event = new Event(); + $event->setTransaction('/foo/bar'); + + yield [ + $event, + true, + ['/foo/bar/baz'], + [], + '/foo/bar', + ]; + + yield [ + new Event(), + false, + ['transaction' => '/foo/bar'], + [], + null, + ]; + } +} diff --git a/tests/SentrySdkExtension.php b/tests/SentrySdkExtension.php new file mode 100644 index 000000000..be72274a3 --- /dev/null +++ b/tests/SentrySdkExtension.php @@ -0,0 +1,22 @@ +setAccessible(true); + $reflectionProperty->setValue(null, []); + $reflectionProperty->setAccessible(false); + } +} From e0b6e8cbef6a945e9543374b2f7e9c0a5c81ee5a Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 18 Sep 2019 16:59:00 +0200 Subject: [PATCH 0490/1161] Deprecate the methods on the HubInterface to get and set the current hub (#847) --- .php_cs => .php_cs.dist | 4 +- CHANGELOG.md | 1 + composer.json | 7 +- src/Integration/ErrorListenerIntegration.php | 4 +- .../ExceptionListenerIntegration.php | 4 +- .../FatalErrorListenerIntegration.php | 4 +- src/Integration/ModulesIntegration.php | 4 +- src/Integration/RequestIntegration.php | 4 +- src/Integration/TransactionIntegration.php | 5 +- src/SentrySdk.php | 70 +++++++ src/State/Hub.php | 22 +-- src/State/HubAdapter.php | 185 ++++++++++++++++++ src/State/HubInterface.php | 11 +- src/{Sdk.php => functions.php} | 18 +- tests/{SdkTest.php => FunctionsTest.php} | 51 ++--- .../TransactionIntegrationTest.php | 4 +- tests/SentrySdkExtension.php | 7 +- tests/SentrySdkTest.php | 39 ++++ tests/Spool/MemorySpoolTest.php | 22 +-- .../error_handler_captures_fatal_error.phpt | 6 +- ...dler_captures_rethrown_exception_once.phpt | 4 +- ...rror_handler_respects_error_reporting.phpt | 4 +- ...tegration_respects_error_types_option.phpt | 4 +- ...tion_skips_fatal_errors_if_configured.phpt | 4 +- ...tegration_respects_error_types_option.phpt | 4 +- tests/phpt/spool_drain.phpt | 10 +- 26 files changed, 390 insertions(+), 112 deletions(-) rename .php_cs => .php_cs.dist (89%) create mode 100644 src/SentrySdk.php create mode 100644 src/State/HubAdapter.php rename src/{Sdk.php => functions.php} (79%) rename tests/{SdkTest.php => FunctionsTest.php} (78%) create mode 100644 tests/SentrySdkTest.php diff --git a/.php_cs b/.php_cs.dist similarity index 89% rename from .php_cs rename to .php_cs.dist index 32bd22813..80ed159fd 100644 --- a/.php_cs +++ b/.php_cs.dist @@ -7,7 +7,9 @@ return PhpCsFixer\Config::create() '@Symfony:risky' => true, 'array_syntax' => ['syntax' => 'short'], 'concat_space' => ['spacing' => 'one'], - 'ordered_imports' => true, + 'ordered_imports' => [ + 'imports_order' => ['class', 'function', 'const'], + ], 'declare_strict_types' => true, 'psr0' => true, 'psr4' => true, diff --git a/CHANGELOG.md b/CHANGELOG.md index c953aca4c..4adb6c476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Support force sending events on-demand and fix sending of events in long-running processes (#813) - Update PHPStan and introduce Psalm (#846) - Add an integration to set the transaction attribute of the event (#865) +- Deprecate `Hub::getCurrent` and `Hub::setCurrent` methods to set the current hub instance (#847) ## 2.1.2 (2019-08-22) diff --git a/composer.json b/composer.json index 3711dc8ef..904aa910b 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ }, "autoload": { "files": [ - "src/Sdk.php" + "src/functions.php" ], "psr-4" : { "Sentry\\" : "src/" @@ -59,11 +59,8 @@ "tests": [ "vendor/bin/phpunit --verbose" ], - "tests-report": [ - "vendor/bin/phpunit --verbose --configuration phpunit.xml.dist --coverage-html tests/html-report" - ], "phpcs": [ - "vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run" + "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run" ], "phpstan": [ "vendor/bin/phpstan analyse" diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index 64015f164..039d2e158 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -8,7 +8,7 @@ use Sentry\Exception\FatalErrorException; use Sentry\Exception\SilencedErrorException; use Sentry\Options; -use Sentry\State\Hub; +use Sentry\SentrySdk; /** * This integration hooks into the global error handlers and emits events to @@ -58,7 +58,7 @@ public function setupOnce(): void return; } - $currentHub = Hub::getCurrent(); + $currentHub = SentrySdk::getCurrentHub(); $integration = $currentHub->getIntegration(self::class); $client = $currentHub->getClient(); diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index 8f7ae1108..4fb95dcaf 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -5,7 +5,7 @@ namespace Sentry\Integration; use Sentry\ErrorHandler; -use Sentry\State\Hub; +use Sentry\SentrySdk; /** * This integration hooks into the global error handlers and emits events to @@ -21,7 +21,7 @@ public function setupOnce(): void /** @psalm-suppress DeprecatedMethod */ $errorHandler = ErrorHandler::registerOnce(ErrorHandler::DEFAULT_RESERVED_MEMORY_SIZE, false); $errorHandler->addExceptionHandlerListener(static function (\Throwable $exception): void { - $currentHub = Hub::getCurrent(); + $currentHub = SentrySdk::getCurrentHub(); $integration = $currentHub->getIntegration(self::class); // The client bound to the current hub, if any, could not have this diff --git a/src/Integration/FatalErrorListenerIntegration.php b/src/Integration/FatalErrorListenerIntegration.php index b528dd366..a1d2bf88d 100644 --- a/src/Integration/FatalErrorListenerIntegration.php +++ b/src/Integration/FatalErrorListenerIntegration.php @@ -7,7 +7,7 @@ use Sentry\ErrorHandler; use Sentry\Exception\FatalErrorException; use Sentry\Options; -use Sentry\State\Hub; +use Sentry\SentrySdk; /** * This integration hooks into the error handler and captures fatal errors. @@ -42,7 +42,7 @@ public function setupOnce(): void { $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); $errorHandler->addFatalErrorHandlerListener(function (FatalErrorException $exception): void { - $currentHub = Hub::getCurrent(); + $currentHub = SentrySdk::getCurrentHub(); $integration = $currentHub->getIntegration(self::class); $client = $currentHub->getClient(); diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index 03ca3232c..bda5a15c0 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -7,7 +7,7 @@ use Jean85\PrettyVersions; use PackageVersions\Versions; use Sentry\Event; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\State\Scope; /** @@ -27,7 +27,7 @@ final class ModulesIntegration implements IntegrationInterface public function setupOnce(): void { Scope::addGlobalEventProcessor(function (Event $event) { - $integration = Hub::getCurrent()->getIntegration(self::class); + $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); // The integration could be bound to a client that is not the one // attached to the current hub. If this is the case, bail out diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 36bb5a276..5cbf9e147 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -9,7 +9,7 @@ use Sentry\Event; use Sentry\Exception\JsonException; use Sentry\Options; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\Util\JSON; use Zend\Diactoros\ServerRequestFactory; @@ -61,7 +61,7 @@ public function __construct(?Options $options = null) public function setupOnce(): void { Scope::addGlobalEventProcessor(function (Event $event): Event { - $currentHub = Hub::getCurrent(); + $currentHub = SentrySdk::getCurrentHub(); $integration = $currentHub->getIntegration(self::class); $client = $currentHub->getClient(); diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index f3604f396..7c669eb83 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -5,7 +5,7 @@ namespace Sentry\Integration; use Sentry\Event; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\State\Scope; /** @@ -23,8 +23,7 @@ final class TransactionIntegration implements IntegrationInterface public function setupOnce(): void { Scope::addGlobalEventProcessor(static function (Event $event, array $payload): Event { - $currentHub = Hub::getCurrent(); - $integration = $currentHub->getIntegration(self::class); + $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out diff --git a/src/SentrySdk.php b/src/SentrySdk.php new file mode 100644 index 000000000..5dc9424c7 --- /dev/null +++ b/src/SentrySdk.php @@ -0,0 +1,70 @@ + + */ +final class SentrySdk +{ + /** + * @var HubInterface|null The current hub + */ + private static $currentHub; + + /** + * Constructor. + */ + private function __construct() + { + } + + /** + * Initializes the SDK by creating a new hub instance each time this method + * gets called. + * + * @return HubInterface + */ + public static function init(): HubInterface + { + self::$currentHub = new Hub(); + + return self::$currentHub; + } + + /** + * Gets the current hub. If it's not initialized then creates a new instance + * and sets it as current hub. + * + * @return HubInterface + */ + public static function getCurrentHub(): HubInterface + { + if (null === self::$currentHub) { + self::$currentHub = new Hub(); + } + + return self::$currentHub; + } + + /** + * Sets the current hub. + * + * @param HubInterface $hub The hub to set + * + * @return HubInterface + */ + public static function setCurrentHub(HubInterface $hub): HubInterface + { + self::$currentHub = $hub; + + return $hub; + } +} diff --git a/src/State/Hub.php b/src/State/Hub.php index a0ee25470..8c76e0d4a 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -7,6 +7,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Integration\IntegrationInterface; +use Sentry\SentrySdk; use Sentry\Severity; /** @@ -24,11 +25,6 @@ final class Hub implements HubInterface */ private $lastEventId; - /** - * @var HubInterface|null The hub that is set as the current one - */ - private static $currentHub; - /** * Hub constructor. * @@ -37,11 +33,7 @@ final class Hub implements HubInterface */ public function __construct(?ClientInterface $client = null, ?Scope $scope = null) { - if (null === $scope) { - $scope = new Scope(); - } - - $this->stack[] = new Layer($client, $scope); + $this->stack[] = new Layer($client, $scope ?? new Scope()); } /** @@ -204,11 +196,9 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool */ public static function getCurrent(): HubInterface { - if (null === self::$currentHub) { - self::$currentHub = new self(); - } + @trigger_error(sprintf('The %s() method is deprecated since version 2.2 and will be removed in 3.0. Use SentrySdk::getCurrentHub() instead.', __METHOD__), E_USER_DEPRECATED); - return self::$currentHub; + return SentrySdk::getCurrentHub(); } /** @@ -216,7 +206,9 @@ public static function getCurrent(): HubInterface */ public static function setCurrent(HubInterface $hub): HubInterface { - self::$currentHub = $hub; + @trigger_error(sprintf('The %s() method is deprecated since version 2.2 and will be removed in 3.0. Use SentrySdk::setCurrentHub() instead.', __METHOD__), E_USER_DEPRECATED); + + SentrySdk::setCurrentHub($hub); return $hub; } diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php new file mode 100644 index 000000000..3eee7b9ac --- /dev/null +++ b/src/State/HubAdapter.php @@ -0,0 +1,185 @@ +getClient(); + } + + /** + * {@inheritdoc} + */ + public function getLastEventId(): ?string + { + return SentrySdk::getCurrentHub()->getLastEventId(); + } + + /** + * {@inheritdoc} + */ + public function pushScope(): Scope + { + return SentrySdk::getCurrentHub()->pushScope(); + } + + /** + * {@inheritdoc} + */ + public function popScope(): bool + { + return SentrySdk::getCurrentHub()->popScope(); + } + + /** + * {@inheritdoc} + */ + public function withScope(callable $callback): void + { + SentrySdk::getCurrentHub()->withScope($callback); + } + + /** + * {@inheritdoc} + */ + public function configureScope(callable $callback): void + { + SentrySdk::getCurrentHub()->configureScope($callback); + } + + /** + * {@inheritdoc} + */ + public function bindClient(ClientInterface $client): void + { + SentrySdk::getCurrentHub()->bindClient($client); + } + + /** + * {@inheritdoc} + */ + public function captureMessage(string $message, ?Severity $level = null): ?string + { + return SentrySdk::getCurrentHub()->captureMessage($message, $level); + } + + /** + * {@inheritdoc} + */ + public function captureException(\Throwable $exception): ?string + { + return SentrySdk::getCurrentHub()->captureException($exception); + } + + /** + * {@inheritdoc} + */ + public function captureEvent(array $payload): ?string + { + return SentrySdk::getCurrentHub()->captureEvent($payload); + } + + /** + * {@inheritdoc} + */ + public function captureLastError(): ?string + { + return SentrySdk::getCurrentHub()->captureLastError(); + } + + /** + * {@inheritdoc} + */ + public function addBreadcrumb(Breadcrumb $breadcrumb): bool + { + return SentrySdk::getCurrentHub()->addBreadcrumb($breadcrumb); + } + + /** + * {@inheritdoc} + */ + public static function getCurrent(): HubInterface + { + @trigger_error(sprintf('The %s() method is deprecated since version 2.2 and will be removed in 3.0. Use SentrySdk::getCurrentHub() instead.', __METHOD__), E_USER_DEPRECATED); + + return SentrySdk::getCurrentHub(); + } + + /** + * {@inheritdoc} + */ + public static function setCurrent(HubInterface $hub): HubInterface + { + @trigger_error(sprintf('The %s() method is deprecated since version 2.2 and will be removed in 3.0. Use SentrySdk::getCurrentHub() instead.', __METHOD__), E_USER_DEPRECATED); + + return SentrySdk::setCurrentHub($hub); + } + + /** + * {@inheritdoc} + */ + public function getIntegration(string $className): ?IntegrationInterface + { + return SentrySdk::getCurrentHub()->getIntegration($className); + } + + /** + * @see https://www.php.net/manual/en/language.oop5.cloning.php#object.clone + */ + public function __clone() + { + throw new \BadMethodCallException('Cloning is forbidden.'); + } + + /** + * @see https://www.php.net/manual/en/language.oop5.magic.php#object.wakeup + */ + public function __wakeup() + { + throw new \BadMethodCallException('Unserializing instances of this class is forbidden.'); + } +} diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index babeeb8a4..03c18fb07 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -7,6 +7,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Integration\IntegrationInterface; +use Sentry\SentrySdk; use Sentry\Severity; /** @@ -123,8 +124,11 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool; * Returns the current global Hub. * * @return HubInterface + * + * @deprecated since version 2.2, to be removed in 3.0 + * @see SentrySdk::getCurrentHub() */ - public static function getCurrent(): HubInterface; + public static function getCurrent(): self; /** * Sets the Hub as the current. @@ -132,8 +136,11 @@ public static function getCurrent(): HubInterface; * @param HubInterface $hub The Hub that will become the current one * * @return HubInterface + * + * @deprecated since version 2.2, to be removed in 3.0 + * @see SentrySdk::setCurrentHub() */ - public static function setCurrent(HubInterface $hub): HubInterface; + public static function setCurrent(self $hub): self; /** * Gets the integration whose FQCN matches the given one if it's available on the current client. diff --git a/src/Sdk.php b/src/functions.php similarity index 79% rename from src/Sdk.php rename to src/functions.php index e76d9befd..238efa153 100644 --- a/src/Sdk.php +++ b/src/functions.php @@ -4,8 +4,6 @@ namespace Sentry; -use Sentry\State\Hub; - /** * Creates a new Client and Hub which will be set as current. * @@ -15,7 +13,7 @@ function init(array $options = []): void { $client = ClientBuilder::create($options)->getClient(); - Hub::setCurrent(new Hub($client)); + SentrySdk::init()->bindClient($client); } /** @@ -28,7 +26,7 @@ function init(array $options = []): void */ function captureMessage(string $message, ?Severity $level = null): ?string { - return Hub::getCurrent()->captureMessage($message, $level); + return SentrySdk::getCurrentHub()->captureMessage($message, $level); } /** @@ -40,7 +38,7 @@ function captureMessage(string $message, ?Severity $level = null): ?string */ function captureException(\Throwable $exception): ?string { - return Hub::getCurrent()->captureException($exception); + return SentrySdk::getCurrentHub()->captureException($exception); } /** @@ -52,7 +50,7 @@ function captureException(\Throwable $exception): ?string */ function captureEvent(array $payload): ?string { - return Hub::getCurrent()->captureEvent($payload); + return SentrySdk::getCurrentHub()->captureEvent($payload); } /** @@ -62,7 +60,7 @@ function captureEvent(array $payload): ?string */ function captureLastError(): ?string { - return Hub::getCurrent()->captureLastError(); + return SentrySdk::getCurrentHub()->captureLastError(); } /** @@ -74,7 +72,7 @@ function captureLastError(): ?string */ function addBreadcrumb(Breadcrumb $breadcrumb): void { - Hub::getCurrent()->addBreadcrumb($breadcrumb); + SentrySdk::getCurrentHub()->addBreadcrumb($breadcrumb); } /** @@ -85,7 +83,7 @@ function addBreadcrumb(Breadcrumb $breadcrumb): void */ function configureScope(callable $callback): void { - Hub::getCurrent()->configureScope($callback); + SentrySdk::getCurrentHub()->configureScope($callback); } /** @@ -96,5 +94,5 @@ function configureScope(callable $callback): void */ function withScope(callable $callback): void { - Hub::getCurrent()->withScope($callback); + SentrySdk::getCurrentHub()->withScope($callback); } diff --git a/tests/SdkTest.php b/tests/FunctionsTest.php similarity index 78% rename from tests/SdkTest.php rename to tests/FunctionsTest.php index fabc1c8d1..00db75d10 100644 --- a/tests/SdkTest.php +++ b/tests/FunctionsTest.php @@ -6,31 +6,32 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use function Sentry\addBreadcrumb; use Sentry\Breadcrumb; +use Sentry\ClientInterface; +use Sentry\Event; +use Sentry\Options; +use Sentry\SentrySdk; +use Sentry\Severity; +use Sentry\State\Scope; +use function Sentry\addBreadcrumb; use function Sentry\captureEvent; use function Sentry\captureException; use function Sentry\captureLastError; use function Sentry\captureMessage; -use Sentry\ClientInterface; use function Sentry\configureScope; -use Sentry\Event; use function Sentry\init; -use Sentry\Options; -use Sentry\State\Hub; -use Sentry\State\Scope; use function Sentry\withScope; -class SdkTest extends TestCase +/** + * @group legacy + */ +final class FunctionsTest extends TestCase { - protected function setUp(): void - { - init(); - } - public function testInit(): void { - $this->assertNotNull(Hub::getCurrent()->getClient()); + init(['default_integrations' => false]); + + $this->assertNotNull(SentrySdk::getCurrentHub()->getClient()); } public function testCaptureMessage(): void @@ -39,12 +40,12 @@ public function testCaptureMessage(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') + ->with('foo', Severity::debug()) ->willReturn('92db40a886c0458288c7c83935a350ef'); - Hub::getCurrent()->bindClient($client); + SentrySdk::getCurrentHub()->bindClient($client); - $this->assertEquals($client, Hub::getCurrent()->getClient()); - $this->assertEquals('92db40a886c0458288c7c83935a350ef', captureMessage('foo')); + $this->assertEquals('92db40a886c0458288c7c83935a350ef', captureMessage('foo', Severity::debug())); } public function testCaptureException(): void @@ -58,7 +59,7 @@ public function testCaptureException(): void ->with($exception) ->willReturn('2b867534eead412cbdb882fd5d441690'); - Hub::getCurrent()->bindClient($client); + SentrySdk::getCurrentHub()->bindClient($client); $this->assertEquals('2b867534eead412cbdb882fd5d441690', captureException($exception)); } @@ -69,12 +70,12 @@ public function testCaptureEvent(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') - ->with(['message' => 'test']) + ->with(['message' => 'foo']) ->willReturn('2b867534eead412cbdb882fd5d441690'); - Hub::getCurrent()->bindClient($client); + SentrySdk::getCurrentHub()->bindClient($client); - $this->assertEquals('2b867534eead412cbdb882fd5d441690', captureEvent(['message' => 'test'])); + $this->assertEquals('2b867534eead412cbdb882fd5d441690', captureEvent(['message' => 'foo'])); } public function testCaptureLastError() @@ -84,7 +85,7 @@ public function testCaptureLastError() $client->expects($this->once()) ->method('captureLastError'); - Hub::getCurrent()->bindClient($client); + SentrySdk::getCurrentHub()->bindClient($client); @trigger_error('foo', E_USER_NOTICE); @@ -99,9 +100,9 @@ public function testAddBreadcrumb(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('getOptions') - ->willReturn(new Options()); + ->willReturn(new Options(['default_integrations' => false])); - Hub::getCurrent()->bindClient($client); + SentrySdk::getCurrentHub()->bindClient($client); addBreadcrumb($breadcrumb); configureScope(function (Scope $scope) use ($breadcrumb): void { @@ -116,7 +117,7 @@ public function testWithScope(): void { $callbackInvoked = false; - withScope(function () use (&$callbackInvoked): void { + withScope(static function () use (&$callbackInvoked): void { $callbackInvoked = true; }); @@ -127,7 +128,7 @@ public function configureScope(): void { $callbackInvoked = false; - configureScope(function () use (&$callbackInvoked): void { + configureScope(static function () use (&$callbackInvoked): void { $callbackInvoked = true; }); diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index b05c78f3c..213c36952 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -9,7 +9,7 @@ use Sentry\ClientInterface; use Sentry\Event; use Sentry\Integration\TransactionIntegration; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\State\Scope; use function Sentry\withScope; @@ -33,7 +33,7 @@ public function testSetupOnce(Event $event, bool $isIntegrationEnabled, array $p ->method('getIntegration') ->willReturn($isIntegrationEnabled ? $integration : null); - Hub::getCurrent()->bindClient($client); + SentrySdk::getCurrentHub()->bindClient($client); withScope(function (Scope $scope) use ($event, $payload, $expectedTransaction): void { $event = $scope->applyToEvent($event, $payload); diff --git a/tests/SentrySdkExtension.php b/tests/SentrySdkExtension.php index be72274a3..946a4f870 100644 --- a/tests/SentrySdkExtension.php +++ b/tests/SentrySdkExtension.php @@ -5,14 +5,17 @@ namespace Sentry\Tests; use PHPUnit\Runner\BeforeTestHook as BeforeTestHookInterface; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\State\Scope; final class SentrySdkExtension implements BeforeTestHookInterface { public function executeBeforeTest(string $test): void { - Hub::setCurrent(new Hub()); + $reflectionProperty = new \ReflectionProperty(SentrySdk::class, 'currentHub'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue(null, null); + $reflectionProperty->setAccessible(false); $reflectionProperty = new \ReflectionProperty(Scope::class, 'globalEventProcessors'); $reflectionProperty->setAccessible(true); diff --git a/tests/SentrySdkTest.php b/tests/SentrySdkTest.php new file mode 100644 index 000000000..f2f9d3960 --- /dev/null +++ b/tests/SentrySdkTest.php @@ -0,0 +1,39 @@ +assertSame($hub1, $hub2); + $this->assertNotSame(SentrySdk::init(), SentrySdk::init()); + } + + public function testGetCurrentHub(): void + { + SentrySdk::init(); + + $hub2 = SentrySdk::getCurrentHub(); + $hub3 = SentrySdk::getCurrentHub(); + + $this->assertSame($hub2, $hub3); + } + + public function testSetCurrentHub(): void + { + $hub = new Hub(); + + $this->assertSame($hub, SentrySdk::setCurrentHub($hub)); + $this->assertSame($hub, SentrySdk::getCurrentHub()); + } +} diff --git a/tests/Spool/MemorySpoolTest.php b/tests/Spool/MemorySpoolTest.php index ad9fcdd3d..ee01c2d0f 100644 --- a/tests/Spool/MemorySpoolTest.php +++ b/tests/Spool/MemorySpoolTest.php @@ -15,20 +15,13 @@ final class MemorySpoolTest extends TestCase /** * @var MemorySpool */ - protected $spool; + private $spool; protected function setUp(): void { $this->spool = new MemorySpool(); } - public function testQueueEvent(): void - { - $this->assertAttributeEmpty('events', $this->spool); - $this->assertTrue($this->spool->queueEvent(new Event())); - $this->assertAttributeNotEmpty('events', $this->spool); - } - public function testFlushQueue(): void { $event1 = new Event(); @@ -38,23 +31,12 @@ public function testFlushQueue(): void $transport = $this->createMock(TransportInterface::class); $transport->expects($this->exactly(2)) ->method('send') - ->withConsecutive($event1, $event2); + ->withConsecutive([$event2], [$event1]); $this->spool->queueEvent($event1); $this->spool->queueEvent($event2); $this->spool->flushQueue($transport); - - $this->assertAttributeEmpty('events', $this->spool); - } - - public function testFlushQueueWithEmptyQueue(): void - { - /** @var TransportInterface|MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->never()) - ->method('send'); - $this->spool->flushQueue($transport); } } diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index 5d2ff8e6f..52293c5d0 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -10,8 +10,8 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\ErrorHandler; use Sentry\Event; -use Sentry\State\Hub; -use Sentry\Transport\TransportInterface; +use Sentry\SentrySdk; +use Sentry\Transport\TransportInterface;; $vendor = __DIR__; @@ -34,7 +34,7 @@ $client = ClientBuilder::create([]) ->setTransport($transport) ->getClient(); -Hub::getCurrent()->bindClient($client); +SentrySdk::getCurrentHub()->bindClient($client); $errorHandler = ErrorHandler::registerOnceErrorHandler(); $errorHandler->addErrorHandlerListener(static function (): void { diff --git a/tests/phpt/error_handler_captures_rethrown_exception_once.phpt b/tests/phpt/error_handler_captures_rethrown_exception_once.phpt index 1a8d02099..0f9913130 100644 --- a/tests/phpt/error_handler_captures_rethrown_exception_once.phpt +++ b/tests/phpt/error_handler_captures_rethrown_exception_once.phpt @@ -9,7 +9,7 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -39,7 +39,7 @@ $client = ClientBuilder::create([]) ->setTransport($transport) ->getClient(); -Hub::getCurrent()->bindClient($client); +SentrySdk::getCurrentHub()->bindClient($client); throw new \Exception('foo bar'); ?> diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt index ac7300300..8ace02528 100644 --- a/tests/phpt/error_handler_respects_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -9,7 +9,7 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -33,7 +33,7 @@ $client = ClientBuilder::create(['capture_silenced_errors' => true]) ->setTransport($transport) ->getClient(); -Hub::getCurrent()->bindClient($client); +SentrySdk::getCurrentHub()->bindClient($client); echo 'Triggering silenced error' . PHP_EOL; diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt index 14d2f0fb2..c4f3b6198 100644 --- a/tests/phpt/error_listener_integration_respects_error_types_option.phpt +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -11,7 +11,7 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Options; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -42,7 +42,7 @@ $client = (new ClientBuilder($options)) ->setTransport($transport) ->getClient(); -Hub::getCurrent()->bindClient($client); +SentrySdk::getCurrentHub()->bindClient($client); trigger_error('Error thrown', E_USER_NOTICE); trigger_error('Error thrown', E_USER_WARNING); diff --git a/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt b/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt index 78dfb56b6..836ca33ab 100644 --- a/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt +++ b/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt @@ -11,7 +11,7 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Options; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -41,7 +41,7 @@ $client = (new ClientBuilder($options)) ->setTransport($transport) ->getClient(); -Hub::getCurrent()->bindClient($client); +SentrySdk::getCurrentHub()->bindClient($client); class FooClass implements \Serializable { diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index de474d15c..df5854d8a 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -11,7 +11,7 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -41,7 +41,7 @@ $client = (new ClientBuilder($options)) ->setTransport($transport) ->getClient(); -Hub::getCurrent()->bindClient($client); +SentrySdk::getCurrentHub()->bindClient($client); class FooClass implements \Serializable { diff --git a/tests/phpt/spool_drain.phpt b/tests/phpt/spool_drain.phpt index 6ff4a1f4d..efe685200 100644 --- a/tests/phpt/spool_drain.phpt +++ b/tests/phpt/spool_drain.phpt @@ -8,11 +8,11 @@ declare(strict_types=1); namespace Sentry\Tests; use PHPUnit\Framework\Assert; +use Sentry\SentrySdk; use Sentry\Spool\MemorySpool; use Sentry\Transport\SpoolTransport; use Sentry\Transport\NullTransport; use Sentry\ClientBuilder; -use Sentry\State\Hub; $vendor = __DIR__; @@ -26,11 +26,13 @@ $spool = new MemorySpool(); $transport = new SpoolTransport($spool); $nullTransport = new NullTransport(); -$builder = ClientBuilder::create()->setTransport($transport); +$client = ClientBuilder::create() + ->setTransport($transport) + ->getClient(); -Hub::getCurrent()->bindClient($builder->getClient()); +SentrySdk::getCurrentHub()->bindClient($client); -register_shutdown_function('register_shutdown_function', function () use ($spool, $nullTransport) { +register_shutdown_function('register_shutdown_function', static function () use ($spool, $nullTransport): void { Assert::assertAttributeCount(1, 'events', $spool); $spool->flushQueue($nullTransport); From bbde8cb231a5f16bc0759d73bb168f1499c65b8b Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 23 Sep 2019 10:32:22 +0200 Subject: [PATCH 0491/1161] meta: Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f43f7bdb5..600b9dc61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.2.0 (2019-09-23) + - Change type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) - Add the `setTags`, `setExtras` and `clearBreadcrumbs` methods to the `Scope` class (#852) - Silently cast numeric values to strings when trying to set the tags instead of throwing (#858) From bfa507f6cac187e38b909ea83c9fc33572466d06 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 23 Sep 2019 13:49:26 +0200 Subject: [PATCH 0492/1161] Disable the deprecation of the HttpTransport transport about delaying sending of events until shutdown (#885) --- src/ClientBuilder.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index dcf1cbf2f..4a94cdc18 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -345,7 +345,12 @@ private function createTransportInstance(): TransportInterface /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */ $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); - return new HttpTransport($this->options, $this->createHttpClientInstance(), $this->messageFactory); + return new HttpTransport( + $this->options, + $this->createHttpClientInstance(), + $this->messageFactory, + false + ); } /** From 5896f9f0a00a7525797764ba42091dfbde22a746 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 23 Sep 2019 13:52:12 +0200 Subject: [PATCH 0493/1161] meta: Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 600b9dc61..cef8f2651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 2.2.1 (2019-09-23) + +- Disable default deprecation warning `Sentry\Transport\HttpTransport` (#884) + ## 2.2.0 (2019-09-23) - Change type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) From 657d5cda35d4d432b32c5d162399e6b4fa42b2af Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 25 Sep 2019 13:59:55 +0200 Subject: [PATCH 0494/1161] meta: change dev branch --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 904aa910b..837fc3ac6 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,7 @@ }, "extra": { "branch-alias": { - "dev-develop": "2.2-dev" + "dev-develop": "2.3-dev" } } } From 7826adf05b8ae7e87e3248d44def812314da8ea8 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 2 Oct 2019 12:30:31 +0200 Subject: [PATCH 0495/1161] Pass the errcontext argument to the previous error handler (#892) --- CHANGELOG.md | 2 ++ src/ErrorHandler.php | 15 ++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cef8f2651..619990143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix handling of fifth argument in the error handler (#892) + ## 2.2.1 (2019-09-23) - Disable default deprecation warning `Sentry\Transport\HttpTransport` (#884) diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index d077117c3..7a19bc140 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -357,18 +357,19 @@ public function addExceptionHandlerListener(callable $listener): void * Handles errors by capturing them through the Raven client according to * the configured bit field. * - * @param int $level The level of the error raised, represented by one - * of the E_* constants - * @param string $message The error message - * @param string $file The filename the error was raised in - * @param int $line The line number the error was raised at + * @param int $level The level of the error raised, represented by + * one of the E_* constants + * @param string $message The error message + * @param string $file The filename the error was raised in + * @param int $line The line number the error was raised at + * @param array $errcontext The error context (deprecated since PHP 7.2) * * @return bool If the function returns `false` then the PHP native error * handler will be called * * @throws \Throwable */ - private function handleError(int $level, string $message, string $file, int $line): bool + private function handleError(int $level, string $message, string $file, int $line, array $errcontext = []): bool { if (0 === error_reporting()) { $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); @@ -383,7 +384,7 @@ private function handleError(int $level, string $message, string $file, int $lin $this->invokeListeners($this->errorListeners, $errorAsException); if (null !== $this->previousErrorHandler) { - return false !== \call_user_func($this->previousErrorHandler, $level, $message, $file, $line); + return false !== \call_user_func($this->previousErrorHandler, $level, $message, $file, $line, $errcontext); } return false; From 45318ade4f668214da3e1bfed84f67e721f62162 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 4 Oct 2019 17:36:06 +0200 Subject: [PATCH 0496/1161] Add support for Guzzle as default HTTP client (#890) --- phpstan.neon | 9 +++++++++ src/ClientBuilder.php | 21 ++++++++++++++------- src/State/HubAdapter.php | 2 +- tests/ClientBuilderTest.php | 7 ------- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 96635fcfe..83e9202e4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -15,6 +15,15 @@ parameters: - message: "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" path: src/Transport/HttpTransport.php + - + message: '/^Class Http\\Adapter\\Guzzle6\\Client not found\.$/' + path: src/ClientBuilder.php + - + message: '/^Call to static method createWithConfig\(\) on an unknown class Http\\Adapter\\Guzzle6\\Client\.$/' + path: src/ClientBuilder.php + - + message: '/^Access to constant PROXY on an unknown class GuzzleHttp\\RequestOptions\.$/' + path: src/ClientBuilder.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index dcf1cbf2f..3e110b358 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,6 +4,8 @@ namespace Sentry; +use GuzzleHttp\RequestOptions as GuzzleHttpClientOptions; +use Http\Adapter\Guzzle6\Client as GuzzleHttpClient; use Http\Client\Common\Plugin as PluginInterface; use Http\Client\Common\Plugin\AuthenticationPlugin; use Http\Client\Common\Plugin\BaseUriPlugin; @@ -12,7 +14,7 @@ use Http\Client\Common\Plugin\HeaderSetPlugin; use Http\Client\Common\Plugin\RetryPlugin; use Http\Client\Common\PluginClient; -use Http\Client\Curl\Client as HttpCurlClient; +use Http\Client\Curl\Client as CurlHttpClient; use Http\Client\HttpAsyncClient; use Http\Discovery\ClassDiscovery; use Http\Discovery\HttpAsyncClientDiscovery; @@ -332,14 +334,19 @@ private function createTransportInstance(): TransportInterface throw new \RuntimeException('The `http_proxy` option does not work together with a custom client.'); } - if (!ClassDiscovery::safeClassExists(HttpCurlClient::class)) { - throw new \RuntimeException('The `http_proxy` option requires the `php-http/curl-client` package to be installed.'); + if (ClassDiscovery::safeClassExists(GuzzleHttpClient::class)) { + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $this->httpClient = GuzzleHttpClient::createWithConfig([ + GuzzleHttpClientOptions::PROXY => $this->options->getHttpProxy(), + ]); + } elseif (ClassDiscovery::safeClassExists(CurlHttpClient::class)) { + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $this->httpClient = new CurlHttpClient(null, null, [ + CURLOPT_PROXY => $this->options->getHttpProxy(), + ]); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->httpClient = new HttpCurlClient(null, null, [ - CURLOPT_PROXY => $this->options->getHttpProxy(), - ]); + throw new \RuntimeException('The `http_proxy` option requires either the `php-http/curl-client` or the `php-http/guzzle6-adapter` package to be installed.'); } /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */ diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 3eee7b9ac..132cdff6c 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -17,7 +17,7 @@ final class HubAdapter implements HubInterface { /** - * @var self The single instance which forwards all calls to {@see SentrySdk} + * @var self|null The single instance which forwards all calls to {@see SentrySdk} */ private static $instance; diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 6a9504b9a..b0f165c96 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -32,13 +32,6 @@ final class ClientBuilderTest extends TestCase { - public function testCreate(): void - { - $clientBuilder = ClientBuilder::create(); - - $this->assertInstanceOf(ClientBuilder::class, $clientBuilder); - } - /** * @group legacy * From 7cb7f7e92feacd45f8da7b1f8ccd2f43b8c5d304 Mon Sep 17 00:00:00 2001 From: Christoph Lehmann Date: Sun, 6 Oct 2019 21:14:45 +0200 Subject: [PATCH 0497/1161] Add 3rd party integrations (#874) * Add 3rd party integrations * Update README.md --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b1c7bf061..b6b1d1426 100644 --- a/README.md +++ b/README.md @@ -74,14 +74,19 @@ The following integrations are fully supported and maintained by the Sentry team The following integrations are available and maintained by members of the Sentry community. -- [ZendFramework](https://github.com/facile-it/sentry-module) -- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [Drupal](https://www.drupal.org/project/raven) -- [OpenCart](https://github.com/BurdaPraha/oc_sentry) -- [TYPO3](https://github.com/networkteam/sentry_client) +- [Flow Framework](https://github.com/networkteam/Networkteam.SentryClient) - [OXID eShop](https://github.com/OXIDprojects/sentry) +- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) +- [ZendFramework](https://github.com/facile-it/sentry-module) - ... feel free to be famous, create a port to your favourite platform! +### 3rd party integrations using old SDK 1.x + +- [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient) +- [OpenCart](https://github.com/BurdaPraha/oc_sentry) +- [TYPO3](https://github.com/networkteam/sentry_client) + ## Community - [Documentation](https://docs.sentry.io/error-reporting/quickstart/?platform=php) From a0d0381a049c910bcb2b527cfc1ed32564b68215 Mon Sep 17 00:00:00 2001 From: Russ Michell Date: Mon, 7 Oct 2019 08:29:40 +1300 Subject: [PATCH 0498/1161] Add phptek/sentry package for SilverStripe applications (#896) Co-authored-by: Russell Michell --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b6b1d1426..3bf81bc5a 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ The following integrations are available and maintained by members of the Sentry - [OXID eShop](https://github.com/OXIDprojects/sentry) - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [ZendFramework](https://github.com/facile-it/sentry-module) +- [SilverStripe](https://github.com/phptek/silverstripe-sentry) - ... feel free to be famous, create a port to your favourite platform! ### 3rd party integrations using old SDK 1.x From 61845ada198ebc9ba357c4bd8e2c89e4f4af0179 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 9 Oct 2019 16:24:52 +0200 Subject: [PATCH 0499/1161] Catch all exceptions thrown while sending an event (#899) --- CHANGELOG.md | 1 + Makefile | 2 +- src/Transport/HttpTransport.php | 11 +++++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 619990143..e4f5358d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix handling of fifth argument in the error handler (#892) +- Catch exception from vendors in `Sentry\Transport\HttpTransport` (#899) ## 2.2.1 (2019-09-23) diff --git a/Makefile b/Makefile index fcdd77fbe..03d373440 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ cs-dry-run: vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run cs-fix: - vendor/bin/php-cs-fixer fix --config=.php_cs + vendor/bin/php-cs-fixer fix phpstan: vendor/bin/phpstan analyse diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 28f974d8b..164485191 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -106,7 +106,11 @@ public function send(Event $event): ?string if ($this->delaySendingUntilShutdown) { $this->pendingRequests[] = $promise; } else { - $promise->wait(false); + try { + $promise->wait(); + } catch (\Exception $exception) { + return null; + } } return $event->getId(); @@ -119,7 +123,10 @@ public function send(Event $event): ?string private function cleanupPendingRequests(): void { while ($promise = array_pop($this->pendingRequests)) { - $promise->wait(false); + try { + $promise->wait(); + } catch (\Exception $exception) { + } } } } From 41e4d910a8d531fd48502f584ece451d37f67054 Mon Sep 17 00:00:00 2001 From: Olivier Lechevalier Date: Thu, 10 Oct 2019 16:45:37 +0900 Subject: [PATCH 0500/1161] Extract the code to create a transport from the client builder to a transport factory (#855) --- .appveyor.yml | 1 + composer.json | 2 +- phpstan.neon | 16 +- src/Breadcrumb.php | 4 + src/ClientBuilder.php | 134 ++++++++--------- src/ClientBuilderInterface.php | 15 ++ .../Authentication/SentryAuthentication.php | 1 - src/HttpClient/HttpClientFactory.php | 138 ++++++++++++++++++ src/HttpClient/HttpClientFactoryInterface.php | 24 +++ src/HttpClient/PluggableHttpClientFactory.php | 52 +++++++ src/Transport/DefaultTransportFactory.php | 55 +++++++ src/Transport/TransportFactoryInterface.php | 23 +++ tests/ClientBuilderTest.php | 83 +++-------- tests/ClientTest.php | 81 +++++----- .../SentryAuthenticationTest.php | 13 +- tests/HttpClient/HttpClientFactoryTest.php | 102 +++++++++++++ .../PluggableHttpClientFactoryTest.php | 54 +++++++ .../Transport/DefaultTransportFactoryTest.php | 44 ++++++ 18 files changed, 652 insertions(+), 190 deletions(-) create mode 100644 src/HttpClient/HttpClientFactory.php create mode 100644 src/HttpClient/HttpClientFactoryInterface.php create mode 100644 src/HttpClient/PluggableHttpClientFactory.php create mode 100644 src/Transport/DefaultTransportFactory.php create mode 100644 src/Transport/TransportFactoryInterface.php create mode 100644 tests/HttpClient/HttpClientFactoryTest.php create mode 100644 tests/HttpClient/PluggableHttpClientFactoryTest.php create mode 100644 tests/Transport/DefaultTransportFactoryTest.php diff --git a/.appveyor.yml b/.appveyor.yml index e652b52c6..1a3dbbccd 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,6 +7,7 @@ branches: only: - master - develop + - /^release\/.+$/ environment: matrix: diff --git a/composer.json b/composer.json index 837fc3ac6..fb7e18c00 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", "monolog/monolog": "^1.3|^2.0", - "php-http/mock-client": "^1.0", + "php-http/mock-client": "^1.3", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.11", "phpstan/phpstan-phpunit": "^0.11", diff --git a/phpstan.neon b/phpstan.neon index 83e9202e4..ed6830c86 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,7 +7,6 @@ parameters: ignoreErrors: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/Binary operation "\*" between array and 2 results in an error\./' - - '/Http\\Client\\Curl\\Client/' - '/^Parameter #1 \$object of method ReflectionProperty::setValue\(\) expects object, null given\.$/' # https://github.com/phpstan/phpstan/pull/2340 - message: /^Cannot assign offset 'os' to array\|string\.$/ @@ -15,15 +14,24 @@ parameters: - message: "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" path: src/Transport/HttpTransport.php + - + message: '/^Class Http\\Client\\Curl\\Client not found\.$/' + path: src/HttpClient/HttpClientFactory.php - message: '/^Class Http\\Adapter\\Guzzle6\\Client not found\.$/' - path: src/ClientBuilder.php + path: src/HttpClient/HttpClientFactory.php + - + message: '/^Instantiated class Http\\Client\\Curl\\Client not found\.$/' + path: src/HttpClient/HttpClientFactory.php - message: '/^Call to static method createWithConfig\(\) on an unknown class Http\\Adapter\\Guzzle6\\Client\.$/' - path: src/ClientBuilder.php + path: src/HttpClient/HttpClientFactory.php - message: '/^Access to constant PROXY on an unknown class GuzzleHttp\\RequestOptions\.$/' - path: src/ClientBuilder.php + path: src/HttpClient/HttpClientFactory.php + - + message: '/^Property Sentry\\HttpClient\\HttpClientFactory::\$httpClient \(Http\\Client\\HttpAsyncClient\|null\) does not accept Http\\Client\\Curl\\Client.$/' + path: src/HttpClient/HttpClientFactory.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index 292e3b7d1..57ff4889e 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -135,9 +135,13 @@ public function __construct(string $level, string $type, string $category, ?stri * @param \ErrorException $exception The exception * * @return string + * + * @deprecated since version 2.3, to be removed in 3.0 */ public static function levelFromErrorException(\ErrorException $exception): string { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + switch ($exception->getSeverity()) { case E_DEPRECATED: case E_USER_DEPRECATED: diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 3e110b358..05dd349af 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,20 +4,8 @@ namespace Sentry; -use GuzzleHttp\RequestOptions as GuzzleHttpClientOptions; -use Http\Adapter\Guzzle6\Client as GuzzleHttpClient; use Http\Client\Common\Plugin as PluginInterface; -use Http\Client\Common\Plugin\AuthenticationPlugin; -use Http\Client\Common\Plugin\BaseUriPlugin; -use Http\Client\Common\Plugin\DecoderPlugin; -use Http\Client\Common\Plugin\ErrorPlugin; -use Http\Client\Common\Plugin\HeaderSetPlugin; -use Http\Client\Common\Plugin\RetryPlugin; -use Http\Client\Common\PluginClient; -use Http\Client\Curl\Client as CurlHttpClient; use Http\Client\HttpAsyncClient; -use Http\Discovery\ClassDiscovery; -use Http\Discovery\HttpAsyncClientDiscovery; use Http\Discovery\MessageFactoryDiscovery; use Http\Discovery\StreamFactoryDiscovery; use Http\Discovery\UriFactoryDiscovery; @@ -25,8 +13,8 @@ use Http\Message\StreamFactory as StreamFactoryInterface; use Http\Message\UriFactory as UriFactoryInterface; use Jean85\PrettyVersions; -use Sentry\HttpClient\Authentication\SentryAuthentication; -use Sentry\HttpClient\Plugin\GzipEncoderPlugin; +use Sentry\HttpClient\HttpClientFactory; +use Sentry\HttpClient\PluggableHttpClientFactory; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -36,8 +24,8 @@ use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; -use Sentry\Transport\HttpTransport; -use Sentry\Transport\NullTransport; +use Sentry\Transport\DefaultTransportFactory; +use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; /** @@ -67,6 +55,11 @@ final class ClientBuilder implements ClientBuilderInterface */ private $messageFactory; + /** + * @var TransportFactoryInterface|null The transport factory + */ + private $transportFactory; + /** * @var TransportInterface|null The transport */ @@ -144,6 +137,8 @@ public function getOptions(): Options */ public function setUriFactory(UriFactoryInterface $uriFactory): ClientBuilderInterface { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + $this->uriFactory = $uriFactory; return $this; @@ -154,6 +149,8 @@ public function setUriFactory(UriFactoryInterface $uriFactory): ClientBuilderInt */ public function setMessageFactory(MessageFactoryInterface $messageFactory): ClientBuilderInterface { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + $this->messageFactory = $messageFactory; return $this; @@ -164,6 +161,8 @@ public function setMessageFactory(MessageFactoryInterface $messageFactory): Clie */ public function setTransport(TransportInterface $transport): ClientBuilderInterface { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the setTransportFactory() method instead.', __METHOD__), E_USER_DEPRECATED); + $this->transport = $transport; return $this; @@ -174,6 +173,8 @@ public function setTransport(TransportInterface $transport): ClientBuilderInterf */ public function setHttpClient(HttpAsyncClient $httpClient): ClientBuilderInterface { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + $this->httpClient = $httpClient; return $this; @@ -184,6 +185,8 @@ public function setHttpClient(HttpAsyncClient $httpClient): ClientBuilderInterfa */ public function addHttpClientPlugin(PluginInterface $plugin): ClientBuilderInterface { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + $this->httpClientPlugins[] = $plugin; return $this; @@ -194,6 +197,8 @@ public function addHttpClientPlugin(PluginInterface $plugin): ClientBuilderInter */ public function removeHttpClientPlugin(string $className): ClientBuilderInterface { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + foreach ($this->httpClientPlugins as $index => $httpClientPlugin) { if (!$httpClientPlugin instanceof $className) { continue; @@ -274,40 +279,17 @@ public function getClient(): ClientInterface } /** - * Creates a new instance of the HTTP client. + * Sets the transport factory. * - * @return PluginClient + * @param TransportFactoryInterface $transportFactory The transport factory + * + * @return $this */ - private function createHttpClientInstance(): PluginClient + public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface { - if (null === $this->uriFactory) { - throw new \RuntimeException('The PSR-7 URI factory must be set.'); - } + $this->transportFactory = $transportFactory; - if (null === $this->streamFactory) { - throw new \RuntimeException('The PSR-17 stream factory must be set.'); - } - - if (null === $this->httpClient) { - throw new \RuntimeException('The PSR-18 HTTP client must be set.'); - } - - if (null !== $this->options->getDsn()) { - $this->addHttpClientPlugin(new BaseUriPlugin($this->uriFactory->createUri($this->options->getDsn()))); - } - - $this->addHttpClientPlugin(new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion])); - $this->addHttpClientPlugin(new AuthenticationPlugin(new SentryAuthentication($this->options, $this->sdkIdentifier, $this->sdkVersion))); - - if ($this->options->isCompressionEnabled()) { - $this->addHttpClientPlugin(new GzipEncoderPlugin($this->streamFactory)); - $this->addHttpClientPlugin(new DecoderPlugin()); - } - - $this->addHttpClientPlugin(new RetryPlugin(['retries' => $this->options->getSendAttempts()])); - $this->addHttpClientPlugin(new ErrorPlugin()); - - return new PluginClient($this->httpClient, $this->httpClientPlugins); + return $this; } /** @@ -321,38 +303,9 @@ private function createTransportInstance(): TransportInterface return $this->transport; } - if (null === $this->options->getDsn()) { - return new NullTransport(); - } + $transportFactory = $this->transportFactory ?? $this->createDefaultTransportFactory(); - $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); - $this->streamFactory = $this->streamFactory ?? StreamFactoryDiscovery::find(); - $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); - - if (null !== $this->options->getHttpProxy()) { - if (null !== $this->httpClient) { - throw new \RuntimeException('The `http_proxy` option does not work together with a custom client.'); - } - - if (ClassDiscovery::safeClassExists(GuzzleHttpClient::class)) { - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->httpClient = GuzzleHttpClient::createWithConfig([ - GuzzleHttpClientOptions::PROXY => $this->options->getHttpProxy(), - ]); - } elseif (ClassDiscovery::safeClassExists(CurlHttpClient::class)) { - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->httpClient = new CurlHttpClient(null, null, [ - CURLOPT_PROXY => $this->options->getHttpProxy(), - ]); - } - - throw new \RuntimeException('The `http_proxy` option requires either the `php-http/curl-client` or the `php-http/guzzle6-adapter` package to be installed.'); - } - - /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */ - $this->httpClient = $this->httpClient ?? HttpAsyncClientDiscovery::find(); - - return new HttpTransport($this->options, $this->createHttpClientInstance(), $this->messageFactory); + return $transportFactory->create($this->options); } /** @@ -367,4 +320,31 @@ private function createEventFactory(): EventFactoryInterface return new EventFactory($this->serializer, $this->representationSerializer, $this->options, $this->sdkIdentifier, $this->sdkVersion); } + + /** + * Creates a new instance of the {@see DefaultTransportFactory} factory. + * + * @return DefaultTransportFactory + */ + private function createDefaultTransportFactory(): DefaultTransportFactory + { + $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); + $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); + $this->streamFactory = $this->streamFactory ?? StreamFactoryDiscovery::find(); + + $httpClientFactory = new HttpClientFactory( + $this->uriFactory, + $this->messageFactory, + $this->streamFactory, + $this->httpClient, + $this->sdkIdentifier, + $this->sdkVersion + ); + + if (!empty($this->httpClientPlugins)) { + $httpClientFactory = new PluggableHttpClientFactory($httpClientFactory, $this->httpClientPlugins); + } + + return new DefaultTransportFactory($this->messageFactory, $httpClientFactory); + } } diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 5a97eb466..6cf380642 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -10,12 +10,15 @@ use Http\Message\UriFactory as UriFactoryInterface; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\SerializerInterface; +use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; /** * A configurable builder for Client objects. * * @author Stefano Arlandini + * + * @method self setTransportFactory(TransportFactoryInterface $transportFactory) */ interface ClientBuilderInterface { @@ -41,6 +44,8 @@ public function getOptions(): Options; * @param UriFactoryInterface $uriFactory The factory * * @return $this + * + * @deprecated Since version 2.3, to be removed in 3.0 */ public function setUriFactory(UriFactoryInterface $uriFactory): self; @@ -50,6 +55,8 @@ public function setUriFactory(UriFactoryInterface $uriFactory): self; * @param MessageFactoryInterface $messageFactory The factory * * @return $this + * + * @deprecated Since version 2.3, to be removed in 3.0 */ public function setMessageFactory(MessageFactoryInterface $messageFactory): self; @@ -59,6 +66,8 @@ public function setMessageFactory(MessageFactoryInterface $messageFactory): self * @param TransportInterface $transport The transport * * @return $this + * + * @deprecated Since version 2.3, to be removed in 3.0 */ public function setTransport(TransportInterface $transport): self; @@ -68,6 +77,8 @@ public function setTransport(TransportInterface $transport): self; * @param HttpAsyncClient $httpClient The HTTP client * * @return $this + * + * @deprecated Since version 2.3, to be removed in 3.0 */ public function setHttpClient(HttpAsyncClient $httpClient): self; @@ -77,6 +88,8 @@ public function setHttpClient(HttpAsyncClient $httpClient): self; * @param PluginInterface $plugin The plugin instance * * @return $this + * + * @deprecated Since version 2.3, to be removed in 3.0 */ public function addHttpClientPlugin(PluginInterface $plugin): self; @@ -86,6 +99,8 @@ public function addHttpClientPlugin(PluginInterface $plugin): self; * @param string $className The class name * * @return $this + * + * @deprecated Since version 2.3, to be removed in 3.0 */ public function removeHttpClientPlugin(string $className): self; diff --git a/src/HttpClient/Authentication/SentryAuthentication.php b/src/HttpClient/Authentication/SentryAuthentication.php index 20a60809c..d28124c8c 100644 --- a/src/HttpClient/Authentication/SentryAuthentication.php +++ b/src/HttpClient/Authentication/SentryAuthentication.php @@ -64,7 +64,6 @@ public function authenticate(RequestInterface $request): RequestInterface $data = [ 'sentry_version' => Client::PROTOCOL_VERSION, 'sentry_client' => $this->sdkIdentifier . '/' . $this->sdkVersion, - 'sentry_timestamp' => sprintf('%F', microtime(true)), 'sentry_key' => $publicKey, ]; diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php new file mode 100644 index 000000000..898bec81d --- /dev/null +++ b/src/HttpClient/HttpClientFactory.php @@ -0,0 +1,138 @@ +uriFactory = $uriFactory; + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + $this->httpClient = $httpClient; + $this->sdkIdentifier = $sdkIdentifier; + $this->sdkVersion = $sdkVersion; + } + + /** + * {@inheritdoc} + */ + public function create(Options $options): HttpAsyncClientInterface + { + if (null === $options->getDsn()) { + throw new \RuntimeException('Cannot create an HTTP client without the Sentry DSN set in the options.'); + } + + $httpClient = $this->httpClient; + + if (null !== $httpClient && null !== $options->getHttpProxy()) { + throw new \RuntimeException('The "http_proxy" option does not work together with a custom HTTP client.'); + } + + if (null === $httpClient && null !== $options->getHttpProxy()) { + if (class_exists(GuzzleHttpClient::class)) { + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $this->httpClient = GuzzleHttpClient::createWithConfig([ + GuzzleHttpClientOptions::PROXY => $options->getHttpProxy(), + ]); + } elseif (class_exists(CurlHttpClient::class)) { + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $this->httpClient = new CurlHttpClient($this->responseFactory, $this->streamFactory, [ + CURLOPT_PROXY => $options->getHttpProxy(), + ]); + } + + throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); + } + + if (null === $httpClient) { + $httpClient = HttpAsyncClientDiscovery::find(); + } + + $httpClientPlugins = [ + new BaseUriPlugin($this->uriFactory->createUri($options->getDsn())), + new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion]), + new AuthenticationPlugin(new SentryAuthentication($options, $this->sdkIdentifier, $this->sdkVersion)), + new RetryPlugin(['retries' => $options->getSendAttempts()]), + new ErrorPlugin(), + ]; + + if ($options->isCompressionEnabled()) { + $httpClientPlugins[] = new GzipEncoderPlugin($this->streamFactory); + $httpClientPlugins[] = new DecoderPlugin(); + } + + return new PluginClient($httpClient, $httpClientPlugins); + } +} diff --git a/src/HttpClient/HttpClientFactoryInterface.php b/src/HttpClient/HttpClientFactoryInterface.php new file mode 100644 index 000000000..9a6ea7eef --- /dev/null +++ b/src/HttpClient/HttpClientFactoryInterface.php @@ -0,0 +1,24 @@ + + * + * @deprecated since version 2.3, to be removed in 3.0 + */ +final class PluggableHttpClientFactory implements HttpClientFactoryInterface +{ + /** + * @var HttpClientFactoryInterface The HTTP factory being decorated + */ + private $decoratedHttpClientFactory; + + /** + * @var HttpClientPluginInterface[] The list of plugins to add to the HTTP client + */ + private $httpClientPlugins; + + /** + * Constructor. + * + * @param HttpClientFactoryInterface $decoratedHttpClientFactory The HTTP factory being decorated + * @param HttpClientPluginInterface[] $httpClientPlugins The list of plugins to add to the HTTP client + */ + public function __construct(HttpClientFactoryInterface $decoratedHttpClientFactory, array $httpClientPlugins) + { + $this->decoratedHttpClientFactory = $decoratedHttpClientFactory; + $this->httpClientPlugins = $httpClientPlugins; + } + + /** + * {@inheritdoc} + */ + public function create(Options $options): HttpAsyncClientInterface + { + $httpClient = $this->decoratedHttpClientFactory->create($options); + + return new PluginClient($httpClient, $this->httpClientPlugins); + } +} diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php new file mode 100644 index 000000000..3c5108e74 --- /dev/null +++ b/src/Transport/DefaultTransportFactory.php @@ -0,0 +1,55 @@ +messageFactory = $messageFactory; + $this->httpClientFactory = $httpClientFactory; + } + + /** + * {@inheritdoc} + */ + public function create(Options $options): TransportInterface + { + if (null === $options->getDsn()) { + return new NullTransport(); + } + + return new HttpTransport( + $options, + $this->httpClientFactory->create($options), + $this->messageFactory, + false + ); + } +} diff --git a/src/Transport/TransportFactoryInterface.php b/src/Transport/TransportFactoryInterface.php new file mode 100644 index 000000000..b3b6454db --- /dev/null +++ b/src/Transport/TransportFactoryInterface.php @@ -0,0 +1,23 @@ +assertInstanceOf(NullTransport::class, $transport); } + /** + * @group legacy + * + * @expectedDeprecation Method Sentry\ClientBuilder::setUriFactory() is deprecated since version 2.3 and will be removed in 3.0. + */ public function testSetUriFactory(): void { /** @var UriFactory|MockObject $uriFactory */ @@ -86,6 +89,11 @@ public function testSetMessageFactory(): void $this->assertAttributeSame($messageFactory, 'requestFactory', $transport); } + /** + * @group legacy + * + * @expectedDeprecation Method Sentry\ClientBuilder::setTransport() is deprecated since version 2.3 and will be removed in 3.0. Use the setTransportFactory() method instead. + */ public function testSetTransport(): void { /** @var TransportInterface|MockObject $transport */ @@ -118,6 +126,11 @@ public function testSetHttpClient(): void $this->assertAttributeSame($httpClient, 'client', $this->getObjectAttribute($transport, 'httpClient')); } + /** + * @group legacy + * + * @expectedDeprecationMessage Method Sentry\ClientBuilder::addHttpClientPlugin() is deprecated since version 2.3 and will be removed in 3.0. + */ public function testAddHttpClientPlugin(): void { /** @var Plugin|MockObject $plugin */ @@ -132,6 +145,12 @@ public function testAddHttpClientPlugin(): void $this->assertSame($plugin, $plugins[0]); } + /** + * @group legacy + * + * @expectedDeprecation Method Sentry\ClientBuilder::addHttpClientPlugin() is deprecated since version 2.3 and will be removed in 3.0. + * @expectedDeprecation Method Sentry\ClientBuilder::removeHttpClientPlugin() is deprecated since version 2.3 and will be removed in 3.0. + */ public function testRemoveHttpClientPlugin(): void { $plugin = new PluginStub1(); @@ -169,10 +188,6 @@ public function testGetClient(): void $this->assertAttributeSame($this->getObjectAttribute($clientBuilder, 'messageFactory'), 'requestFactory', $transport); $this->assertAttributeInstanceOf(PluginClient::class, 'httpClient', $transport); - - $httpClientPlugin = $this->getObjectAttribute($transport, 'httpClient'); - - $this->assertAttributeSame($this->getObjectAttribute($clientBuilder, 'httpClient'), 'client', $httpClientPlugin); } /** @@ -274,46 +289,6 @@ public function testClientBuilderSetsSdkIdentifierAndVersion(): void $this->assertTrue($callbackCalled, 'Callback not invoked, no assertions performed'); } - /** - * @group legacy - * - * @dataProvider getClientTogglesCompressionPluginInHttpClientDataProvider - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ - public function testGetClientTogglesCompressionPluginInHttpClient(bool $enabled): void - { - $options = new Options([ - 'dsn' => 'http://public:secret@example.com/sentry/1', - 'enable_compression' => $enabled, - ]); - - $builder = new ClientBuilder($options); - $builder->getClient(); - - $encoderPluginFound = false; - $decoderPluginFound = false; - - foreach ($this->getObjectAttribute($builder, 'httpClientPlugins') as $plugin) { - if ($plugin instanceof GzipEncoderPlugin) { - $encoderPluginFound = true; - } elseif ($plugin instanceof DecoderPlugin) { - $decoderPluginFound = true; - } - } - - $this->assertSame($enabled, $encoderPluginFound); - $this->assertSame($enabled, $decoderPluginFound); - } - - public function getClientTogglesCompressionPluginInHttpClientDataProvider(): array - { - return [ - [true], - [false], - ]; - } - public function testCreateWithNoOptionsIsTheSameAsDefaultOptions(): void { $this->assertEquals( @@ -321,22 +296,6 @@ public function testCreateWithNoOptionsIsTheSameAsDefaultOptions(): void ClientBuilder::create([]) ); } - - public function testCreateWithHttpProxyAndCustomTransportThrowsException(): void - { - $options = new Options([ - 'dsn' => 'http://public:secret@example.com/sentry/1', - 'http_proxy' => 'some-proxy', - ]); - - $clientBuilder = new ClientBuilder($options); - $clientBuilder->setHttpClient($this->createMock(HttpAsyncClient::class)); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('The `http_proxy` option does not work together with a custom client.'); - - $clientBuilder->getClient(); - } } final class StubIntegration implements IntegrationInterface diff --git a/tests/ClientTest.php b/tests/ClientTest.php index e12e90c9c..31090b739 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,21 +10,19 @@ use PHPUnit\Framework\TestCase; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\EventFactory; use Sentry\Options; -use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\Serializer; -use Sentry\Serializer\SerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\Transport\HttpTransport; +use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; class ClientTest extends TestCase { public function testCaptureMessage(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -36,7 +34,7 @@ public function testCaptureMessage(): void })); $client = ClientBuilder::create() - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); $client->captureMessage('foo', Severity::fatal()); @@ -46,7 +44,7 @@ public function testCaptureException(): void { $exception = new \Exception('Some foo error'); - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -62,7 +60,7 @@ public function testCaptureException(): void })); $client = ClientBuilder::create() - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); $client->captureException($exception); @@ -74,6 +72,7 @@ public function testCaptureException(): void public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches(bool $shouldCapture, string $excluded, \Throwable $thrown): void { $transport = $this->createMock(TransportInterface::class); + $transportFactory = $this->createTransportFactory($transport); $transport->expects($shouldCapture ? $this->once() : $this->never()) ->method('send') @@ -84,7 +83,7 @@ public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches })); $client = ClientBuilder::create(['excluded_exceptions' => [$excluded]]) - ->setTransport($transport) + ->setTransportFactory($transportFactory) ->getClient(); $client->captureException($thrown); @@ -113,14 +112,14 @@ public function captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesData public function testCapture(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') ->willReturn('500a339f3ab2450b96dee542adf36ba7'); $client = ClientBuilder::create() - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); $inputData = [ @@ -137,7 +136,7 @@ public function testCapture(): void public function testCaptureLastError(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -151,7 +150,7 @@ public function testCaptureLastError(): void })); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); @trigger_error('foo', E_USER_NOTICE); @@ -163,13 +162,13 @@ public function testCaptureLastError(): void public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) ->method('send'); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); $this->clearLastError(); @@ -202,7 +201,7 @@ public function testSendChecksBeforeSendOption(): void { $beforeSendCalled = false; - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) ->method('send'); @@ -215,7 +214,7 @@ public function testSendChecksBeforeSendOption(): void }); $client = (new ClientBuilder($options)) - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); $client->captureEvent([]); @@ -231,9 +230,10 @@ public function testSampleRateAbsolute(float $sampleRate): void $httpClient = new MockClient(); $options = new Options(['dsn' => 'http://public:secret@example.com/1']); $options->setSampleRate($sampleRate); + $transportFactory = $this->createTransportFactory(new HttpTransport($options, $httpClient, MessageFactoryDiscovery::find(), false)); $client = (new ClientBuilder($options)) - ->setTransport(new HttpTransport($options, $httpClient, MessageFactoryDiscovery::find(), false)) + ->setTransportFactory($transportFactory) ->getClient(); for ($i = 0; $i < 10; ++$i) { @@ -263,7 +263,7 @@ public function sampleRateAbsoluteDataProvider(): array */ public function testConvertException(\Exception $exception, array $expectedResult): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -281,7 +281,7 @@ public function testConvertException(\Exception $exception, array $expectedResul })); $client = ClientBuilder::create() - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); $client->captureException($exception); @@ -323,7 +323,7 @@ public function convertExceptionDataProvider(): array public function testConvertExceptionThrownInLatin1File(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -358,7 +358,7 @@ public function testConvertExceptionThrownInLatin1File(): void $serializer->setMbDetectOrder('ISO-8859-1, ASCII, UTF-8'); $client = ClientBuilder::create() - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->setSerializer($serializer) ->getClient(); @@ -367,7 +367,7 @@ public function testConvertExceptionThrownInLatin1File(): void public function testAttachStacktrace(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -386,7 +386,7 @@ public function testAttachStacktrace(): void })); $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); $client->captureMessage('test'); @@ -394,7 +394,7 @@ public function testAttachStacktrace(): void public function testAttachStacktraceShouldNotWorkWithCaptureEvent(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -405,7 +405,7 @@ public function testAttachStacktraceShouldNotWorkWithCaptureEvent(): void })); $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransport($transport) + ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); $client->captureEvent([]); @@ -416,23 +416,32 @@ public function testAttachStacktraceShouldNotWorkWithCaptureEvent(): void */ private function clearLastError(): void { - $handler = static function () { + set_error_handler(static function (): bool { return false; - }; + }); - set_error_handler($handler); @trigger_error(''); + restore_error_handler(); } - private function createEventFactory(): EventFactory + private function createTransportFactory(TransportInterface $transport): TransportFactoryInterface { - return new EventFactory( - $this->createMock(SerializerInterface::class), - $this->createMock(RepresentationSerializerInterface::class), - new Options(), - 'sentry.sdk.identifier', - '1.2.3' - ); + return new class($transport) implements TransportFactoryInterface { + /** + * @var TransportInterface + */ + private $transport; + + public function __construct(TransportInterface $transport) + { + $this->transport = $transport; + } + + public function create(Options $options): TransportInterface + { + return $this->transport; + } + }; } } diff --git a/tests/HttpClient/Authentication/SentryAuthenticationTest.php b/tests/HttpClient/Authentication/SentryAuthenticationTest.php index b99af9395..153577adb 100644 --- a/tests/HttpClient/Authentication/SentryAuthenticationTest.php +++ b/tests/HttpClient/Authentication/SentryAuthenticationTest.php @@ -12,9 +12,6 @@ use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\Options; -/** - * @group time-sensitive - */ final class SentryAuthenticationTest extends TestCase { public function testAuthenticate(): void @@ -31,10 +28,9 @@ public function testAuthenticate(): void ->getMock(); $headerValue = sprintf( - 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=public, sentry_secret=secret', + 'Sentry sentry_version=%s, sentry_client=%s, sentry_key=public, sentry_secret=secret', Client::PROTOCOL_VERSION, - 'sentry.php.test/1.2.3', - microtime(true) + 'sentry.php.test/1.2.3' ); $request->expects($this->once()) @@ -59,10 +55,9 @@ public function testAuthenticateWithNoSecretKey(): void ->getMock(); $headerValue = sprintf( - 'Sentry sentry_version=%s, sentry_client=%s, sentry_timestamp=%F, sentry_key=public', + 'Sentry sentry_version=%s, sentry_client=%s, sentry_key=public', Client::PROTOCOL_VERSION, - 'sentry.php.test/2.0.0', - microtime(true) + 'sentry.php.test/2.0.0' ); $request->expects($this->once()) diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php new file mode 100644 index 000000000..f261efd2d --- /dev/null +++ b/tests/HttpClient/HttpClientFactoryTest.php @@ -0,0 +1,102 @@ +create(new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + 'default_integrations' => false, + 'enable_compression' => $isCompressionEnabled, + ])); + + $httpClient->sendAsyncRequest(MessageFactoryDiscovery::find()->createRequest('POST', '/foo', [], 'foo bar')); + + /** @var RequestInterface|bool $httpRequest */ + $httpRequest = $mockHttpClient->getLastRequest(); + + $this->assertInstanceOf(RequestInterface::class, $httpRequest); + $this->assertSame('http://example.com/sentry/foo', (string) $httpRequest->getUri()); + $this->assertSame('sentry.php.test/1.2.3', (string) $httpRequest->getHeaderLine('User-Agent')); + $this->assertSame('Sentry sentry_version=7, sentry_client=sentry.php.test/1.2.3, sentry_key=public', (string) $httpRequest->getHeaderLine('X-Sentry-Auth')); + $this->assertSame($expectedRequestBody, (string) $httpRequest->getBody()); + } + + public function createDataProvider(): \Generator + { + yield [ + false, + 'foo bar', + ]; + + yield [ + true, + gzcompress('foo bar', -1, ZLIB_ENCODING_GZIP), + ]; + } + + public function testCreateThrowsIfDsnOptionIsNotConfigured(): void + { + $httpClientFactory = new HttpClientFactory( + UriFactoryDiscovery::find(), + MessageFactoryDiscovery::find(), + StreamFactoryDiscovery::find(), + null, + 'sentry.php.test', + '1.2.3' + ); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Cannot create an HTTP client without the Sentry DSN set in the options.'); + + $httpClientFactory->create(new Options(['default_integrations' => false])); + } + + public function testCreateThrowsIfHttpProxyOptionIsUsedWithCustomHttpClient(): void + { + $httpClientFactory = new HttpClientFactory( + UriFactoryDiscovery::find(), + MessageFactoryDiscovery::find(), + StreamFactoryDiscovery::find(), + $this->createMock(HttpAsyncClientInterface::class), + 'sentry.php.test', + '1.2.3' + ); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The "http_proxy" option does not work together with a custom HTTP client.'); + + $httpClientFactory->create(new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + 'default_integrations' => false, + 'http_proxy' => 'http://example.com', + ])); + } +} diff --git a/tests/HttpClient/PluggableHttpClientFactoryTest.php b/tests/HttpClient/PluggableHttpClientFactoryTest.php new file mode 100644 index 000000000..797850d52 --- /dev/null +++ b/tests/HttpClient/PluggableHttpClientFactoryTest.php @@ -0,0 +1,54 @@ +createMock(HttpAsyncClientInterface::class); + $wrappedHttpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willReturn($this->createMock(PromiseInterface::class)); + + /** @var HttpClientPluginInterface&MockObject $httpClientPlugin */ + $httpClientPlugin = $this->createMock(HttpClientPluginInterface::class); + $httpClientPlugin->expects($this->once()) + ->method('handleRequest') + ->willReturnCallback(static function (RequestInterface $request, callable $next): PromiseInterface { + return $next($request); + }); + + $httpClientFactory = new class($wrappedHttpClient) implements HttpClientFactoryInterface { + private $httpClient; + + public function __construct(HttpAsyncClientInterface $httpClient) + { + $this->httpClient = $httpClient; + } + + public function create(Options $options): HttpAsyncClientInterface + { + return $this->httpClient; + } + }; + + $httpClientFactory = new PluggableHttpClientFactory($httpClientFactory, [$httpClientPlugin]); + $httpClient = $httpClientFactory->create(new Options(['default_integrations' => false])); + + $httpClient->sendAsyncRequest($this->createMock(RequestInterface::class)); + } +} diff --git a/tests/Transport/DefaultTransportFactoryTest.php b/tests/Transport/DefaultTransportFactoryTest.php new file mode 100644 index 000000000..f3ceee135 --- /dev/null +++ b/tests/Transport/DefaultTransportFactoryTest.php @@ -0,0 +1,44 @@ +createMock(HttpClientFactoryInterface::class) + ); + + $this->assertInstanceOf(NullTransport::class, $factory->create(new Options())); + } + + public function testCreateReturnsHttpTransportWhenDsnOptionIsConfigured(): void + { + $options = new Options(['dsn' => 'http://public@example.com/sentry/1']); + + /** @var HttpClientFactoryInterface&MockObject $clientFactory */ + $clientFactory = $this->createMock(HttpClientFactoryInterface::class); + $clientFactory->expects($this->once()) + ->method('create') + ->with($options) + ->willReturn($this->createMock(HttpAsyncClientInterface::class)); + + $factory = new DefaultTransportFactory(MessageFactoryDiscovery::find(), $clientFactory); + + $this->assertInstanceOf(HttpTransport::class, $factory->create($options)); + } +} From 217c9e550eb3e06bbe48a4da4031223e1aab76f0 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 10 Oct 2019 10:16:00 +0200 Subject: [PATCH 0501/1161] meta: Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4f5358d2..7f18e9483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.2.2 (2019-10-10) + - Fix handling of fifth argument in the error handler (#892) - Catch exception from vendors in `Sentry\Transport\HttpTransport` (#899) From be044bfa83ff98c5dd0ea0a7ff243ff0eade721c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 14 Oct 2019 11:09:40 +0200 Subject: [PATCH 0502/1161] Handle callable typehint throwing possible errors while triggering autoload (#821) --- CHANGELOG.md | 2 ++ src/Serializer/AbstractSerializer.php | 39 ++++++++++++++++++--- src/Stacktrace.php | 5 +-- tests/Serializer/AbstractSerializerTest.php | 6 +++- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f18e9483..a2e1deefd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix deprecation raised when serializing callable in certain circumstances (#821) + ## 2.2.2 (2019-10-10) - Fix handling of fifth argument in the error handler (#892) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index a5e316649..76e637080 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -20,6 +20,7 @@ namespace Sentry\Serializer; +use Sentry\Exception\InvalidArgumentException; use Sentry\Options; /** @@ -102,7 +103,7 @@ protected function serializeRecursively($value, int $_depth = 0) } if (\is_callable($value)) { - return $this->serializeCallable($value); + return $this->serializeCallableWithoutTypeHint($value); } if (\is_array($value)) { @@ -250,7 +251,7 @@ protected function serializeValue($value) } if (\is_callable($value)) { - return $this->serializeCallable($value); + return $this->serializeCallableWithoutTypeHint($value); } if (\is_array($value)) { @@ -261,7 +262,37 @@ protected function serializeValue($value) } /** - * @param callable $callable + * This method is provided as a non-BC upgrade of serializeCallable, + * since using the callable type raises a deprecation in some cases. + * + * @param callable|mixed $callable + * + * @return string + */ + protected function serializeCallableWithoutTypeHint($callable): string + { + if (\is_string($callable) && !\function_exists($callable)) { + return $callable; + } + + if (!\is_callable($callable)) { + throw new InvalidArgumentException(sprintf( + 'Expecting callable, got %s', + \is_object($callable) + ? \get_class($callable) + : \gettype($callable) + )); + } + + return $this->serializeCallable($callable); + } + + /** + * Use serializeCallableWithoutTypeHint instead (no type in argument). + * + * @see https://github.com/getsentry/sentry-php/pull/821 + * + * @param callable $callable callable type to be removed in 3.0, see #821 * * @return string */ @@ -271,7 +302,7 @@ protected function serializeCallable(callable $callable): string if (\is_array($callable)) { $reflection = new \ReflectionMethod($callable[0], $callable[1]); $class = $reflection->getDeclaringClass(); - } elseif ($callable instanceof \Closure || \is_string($callable)) { + } elseif ($callable instanceof \Closure || (\is_string($callable) && \function_exists($callable))) { $reflection = new \ReflectionFunction($callable); $class = null; } elseif (\is_object($callable) && method_exists($callable, '__invoke')) { diff --git a/src/Stacktrace.php b/src/Stacktrace.php index fae71c531..3e7262cc3 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -310,8 +310,9 @@ protected function getFrameArgumentsValues(array $frame): array if (\is_string(array_keys($frame['args'])[0])) { $result = array_map([$this, 'serializeArgument'], $frame['args']); } else { - foreach (array_values($frame['args']) as $index => $argument) { - $result['param' . ($index + 1)] = $this->serializeArgument($argument); + $index = 0; + foreach (array_values($frame['args']) as $argument) { + $result['param' . (++$index)] = $this->serializeArgument($argument); } } diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index a794005cb..811b08afa 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -455,13 +455,17 @@ public function serializableCallableProvider(): array 'callable' => $callableWithoutNamespaces, 'expected' => 'Lambda void {closure} [int|null param1_70ns]', ], + [ + 'callable' => __METHOD__, + 'expected' => __METHOD__, + ], ]; } /** * @dataProvider serializableCallableProvider */ - public function testSerializeCallable(callable $callable, string $expected): void + public function testSerializeCallable($callable, string $expected): void { $serializer = $this->createSerializer(); $actual = $this->invokeSerialization($serializer, $callable); From fad86b6870fb37c5dbf232ddd108c892d14b745f Mon Sep 17 00:00:00 2001 From: Petr Levtonov Date: Thu, 17 Oct 2019 09:13:42 +0200 Subject: [PATCH 0503/1161] #901: Replace critical with fatal status (#902) * #901: Replace critical with fatal status Critical breadcrumb status is not supported. Replaced it with fatal. * Consistent deprecation comment Use consistent deprecation comment. Co-Authored-By: Alessandro Lai * #901: Update CHANGELOG.md --- CHANGELOG.md | 1 + src/Breadcrumb.php | 10 +++++++++- tests/State/ScopeTest.php | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2e1deefd..5a0762eb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix deprecation raised when serializing callable in certain circumstances (#821) +- Replace `critical` (deprecated) with `fatal` breadcrumb status (#901) ## 2.2.2 (2019-10-10) diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index 292e3b7d1..3532727e4 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -60,9 +60,16 @@ final class Breadcrumb implements \JsonSerializable /** * This constant defines the critical level for a breadcrumb. + * + * @deprecated since version 2.2.2, to be removed in 3.0; use fatal instead. */ public const LEVEL_CRITICAL = 'critical'; + /** + * This constant defines the fatal level for a breadcrumb. + */ + public const LEVEL_FATAL = 'fatal'; + /** * This constant defines the list of values allowed to be set as severity * level of the breadcrumb. @@ -73,6 +80,7 @@ final class Breadcrumb implements \JsonSerializable self::LEVEL_WARNING, self::LEVEL_ERROR, self::LEVEL_CRITICAL, + self::LEVEL_FATAL, ]; /** @@ -151,7 +159,7 @@ public static function levelFromErrorException(\ErrorException $exception): stri case E_CORE_WARNING: case E_COMPILE_ERROR: case E_COMPILE_WARNING: - return self::LEVEL_CRITICAL; + return self::LEVEL_FATAL; case E_USER_ERROR: return self::LEVEL_ERROR; case E_NOTICE: diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 51a9ace52..57d2d7c92 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -286,7 +286,7 @@ public function testApplyToEvent(): void $this->assertEquals(['foo' => 'baz'], $event->getUserContext()->toArray()); $scope->setFingerprint(['foo', 'bar']); - $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_CRITICAL, Breadcrumb::TYPE_ERROR, 'error_reporting')); + $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_FATAL, Breadcrumb::TYPE_ERROR, 'error_reporting')); $scope->setLevel(Severity::fatal()); $scope->setTag('bar', 'foo'); $scope->setExtra('foo', 'bar'); From 3ea3d3dc8ba998b02f4c775c27f51c54aa049854 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 18 Oct 2019 10:10:39 +0200 Subject: [PATCH 0504/1161] Fix regression on default sending behavior of the HttpTransport transport (#905) --- CHANGELOG.md | 3 +- src/ClientBuilder.php | 1 + src/Transport/HttpTransport.php | 48 +++++++++++++++++++++------ tests/ClientBuilderTest.php | 31 ----------------- tests/Transport/HttpTransportTest.php | 5 +-- 5 files changed, 42 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a0762eb5..63ac6eaad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## Unreleased - Fix deprecation raised when serializing callable in certain circumstances (#821) -- Replace `critical` (deprecated) with `fatal` breadcrumb status (#901) +- Fix incorrect `critical` breadcrumb level by replacing it with the `fatal` level (#901) +- Fix regression on default sending behavior of the `HttpTransport` transport (#905) ## 2.2.2 (2019-10-10) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 4a94cdc18..b732f25a2 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -349,6 +349,7 @@ private function createTransportInstance(): TransportInterface $this->options, $this->createHttpClientInstance(), $this->messageFactory, + true, false ); } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 164485191..19bd948a6 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -4,9 +4,11 @@ namespace Sentry\Transport; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Message\RequestFactory as RequestFactoryInterface; -use Http\Promise\Promise as PromiseInterface; +use Http\Promise\Promise as HttpPromiseInterface; use Sentry\Event; use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; @@ -18,7 +20,7 @@ * * @author Stefano Arlandini */ -final class HttpTransport implements TransportInterface +final class HttpTransport implements TransportInterface, ClosableTransportInterface { /** * @var Options The Raven client configuration @@ -36,7 +38,7 @@ final class HttpTransport implements TransportInterface private $requestFactory; /** - * @var PromiseInterface[] The list of pending promises + * @var HttpPromiseInterface[] The list of pending promises */ private $pendingRequests = []; @@ -54,12 +56,21 @@ final class HttpTransport implements TransportInterface * @param RequestFactoryInterface $requestFactory The PSR-7 request factory * @param bool $delaySendingUntilShutdown This flag controls whether to delay * sending of the events until the shutdown - * of the application. This is a legacy feature - * that will stop working in version 3.0. + * of the application + * @param bool $triggerDeprecation Flag controlling whether to throw + * a deprecation if the transport is + * used relying on the deprecated behavior + * of delaying the sending of the events + * until the shutdown of the application */ - public function __construct(Options $config, HttpAsyncClientInterface $httpClient, RequestFactoryInterface $requestFactory, bool $delaySendingUntilShutdown = true) - { - if ($delaySendingUntilShutdown) { + public function __construct( + Options $config, + HttpAsyncClientInterface $httpClient, + RequestFactoryInterface $requestFactory, + bool $delaySendingUntilShutdown = true, + bool $triggerDeprecation = true + ) { + if ($delaySendingUntilShutdown && $triggerDeprecation) { @trigger_error(sprintf('Delaying the sending of the events using the "%s" class is deprecated since version 2.2 and will not work in 3.0.', __CLASS__), E_USER_DEPRECATED); } @@ -108,7 +119,7 @@ public function send(Event $event): ?string } else { try { $promise->wait(); - } catch (\Exception $exception) { + } catch (\Throwable $exception) { return null; } } @@ -116,16 +127,33 @@ public function send(Event $event): ?string return $event->getId(); } + /** + * {@inheritdoc} + */ + public function close(?int $timeout = null): PromiseInterface + { + $this->cleanupPendingRequests(); + + return new FulfilledPromise(true); + } + /** * Cleanups the pending promises by awaiting for them. Any error that occurs * will be ignored. + * + * @deprecated since version 2.2.3, to be removed in 3.0. Even though this + * method is `private` we cannot delete it because it's used + * in some old versions of the `sentry-laravel` package using + * tricky code involving reflection and Closure binding */ private function cleanupPendingRequests(): void { while ($promise = array_pop($this->pendingRequests)) { try { $promise->wait(); - } catch (\Exception $exception) { + } catch (\Throwable $exception) { + // Do nothing because we don't want to break applications while + // trying to send events } } } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 6a9504b9a..2460a02c1 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -32,18 +32,6 @@ final class ClientBuilderTest extends TestCase { - public function testCreate(): void - { - $clientBuilder = ClientBuilder::create(); - - $this->assertInstanceOf(ClientBuilder::class, $clientBuilder); - } - - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ public function testHttpTransportIsUsedWhenServerIsConfigured(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -73,11 +61,6 @@ public function testSetUriFactory(): void $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); } - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ public function testSetMessageFactory(): void { /** @var MessageFactory|MockObject $messageFactory */ @@ -105,11 +88,6 @@ public function testSetTransport(): void $this->assertAttributeSame($transport, 'transport', $clientBuilder->getClient()); } - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ public function testSetHttpClient(): void { /** @var HttpAsyncClient|MockObject $httpClient */ @@ -159,11 +137,6 @@ public function testRemoveHttpClientPlugin(): void $this->assertSame($plugin2, reset($plugins)); } - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ public function testGetClient(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -282,11 +255,7 @@ public function testClientBuilderSetsSdkIdentifierAndVersion(): void } /** - * @group legacy - * * @dataProvider getClientTogglesCompressionPluginInHttpClientDataProvider - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. */ public function testGetClientTogglesCompressionPluginInHttpClient(bool $enabled): void { diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 928e451a3..5b636fbbe 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -41,10 +41,7 @@ public function testSendDelaysExecutionUntilShutdown(): void $this->assertAttributeNotEmpty('pendingRequests', $transport); - $reflectionMethod = new \ReflectionMethod(HttpTransport::class, 'cleanupPendingRequests'); - $reflectionMethod->setAccessible(true); - $reflectionMethod->invoke($transport); - $reflectionMethod->setAccessible(false); + $transport->close(); $this->assertAttributeEmpty('pendingRequests', $transport); } From c38ac644b75f66c3cd70e1a5b7121ea4e2baa937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ch=C3=A1bek?= Date: Tue, 29 Oct 2019 14:02:08 +0100 Subject: [PATCH 0505/1161] Fix frame inApp detection (#911) --- CHANGELOG.md | 1 + src/Stacktrace.php | 4 +++- tests/StacktraceTest.php | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ac6eaad..e256755b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix deprecation raised when serializing callable in certain circumstances (#821) - Fix incorrect `critical` breadcrumb level by replacing it with the `fatal` level (#901) - Fix regression on default sending behavior of the `HttpTransport` transport (#905) +- Fix stacktrace frame inApp detection: all paths outside the project_root are now considered as not in app (#911) ## 2.2.2 (2019-10-10) diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 3e7262cc3..894d6a5c5 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -148,7 +148,9 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void $absoluteFilePath = @realpath($file) ?: $file; $isApplicationFile = 0 === strpos($absoluteFilePath, $this->options->getProjectRoot()); - if ($isApplicationFile && !empty($excludedAppPaths)) { + if (!$isApplicationFile) { + $frame->setIsInApp(false); + } elseif (!empty($excludedAppPaths)) { foreach ($excludedAppPaths as $path) { if (0 === mb_strpos($absoluteFilePath, $path)) { $frame->setIsInApp(false); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 08222be4a..ba60068ec 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -136,11 +136,13 @@ public function testAddFrameMarksAsInApp(): void $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function']); $stacktrace->addFrame('path/to/excluded/path/to/file', 12, ['function' => 'test_function']); + $stacktrace->addFrame('path/elsewhere', 12, ['function' => 'test_function']); $frames = $stacktrace->getFrames(); $this->assertFalse($frames[0]->isInApp()); - $this->assertTrue($frames[1]->isInApp()); + $this->assertFalse($frames[1]->isInApp()); + $this->assertTrue($frames[2]->isInApp()); } /** From 71abca33b75f6bafb8b3829a620ff9ea5e5c1d9b Mon Sep 17 00:00:00 2001 From: HazA Date: Thu, 31 Oct 2019 12:08:21 +0100 Subject: [PATCH 0506/1161] meta: Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e256755b4..249cbf67d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.2.3 (2019-10-31) + - Fix deprecation raised when serializing callable in certain circumstances (#821) - Fix incorrect `critical` breadcrumb level by replacing it with the `fatal` level (#901) - Fix regression on default sending behavior of the `HttpTransport` transport (#905) From 152117acbde6be2df0247d18dfa9651183c814da Mon Sep 17 00:00:00 2001 From: Petr Levtonov Date: Fri, 1 Nov 2019 22:25:34 +0100 Subject: [PATCH 0507/1161] Add Monolog to the suggested packages in composer.json (#916) --- CHANGELOG.md | 2 ++ composer.json | 3 +++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 249cbf67d..4a2bd2362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Suggest installing Monolog to send log messages directly to Sentry (#908) + ## 2.2.3 (2019-10-31) - Fix deprecation raised when serializing callable in certain circumstances (#821) diff --git a/composer.json b/composer.json index 904aa910b..79c8deaba 100644 --- a/composer.json +++ b/composer.json @@ -38,6 +38,9 @@ "symfony/phpunit-bridge": "^4.3", "vimeo/psalm": "^3.4" }, + "suggest": { + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." + }, "conflict": { "php-http/client-common": "1.8.0", "raven/raven": "*" From b5e180b62e3adad45bba1c9be5f1560fb0f5f0f9 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 2 Nov 2019 22:45:08 +0100 Subject: [PATCH 0508/1161] Make the $errcontext argument of the ErrorHandler::handleError() method nullable (#917) --- CHANGELOG.md | 1 + src/ErrorHandler.php | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2bd2362..e06aa8ada 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Suggest installing Monolog to send log messages directly to Sentry (#908) +- Make the `$errcontext` argument of the `ErrorHandler::handleError()` method `nullable` (#917) ## 2.2.3 (2019-10-31) diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 7a19bc140..6716404c7 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -357,19 +357,19 @@ public function addExceptionHandlerListener(callable $listener): void * Handles errors by capturing them through the Raven client according to * the configured bit field. * - * @param int $level The level of the error raised, represented by - * one of the E_* constants - * @param string $message The error message - * @param string $file The filename the error was raised in - * @param int $line The line number the error was raised at - * @param array $errcontext The error context (deprecated since PHP 7.2) + * @param int $level The level of the error raised, represented by + * one of the E_* constants + * @param string $message The error message + * @param string $file The filename the error was raised in + * @param int $line The line number the error was raised at + * @param array|null $errcontext The error context (deprecated since PHP 7.2) * * @return bool If the function returns `false` then the PHP native error * handler will be called * * @throws \Throwable */ - private function handleError(int $level, string $message, string $file, int $line, array $errcontext = []): bool + private function handleError(int $level, string $message, string $file, int $line, ?array $errcontext = []): bool { if (0 === error_reporting()) { $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); From 461679905e411b8076c1c2640bc80a1e47b51530 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 4 Nov 2019 11:25:02 +0100 Subject: [PATCH 0509/1161] meta: Changelog 2.2.4 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e06aa8ada..8ab3b511f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.2.4 (2019-11-04) + - Suggest installing Monolog to send log messages directly to Sentry (#908) - Make the `$errcontext` argument of the `ErrorHandler::handleError()` method `nullable` (#917) From a74999536b9119257cb1a4b1aa038e4a08439f67 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 4 Nov 2019 11:30:51 +0100 Subject: [PATCH 0510/1161] fix: CS --- src/Breadcrumb.php | 16 ------ src/ClientBuilder.php | 6 -- src/ClientBuilderInterface.php | 4 -- src/ClientInterface.php | 12 ---- src/Context/Context.php | 4 -- src/Context/RuntimeContext.php | 4 -- src/Context/ServerOsContext.php | 8 --- src/Context/TagsContext.php | 2 - src/Context/UserContext.php | 8 --- src/ErrorHandler.php | 10 ---- src/Event.php | 46 --------------- src/EventFactoryInterface.php | 4 -- src/FlushableClientInterface.php | 2 - src/Frame.php | 14 ----- src/Integration/RequestIntegration.php | 2 - src/Monolog/Handler.php | 2 - src/Options.php | 60 -------------------- src/SentrySdk.php | 6 -- src/Serializer/AbstractSerializer.php | 34 +---------- src/Serializer/SerializableInterface.php | 2 - src/Severity.php | 12 ---- src/Stacktrace.php | 8 --- src/State/Hub.php | 4 -- src/State/HubAdapter.php | 2 - src/State/HubInterface.php | 18 ------ src/State/Layer.php | 4 -- src/State/Scope.php | 2 - src/Transport/ClosableTransportInterface.php | 2 - src/Transport/SpoolTransport.php | 2 - src/Util/JSON.php | 2 - src/Util/PHPVersion.php | 2 - src/functions.php | 8 --- 32 files changed, 2 insertions(+), 310 deletions(-) diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index 3532727e4..2809577d7 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -141,8 +141,6 @@ public function __construct(string $level, string $type, string $category, ?stri * breadcrumbs. * * @param \ErrorException $exception The exception - * - * @return string */ public static function levelFromErrorException(\ErrorException $exception): string { @@ -173,8 +171,6 @@ public static function levelFromErrorException(\ErrorException $exception): stri /** * Gets the breadcrumb type. - * - * @return string */ public function getType(): string { @@ -202,8 +198,6 @@ public function withType(string $type): self /** * Gets the breadcrumb level. - * - * @return string */ public function getLevel(): string { @@ -235,8 +229,6 @@ public function withLevel(string $level): self /** * Gets the breadcrumb category. - * - * @return string */ public function getCategory(): string { @@ -264,8 +256,6 @@ public function withCategory(string $category): self /** * Gets the breadcrumb message. - * - * @return string|null */ public function getMessage(): ?string { @@ -293,8 +283,6 @@ public function withMessage(string $message): self /** * Gets the breadcrumb meta data. - * - * @return array */ public function getMetadata(): array { @@ -345,8 +333,6 @@ public function withoutMetadata(string $name): self /** * Gets the breadcrumb timestamp. - * - * @return float */ public function getTimestamp(): float { @@ -355,8 +341,6 @@ public function getTimestamp(): float /** * Gets the breadcrumb as an array. - * - * @return array */ public function toArray(): array { diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index b732f25a2..4a8d67edb 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -273,8 +273,6 @@ public function getClient(): ClientInterface /** * Creates a new instance of the HTTP client. - * - * @return PluginClient */ private function createHttpClientInstance(): PluginClient { @@ -310,8 +308,6 @@ private function createHttpClientInstance(): PluginClient /** * Creates a new instance of the transport mechanism. - * - * @return TransportInterface */ private function createTransportInstance(): TransportInterface { @@ -356,8 +352,6 @@ private function createTransportInstance(): TransportInterface /** * Instantiate the {@see EventFactory} with the configured serializers. - * - * @return EventFactoryInterface */ private function createEventFactory(): EventFactoryInterface { diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 5a97eb466..c677f6ed0 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -30,8 +30,6 @@ public static function create(array $options = []): self; /** * The options that will be used to create the {@see Client}. - * - * @return Options */ public function getOptions(): Options; @@ -91,8 +89,6 @@ public function removeHttpClientPlugin(string $className): self; /** * Gets the instance of the client built using the configured options. - * - * @return ClientInterface */ public function getClient(): ClientInterface; diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 9b4a8950a..5937d3f15 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -16,8 +16,6 @@ interface ClientInterface { /** * Returns the options of the client. - * - * @return Options */ public function getOptions(): Options; @@ -27,8 +25,6 @@ public function getOptions(): Options; * @param string $message The message (primary description) for the event * @param Severity $level The level of the message to be sent * @param Scope|null $scope An optional scope keeping the state - * - * @return string|null */ public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string; @@ -37,8 +33,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope * * @param \Throwable $exception The exception object * @param Scope|null $scope An optional scope keeping the state - * - * @return string|null */ public function captureException(\Throwable $exception, ?Scope $scope = null): ?string; @@ -46,8 +40,6 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? * Logs the most recent error (obtained with {@link error_get_last}). * * @param Scope|null $scope An optional scope keeping the state - * - * @return string|null */ public function captureLastError(?Scope $scope = null): ?string; @@ -56,8 +48,6 @@ public function captureLastError(?Scope $scope = null): ?string; * * @param array $payload The data of the event being captured * @param Scope|null $scope An optional scope keeping the state - * - * @return string|null */ public function captureEvent(array $payload, ?Scope $scope = null): ?string; @@ -65,8 +55,6 @@ public function captureEvent(array $payload, ?Scope $scope = null): ?string; * Returns the integration instance if it is installed on the Client. * * @param string $className the classname of the integration - * - * @return IntegrationInterface|null */ public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/src/Context/Context.php b/src/Context/Context.php index 4a045df37..92cdec7cb 100644 --- a/src/Context/Context.php +++ b/src/Context/Context.php @@ -107,8 +107,6 @@ public function clear(): void /** * Checks whether the object is not storing any data. - * - * @return bool */ public function isEmpty(): bool { @@ -117,8 +115,6 @@ public function isEmpty(): bool /** * Returns an array representation of the data stored by the object. - * - * @return array */ public function toArray(): array { diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 1bddb653a..91dd25860 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -101,8 +101,6 @@ public function offsetSet($offset, $value): void /** * Gets the name of the runtime. - * - * @return string */ public function getName(): string { @@ -121,8 +119,6 @@ public function setName(string $name): void /** * Gets the version of the runtime. - * - * @return string */ public function getVersion(): string { diff --git a/src/Context/ServerOsContext.php b/src/Context/ServerOsContext.php index 1639d5fb4..41bd04c97 100644 --- a/src/Context/ServerOsContext.php +++ b/src/Context/ServerOsContext.php @@ -100,8 +100,6 @@ public function offsetSet($offset, $value): void /** * Gets the name of the operating system. - * - * @return string */ public function getName(): string { @@ -120,8 +118,6 @@ public function setName(string $name): void /** * Gets the version of the operating system. - * - * @return string */ public function getVersion(): string { @@ -140,8 +136,6 @@ public function setVersion(string $version): void /** * Gets the build of the operating system. - * - * @return string */ public function getBuild(): string { @@ -160,8 +154,6 @@ public function setBuild(string $build): void /** * Gets the version of the kernel of the operating system. - * - * @return string */ public function getKernelVersion(): string { diff --git a/src/Context/TagsContext.php b/src/Context/TagsContext.php index 8de06c66a..56bb8ab04 100644 --- a/src/Context/TagsContext.php +++ b/src/Context/TagsContext.php @@ -61,8 +61,6 @@ public function offsetSet($offset, $value): void * * @param array $data The data to sanitize * - * @return array - * * @throws \InvalidArgumentException If any of the values of the input data * is not a number or a string */ diff --git a/src/Context/UserContext.php b/src/Context/UserContext.php index 3263b6f2e..bafe3586f 100644 --- a/src/Context/UserContext.php +++ b/src/Context/UserContext.php @@ -12,8 +12,6 @@ final class UserContext extends Context { /** * Gets the ID of the user. - * - * @return string|null */ public function getId(): ?string { @@ -32,8 +30,6 @@ public function setId(?string $id): void /** * Gets the username of the user. - * - * @return string|null */ public function getUsername(): ?string { @@ -52,8 +48,6 @@ public function setUsername(?string $username): void /** * Gets the email of the user. - * - * @return string|null */ public function getEmail(): ?string { @@ -72,8 +66,6 @@ public function setEmail(?string $email): void /** * Gets the ip address of the user. - * - * @return string|null */ public function getIpAddress(): ?string { diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 6716404c7..8df4a7638 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -133,8 +133,6 @@ private function __construct() * their implementation and behavior of * registering all handlers can be changed * - * @return self - * * @deprecated since version 2.1, to be removed in 3.0. */ public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE, bool $triggerDeprecation = true): self @@ -156,8 +154,6 @@ public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESE /** * Registers the error handler once and returns its instance. - * - * @return self */ public static function registerOnceErrorHandler(): self { @@ -194,8 +190,6 @@ public static function registerOnceErrorHandler(): self * * @param int $reservedMemorySize The amount of memory to reserve for the fatal * error handler expressed in bytes - * - * @return self */ public static function registerOnceFatalErrorHandler(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE): self { @@ -223,8 +217,6 @@ public static function registerOnceFatalErrorHandler(int $reservedMemorySize = s * Registers the exception handler, effectively replacing the current one * and returns its instance. The previous one will be saved anyway and * called when appropriate. - * - * @return self */ public static function registerOnceExceptionHandler(): self { @@ -469,8 +461,6 @@ private function handleException(\Throwable $exception): void * @param array $backtrace The backtrace to clear * @param string $file The filename the backtrace was raised in * @param int $line The line number the backtrace was raised at - * - * @return array */ private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array { diff --git a/src/Event.php b/src/Event.php index 17353dfe7..c0c8fe3e0 100644 --- a/src/Event.php +++ b/src/Event.php @@ -169,8 +169,6 @@ public function __construct() /** * Gets the UUID of this event. - * - * @return string */ public function getId(): string { @@ -180,8 +178,6 @@ public function getId(): string /** * Gets the identifier of the SDK package that generated this event. * - * @return string - * * @internal */ public function getSdkIdentifier(): string @@ -192,8 +188,6 @@ public function getSdkIdentifier(): string /** * Sets the identifier of the SDK package that generated this event. * - * @param string $sdkIdentifier - * * @internal */ public function setSdkIdentifier(string $sdkIdentifier): void @@ -204,8 +198,6 @@ public function setSdkIdentifier(string $sdkIdentifier): void /** * Gets the version of the SDK package that generated this Event. * - * @return string - * * @internal */ public function getSdkVersion(): string @@ -216,8 +208,6 @@ public function getSdkVersion(): string /** * Sets the version of the SDK package that generated this Event. * - * @param string $sdkVersion - * * @internal */ public function setSdkVersion(string $sdkVersion): void @@ -227,8 +217,6 @@ public function setSdkVersion(string $sdkVersion): void /** * Gets the timestamp of when this event was generated. - * - * @return string */ public function getTimestamp(): string { @@ -237,8 +225,6 @@ public function getTimestamp(): string /** * Gets the severity of this event. - * - * @return Severity */ public function getLevel(): Severity { @@ -257,8 +243,6 @@ public function setLevel(Severity $level): void /** * Gets the name of the logger which created the event. - * - * @return string|null */ public function getLogger(): ?string { @@ -278,8 +262,6 @@ public function setLogger(?string $logger): void /** * Gets the name of the transaction (or culprit) which caused this * exception. - * - * @return string|null */ public function getTransaction(): ?string { @@ -299,8 +281,6 @@ public function setTransaction(?string $transaction): void /** * Gets the name of the server. - * - * @return string|null */ public function getServerName(): ?string { @@ -319,8 +299,6 @@ public function setServerName(?string $serverName): void /** * Gets the release of the program. - * - * @return string|null */ public function getRelease(): ?string { @@ -339,8 +317,6 @@ public function setRelease(?string $release): void /** * Gets the error message. - * - * @return string|null */ public function getMessage(): ?string { @@ -349,8 +325,6 @@ public function getMessage(): ?string /** * Gets the formatted message. - * - * @return string|null */ public function getMessageFormatted(): ?string { @@ -403,8 +377,6 @@ public function setModules(array $modules): void /** * Gets the request data. - * - * @return array */ public function getRequest(): array { @@ -423,8 +395,6 @@ public function setRequest(array $request): void /** * Gets an arbitrary mapping of additional metadata. - * - * @return Context */ public function getExtraContext(): Context { @@ -433,8 +403,6 @@ public function getExtraContext(): Context /** * Gets a list of tags. - * - * @return TagsContext */ public function getTagsContext(): TagsContext { @@ -443,8 +411,6 @@ public function getTagsContext(): TagsContext /** * Gets the user context. - * - * @return UserContext */ public function getUserContext(): UserContext { @@ -453,8 +419,6 @@ public function getUserContext(): UserContext /** * Gets the server OS context. - * - * @return ServerOsContext */ public function getServerOsContext(): ServerOsContext { @@ -463,8 +427,6 @@ public function getServerOsContext(): ServerOsContext /** * Gets the runtime context data. - * - * @return RuntimeContext */ public function getRuntimeContext(): RuntimeContext { @@ -495,8 +457,6 @@ public function setFingerprint(array $fingerprint): void /** * Gets the environment in which this event was generated. - * - * @return string|null */ public function getEnvironment(): ?string { @@ -535,8 +495,6 @@ public function setBreadcrumb(array $breadcrumbs): void /** * Gets the exception. - * - * @return array */ public function getExceptions(): array { @@ -561,8 +519,6 @@ public function setExceptions(array $exceptions): void /** * Gets the stacktrace that generated this event. - * - * @return Stacktrace|null */ public function getStacktrace(): ?Stacktrace { @@ -582,8 +538,6 @@ public function setStacktrace(Stacktrace $stacktrace): void /** * Gets the event as an array. * - * @return array - * * @psalm-return array{ * event_id: string, * timestamp: string, diff --git a/src/EventFactoryInterface.php b/src/EventFactoryInterface.php index 9441775eb..479073ad8 100644 --- a/src/EventFactoryInterface.php +++ b/src/EventFactoryInterface.php @@ -13,8 +13,6 @@ interface EventFactoryInterface * Create an {@see Event} with a stacktrace attached to it. * * @param array $payload The data to be attached to the Event - * - * @return Event */ public function createWithStacktrace(array $payload): Event; @@ -22,8 +20,6 @@ public function createWithStacktrace(array $payload): Event; * Create an {@see Event} from a data payload. * * @param array $payload The data to be attached to the Event - * - * @return Event */ public function create(array $payload): Event; } diff --git a/src/FlushableClientInterface.php b/src/FlushableClientInterface.php index d5d2ee442..b68c5e6ea 100644 --- a/src/FlushableClientInterface.php +++ b/src/FlushableClientInterface.php @@ -19,8 +19,6 @@ interface FlushableClientInterface extends ClientInterface * and the queue takes longer to drain, the promise resolves with `false`. * * @param int|null $timeout Maximum time in seconds the client should wait - * - * @return PromiseInterface */ public function flush(?int $timeout = null): PromiseInterface; } diff --git a/src/Frame.php b/src/Frame.php index be1b2fdbe..7f66a84d8 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -72,8 +72,6 @@ public function __construct(?string $functionName, string $file, int $line) /** * Gets the name of the function being called. - * - * @return string|null */ public function getFunctionName(): ?string { @@ -82,8 +80,6 @@ public function getFunctionName(): ?string /** * Gets the file where the frame originated. - * - * @return string */ public function getFile(): string { @@ -92,8 +88,6 @@ public function getFile(): string /** * Gets the line at which the frame originated. - * - * @return int */ public function getLine(): int { @@ -123,8 +117,6 @@ public function setPreContext(array $preContext): void /** * Gets the source code written at the line number of the file that originated * this frame. - * - * @return string|null */ public function getContextLine(): ?string { @@ -165,8 +157,6 @@ public function setPostContext(array $postContext): void /** * Gets whether the frame is related to the execution of the relevant code * in this stacktrace. - * - * @return bool */ public function isInApp(): bool { @@ -187,8 +177,6 @@ public function setIsInApp(bool $inApp): void /** * Gets a mapping of variables which were available within this frame * (usually context-locals). - * - * @return array */ public function getVars(): array { @@ -209,8 +197,6 @@ public function setVars(array $vars): void /** * Returns an array representation of the data of this frame modeled according * to the specifications of the Sentry SDK Stacktrace Interface. - * - * @return array */ public function toArray(): array { diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 5cbf9e147..cde8c60bd 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -212,8 +212,6 @@ private function captureRequestBody(Options $options, ServerRequestInterface $se * each UploadedFileInterface with an array of info. * * @param array $uploadedFiles The uploaded files info from a PSR-7 server request - * - * @return array */ private function parseUploadedFiles(array $uploadedFiles): array { diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 46629d3f8..7d62beb57 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -78,8 +78,6 @@ protected function write(array $record): void * Translates the Monolog level into the Sentry severity. * * @param int $level The Monolog log level - * - * @return Severity */ private function getSeverityFromLevel(int $level): Severity { diff --git a/src/Options.php b/src/Options.php index 7cd88508a..755261b65 100644 --- a/src/Options.php +++ b/src/Options.php @@ -66,8 +66,6 @@ public function __construct(array $options = []) /** * Gets the number of attempts to resend an event that failed to be sent. - * - * @return int */ public function getSendAttempts(): int { @@ -113,8 +111,6 @@ public function setPrefixes(array $prefixes): void /** * Gets the sampling factor to apply to events. A value of 0 will deny * sending any events, and a value of 1 will send 100% of events. - * - * @return float */ public function getSampleRate(): float { @@ -136,8 +132,6 @@ public function setSampleRate(float $sampleRate): void /** * Gets whether the stacktrace will be attached on captureMessage. - * - * @return bool */ public function shouldAttachStacktrace(): bool { @@ -158,8 +152,6 @@ public function setAttachStacktrace(bool $enable): void /** * Gets the number of lines of code context to capture, or null if none. - * - * @return int */ public function getContextLines(): int { @@ -180,8 +172,6 @@ public function setContextLines(int $contextLines): void /** * Returns whether the requests should be compressed using GZIP or not. - * - * @return bool */ public function isCompressionEnabled(): bool { @@ -202,8 +192,6 @@ public function setEnableCompression(bool $enabled): void /** * Gets the environment. - * - * @return string|null */ public function getEnvironment(): ?string { @@ -251,8 +239,6 @@ public function setExcludedExceptions(array $exceptions): void * to Sentry. * * @param \Throwable $exception The exception - * - * @return bool */ public function isExcludedException(\Throwable $exception): bool { @@ -289,8 +275,6 @@ public function setInAppExcludedPaths(array $paths): void /** * Gets the project ID number to send to the Sentry server. - * - * @return string|null */ public function getProjectId(): ?string { @@ -299,8 +283,6 @@ public function getProjectId(): ?string /** * Gets the project which the authenticated user is bound to. - * - * @return string|null */ public function getProjectRoot(): ?string { @@ -321,8 +303,6 @@ public function setProjectRoot(?string $path): void /** * Gets the public key to authenticate the SDK. - * - * @return string|null */ public function getPublicKey(): ?string { @@ -331,8 +311,6 @@ public function getPublicKey(): ?string /** * Gets the secret key to authenticate the SDK. - * - * @return string|null */ public function getSecretKey(): ?string { @@ -341,8 +319,6 @@ public function getSecretKey(): ?string /** * Gets the logger used by Sentry. - * - * @return string */ public function getLogger(): string { @@ -385,8 +361,6 @@ public function setRelease(?string $release): void /** * Gets the DSN of the Sentry server the authenticated user is bound to. - * - * @return string|null */ public function getDsn(): ?string { @@ -395,8 +369,6 @@ public function getDsn(): ?string /** * Gets the name of the server the SDK is running on (e.g. the hostname). - * - * @return string */ public function getServerName(): string { @@ -419,8 +391,6 @@ public function setServerName(string $serverName): void * Gets a callback that will be invoked before an event is sent to the server. * If `null` is returned it won't be sent. * - * @return callable - * * @psalm-return callable(Event): ?Event */ public function getBeforeSendCallback(): callable @@ -467,8 +437,6 @@ public function setTags(array $tags): void /** * Gets a bit mask for error_reporting used in {@link ErrorListenerIntegration} to filter which errors to report. - * - * @return int */ public function getErrorTypes(): int { @@ -489,8 +457,6 @@ public function setErrorTypes(int $errorTypes): void /** * Gets the maximum number of breadcrumbs sent with events. - * - * @return int */ public function getMaxBreadcrumbs(): int { @@ -512,8 +478,6 @@ public function setMaxBreadcrumbs(int $maxBreadcrumbs): void /** * Gets a callback that will be invoked when adding a breadcrumb. * - * @return callable - * * @psalm-return callable(Breadcrumb): ?Breadcrumb */ public function getBeforeBreadcrumbCallback(): callable @@ -563,8 +527,6 @@ public function getIntegrations(): array /** * Should default PII be sent by default. - * - * @return bool */ public function shouldSendDefaultPii(): bool { @@ -585,8 +547,6 @@ public function setSendDefaultPii(bool $enable): void /** * Returns whether the default integrations are enabled. - * - * @return bool */ public function hasDefaultIntegrations(): bool { @@ -607,8 +567,6 @@ public function setDefaultIntegrations(bool $enable): void /** * Gets the max length for values in the event payload. - * - * @return int */ public function getMaxValueLength(): int { @@ -629,8 +587,6 @@ public function setMaxValueLength(int $maxValueLength): void /** * Gets the http proxy setting. - * - * @return string|null */ public function getHttpProxy(): ?string { @@ -676,8 +632,6 @@ public function setCaptureSilencedErrors(bool $shouldCapture): void /** * Gets the limit up to which integrations should capture the HTTP request * body. - * - * @return string */ public function getMaxRequestBodySize(): string { @@ -835,8 +789,6 @@ private function configureOptions(OptionsResolver $resolver): void * Normalizes the given path as an absolute path. * * @param string $value The path - * - * @return string */ private function normalizeAbsolutePath(string $value): string { @@ -855,8 +807,6 @@ private function normalizeAbsolutePath(string $value): string * * @param SymfonyOptions$options The configuration options * @param string|null $dsn The actual value of the option to normalize - * - * @return string|null */ private function normalizeDsnOption(SymfonyOptions $options, ?string $dsn): ?string { @@ -910,8 +860,6 @@ private function normalizeDsnOption(SymfonyOptions $options, ?string $dsn): ?str * that the URL is valid. * * @param string|null $dsn The value of the option - * - * @return bool */ private function validateDsnOption(?string $dsn): bool { @@ -956,8 +904,6 @@ private function validateDsnOption(?string $dsn): bool * implements the {@see IntegrationInterface} interface. * * @param array $integrations The value to validate - * - * @return bool */ private function validateIntegrationsOption(array $integrations): bool { @@ -974,8 +920,6 @@ private function validateIntegrationsOption(array $integrations): bool * Validates if the value of the max_breadcrumbs option is in range. * * @param int $value The value to validate - * - * @return bool */ private function validateMaxBreadcrumbsOptions(int $value): bool { @@ -986,8 +930,6 @@ private function validateMaxBreadcrumbsOptions(int $value): bool * Validates that the values passed to the `class_serializers` option are valid. * * @param array $serializers The value to validate - * - * @return bool */ private function validateClassSerializersOption(array $serializers): bool { @@ -1004,8 +946,6 @@ private function validateClassSerializersOption(array $serializers): bool * Validates that the values passed to the `tags` option are valid. * * @param array $tags The value to validate - * - * @return bool */ private function validateTagsOption(array $tags): bool { diff --git a/src/SentrySdk.php b/src/SentrySdk.php index 5dc9424c7..a8773f185 100644 --- a/src/SentrySdk.php +++ b/src/SentrySdk.php @@ -29,8 +29,6 @@ private function __construct() /** * Initializes the SDK by creating a new hub instance each time this method * gets called. - * - * @return HubInterface */ public static function init(): HubInterface { @@ -42,8 +40,6 @@ public static function init(): HubInterface /** * Gets the current hub. If it's not initialized then creates a new instance * and sets it as current hub. - * - * @return HubInterface */ public static function getCurrentHub(): HubInterface { @@ -58,8 +54,6 @@ public static function getCurrentHub(): HubInterface * Sets the current hub. * * @param HubInterface $hub The hub to set - * - * @return HubInterface */ public static function setCurrentHub(HubInterface $hub): HubInterface { diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 76e637080..326e2e4fe 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -71,9 +71,7 @@ abstract class AbstractSerializer /** * AbstractSerializer constructor. * - * @param Options $options The SDK configuration options - * @param int $maxDepth - * @param string|null $mbDetectOrder + * @param Options $options The SDK configuration options */ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDetectOrder = null) { @@ -91,7 +89,6 @@ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDete * sanitization and encoding. * * @param mixed $value - * @param int $_depth * * @return string|bool|float|int|array|null */ @@ -157,8 +154,6 @@ protected function serializeRecursively($value, int $_depth = 0) * objects implementing the `SerializableInterface`. * * @param object $object - * - * @return array */ protected function resolveClassSerializers($object): array { @@ -181,7 +176,6 @@ protected function resolveClassSerializers($object): array /** * @param object $object - * @param int $_depth * @param string[] $hashes * * @return array|string|bool|float|int|null @@ -210,8 +204,6 @@ protected function serializeObject($object, int $_depth = 0, array $hashes = []) * Serializes the given value to a string. * * @param mixed $value The value to serialize - * - * @return string */ protected function serializeString($value): string { @@ -266,8 +258,6 @@ protected function serializeValue($value) * since using the callable type raises a deprecation in some cases. * * @param callable|mixed $callable - * - * @return string */ protected function serializeCallableWithoutTypeHint($callable): string { @@ -276,12 +266,7 @@ protected function serializeCallableWithoutTypeHint($callable): string } if (!\is_callable($callable)) { - throw new InvalidArgumentException(sprintf( - 'Expecting callable, got %s', - \is_object($callable) - ? \get_class($callable) - : \gettype($callable) - )); + throw new InvalidArgumentException(sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); } return $this->serializeCallable($callable); @@ -293,8 +278,6 @@ protected function serializeCallableWithoutTypeHint($callable): string * @see https://github.com/getsentry/sentry-php/pull/821 * * @param callable $callable callable type to be removed in 3.0, see #821 - * - * @return string */ protected function serializeCallable(callable $callable): string { @@ -329,11 +312,6 @@ protected function serializeCallable(callable $callable): string return $callableType . $reflection->getName() . ' ' . $this->serializeCallableParameters($reflection); } - /** - * @param \ReflectionFunctionAbstract $reflection - * - * @return string - */ private function serializeCallableParameters(\ReflectionFunctionAbstract $reflection): string { $params = []; @@ -362,8 +340,6 @@ public function getMbDetectOrder(): string } /** - * @param string $mbDetectOrder - * * @return $this */ public function setMbDetectOrder(string $mbDetectOrder): self @@ -373,17 +349,11 @@ public function setMbDetectOrder(string $mbDetectOrder): self return $this; } - /** - * @param bool $value - */ public function setSerializeAllObjects(bool $value): void { $this->serializeAllObjects = $value; } - /** - * @return bool - */ public function getSerializeAllObjects(): bool { return $this->serializeAllObjects; diff --git a/src/Serializer/SerializableInterface.php b/src/Serializer/SerializableInterface.php index e6d756174..81ba38674 100644 --- a/src/Serializer/SerializableInterface.php +++ b/src/Serializer/SerializableInterface.php @@ -12,8 +12,6 @@ interface SerializableInterface { /** * Returns an array representation of the object for Sentry. - * - * @return array|null */ public function toSentry(): ?array; } diff --git a/src/Severity.php b/src/Severity.php index cfb204cc6..aad1c8000 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -115,8 +115,6 @@ public static function fromError(int $severity): self /** * Creates a new instance of this enum for the "debug" value. - * - * @return self */ public static function debug(): self { @@ -125,8 +123,6 @@ public static function debug(): self /** * Creates a new instance of this enum for the "info" value. - * - * @return self */ public static function info(): self { @@ -135,8 +131,6 @@ public static function info(): self /** * Creates a new instance of this enum for the "warning" value. - * - * @return self */ public static function warning(): self { @@ -145,8 +139,6 @@ public static function warning(): self /** * Creates a new instance of this enum for the "error" value. - * - * @return self */ public static function error(): self { @@ -155,8 +147,6 @@ public static function error(): self /** * Creates a new instance of this enum for the "fatal" value. - * - * @return self */ public static function fatal(): self { @@ -167,8 +157,6 @@ public static function fatal(): self * Returns whether two object instances of this class are equal. * * @param self $other The object to compare - * - * @return bool */ public function isEqualTo(self $other): bool { diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 894d6a5c5..202129c4c 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -221,8 +221,6 @@ public function jsonSerialize() * @param string $path The file path * @param int $lineNumber The line to centre about * @param int $maxLinesToFetch The maximum number of lines to fetch - * - * @return array */ protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array { @@ -280,8 +278,6 @@ protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxL * Removes from the given file path the specified prefixes. * * @param string $filePath The path to the file - * - * @return string */ protected function stripPrefixFromFilePath(string $filePath): string { @@ -298,8 +294,6 @@ protected function stripPrefixFromFilePath(string $filePath): string * Gets the values of the arguments of the given stackframe. * * @param array $frame The frame from where arguments are retrieved - * - * @return array */ protected function getFrameArgumentsValues(array $frame): array { @@ -325,8 +319,6 @@ protected function getFrameArgumentsValues(array $frame): array * Gets the arguments of the given stackframe. * * @param array $frame The frame from where arguments are retrieved - * - * @return array */ public function getFrameArguments(array $frame): array { diff --git a/src/State/Hub.php b/src/State/Hub.php index 8c76e0d4a..eaf7a0ab6 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -229,8 +229,6 @@ public function getIntegration(string $className): ?IntegrationInterface /** * Gets the scope bound to the top of the stack. - * - * @return Scope */ private function getScope(): Scope { @@ -239,8 +237,6 @@ private function getScope(): Scope /** * Gets the topmost client/layer pair in the stack. - * - * @return Layer */ private function getStackTop(): Layer { diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 3eee7b9ac..ebe1b2f5b 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -31,8 +31,6 @@ private function __construct() /** * Gets the instance of this class. This is a singleton, so once initialized * you will always get the same instance. - * - * @return self */ public static function getInstance(): self { diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 03c18fb07..717a520d1 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -19,15 +19,11 @@ interface HubInterface { /** * Gets the client bound to the top of the stack. - * - * @return ClientInterface|null */ public function getClient(): ?ClientInterface; /** * Gets the ID of the last captured event. - * - * @return string|null */ public function getLastEventId(): ?string; @@ -37,8 +33,6 @@ public function getLastEventId(): ?string; * information added to this scope will be removed once the scope ends. Be * sure to always remove this scope with {@see Hub::popScope} when the * operation finishes or throws. - * - * @return Scope */ public function pushScope(): Scope; @@ -46,8 +40,6 @@ public function pushScope(): Scope; * Removes a previously pushed scope from the stack. This restores the state * before the scope was pushed. All breadcrumbs and context information added * since the last call to {@see Hub::pushScope} are discarded. - * - * @return bool */ public function popScope(): bool; @@ -79,8 +71,6 @@ public function bindClient(ClientInterface $client): void; * * @param string $message The message * @param Severity $level The severity level of the message - * - * @return string|null */ public function captureMessage(string $message, ?Severity $level = null): ?string; @@ -88,8 +78,6 @@ public function captureMessage(string $message, ?Severity $level = null): ?strin * Captures an exception event and sends it to Sentry. * * @param \Throwable $exception The exception - * - * @return string|null */ public function captureException(\Throwable $exception): ?string; @@ -97,15 +85,11 @@ public function captureException(\Throwable $exception): ?string; * Captures a new event using the provided data. * * @param array $payload The data of the event being captured - * - * @return string|null */ public function captureEvent(array $payload): ?string; /** * Captures an event that logs the last occurred error. - * - * @return string|null */ public function captureLastError(): ?string; @@ -146,8 +130,6 @@ public static function setCurrent(self $hub): self; * Gets the integration whose FQCN matches the given one if it's available on the current client. * * @param string $className The FQCN of the integration - * - * @return IntegrationInterface|null */ public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/src/State/Layer.php b/src/State/Layer.php index 50305ebb1..0e0e734c6 100644 --- a/src/State/Layer.php +++ b/src/State/Layer.php @@ -38,8 +38,6 @@ public function __construct(?ClientInterface $client, Scope $scope) /** * Gets the client held by this layer. - * - * @return ClientInterface|null */ public function getClient(): ?ClientInterface { @@ -62,8 +60,6 @@ public function setClient(?ClientInterface $client): self /** * Gets the scope held by this layer. - * - * @return Scope */ public function getScope(): Scope { diff --git a/src/State/Scope.php b/src/State/Scope.php index dcbf6b112..a3764b63c 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -247,8 +247,6 @@ public function clear(): self * * @param Event $event The event object that will be enriched with scope data * @param array $payload The raw payload of the event that will be propagated to the event processors - * - * @return Event|null */ public function applyToEvent(Event $event, array $payload): ?Event { diff --git a/src/Transport/ClosableTransportInterface.php b/src/Transport/ClosableTransportInterface.php index 61ee025ed..65d758d51 100644 --- a/src/Transport/ClosableTransportInterface.php +++ b/src/Transport/ClosableTransportInterface.php @@ -18,8 +18,6 @@ interface ClosableTransportInterface * * @param int|null $timeout Maximum time in seconds before the sending * operation is interrupted - * - * @return PromiseInterface */ public function close(?int $timeout = null): PromiseInterface; } diff --git a/src/Transport/SpoolTransport.php b/src/Transport/SpoolTransport.php index d1db77c8e..10bebd8bb 100644 --- a/src/Transport/SpoolTransport.php +++ b/src/Transport/SpoolTransport.php @@ -31,8 +31,6 @@ public function __construct(SpoolInterface $spool) /** * Gets the spool. - * - * @return SpoolInterface */ public function getSpool(): SpoolInterface { diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 454cce129..5a1b3ca3d 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -18,8 +18,6 @@ final class JSON * * @param mixed $data The data to encode * - * @return string - * * @throws JsonException If the encoding failed */ public static function encode($data): string diff --git a/src/Util/PHPVersion.php b/src/Util/PHPVersion.php index 700dea760..708d98369 100644 --- a/src/Util/PHPVersion.php +++ b/src/Util/PHPVersion.php @@ -13,8 +13,6 @@ final class PHPVersion * normalized form. * * @param string $version The string to parse - * - * @return string */ public static function parseVersion(string $version = PHP_VERSION): string { diff --git a/src/functions.php b/src/functions.php index 238efa153..efcf2614b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -21,8 +21,6 @@ function init(array $options = []): void * * @param string $message The message * @param Severity $level The severity level of the message - * - * @return string|null */ function captureMessage(string $message, ?Severity $level = null): ?string { @@ -33,8 +31,6 @@ function captureMessage(string $message, ?Severity $level = null): ?string * Captures an exception event and sends it to Sentry. * * @param \Throwable $exception The exception - * - * @return string|null */ function captureException(\Throwable $exception): ?string { @@ -45,8 +41,6 @@ function captureException(\Throwable $exception): ?string * Captures a new event using the provided data. * * @param array $payload The data of the event being captured - * - * @return string|null */ function captureEvent(array $payload): ?string { @@ -55,8 +49,6 @@ function captureEvent(array $payload): ?string /** * Logs the most recent error (obtained with {@link error_get_last}). - * - * @return string|null */ function captureLastError(): ?string { From 976dad482536e83195215fe75bd9b437cbf88ba2 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 8 Nov 2019 09:30:20 +0100 Subject: [PATCH 0511/1161] Merge master into develop (#915) --- CHANGELOG.md | 21 +++++++ Makefile | 2 +- README.md | 14 +++-- composer.json | 3 + src/Breadcrumb.php | 26 +++----- src/ClientBuilder.php | 6 -- src/ClientBuilderInterface.php | 4 -- src/ClientInterface.php | 12 ---- src/Context/Context.php | 4 -- src/Context/RuntimeContext.php | 4 -- src/Context/ServerOsContext.php | 8 --- src/Context/TagsContext.php | 2 - src/Context/UserContext.php | 8 --- src/ErrorHandler.php | 25 +++----- src/Event.php | 46 -------------- src/EventFactoryInterface.php | 4 -- src/FlushableClientInterface.php | 2 - src/Frame.php | 14 ----- src/HttpClient/HttpClientFactoryInterface.php | 2 - src/Integration/RequestIntegration.php | 2 - src/Monolog/Handler.php | 2 - src/Options.php | 60 ------------------- src/SentrySdk.php | 6 -- src/Serializer/AbstractSerializer.php | 55 ++++++++--------- src/Serializer/SerializableInterface.php | 2 - src/Severity.php | 12 ---- src/Stacktrace.php | 17 ++---- src/State/Hub.php | 4 -- src/State/HubAdapter.php | 2 - src/State/HubInterface.php | 18 ------ src/State/Layer.php | 4 -- src/State/Scope.php | 2 - src/Transport/ClosableTransportInterface.php | 2 - src/Transport/DefaultTransportFactory.php | 1 + src/Transport/HttpTransport.php | 55 +++++++++++++---- src/Transport/SpoolTransport.php | 2 - src/Transport/TransportFactoryInterface.php | 2 - src/Util/JSON.php | 2 - src/Util/PHPVersion.php | 2 - src/functions.php | 8 --- tests/ClientBuilderTest.php | 5 -- tests/ClientTest.php | 2 +- tests/Serializer/AbstractSerializerTest.php | 6 +- tests/StacktraceTest.php | 4 +- tests/State/ScopeTest.php | 2 +- tests/Transport/HttpTransportTest.php | 11 ++-- 46 files changed, 146 insertions(+), 351 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 600b9dc61..8ab3b511f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ ## Unreleased +## 2.2.4 (2019-11-04) + +- Suggest installing Monolog to send log messages directly to Sentry (#908) +- Make the `$errcontext` argument of the `ErrorHandler::handleError()` method `nullable` (#917) + +## 2.2.3 (2019-10-31) + +- Fix deprecation raised when serializing callable in certain circumstances (#821) +- Fix incorrect `critical` breadcrumb level by replacing it with the `fatal` level (#901) +- Fix regression on default sending behavior of the `HttpTransport` transport (#905) +- Fix stacktrace frame inApp detection: all paths outside the project_root are now considered as not in app (#911) + +## 2.2.2 (2019-10-10) + +- Fix handling of fifth argument in the error handler (#892) +- Catch exception from vendors in `Sentry\Transport\HttpTransport` (#899) + +## 2.2.1 (2019-09-23) + +- Disable default deprecation warning `Sentry\Transport\HttpTransport` (#884) + ## 2.2.0 (2019-09-23) - Change type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) diff --git a/Makefile b/Makefile index fcdd77fbe..03d373440 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ cs-dry-run: vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run cs-fix: - vendor/bin/php-cs-fixer fix --config=.php_cs + vendor/bin/php-cs-fixer fix phpstan: vendor/bin/phpstan analyse diff --git a/README.md b/README.md index b1c7bf061..3bf81bc5a 100644 --- a/README.md +++ b/README.md @@ -74,14 +74,20 @@ The following integrations are fully supported and maintained by the Sentry team The following integrations are available and maintained by members of the Sentry community. -- [ZendFramework](https://github.com/facile-it/sentry-module) -- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [Drupal](https://www.drupal.org/project/raven) -- [OpenCart](https://github.com/BurdaPraha/oc_sentry) -- [TYPO3](https://github.com/networkteam/sentry_client) +- [Flow Framework](https://github.com/networkteam/Networkteam.SentryClient) - [OXID eShop](https://github.com/OXIDprojects/sentry) +- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) +- [ZendFramework](https://github.com/facile-it/sentry-module) +- [SilverStripe](https://github.com/phptek/silverstripe-sentry) - ... feel free to be famous, create a port to your favourite platform! +### 3rd party integrations using old SDK 1.x + +- [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient) +- [OpenCart](https://github.com/BurdaPraha/oc_sentry) +- [TYPO3](https://github.com/networkteam/sentry_client) + ## Community - [Documentation](https://docs.sentry.io/error-reporting/quickstart/?platform=php) diff --git a/composer.json b/composer.json index fb7e18c00..0e7553762 100644 --- a/composer.json +++ b/composer.json @@ -38,6 +38,9 @@ "symfony/phpunit-bridge": "^4.3", "vimeo/psalm": "^3.4" }, + "suggest": { + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." + }, "conflict": { "php-http/client-common": "1.8.0", "raven/raven": "*" diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index 57ff4889e..c0a8c5973 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -60,9 +60,16 @@ final class Breadcrumb implements \JsonSerializable /** * This constant defines the critical level for a breadcrumb. + * + * @deprecated since version 2.2.2, to be removed in 3.0; use fatal instead. */ public const LEVEL_CRITICAL = 'critical'; + /** + * This constant defines the fatal level for a breadcrumb. + */ + public const LEVEL_FATAL = 'fatal'; + /** * This constant defines the list of values allowed to be set as severity * level of the breadcrumb. @@ -73,6 +80,7 @@ final class Breadcrumb implements \JsonSerializable self::LEVEL_WARNING, self::LEVEL_ERROR, self::LEVEL_CRITICAL, + self::LEVEL_FATAL, ]; /** @@ -134,8 +142,6 @@ public function __construct(string $level, string $type, string $category, ?stri * * @param \ErrorException $exception The exception * - * @return string - * * @deprecated since version 2.3, to be removed in 3.0 */ public static function levelFromErrorException(\ErrorException $exception): string @@ -155,7 +161,7 @@ public static function levelFromErrorException(\ErrorException $exception): stri case E_CORE_WARNING: case E_COMPILE_ERROR: case E_COMPILE_WARNING: - return self::LEVEL_CRITICAL; + return self::LEVEL_FATAL; case E_USER_ERROR: return self::LEVEL_ERROR; case E_NOTICE: @@ -169,8 +175,6 @@ public static function levelFromErrorException(\ErrorException $exception): stri /** * Gets the breadcrumb type. - * - * @return string */ public function getType(): string { @@ -198,8 +202,6 @@ public function withType(string $type): self /** * Gets the breadcrumb level. - * - * @return string */ public function getLevel(): string { @@ -231,8 +233,6 @@ public function withLevel(string $level): self /** * Gets the breadcrumb category. - * - * @return string */ public function getCategory(): string { @@ -260,8 +260,6 @@ public function withCategory(string $category): self /** * Gets the breadcrumb message. - * - * @return string|null */ public function getMessage(): ?string { @@ -289,8 +287,6 @@ public function withMessage(string $message): self /** * Gets the breadcrumb meta data. - * - * @return array */ public function getMetadata(): array { @@ -341,8 +337,6 @@ public function withoutMetadata(string $name): self /** * Gets the breadcrumb timestamp. - * - * @return float */ public function getTimestamp(): float { @@ -351,8 +345,6 @@ public function getTimestamp(): float /** * Gets the breadcrumb as an array. - * - * @return array */ public function toArray(): array { diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 05dd349af..410836492 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -294,8 +294,6 @@ public function setTransportFactory(TransportFactoryInterface $transportFactory) /** * Creates a new instance of the transport mechanism. - * - * @return TransportInterface */ private function createTransportInstance(): TransportInterface { @@ -310,8 +308,6 @@ private function createTransportInstance(): TransportInterface /** * Instantiate the {@see EventFactory} with the configured serializers. - * - * @return EventFactoryInterface */ private function createEventFactory(): EventFactoryInterface { @@ -323,8 +319,6 @@ private function createEventFactory(): EventFactoryInterface /** * Creates a new instance of the {@see DefaultTransportFactory} factory. - * - * @return DefaultTransportFactory */ private function createDefaultTransportFactory(): DefaultTransportFactory { diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 6cf380642..7e90a3199 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -33,8 +33,6 @@ public static function create(array $options = []): self; /** * The options that will be used to create the {@see Client}. - * - * @return Options */ public function getOptions(): Options; @@ -106,8 +104,6 @@ public function removeHttpClientPlugin(string $className): self; /** * Gets the instance of the client built using the configured options. - * - * @return ClientInterface */ public function getClient(): ClientInterface; diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 9b4a8950a..5937d3f15 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -16,8 +16,6 @@ interface ClientInterface { /** * Returns the options of the client. - * - * @return Options */ public function getOptions(): Options; @@ -27,8 +25,6 @@ public function getOptions(): Options; * @param string $message The message (primary description) for the event * @param Severity $level The level of the message to be sent * @param Scope|null $scope An optional scope keeping the state - * - * @return string|null */ public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string; @@ -37,8 +33,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope * * @param \Throwable $exception The exception object * @param Scope|null $scope An optional scope keeping the state - * - * @return string|null */ public function captureException(\Throwable $exception, ?Scope $scope = null): ?string; @@ -46,8 +40,6 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? * Logs the most recent error (obtained with {@link error_get_last}). * * @param Scope|null $scope An optional scope keeping the state - * - * @return string|null */ public function captureLastError(?Scope $scope = null): ?string; @@ -56,8 +48,6 @@ public function captureLastError(?Scope $scope = null): ?string; * * @param array $payload The data of the event being captured * @param Scope|null $scope An optional scope keeping the state - * - * @return string|null */ public function captureEvent(array $payload, ?Scope $scope = null): ?string; @@ -65,8 +55,6 @@ public function captureEvent(array $payload, ?Scope $scope = null): ?string; * Returns the integration instance if it is installed on the Client. * * @param string $className the classname of the integration - * - * @return IntegrationInterface|null */ public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/src/Context/Context.php b/src/Context/Context.php index 4a045df37..92cdec7cb 100644 --- a/src/Context/Context.php +++ b/src/Context/Context.php @@ -107,8 +107,6 @@ public function clear(): void /** * Checks whether the object is not storing any data. - * - * @return bool */ public function isEmpty(): bool { @@ -117,8 +115,6 @@ public function isEmpty(): bool /** * Returns an array representation of the data stored by the object. - * - * @return array */ public function toArray(): array { diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 1bddb653a..91dd25860 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -101,8 +101,6 @@ public function offsetSet($offset, $value): void /** * Gets the name of the runtime. - * - * @return string */ public function getName(): string { @@ -121,8 +119,6 @@ public function setName(string $name): void /** * Gets the version of the runtime. - * - * @return string */ public function getVersion(): string { diff --git a/src/Context/ServerOsContext.php b/src/Context/ServerOsContext.php index 1639d5fb4..41bd04c97 100644 --- a/src/Context/ServerOsContext.php +++ b/src/Context/ServerOsContext.php @@ -100,8 +100,6 @@ public function offsetSet($offset, $value): void /** * Gets the name of the operating system. - * - * @return string */ public function getName(): string { @@ -120,8 +118,6 @@ public function setName(string $name): void /** * Gets the version of the operating system. - * - * @return string */ public function getVersion(): string { @@ -140,8 +136,6 @@ public function setVersion(string $version): void /** * Gets the build of the operating system. - * - * @return string */ public function getBuild(): string { @@ -160,8 +154,6 @@ public function setBuild(string $build): void /** * Gets the version of the kernel of the operating system. - * - * @return string */ public function getKernelVersion(): string { diff --git a/src/Context/TagsContext.php b/src/Context/TagsContext.php index 8de06c66a..56bb8ab04 100644 --- a/src/Context/TagsContext.php +++ b/src/Context/TagsContext.php @@ -61,8 +61,6 @@ public function offsetSet($offset, $value): void * * @param array $data The data to sanitize * - * @return array - * * @throws \InvalidArgumentException If any of the values of the input data * is not a number or a string */ diff --git a/src/Context/UserContext.php b/src/Context/UserContext.php index 3263b6f2e..bafe3586f 100644 --- a/src/Context/UserContext.php +++ b/src/Context/UserContext.php @@ -12,8 +12,6 @@ final class UserContext extends Context { /** * Gets the ID of the user. - * - * @return string|null */ public function getId(): ?string { @@ -32,8 +30,6 @@ public function setId(?string $id): void /** * Gets the username of the user. - * - * @return string|null */ public function getUsername(): ?string { @@ -52,8 +48,6 @@ public function setUsername(?string $username): void /** * Gets the email of the user. - * - * @return string|null */ public function getEmail(): ?string { @@ -72,8 +66,6 @@ public function setEmail(?string $email): void /** * Gets the ip address of the user. - * - * @return string|null */ public function getIpAddress(): ?string { diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index d077117c3..8df4a7638 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -133,8 +133,6 @@ private function __construct() * their implementation and behavior of * registering all handlers can be changed * - * @return self - * * @deprecated since version 2.1, to be removed in 3.0. */ public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE, bool $triggerDeprecation = true): self @@ -156,8 +154,6 @@ public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESE /** * Registers the error handler once and returns its instance. - * - * @return self */ public static function registerOnceErrorHandler(): self { @@ -194,8 +190,6 @@ public static function registerOnceErrorHandler(): self * * @param int $reservedMemorySize The amount of memory to reserve for the fatal * error handler expressed in bytes - * - * @return self */ public static function registerOnceFatalErrorHandler(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE): self { @@ -223,8 +217,6 @@ public static function registerOnceFatalErrorHandler(int $reservedMemorySize = s * Registers the exception handler, effectively replacing the current one * and returns its instance. The previous one will be saved anyway and * called when appropriate. - * - * @return self */ public static function registerOnceExceptionHandler(): self { @@ -357,18 +349,19 @@ public function addExceptionHandlerListener(callable $listener): void * Handles errors by capturing them through the Raven client according to * the configured bit field. * - * @param int $level The level of the error raised, represented by one - * of the E_* constants - * @param string $message The error message - * @param string $file The filename the error was raised in - * @param int $line The line number the error was raised at + * @param int $level The level of the error raised, represented by + * one of the E_* constants + * @param string $message The error message + * @param string $file The filename the error was raised in + * @param int $line The line number the error was raised at + * @param array|null $errcontext The error context (deprecated since PHP 7.2) * * @return bool If the function returns `false` then the PHP native error * handler will be called * * @throws \Throwable */ - private function handleError(int $level, string $message, string $file, int $line): bool + private function handleError(int $level, string $message, string $file, int $line, ?array $errcontext = []): bool { if (0 === error_reporting()) { $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); @@ -383,7 +376,7 @@ private function handleError(int $level, string $message, string $file, int $lin $this->invokeListeners($this->errorListeners, $errorAsException); if (null !== $this->previousErrorHandler) { - return false !== \call_user_func($this->previousErrorHandler, $level, $message, $file, $line); + return false !== \call_user_func($this->previousErrorHandler, $level, $message, $file, $line, $errcontext); } return false; @@ -468,8 +461,6 @@ private function handleException(\Throwable $exception): void * @param array $backtrace The backtrace to clear * @param string $file The filename the backtrace was raised in * @param int $line The line number the backtrace was raised at - * - * @return array */ private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array { diff --git a/src/Event.php b/src/Event.php index 17353dfe7..c0c8fe3e0 100644 --- a/src/Event.php +++ b/src/Event.php @@ -169,8 +169,6 @@ public function __construct() /** * Gets the UUID of this event. - * - * @return string */ public function getId(): string { @@ -180,8 +178,6 @@ public function getId(): string /** * Gets the identifier of the SDK package that generated this event. * - * @return string - * * @internal */ public function getSdkIdentifier(): string @@ -192,8 +188,6 @@ public function getSdkIdentifier(): string /** * Sets the identifier of the SDK package that generated this event. * - * @param string $sdkIdentifier - * * @internal */ public function setSdkIdentifier(string $sdkIdentifier): void @@ -204,8 +198,6 @@ public function setSdkIdentifier(string $sdkIdentifier): void /** * Gets the version of the SDK package that generated this Event. * - * @return string - * * @internal */ public function getSdkVersion(): string @@ -216,8 +208,6 @@ public function getSdkVersion(): string /** * Sets the version of the SDK package that generated this Event. * - * @param string $sdkVersion - * * @internal */ public function setSdkVersion(string $sdkVersion): void @@ -227,8 +217,6 @@ public function setSdkVersion(string $sdkVersion): void /** * Gets the timestamp of when this event was generated. - * - * @return string */ public function getTimestamp(): string { @@ -237,8 +225,6 @@ public function getTimestamp(): string /** * Gets the severity of this event. - * - * @return Severity */ public function getLevel(): Severity { @@ -257,8 +243,6 @@ public function setLevel(Severity $level): void /** * Gets the name of the logger which created the event. - * - * @return string|null */ public function getLogger(): ?string { @@ -278,8 +262,6 @@ public function setLogger(?string $logger): void /** * Gets the name of the transaction (or culprit) which caused this * exception. - * - * @return string|null */ public function getTransaction(): ?string { @@ -299,8 +281,6 @@ public function setTransaction(?string $transaction): void /** * Gets the name of the server. - * - * @return string|null */ public function getServerName(): ?string { @@ -319,8 +299,6 @@ public function setServerName(?string $serverName): void /** * Gets the release of the program. - * - * @return string|null */ public function getRelease(): ?string { @@ -339,8 +317,6 @@ public function setRelease(?string $release): void /** * Gets the error message. - * - * @return string|null */ public function getMessage(): ?string { @@ -349,8 +325,6 @@ public function getMessage(): ?string /** * Gets the formatted message. - * - * @return string|null */ public function getMessageFormatted(): ?string { @@ -403,8 +377,6 @@ public function setModules(array $modules): void /** * Gets the request data. - * - * @return array */ public function getRequest(): array { @@ -423,8 +395,6 @@ public function setRequest(array $request): void /** * Gets an arbitrary mapping of additional metadata. - * - * @return Context */ public function getExtraContext(): Context { @@ -433,8 +403,6 @@ public function getExtraContext(): Context /** * Gets a list of tags. - * - * @return TagsContext */ public function getTagsContext(): TagsContext { @@ -443,8 +411,6 @@ public function getTagsContext(): TagsContext /** * Gets the user context. - * - * @return UserContext */ public function getUserContext(): UserContext { @@ -453,8 +419,6 @@ public function getUserContext(): UserContext /** * Gets the server OS context. - * - * @return ServerOsContext */ public function getServerOsContext(): ServerOsContext { @@ -463,8 +427,6 @@ public function getServerOsContext(): ServerOsContext /** * Gets the runtime context data. - * - * @return RuntimeContext */ public function getRuntimeContext(): RuntimeContext { @@ -495,8 +457,6 @@ public function setFingerprint(array $fingerprint): void /** * Gets the environment in which this event was generated. - * - * @return string|null */ public function getEnvironment(): ?string { @@ -535,8 +495,6 @@ public function setBreadcrumb(array $breadcrumbs): void /** * Gets the exception. - * - * @return array */ public function getExceptions(): array { @@ -561,8 +519,6 @@ public function setExceptions(array $exceptions): void /** * Gets the stacktrace that generated this event. - * - * @return Stacktrace|null */ public function getStacktrace(): ?Stacktrace { @@ -582,8 +538,6 @@ public function setStacktrace(Stacktrace $stacktrace): void /** * Gets the event as an array. * - * @return array - * * @psalm-return array{ * event_id: string, * timestamp: string, diff --git a/src/EventFactoryInterface.php b/src/EventFactoryInterface.php index 9441775eb..479073ad8 100644 --- a/src/EventFactoryInterface.php +++ b/src/EventFactoryInterface.php @@ -13,8 +13,6 @@ interface EventFactoryInterface * Create an {@see Event} with a stacktrace attached to it. * * @param array $payload The data to be attached to the Event - * - * @return Event */ public function createWithStacktrace(array $payload): Event; @@ -22,8 +20,6 @@ public function createWithStacktrace(array $payload): Event; * Create an {@see Event} from a data payload. * * @param array $payload The data to be attached to the Event - * - * @return Event */ public function create(array $payload): Event; } diff --git a/src/FlushableClientInterface.php b/src/FlushableClientInterface.php index d5d2ee442..b68c5e6ea 100644 --- a/src/FlushableClientInterface.php +++ b/src/FlushableClientInterface.php @@ -19,8 +19,6 @@ interface FlushableClientInterface extends ClientInterface * and the queue takes longer to drain, the promise resolves with `false`. * * @param int|null $timeout Maximum time in seconds the client should wait - * - * @return PromiseInterface */ public function flush(?int $timeout = null): PromiseInterface; } diff --git a/src/Frame.php b/src/Frame.php index be1b2fdbe..7f66a84d8 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -72,8 +72,6 @@ public function __construct(?string $functionName, string $file, int $line) /** * Gets the name of the function being called. - * - * @return string|null */ public function getFunctionName(): ?string { @@ -82,8 +80,6 @@ public function getFunctionName(): ?string /** * Gets the file where the frame originated. - * - * @return string */ public function getFile(): string { @@ -92,8 +88,6 @@ public function getFile(): string /** * Gets the line at which the frame originated. - * - * @return int */ public function getLine(): int { @@ -123,8 +117,6 @@ public function setPreContext(array $preContext): void /** * Gets the source code written at the line number of the file that originated * this frame. - * - * @return string|null */ public function getContextLine(): ?string { @@ -165,8 +157,6 @@ public function setPostContext(array $postContext): void /** * Gets whether the frame is related to the execution of the relevant code * in this stacktrace. - * - * @return bool */ public function isInApp(): bool { @@ -187,8 +177,6 @@ public function setIsInApp(bool $inApp): void /** * Gets a mapping of variables which were available within this frame * (usually context-locals). - * - * @return array */ public function getVars(): array { @@ -209,8 +197,6 @@ public function setVars(array $vars): void /** * Returns an array representation of the data of this frame modeled according * to the specifications of the Sentry SDK Stacktrace Interface. - * - * @return array */ public function toArray(): array { diff --git a/src/HttpClient/HttpClientFactoryInterface.php b/src/HttpClient/HttpClientFactoryInterface.php index 9a6ea7eef..a0b409d70 100644 --- a/src/HttpClient/HttpClientFactoryInterface.php +++ b/src/HttpClient/HttpClientFactoryInterface.php @@ -17,8 +17,6 @@ interface HttpClientFactoryInterface * Create HTTP Client wrapped with configured plugins. * * @param Options $options The client options - * - * @return HttpAsyncClientInterface */ public function create(Options $options): HttpAsyncClientInterface; } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 5cbf9e147..cde8c60bd 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -212,8 +212,6 @@ private function captureRequestBody(Options $options, ServerRequestInterface $se * each UploadedFileInterface with an array of info. * * @param array $uploadedFiles The uploaded files info from a PSR-7 server request - * - * @return array */ private function parseUploadedFiles(array $uploadedFiles): array { diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 46629d3f8..7d62beb57 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -78,8 +78,6 @@ protected function write(array $record): void * Translates the Monolog level into the Sentry severity. * * @param int $level The Monolog log level - * - * @return Severity */ private function getSeverityFromLevel(int $level): Severity { diff --git a/src/Options.php b/src/Options.php index 7cd88508a..755261b65 100644 --- a/src/Options.php +++ b/src/Options.php @@ -66,8 +66,6 @@ public function __construct(array $options = []) /** * Gets the number of attempts to resend an event that failed to be sent. - * - * @return int */ public function getSendAttempts(): int { @@ -113,8 +111,6 @@ public function setPrefixes(array $prefixes): void /** * Gets the sampling factor to apply to events. A value of 0 will deny * sending any events, and a value of 1 will send 100% of events. - * - * @return float */ public function getSampleRate(): float { @@ -136,8 +132,6 @@ public function setSampleRate(float $sampleRate): void /** * Gets whether the stacktrace will be attached on captureMessage. - * - * @return bool */ public function shouldAttachStacktrace(): bool { @@ -158,8 +152,6 @@ public function setAttachStacktrace(bool $enable): void /** * Gets the number of lines of code context to capture, or null if none. - * - * @return int */ public function getContextLines(): int { @@ -180,8 +172,6 @@ public function setContextLines(int $contextLines): void /** * Returns whether the requests should be compressed using GZIP or not. - * - * @return bool */ public function isCompressionEnabled(): bool { @@ -202,8 +192,6 @@ public function setEnableCompression(bool $enabled): void /** * Gets the environment. - * - * @return string|null */ public function getEnvironment(): ?string { @@ -251,8 +239,6 @@ public function setExcludedExceptions(array $exceptions): void * to Sentry. * * @param \Throwable $exception The exception - * - * @return bool */ public function isExcludedException(\Throwable $exception): bool { @@ -289,8 +275,6 @@ public function setInAppExcludedPaths(array $paths): void /** * Gets the project ID number to send to the Sentry server. - * - * @return string|null */ public function getProjectId(): ?string { @@ -299,8 +283,6 @@ public function getProjectId(): ?string /** * Gets the project which the authenticated user is bound to. - * - * @return string|null */ public function getProjectRoot(): ?string { @@ -321,8 +303,6 @@ public function setProjectRoot(?string $path): void /** * Gets the public key to authenticate the SDK. - * - * @return string|null */ public function getPublicKey(): ?string { @@ -331,8 +311,6 @@ public function getPublicKey(): ?string /** * Gets the secret key to authenticate the SDK. - * - * @return string|null */ public function getSecretKey(): ?string { @@ -341,8 +319,6 @@ public function getSecretKey(): ?string /** * Gets the logger used by Sentry. - * - * @return string */ public function getLogger(): string { @@ -385,8 +361,6 @@ public function setRelease(?string $release): void /** * Gets the DSN of the Sentry server the authenticated user is bound to. - * - * @return string|null */ public function getDsn(): ?string { @@ -395,8 +369,6 @@ public function getDsn(): ?string /** * Gets the name of the server the SDK is running on (e.g. the hostname). - * - * @return string */ public function getServerName(): string { @@ -419,8 +391,6 @@ public function setServerName(string $serverName): void * Gets a callback that will be invoked before an event is sent to the server. * If `null` is returned it won't be sent. * - * @return callable - * * @psalm-return callable(Event): ?Event */ public function getBeforeSendCallback(): callable @@ -467,8 +437,6 @@ public function setTags(array $tags): void /** * Gets a bit mask for error_reporting used in {@link ErrorListenerIntegration} to filter which errors to report. - * - * @return int */ public function getErrorTypes(): int { @@ -489,8 +457,6 @@ public function setErrorTypes(int $errorTypes): void /** * Gets the maximum number of breadcrumbs sent with events. - * - * @return int */ public function getMaxBreadcrumbs(): int { @@ -512,8 +478,6 @@ public function setMaxBreadcrumbs(int $maxBreadcrumbs): void /** * Gets a callback that will be invoked when adding a breadcrumb. * - * @return callable - * * @psalm-return callable(Breadcrumb): ?Breadcrumb */ public function getBeforeBreadcrumbCallback(): callable @@ -563,8 +527,6 @@ public function getIntegrations(): array /** * Should default PII be sent by default. - * - * @return bool */ public function shouldSendDefaultPii(): bool { @@ -585,8 +547,6 @@ public function setSendDefaultPii(bool $enable): void /** * Returns whether the default integrations are enabled. - * - * @return bool */ public function hasDefaultIntegrations(): bool { @@ -607,8 +567,6 @@ public function setDefaultIntegrations(bool $enable): void /** * Gets the max length for values in the event payload. - * - * @return int */ public function getMaxValueLength(): int { @@ -629,8 +587,6 @@ public function setMaxValueLength(int $maxValueLength): void /** * Gets the http proxy setting. - * - * @return string|null */ public function getHttpProxy(): ?string { @@ -676,8 +632,6 @@ public function setCaptureSilencedErrors(bool $shouldCapture): void /** * Gets the limit up to which integrations should capture the HTTP request * body. - * - * @return string */ public function getMaxRequestBodySize(): string { @@ -835,8 +789,6 @@ private function configureOptions(OptionsResolver $resolver): void * Normalizes the given path as an absolute path. * * @param string $value The path - * - * @return string */ private function normalizeAbsolutePath(string $value): string { @@ -855,8 +807,6 @@ private function normalizeAbsolutePath(string $value): string * * @param SymfonyOptions$options The configuration options * @param string|null $dsn The actual value of the option to normalize - * - * @return string|null */ private function normalizeDsnOption(SymfonyOptions $options, ?string $dsn): ?string { @@ -910,8 +860,6 @@ private function normalizeDsnOption(SymfonyOptions $options, ?string $dsn): ?str * that the URL is valid. * * @param string|null $dsn The value of the option - * - * @return bool */ private function validateDsnOption(?string $dsn): bool { @@ -956,8 +904,6 @@ private function validateDsnOption(?string $dsn): bool * implements the {@see IntegrationInterface} interface. * * @param array $integrations The value to validate - * - * @return bool */ private function validateIntegrationsOption(array $integrations): bool { @@ -974,8 +920,6 @@ private function validateIntegrationsOption(array $integrations): bool * Validates if the value of the max_breadcrumbs option is in range. * * @param int $value The value to validate - * - * @return bool */ private function validateMaxBreadcrumbsOptions(int $value): bool { @@ -986,8 +930,6 @@ private function validateMaxBreadcrumbsOptions(int $value): bool * Validates that the values passed to the `class_serializers` option are valid. * * @param array $serializers The value to validate - * - * @return bool */ private function validateClassSerializersOption(array $serializers): bool { @@ -1004,8 +946,6 @@ private function validateClassSerializersOption(array $serializers): bool * Validates that the values passed to the `tags` option are valid. * * @param array $tags The value to validate - * - * @return bool */ private function validateTagsOption(array $tags): bool { diff --git a/src/SentrySdk.php b/src/SentrySdk.php index 5dc9424c7..a8773f185 100644 --- a/src/SentrySdk.php +++ b/src/SentrySdk.php @@ -29,8 +29,6 @@ private function __construct() /** * Initializes the SDK by creating a new hub instance each time this method * gets called. - * - * @return HubInterface */ public static function init(): HubInterface { @@ -42,8 +40,6 @@ public static function init(): HubInterface /** * Gets the current hub. If it's not initialized then creates a new instance * and sets it as current hub. - * - * @return HubInterface */ public static function getCurrentHub(): HubInterface { @@ -58,8 +54,6 @@ public static function getCurrentHub(): HubInterface * Sets the current hub. * * @param HubInterface $hub The hub to set - * - * @return HubInterface */ public static function setCurrentHub(HubInterface $hub): HubInterface { diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index a5e316649..326e2e4fe 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -20,6 +20,7 @@ namespace Sentry\Serializer; +use Sentry\Exception\InvalidArgumentException; use Sentry\Options; /** @@ -70,9 +71,7 @@ abstract class AbstractSerializer /** * AbstractSerializer constructor. * - * @param Options $options The SDK configuration options - * @param int $maxDepth - * @param string|null $mbDetectOrder + * @param Options $options The SDK configuration options */ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDetectOrder = null) { @@ -90,7 +89,6 @@ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDete * sanitization and encoding. * * @param mixed $value - * @param int $_depth * * @return string|bool|float|int|array|null */ @@ -102,7 +100,7 @@ protected function serializeRecursively($value, int $_depth = 0) } if (\is_callable($value)) { - return $this->serializeCallable($value); + return $this->serializeCallableWithoutTypeHint($value); } if (\is_array($value)) { @@ -156,8 +154,6 @@ protected function serializeRecursively($value, int $_depth = 0) * objects implementing the `SerializableInterface`. * * @param object $object - * - * @return array */ protected function resolveClassSerializers($object): array { @@ -180,7 +176,6 @@ protected function resolveClassSerializers($object): array /** * @param object $object - * @param int $_depth * @param string[] $hashes * * @return array|string|bool|float|int|null @@ -209,8 +204,6 @@ protected function serializeObject($object, int $_depth = 0, array $hashes = []) * Serializes the given value to a string. * * @param mixed $value The value to serialize - * - * @return string */ protected function serializeString($value): string { @@ -250,7 +243,7 @@ protected function serializeValue($value) } if (\is_callable($value)) { - return $this->serializeCallable($value); + return $this->serializeCallableWithoutTypeHint($value); } if (\is_array($value)) { @@ -261,9 +254,30 @@ protected function serializeValue($value) } /** - * @param callable $callable + * This method is provided as a non-BC upgrade of serializeCallable, + * since using the callable type raises a deprecation in some cases. * - * @return string + * @param callable|mixed $callable + */ + protected function serializeCallableWithoutTypeHint($callable): string + { + if (\is_string($callable) && !\function_exists($callable)) { + return $callable; + } + + if (!\is_callable($callable)) { + throw new InvalidArgumentException(sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); + } + + return $this->serializeCallable($callable); + } + + /** + * Use serializeCallableWithoutTypeHint instead (no type in argument). + * + * @see https://github.com/getsentry/sentry-php/pull/821 + * + * @param callable $callable callable type to be removed in 3.0, see #821 */ protected function serializeCallable(callable $callable): string { @@ -271,7 +285,7 @@ protected function serializeCallable(callable $callable): string if (\is_array($callable)) { $reflection = new \ReflectionMethod($callable[0], $callable[1]); $class = $reflection->getDeclaringClass(); - } elseif ($callable instanceof \Closure || \is_string($callable)) { + } elseif ($callable instanceof \Closure || (\is_string($callable) && \function_exists($callable))) { $reflection = new \ReflectionFunction($callable); $class = null; } elseif (\is_object($callable) && method_exists($callable, '__invoke')) { @@ -298,11 +312,6 @@ protected function serializeCallable(callable $callable): string return $callableType . $reflection->getName() . ' ' . $this->serializeCallableParameters($reflection); } - /** - * @param \ReflectionFunctionAbstract $reflection - * - * @return string - */ private function serializeCallableParameters(\ReflectionFunctionAbstract $reflection): string { $params = []; @@ -331,8 +340,6 @@ public function getMbDetectOrder(): string } /** - * @param string $mbDetectOrder - * * @return $this */ public function setMbDetectOrder(string $mbDetectOrder): self @@ -342,17 +349,11 @@ public function setMbDetectOrder(string $mbDetectOrder): self return $this; } - /** - * @param bool $value - */ public function setSerializeAllObjects(bool $value): void { $this->serializeAllObjects = $value; } - /** - * @return bool - */ public function getSerializeAllObjects(): bool { return $this->serializeAllObjects; diff --git a/src/Serializer/SerializableInterface.php b/src/Serializer/SerializableInterface.php index e6d756174..81ba38674 100644 --- a/src/Serializer/SerializableInterface.php +++ b/src/Serializer/SerializableInterface.php @@ -12,8 +12,6 @@ interface SerializableInterface { /** * Returns an array representation of the object for Sentry. - * - * @return array|null */ public function toSentry(): ?array; } diff --git a/src/Severity.php b/src/Severity.php index cfb204cc6..aad1c8000 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -115,8 +115,6 @@ public static function fromError(int $severity): self /** * Creates a new instance of this enum for the "debug" value. - * - * @return self */ public static function debug(): self { @@ -125,8 +123,6 @@ public static function debug(): self /** * Creates a new instance of this enum for the "info" value. - * - * @return self */ public static function info(): self { @@ -135,8 +131,6 @@ public static function info(): self /** * Creates a new instance of this enum for the "warning" value. - * - * @return self */ public static function warning(): self { @@ -145,8 +139,6 @@ public static function warning(): self /** * Creates a new instance of this enum for the "error" value. - * - * @return self */ public static function error(): self { @@ -155,8 +147,6 @@ public static function error(): self /** * Creates a new instance of this enum for the "fatal" value. - * - * @return self */ public static function fatal(): self { @@ -167,8 +157,6 @@ public static function fatal(): self * Returns whether two object instances of this class are equal. * * @param self $other The object to compare - * - * @return bool */ public function isEqualTo(self $other): bool { diff --git a/src/Stacktrace.php b/src/Stacktrace.php index fae71c531..202129c4c 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -148,7 +148,9 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void $absoluteFilePath = @realpath($file) ?: $file; $isApplicationFile = 0 === strpos($absoluteFilePath, $this->options->getProjectRoot()); - if ($isApplicationFile && !empty($excludedAppPaths)) { + if (!$isApplicationFile) { + $frame->setIsInApp(false); + } elseif (!empty($excludedAppPaths)) { foreach ($excludedAppPaths as $path) { if (0 === mb_strpos($absoluteFilePath, $path)) { $frame->setIsInApp(false); @@ -219,8 +221,6 @@ public function jsonSerialize() * @param string $path The file path * @param int $lineNumber The line to centre about * @param int $maxLinesToFetch The maximum number of lines to fetch - * - * @return array */ protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array { @@ -278,8 +278,6 @@ protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxL * Removes from the given file path the specified prefixes. * * @param string $filePath The path to the file - * - * @return string */ protected function stripPrefixFromFilePath(string $filePath): string { @@ -296,8 +294,6 @@ protected function stripPrefixFromFilePath(string $filePath): string * Gets the values of the arguments of the given stackframe. * * @param array $frame The frame from where arguments are retrieved - * - * @return array */ protected function getFrameArgumentsValues(array $frame): array { @@ -310,8 +306,9 @@ protected function getFrameArgumentsValues(array $frame): array if (\is_string(array_keys($frame['args'])[0])) { $result = array_map([$this, 'serializeArgument'], $frame['args']); } else { - foreach (array_values($frame['args']) as $index => $argument) { - $result['param' . ($index + 1)] = $this->serializeArgument($argument); + $index = 0; + foreach (array_values($frame['args']) as $argument) { + $result['param' . (++$index)] = $this->serializeArgument($argument); } } @@ -322,8 +319,6 @@ protected function getFrameArgumentsValues(array $frame): array * Gets the arguments of the given stackframe. * * @param array $frame The frame from where arguments are retrieved - * - * @return array */ public function getFrameArguments(array $frame): array { diff --git a/src/State/Hub.php b/src/State/Hub.php index 8c76e0d4a..eaf7a0ab6 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -229,8 +229,6 @@ public function getIntegration(string $className): ?IntegrationInterface /** * Gets the scope bound to the top of the stack. - * - * @return Scope */ private function getScope(): Scope { @@ -239,8 +237,6 @@ private function getScope(): Scope /** * Gets the topmost client/layer pair in the stack. - * - * @return Layer */ private function getStackTop(): Layer { diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 132cdff6c..1085f9658 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -31,8 +31,6 @@ private function __construct() /** * Gets the instance of this class. This is a singleton, so once initialized * you will always get the same instance. - * - * @return self */ public static function getInstance(): self { diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 03c18fb07..717a520d1 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -19,15 +19,11 @@ interface HubInterface { /** * Gets the client bound to the top of the stack. - * - * @return ClientInterface|null */ public function getClient(): ?ClientInterface; /** * Gets the ID of the last captured event. - * - * @return string|null */ public function getLastEventId(): ?string; @@ -37,8 +33,6 @@ public function getLastEventId(): ?string; * information added to this scope will be removed once the scope ends. Be * sure to always remove this scope with {@see Hub::popScope} when the * operation finishes or throws. - * - * @return Scope */ public function pushScope(): Scope; @@ -46,8 +40,6 @@ public function pushScope(): Scope; * Removes a previously pushed scope from the stack. This restores the state * before the scope was pushed. All breadcrumbs and context information added * since the last call to {@see Hub::pushScope} are discarded. - * - * @return bool */ public function popScope(): bool; @@ -79,8 +71,6 @@ public function bindClient(ClientInterface $client): void; * * @param string $message The message * @param Severity $level The severity level of the message - * - * @return string|null */ public function captureMessage(string $message, ?Severity $level = null): ?string; @@ -88,8 +78,6 @@ public function captureMessage(string $message, ?Severity $level = null): ?strin * Captures an exception event and sends it to Sentry. * * @param \Throwable $exception The exception - * - * @return string|null */ public function captureException(\Throwable $exception): ?string; @@ -97,15 +85,11 @@ public function captureException(\Throwable $exception): ?string; * Captures a new event using the provided data. * * @param array $payload The data of the event being captured - * - * @return string|null */ public function captureEvent(array $payload): ?string; /** * Captures an event that logs the last occurred error. - * - * @return string|null */ public function captureLastError(): ?string; @@ -146,8 +130,6 @@ public static function setCurrent(self $hub): self; * Gets the integration whose FQCN matches the given one if it's available on the current client. * * @param string $className The FQCN of the integration - * - * @return IntegrationInterface|null */ public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/src/State/Layer.php b/src/State/Layer.php index 50305ebb1..0e0e734c6 100644 --- a/src/State/Layer.php +++ b/src/State/Layer.php @@ -38,8 +38,6 @@ public function __construct(?ClientInterface $client, Scope $scope) /** * Gets the client held by this layer. - * - * @return ClientInterface|null */ public function getClient(): ?ClientInterface { @@ -62,8 +60,6 @@ public function setClient(?ClientInterface $client): self /** * Gets the scope held by this layer. - * - * @return Scope */ public function getScope(): Scope { diff --git a/src/State/Scope.php b/src/State/Scope.php index dcbf6b112..a3764b63c 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -247,8 +247,6 @@ public function clear(): self * * @param Event $event The event object that will be enriched with scope data * @param array $payload The raw payload of the event that will be propagated to the event processors - * - * @return Event|null */ public function applyToEvent(Event $event, array $payload): ?Event { diff --git a/src/Transport/ClosableTransportInterface.php b/src/Transport/ClosableTransportInterface.php index 61ee025ed..65d758d51 100644 --- a/src/Transport/ClosableTransportInterface.php +++ b/src/Transport/ClosableTransportInterface.php @@ -18,8 +18,6 @@ interface ClosableTransportInterface * * @param int|null $timeout Maximum time in seconds before the sending * operation is interrupted - * - * @return PromiseInterface */ public function close(?int $timeout = null): PromiseInterface; } diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php index 3c5108e74..5d42a03dd 100644 --- a/src/Transport/DefaultTransportFactory.php +++ b/src/Transport/DefaultTransportFactory.php @@ -49,6 +49,7 @@ public function create(Options $options): TransportInterface $options, $this->httpClientFactory->create($options), $this->messageFactory, + true, false ); } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 28f974d8b..19bd948a6 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -4,9 +4,11 @@ namespace Sentry\Transport; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Message\RequestFactory as RequestFactoryInterface; -use Http\Promise\Promise as PromiseInterface; +use Http\Promise\Promise as HttpPromiseInterface; use Sentry\Event; use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; @@ -18,7 +20,7 @@ * * @author Stefano Arlandini */ -final class HttpTransport implements TransportInterface +final class HttpTransport implements TransportInterface, ClosableTransportInterface { /** * @var Options The Raven client configuration @@ -36,7 +38,7 @@ final class HttpTransport implements TransportInterface private $requestFactory; /** - * @var PromiseInterface[] The list of pending promises + * @var HttpPromiseInterface[] The list of pending promises */ private $pendingRequests = []; @@ -54,12 +56,21 @@ final class HttpTransport implements TransportInterface * @param RequestFactoryInterface $requestFactory The PSR-7 request factory * @param bool $delaySendingUntilShutdown This flag controls whether to delay * sending of the events until the shutdown - * of the application. This is a legacy feature - * that will stop working in version 3.0. + * of the application + * @param bool $triggerDeprecation Flag controlling whether to throw + * a deprecation if the transport is + * used relying on the deprecated behavior + * of delaying the sending of the events + * until the shutdown of the application */ - public function __construct(Options $config, HttpAsyncClientInterface $httpClient, RequestFactoryInterface $requestFactory, bool $delaySendingUntilShutdown = true) - { - if ($delaySendingUntilShutdown) { + public function __construct( + Options $config, + HttpAsyncClientInterface $httpClient, + RequestFactoryInterface $requestFactory, + bool $delaySendingUntilShutdown = true, + bool $triggerDeprecation = true + ) { + if ($delaySendingUntilShutdown && $triggerDeprecation) { @trigger_error(sprintf('Delaying the sending of the events using the "%s" class is deprecated since version 2.2 and will not work in 3.0.', __CLASS__), E_USER_DEPRECATED); } @@ -106,20 +117,44 @@ public function send(Event $event): ?string if ($this->delaySendingUntilShutdown) { $this->pendingRequests[] = $promise; } else { - $promise->wait(false); + try { + $promise->wait(); + } catch (\Throwable $exception) { + return null; + } } return $event->getId(); } + /** + * {@inheritdoc} + */ + public function close(?int $timeout = null): PromiseInterface + { + $this->cleanupPendingRequests(); + + return new FulfilledPromise(true); + } + /** * Cleanups the pending promises by awaiting for them. Any error that occurs * will be ignored. + * + * @deprecated since version 2.2.3, to be removed in 3.0. Even though this + * method is `private` we cannot delete it because it's used + * in some old versions of the `sentry-laravel` package using + * tricky code involving reflection and Closure binding */ private function cleanupPendingRequests(): void { while ($promise = array_pop($this->pendingRequests)) { - $promise->wait(false); + try { + $promise->wait(); + } catch (\Throwable $exception) { + // Do nothing because we don't want to break applications while + // trying to send events + } } } } diff --git a/src/Transport/SpoolTransport.php b/src/Transport/SpoolTransport.php index d1db77c8e..10bebd8bb 100644 --- a/src/Transport/SpoolTransport.php +++ b/src/Transport/SpoolTransport.php @@ -31,8 +31,6 @@ public function __construct(SpoolInterface $spool) /** * Gets the spool. - * - * @return SpoolInterface */ public function getSpool(): SpoolInterface { diff --git a/src/Transport/TransportFactoryInterface.php b/src/Transport/TransportFactoryInterface.php index b3b6454db..e38f12b1f 100644 --- a/src/Transport/TransportFactoryInterface.php +++ b/src/Transport/TransportFactoryInterface.php @@ -16,8 +16,6 @@ interface TransportFactoryInterface * Creates a new instance of a transport that will be used to send events. * * @param Options $options The options of the Sentry client - * - * @return TransportInterface */ public function create(Options $options): TransportInterface; } diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 454cce129..5a1b3ca3d 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -18,8 +18,6 @@ final class JSON * * @param mixed $data The data to encode * - * @return string - * * @throws JsonException If the encoding failed */ public static function encode($data): string diff --git a/src/Util/PHPVersion.php b/src/Util/PHPVersion.php index 700dea760..708d98369 100644 --- a/src/Util/PHPVersion.php +++ b/src/Util/PHPVersion.php @@ -13,8 +13,6 @@ final class PHPVersion * normalized form. * * @param string $version The string to parse - * - * @return string */ public static function parseVersion(string $version = PHP_VERSION): string { diff --git a/src/functions.php b/src/functions.php index 238efa153..efcf2614b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -21,8 +21,6 @@ function init(array $options = []): void * * @param string $message The message * @param Severity $level The severity level of the message - * - * @return string|null */ function captureMessage(string $message, ?Severity $level = null): ?string { @@ -33,8 +31,6 @@ function captureMessage(string $message, ?Severity $level = null): ?string * Captures an exception event and sends it to Sentry. * * @param \Throwable $exception The exception - * - * @return string|null */ function captureException(\Throwable $exception): ?string { @@ -45,8 +41,6 @@ function captureException(\Throwable $exception): ?string * Captures a new event using the provided data. * * @param array $payload The data of the event being captured - * - * @return string|null */ function captureEvent(array $payload): ?string { @@ -55,8 +49,6 @@ function captureEvent(array $payload): ?string /** * Logs the most recent error (obtained with {@link error_get_last}). - * - * @return string|null */ function captureLastError(): ?string { diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 9d32ecdfc..3c8eb5ce6 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -171,11 +171,6 @@ public function testRemoveHttpClientPlugin(): void $this->assertSame($plugin2, reset($plugins)); } - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ public function testGetClient(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 31090b739..f689228e7 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -230,7 +230,7 @@ public function testSampleRateAbsolute(float $sampleRate): void $httpClient = new MockClient(); $options = new Options(['dsn' => 'http://public:secret@example.com/1']); $options->setSampleRate($sampleRate); - $transportFactory = $this->createTransportFactory(new HttpTransport($options, $httpClient, MessageFactoryDiscovery::find(), false)); + $transportFactory = $this->createTransportFactory(new HttpTransport($options, $httpClient, MessageFactoryDiscovery::find(), true, false)); $client = (new ClientBuilder($options)) ->setTransportFactory($transportFactory) diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index a794005cb..811b08afa 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -455,13 +455,17 @@ public function serializableCallableProvider(): array 'callable' => $callableWithoutNamespaces, 'expected' => 'Lambda void {closure} [int|null param1_70ns]', ], + [ + 'callable' => __METHOD__, + 'expected' => __METHOD__, + ], ]; } /** * @dataProvider serializableCallableProvider */ - public function testSerializeCallable(callable $callable, string $expected): void + public function testSerializeCallable($callable, string $expected): void { $serializer = $this->createSerializer(); $actual = $this->invokeSerialization($serializer, $callable); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 08222be4a..ba60068ec 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -136,11 +136,13 @@ public function testAddFrameMarksAsInApp(): void $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function']); $stacktrace->addFrame('path/to/excluded/path/to/file', 12, ['function' => 'test_function']); + $stacktrace->addFrame('path/elsewhere', 12, ['function' => 'test_function']); $frames = $stacktrace->getFrames(); $this->assertFalse($frames[0]->isInApp()); - $this->assertTrue($frames[1]->isInApp()); + $this->assertFalse($frames[1]->isInApp()); + $this->assertTrue($frames[2]->isInApp()); } /** diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 51a9ace52..57d2d7c92 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -286,7 +286,7 @@ public function testApplyToEvent(): void $this->assertEquals(['foo' => 'baz'], $event->getUserContext()->toArray()); $scope->setFingerprint(['foo', 'bar']); - $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_CRITICAL, Breadcrumb::TYPE_ERROR, 'error_reporting')); + $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_FATAL, Breadcrumb::TYPE_ERROR, 'error_reporting')); $scope->setLevel(Severity::fatal()); $scope->setTag('bar', 'foo'); $scope->setExtra('foo', 'bar'); diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 928e451a3..224f63ae3 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -33,7 +33,7 @@ public function testSendDelaysExecutionUntilShutdown(): void ->willReturn($promise); $config = new Options(['dsn' => 'http://public@example.com/sentry/1']); - $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find()); + $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find(), true, false); $this->assertAttributeEmpty('pendingRequests', $transport); @@ -41,10 +41,7 @@ public function testSendDelaysExecutionUntilShutdown(): void $this->assertAttributeNotEmpty('pendingRequests', $transport); - $reflectionMethod = new \ReflectionMethod(HttpTransport::class, 'cleanupPendingRequests'); - $reflectionMethod->setAccessible(true); - $reflectionMethod->invoke($transport); - $reflectionMethod->setAccessible(false); + $transport->close(); $this->assertAttributeEmpty('pendingRequests', $transport); } @@ -60,7 +57,7 @@ public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoI ->willReturn($promise); $config = new Options(['dsn' => 'http://public@example.com/sentry/1']); - $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find(), false); + $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find(), false, false); $transport->send(new Event()); @@ -73,7 +70,7 @@ public function testSendThrowsOnMissingProjectIdCredential(): void /** @var HttpAsyncClient&MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); - $transport = new HttpTransport(new Options(), $httpClient, MessageFactoryDiscovery::find(), false); + $transport = new HttpTransport(new Options(), $httpClient, MessageFactoryDiscovery::find(), true, false); $transport->send(new Event()); } From 5e762e2d050a38d48865a76fec9aee53a53db1a1 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 11 Nov 2019 09:50:28 +0100 Subject: [PATCH 0512/1161] Fix failing build on PHP 7.4 (#894) --- .travis.yml | 2 -- composer.json | 2 +- tests/phpt/error_handler_respects_error_reporting.phpt | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e886d7ba..3e6a67b5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,6 @@ env: matrix: fast_finish: true - allow_failures: - - php: 7.4snapshot cache: directories: diff --git a/composer.json b/composer.json index 0e7553762..7decfedb2 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "psr/http-message-implementation": "^1.0", "ramsey/uuid": "^3.3", "symfony/options-resolver": "^2.7|^3.0|^4.0", - "zendframework/zend-diactoros": "^1.4|^2.0" + "zendframework/zend-diactoros": "^1.7.1|^2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt index 8ace02528..146a7c335 100644 --- a/tests/phpt/error_handler_respects_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -37,13 +37,13 @@ SentrySdk::getCurrentHub()->bindClient($client); echo 'Triggering silenced error' . PHP_EOL; -@$a['missing']; +@$a++; $client->getOptions()->setCaptureSilencedErrors(false); echo 'Triggering silenced error' . PHP_EOL; -@$a['missing']; +@$b++; ?> --EXPECT-- Triggering silenced error From 8f5a77c4ed8ba6b2489923aac8f5372fc7a6cede Mon Sep 17 00:00:00 2001 From: Richard van Laak Date: Tue, 26 Nov 2019 09:28:41 +0100 Subject: [PATCH 0513/1161] Allow Symfony 5 components (#925) --- composer.json | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 79c8deaba..4852a9eee 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,15 @@ "name": "sentry/sentry", "type": "library", "description": "A PHP SDK for Sentry (http://sentry.io)", - "keywords": ["sentry", "log", "logging", "error-monitoring", "error-handler", "crash-reporting", "crash-reports"], + "keywords": [ + "sentry", + "log", + "logging", + "error-monitoring", + "error-handler", + "crash-reporting", + "crash-reports" + ], "homepage": "http://sentry.io", "license": "BSD-3-Clause", "authors": [ @@ -24,7 +32,7 @@ "php-http/message": "^1.5", "psr/http-message-implementation": "^1.0", "ramsey/uuid": "^3.3", - "symfony/options-resolver": "^2.7|^3.0|^4.0", + "symfony/options-resolver": "^2.7|^3.0|^4.0|^5.0", "zendframework/zend-diactoros": "^1.4|^2.0" }, "require-dev": { @@ -35,7 +43,7 @@ "phpstan/phpstan": "^0.11", "phpstan/phpstan-phpunit": "^0.11", "phpunit/phpunit": "^7.5", - "symfony/phpunit-bridge": "^4.3", + "symfony/phpunit-bridge": "^4.3|^5.0", "vimeo/psalm": "^3.4" }, "suggest": { @@ -49,8 +57,8 @@ "files": [ "src/functions.php" ], - "psr-4" : { - "Sentry\\" : "src/" + "psr-4": { + "Sentry\\": "src/" } }, "autoload-dev": { @@ -75,6 +83,7 @@ "config": { "sort-packages": true }, + "prefer-stable": true, "extra": { "branch-alias": { "dev-develop": "2.2-dev" From c32dd8786c2a6a9b04df96a45096a517f8925612 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 26 Nov 2019 18:49:24 +0100 Subject: [PATCH 0514/1161] Fix compatibility issues with PHP 7.4 (#926) --- .travis.yml | 2 -- CHANGELOG.md | 2 ++ composer.json | 2 +- tests/phpt/error_handler_respects_error_reporting.phpt | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e886d7ba..3e6a67b5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,6 @@ env: matrix: fast_finish: true - allow_failures: - - php: 7.4snapshot cache: directories: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ab3b511f..903ce31d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # CHANGELOG ## Unreleased +- Add compatibility with Symfony 5 (#925) +- Ensure compatibility with PHP 7.4 (#894, #926) ## 2.2.4 (2019-11-04) diff --git a/composer.json b/composer.json index 4852a9eee..1b274e10c 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "psr/http-message-implementation": "^1.0", "ramsey/uuid": "^3.3", "symfony/options-resolver": "^2.7|^3.0|^4.0|^5.0", - "zendframework/zend-diactoros": "^1.4|^2.0" + "zendframework/zend-diactoros": "^1.7.1|^2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt index 8ace02528..146a7c335 100644 --- a/tests/phpt/error_handler_respects_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -37,13 +37,13 @@ SentrySdk::getCurrentHub()->bindClient($client); echo 'Triggering silenced error' . PHP_EOL; -@$a['missing']; +@$a++; $client->getOptions()->setCaptureSilencedErrors(false); echo 'Triggering silenced error' . PHP_EOL; -@$a['missing']; +@$b++; ?> --EXPECT-- Triggering silenced error From 234de4ee768d52a3342ce738bb71013f0eb91525 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 27 Nov 2019 09:35:28 +0100 Subject: [PATCH 0515/1161] Make the `integrations` option accept a callable (#919) --- CHANGELOG.md | 1 + src/ClientBuilder.php | 15 ----- src/Integration/Handler.php | 5 +- src/Options.php | 70 ++++++++++++++++++-- src/Transport/NullTransport.php | 2 + tests/OptionsTest.php | 109 ++++++++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ab3b511f..79358c465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Update PHPStan and introduce Psalm (#846) - Add an integration to set the transaction attribute of the event (#865) - Deprecate `Hub::getCurrent` and `Hub::setCurrent` methods to set the current hub instance (#847) +- Make the `integrations` option accept a `callable` that will receive the list of default integrations and returns a customized list (#919) ## 2.1.3 (2019-09-06) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 410836492..c3b512f6f 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -15,11 +15,6 @@ use Jean85\PrettyVersions; use Sentry\HttpClient\HttpClientFactory; use Sentry\HttpClient\PluggableHttpClientFactory; -use Sentry\Integration\ErrorListenerIntegration; -use Sentry\Integration\ExceptionListenerIntegration; -use Sentry\Integration\FatalErrorListenerIntegration; -use Sentry\Integration\RequestIntegration; -use Sentry\Integration\TransactionIntegration; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\Serializer; @@ -104,16 +99,6 @@ public function __construct(Options $options = null) { $this->options = $options ?? new Options(); $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); - - if ($this->options->hasDefaultIntegrations()) { - $this->options->setIntegrations(array_merge([ - new ExceptionListenerIntegration(), - new ErrorListenerIntegration(null, false), - new FatalErrorListenerIntegration(), - new RequestIntegration(), - new TransactionIntegration(), - ], $this->options->getIntegrations())); - } } /** diff --git a/src/Integration/Handler.php b/src/Integration/Handler.php index 4871b3304..e41d901a6 100644 --- a/src/Integration/Handler.php +++ b/src/Integration/Handler.php @@ -16,9 +16,10 @@ final class Handler private static $integrations = []; /** - * Calls {@link IntegrationInterface::setupOnce} for all passed integrations if it hasn't been called yet. + * Calls {@link IntegrationInterface::setupOnce} for all passed integrations + * if it hasn't been called yet. * - * @param array $integrations The integrations + * @param IntegrationInterface[] $integrations The integrations * * @return array */ diff --git a/src/Options.php b/src/Options.php index 755261b65..8aed2d2ed 100644 --- a/src/Options.php +++ b/src/Options.php @@ -4,7 +4,12 @@ namespace Sentry; +use Sentry\Integration\ErrorListenerIntegration; +use Sentry\Integration\ExceptionListenerIntegration; +use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Integration\IntegrationInterface; +use Sentry\Integration\RequestIntegration; +use Sentry\Integration\TransactionIntegration; use Symfony\Component\OptionsResolver\Options as SymfonyOptions; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -50,6 +55,11 @@ final class Options */ private $resolver; + /** + * @var IntegrationInterface[]|null The list of default integrations + */ + private $defaultIntegrations; + /** * Class constructor. * @@ -504,11 +514,13 @@ public function setBeforeBreadcrumbCallback(callable $callback): void } /** - * Set integrations that will be used by the created client. + * Sets the list of integrations that should be installed after SDK was + * initialized or a function that receives default integrations and returns + * a new, updated list. * - * @param IntegrationInterface[] $integrations The integrations + * @param IntegrationInterface[]|callable $integrations The list or callable */ - public function setIntegrations(array $integrations): void + public function setIntegrations($integrations): void { $options = array_merge($this->options, ['integrations' => $integrations]); @@ -522,7 +534,23 @@ public function setIntegrations(array $integrations): void */ public function getIntegrations(): array { - return $this->options['integrations']; + $defaultIntegrations = $this->getDefaultIntegrations(); + $userIntegrations = $this->options['integrations']; + $integrations = []; + + if (\is_callable($userIntegrations)) { + return $userIntegrations($defaultIntegrations); + } + + foreach ($defaultIntegrations as $defaultIntegration) { + $integrations[\get_class($defaultIntegration)] = $defaultIntegration; + } + + foreach ($userIntegrations as $userIntegration) { + $integrations[\get_class($userIntegration)] = $userIntegration; + } + + return array_values($integrations); } /** @@ -751,7 +779,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('error_types', ['int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); $resolver->setAllowedTypes('before_breadcrumb', ['callable']); - $resolver->setAllowedTypes('integrations', 'array'); + $resolver->setAllowedTypes('integrations', ['array', 'callable']); $resolver->setAllowedTypes('send_default_pii', 'bool'); $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); @@ -903,10 +931,14 @@ private function validateDsnOption(?string $dsn): bool * Validates that the elements of this option are all class instances that * implements the {@see IntegrationInterface} interface. * - * @param array $integrations The value to validate + * @param array|callable $integrations The value to validate */ - private function validateIntegrationsOption(array $integrations): bool + private function validateIntegrationsOption($integrations): bool { + if (\is_callable($integrations)) { + return true; + } + foreach ($integrations as $integration) { if (!$integration instanceof IntegrationInterface) { return false; @@ -957,4 +989,28 @@ private function validateTagsOption(array $tags): bool return true; } + + /** + * Gets the list of default integrations. + * + * @return IntegrationInterface[] + */ + private function getDefaultIntegrations(): array + { + if (!$this->options['default_integrations']) { + return []; + } + + if (null === $this->defaultIntegrations) { + $this->defaultIntegrations = [ + new ExceptionListenerIntegration(), + new ErrorListenerIntegration(null, false), + new FatalErrorListenerIntegration(), + new RequestIntegration(), + new TransactionIntegration(), + ]; + } + + return $this->defaultIntegrations; + } } diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index ea1a895d4..6597448c8 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -10,6 +10,8 @@ * This transport fakes the sending of events by just ignoring them. * * @author Stefano Arlandini + * + * @final since 2.3 */ class NullTransport implements TransportInterface { diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index ae50b4753..1c4c6d7c8 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -5,6 +5,12 @@ namespace Sentry\Tests; use PHPUnit\Framework\TestCase; +use Sentry\Integration\ErrorListenerIntegration; +use Sentry\Integration\ExceptionListenerIntegration; +use Sentry\Integration\FatalErrorListenerIntegration; +use Sentry\Integration\IntegrationInterface; +use Sentry\Integration\RequestIntegration; +use Sentry\Integration\TransactionIntegration; use Sentry\Options; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; @@ -277,7 +283,9 @@ public function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array public function testDsnOptionSupportsEnvironmentVariable(): void { $_SERVER['SENTRY_DSN'] = 'http://public@example.com/1'; + $options = new Options(); + unset($_SERVER['SENTRY_DSN']); $this->assertSame('http://example.com', $options->getDsn()); @@ -288,7 +296,9 @@ public function testDsnOptionSupportsEnvironmentVariable(): void public function testEnvironmentOptionSupportsEnvironmentVariable(): void { $_SERVER['SENTRY_ENVIRONMENT'] = 'test_environment'; + $options = new Options(); + unset($_SERVER['SENTRY_ENVIRONMENT']); $this->assertSame('test_environment', $options->getEnvironment()); @@ -297,9 +307,108 @@ public function testEnvironmentOptionSupportsEnvironmentVariable(): void public function testReleaseOptionSupportsEnvironmentVariable(): void { $_SERVER['SENTRY_RELEASE'] = '0.0.1'; + $options = new Options(); + unset($_SERVER['SENTRY_RELEASE']); $this->assertSame('0.0.1', $options->getRelease()); } + + /** + * @dataProvider integrationsOptionAsCallableDataProvider + */ + public function testIntegrationsOptionAsCallable(bool $useDefaultIntegrations, $integrations, array $expectedResult): void + { + $options = new Options([ + 'default_integrations' => $useDefaultIntegrations, + 'integrations' => $integrations, + ]); + + $this->assertEquals($expectedResult, $options->getIntegrations()); + } + + public function integrationsOptionAsCallableDataProvider(): \Generator + { + yield 'No default integrations && no user integrations' => [ + false, + [], + [], + ]; + + $integration = new class() implements IntegrationInterface { + public function setupOnce(): void + { + } + }; + + yield 'User integration added && default integration appearing only once' => [ + true, + [ + $integration, + new ExceptionListenerIntegration(), + ], + [ + new ExceptionListenerIntegration(), + new ErrorListenerIntegration(null, false), + new FatalErrorListenerIntegration(), + new RequestIntegration(), + new TransactionIntegration(), + $integration, + ], + ]; + + $integration = new class() implements IntegrationInterface { + public function setupOnce(): void + { + } + }; + + yield 'User integration added twice' => [ + false, + [ + $integration, + $integration, + ], + [ + $integration, + ], + ]; + + yield 'User integrations as callable returning empty list' => [ + true, + static function (): array { + return []; + }, + [], + ]; + + $integration = new class() implements IntegrationInterface { + public function setupOnce(): void + { + } + }; + + yield 'User integrations as callable returning custom list' => [ + true, + static function () use ($integration): array { + return [$integration]; + }, + [$integration], + ]; + + yield 'User integrations as callable returning $defaultIntegrations argument' => [ + true, + static function (array $defaultIntegrations): array { + return $defaultIntegrations; + }, + [ + new ExceptionListenerIntegration(), + new ErrorListenerIntegration(null, false), + new FatalErrorListenerIntegration(), + new RequestIntegration(), + new TransactionIntegration(), + ], + ]; + } } From 24aa98be497ff089e2d58807ee3db0bb6a021fc2 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 27 Nov 2019 09:37:42 +0100 Subject: [PATCH 0516/1161] meta: Changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 903ce31d4..e30329b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # CHANGELOG ## Unreleased + +## 2.2.5 (2019-11-27) + - Add compatibility with Symfony 5 (#925) - Ensure compatibility with PHP 7.4 (#894, #926) From ffcd2f488d585d2a4cb54498e7038c15059e053b Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 10 Dec 2019 22:33:22 +0100 Subject: [PATCH 0517/1161] Fix deprecations thrown when using PHP 7.4 (#930) --- .appveyor.yml | 8 +++++++- .travis.yml | 2 +- CHANGELOG.md | 2 ++ composer.json | 2 +- phpstan.neon | 1 - src/Serializer/AbstractSerializer.php | 11 ++++++++--- tests/Serializer/AbstractSerializerTest.php | 2 +- 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index e652b52c6..47c96285a 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,10 +3,12 @@ build: false clone_depth: 2 clone_folder: c:\projects\sentry-php skip_branch_with_pr: true +image: Visual Studio 2019 branches: only: - master - develop + - /^release\/.+$/ environment: matrix: @@ -22,6 +24,10 @@ environment: DEPENDENCIES: lowest - PHP_VERSION: 7.3-Win32-VC15 DEPENDENCIES: highest + - PHP_VERSION: 7.4-Win32-VC15 + DEPENDENCIES: lowest + - PHP_VERSION: 7.4-Win32-VC15 + DEPENDENCIES: highest matrix: fast_finish: true @@ -49,7 +55,7 @@ install: - IF %INSTALL_PHP%==1 echo extension=php_mbstring.dll >> php.ini - IF %INSTALL_PHP%==1 echo extension=php_openssl.dll >> php.ini - cd c:\projects\sentry-php - - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/1.8.3/composer.phar + - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/1.9.1/composer.phar - php composer.phar self-update - IF %DEPENDENCIES%==lowest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-lowest --prefer-dist - IF %DEPENDENCIES%==highest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-dist diff --git a/.travis.yml b/.travis.yml index 3e6a67b5e..d4b8f0190 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ php: - 7.1 - 7.2 - 7.3 - - 7.4snapshot + - 7.4 env: - dependencies=highest diff --git a/CHANGELOG.md b/CHANGELOG.md index e30329b66..83832626c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix remaining PHP 7.4 deprecations (#930) + ## 2.2.5 (2019-11-27) - Add compatibility with Symfony 5 (#925) diff --git a/composer.json b/composer.json index 1b274e10c..e55c313ee 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.11", "phpstan/phpstan-phpunit": "^0.11", - "phpunit/phpunit": "^7.5", + "phpunit/phpunit": "^7.5.18", "symfony/phpunit-bridge": "^4.3|^5.0", "vimeo/psalm": "^3.4" }, diff --git a/phpstan.neon b/phpstan.neon index 96635fcfe..253c33e78 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,7 +6,6 @@ parameters: - tests ignoreErrors: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - - '/Binary operation "\*" between array and 2 results in an error\./' - '/Http\\Client\\Curl\\Client/' - '/^Parameter #1 \$object of method ReflectionProperty::setValue\(\) expects object, null given\.$/' # https://github.com/phpstan/phpstan/pull/2340 - diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 326e2e4fe..fe7af7ec1 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -301,8 +301,8 @@ protected function serializeCallable(callable $callable): string $callableType = $reflection->isClosure() ? 'Lambda ' : 'Callable '; $callableReturnType = $reflection->getReturnType(); - if (null !== $callableReturnType) { - $callableType .= $callableReturnType . ' '; + if ($callableReturnType instanceof \ReflectionNamedType) { + $callableType .= $callableReturnType->getName() . ' '; } if ($class) { @@ -316,7 +316,12 @@ private function serializeCallableParameters(\ReflectionFunctionAbstract $reflec { $params = []; foreach ($reflection->getParameters() as &$param) { - $paramType = $param->getType() ?? 'mixed'; + $reflectionType = $param->getType(); + if ($reflectionType instanceof \ReflectionNamedType) { + $paramType = $reflectionType->getName(); + } else { + $paramType = 'mixed'; + } if ($param->allowsNull()) { $paramType .= '|null'; diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 811b08afa..b4cc24695 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -361,7 +361,7 @@ public function serializableCallableProvider(): array return [ [ 'callable' => function (array $param1) { - return $param1 * 2; + throw new \Exception('Don\'t even think about invoke me'); }, 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array param1]', ], From 3b09f8163fd104e8172a1823e0ad999a953cb39f Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 11 Dec 2019 23:02:06 +0000 Subject: [PATCH 0518/1161] Add the IgnoreErrorsIntegration integration and deprecate the excluded_exceptions option (#928) --- CHANGELOG.md | 1 + README.md | 8 +- phpstan.neon | 6 + src/Client.php | 14 ++- src/ClientInterface.php | 8 +- src/Integration/Handler.php | 2 + src/Integration/IgnoreErrorsIntegration.php | 103 +++++++++++++++++ src/Options.php | 21 +++- src/State/HubInterface.php | 9 +- tests/ClientTest.php | 5 +- .../IgnoreErrorsIntegrationTest.php | 107 ++++++++++++++++++ tests/OptionsTest.php | 6 +- tests/SentrySdkExtension.php | 6 + .../error_handler_captures_fatal_error.phpt | 19 +++- ...dler_captures_rethrown_exception_once.phpt | 19 +++- ...rror_handler_respects_error_reporting.phpt | 19 +++- ...tegration_respects_error_types_option.phpt | 29 +++-- ...tion_skips_fatal_errors_if_configured.phpt | 27 +++-- ...tegration_respects_error_types_option.phpt | 25 ++-- tests/phpt/spool_drain.phpt | 25 +++- 20 files changed, 393 insertions(+), 66 deletions(-) create mode 100644 src/Integration/IgnoreErrorsIntegration.php create mode 100644 tests/Integration/IgnoreErrorsIntegrationTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 373b8d876..397a9caf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix remaining PHP 7.4 deprecations (#930) - Make the `integrations` option accept a `callable` that will receive the list of default integrations and returns a customized list (#919) +- Add the `IgnoreErrorsIntegration` integration to deprecate and replace the `exclude_exceptions` option (#928) ## 2.2.5 (2019-11-27) diff --git a/README.md b/README.md index 3bf81bc5a..0b310449a 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ # Sentry for PHP -[![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=develop)](http://travis-ci.org/getsentry/sentry-php) -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/getsentry/sentry-php?branch=master&svg=true)](https://ci.appveyor.com/project/sentry/sentry-php) +[![Build Status](https://img.shields.io/travis/getsentry/sentry-php/develop?logo=travis)](http://travis-ci.org/getsentry/sentry-php) +[![AppVeyor Build Status](https://img.shields.io/appveyor/ci/sentry/sentry-php/develop?logo=appveyor)](https://ci.appveyor.com/project/sentry/sentry-php) [![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) [![Monthly Downloads](https://poser.pugx.org/sentry/sentry/d/monthly)](https://packagist.org/packages/sentry/sentry) [![Latest Stable Version](https://poser.pugx.org/sentry/sentry/v/stable)](https://packagist.org/packages/sentry/sentry) @@ -24,8 +24,8 @@ information needed to prioritize, identify, reproduce and fix each issue. To install the SDK you will need to be using [Composer]([https://getcomposer.org/) in your project. To install it please see the [docs](https://getcomposer.org/download/). -This is our "core" SDK, meaning that all the important code regarding error handling lives here. -If you are happy with using the HTTP client we recommend install the SDK like: [`sentry/sdk`](https://github.com/getsentry/sentry-php-sdk) +This is our "core" SDK, meaning that all the important code regarding error handling lives here. +If you are happy with using the HTTP client we recommend install the SDK like: [`sentry/sdk`](https://github.com/getsentry/sentry-php-sdk) ```bash php composer.phar require sentry/sdk diff --git a/phpstan.neon b/phpstan.neon index 3f7fb4e33..634221f1b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -31,6 +31,12 @@ parameters: - message: '/^Property Sentry\\HttpClient\\HttpClientFactory::\$httpClient \(Http\\Client\\HttpAsyncClient\|null\) does not accept Http\\Client\\Curl\\Client.$/' path: src/HttpClient/HttpClientFactory.php + - + message: '/^Access to an undefined property Sentry\\Integration\\IntegrationInterface::\$options\.$/' + path: src/Integration/IgnoreErrorsIntegration.php + - + message: '/^Call to an undefined method Sentry\\Integration\\IntegrationInterface::shouldDropEvent\(\)\.$/' + path: src/Integration/IgnoreErrorsIntegration.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Client.php b/src/Client.php index 5a125e33a..be4cb6e4e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -7,6 +7,7 @@ use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; use Sentry\Integration\Handler; +use Sentry\Integration\IgnoreErrorsIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; use Sentry\Transport\ClosableTransportInterface; @@ -45,7 +46,9 @@ final class Client implements FlushableClientInterface private $eventFactory; /** - * @var IntegrationInterface[] The stack of integrations + * @var array The stack of integrations + * + * @psalm-var array, IntegrationInterface> */ private $integrations; @@ -96,7 +99,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope */ public function captureException(\Throwable $exception, ?Scope $scope = null): ?string { - if ($this->options->isExcludedException($exception)) { + if (!isset($this->integrations[IgnoreErrorsIntegration::class]) && $this->options->isExcludedException($exception, false)) { return null; } @@ -135,9 +138,12 @@ public function captureLastError(?Scope $scope = null): ?string /** * {@inheritdoc} + * + * @psalm-template T of IntegrationInterface */ public function getIntegration(string $className): ?IntegrationInterface { + /** @psalm-var T|null */ return $this->integrations[$className] ?? null; } @@ -178,6 +184,10 @@ private function prepareEvent(array $payload, ?Scope $scope = null, bool $withSt if (null !== $scope) { $event = $scope->applyToEvent($event, $payload); + + if (null === $event) { + return null; + } } return \call_user_func($this->options->getBeforeSendCallback(), $event); diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 5937d3f15..6007369ca 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -52,9 +52,15 @@ public function captureLastError(?Scope $scope = null): ?string; public function captureEvent(array $payload, ?Scope $scope = null): ?string; /** - * Returns the integration instance if it is installed on the Client. + * Returns the integration instance if it is installed on the client. * * @param string $className the classname of the integration + * + * @psalm-template T of IntegrationInterface + * + * @psalm-param class-string $className + * + * @psalm-return T|null */ public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/src/Integration/Handler.php b/src/Integration/Handler.php index e41d901a6..3b7c35fdb 100644 --- a/src/Integration/Handler.php +++ b/src/Integration/Handler.php @@ -22,6 +22,8 @@ final class Handler * @param IntegrationInterface[] $integrations The integrations * * @return array + * + * @psalm-return array, IntegrationInterface> */ public static function setupIntegrations(array $integrations): array { diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php new file mode 100644 index 000000000..d83dbe8a3 --- /dev/null +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -0,0 +1,103 @@ + + */ +final class IgnoreErrorsIntegration implements IntegrationInterface +{ + /** + * @var array The options + */ + private $options; + + /** + * Creates a new instance of this integration and configures it with the + * given options. + * + * @param array $options The options + * + * @psalm-param array{ + * ignore_exceptions?: bool + * } $options + */ + public function __construct(array $options = []) + { + $resolver = new OptionsResolver(); + $resolver->setDefaults([ + 'ignore_exceptions' => [], + ]); + + $resolver->setAllowedTypes('ignore_exceptions', ['array']); + + $this->options = $resolver->resolve($options); + } + + /** + * {@inheritdoc} + */ + public function setupOnce(): void + { + Scope::addGlobalEventProcessor(function (Event $event): ?Event { + $currentHub = SentrySdk::getCurrentHub(); + $integration = $currentHub->getIntegration(self::class); + + if (null !== $integration && $integration->shouldDropEvent($event, $integration->options)) { + return null; + } + + return $event; + }); + } + + /** + * Checks whether the given event should be dropped according to the options + * that configures the current instance of this integration. + * + * @param Event $event The event to check + * @param array $options The options of the integration + */ + private function shouldDropEvent(Event $event, array $options): bool + { + if ($this->isIgnoredException($event, $options)) { + return true; + } + + return false; + } + + /** + * Checks whether the given event should be dropped or not according to the + * criteria defined in the integration's options. + * + * @param Event $event The event instance + * @param array $options The options of the integration + */ + private function isIgnoredException(Event $event, array $options): bool + { + $exceptions = $event->getExceptions(); + + if (empty($exceptions) || !isset($exceptions[0]['type'])) { + return false; + } + + foreach ($options['ignore_exceptions'] as $ignoredException) { + if (is_a($exceptions[0]['type'], $ignoredException, true)) { + return true; + } + } + + return false; + } +} diff --git a/src/Options.php b/src/Options.php index 8aed2d2ed..e9679cadd 100644 --- a/src/Options.php +++ b/src/Options.php @@ -225,9 +225,13 @@ public function setEnvironment(string $environment): void * events to Sentry. * * @return string[] + * + * @deprecated since version 2.3, to be removed in 3.0 */ public function getExcludedExceptions(): array { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); + return $this->options['excluded_exceptions']; } @@ -236,9 +240,13 @@ public function getExcludedExceptions(): array * events to Sentry. * * @param string[] $exceptions The list of exception classes + * + * @deprecated since version 2.3, to be removed in 3.0 */ public function setExcludedExceptions(array $exceptions): void { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); + $options = array_merge($this->options, ['excluded_exceptions' => $exceptions]); $this->options = $this->resolver->resolve($options); @@ -248,10 +256,19 @@ public function setExcludedExceptions(array $exceptions): void * Checks whether the given exception should be ignored when sending events * to Sentry. * - * @param \Throwable $exception The exception + * @param \Throwable $exception The exception + * @param bool $throwDeprecation Flag indicating whether to throw a + * deprecation for the usage of this + * method + * + * @deprecated since version 2.3, to be removed in 3.0 */ - public function isExcludedException(\Throwable $exception): bool + public function isExcludedException(\Throwable $exception, bool $throwDeprecation = true): bool { + if ($throwDeprecation) { + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); + } + foreach ($this->options['excluded_exceptions'] as $exceptionClass) { if ($exception instanceof $exceptionClass) { return true; diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 717a520d1..2aaa57c20 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -7,7 +7,6 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Integration\IntegrationInterface; -use Sentry\SentrySdk; use Sentry\Severity; /** @@ -110,7 +109,6 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool; * @return HubInterface * * @deprecated since version 2.2, to be removed in 3.0 - * @see SentrySdk::getCurrentHub() */ public static function getCurrent(): self; @@ -122,7 +120,6 @@ public static function getCurrent(): self; * @return HubInterface * * @deprecated since version 2.2, to be removed in 3.0 - * @see SentrySdk::setCurrentHub() */ public static function setCurrent(self $hub): self; @@ -130,6 +127,12 @@ public static function setCurrent(self $hub): self; * Gets the integration whose FQCN matches the given one if it's available on the current client. * * @param string $className The FQCN of the integration + * + * @psalm-template T of IntegrationInterface + * + * @psalm-param class-string $className + * + * @psalm-return T|null */ public function getIntegration(string $className): ?IntegrationInterface; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f689228e7..0926e8036 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -11,6 +11,7 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; +use Sentry\SentrySdk; use Sentry\Serializer\Serializer; use Sentry\Severity; use Sentry\Stacktrace; @@ -71,6 +72,7 @@ public function testCaptureException(): void */ public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches(bool $shouldCapture, string $excluded, \Throwable $thrown): void { + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transportFactory = $this->createTransportFactory($transport); @@ -86,7 +88,8 @@ public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches ->setTransportFactory($transportFactory) ->getClient(); - $client->captureException($thrown); + SentrySdk::getCurrentHub()->bindClient($client); + SentrySdk::getCurrentHub()->captureException($thrown); } public function captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesDataProvider(): array diff --git a/tests/Integration/IgnoreErrorsIntegrationTest.php b/tests/Integration/IgnoreErrorsIntegrationTest.php new file mode 100644 index 000000000..d8b2e648d --- /dev/null +++ b/tests/Integration/IgnoreErrorsIntegrationTest.php @@ -0,0 +1,107 @@ +setupOnce(); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getIntegration') + ->willReturn($isIntegrationEnabled ? $integration : null); + + SentrySdk::getCurrentHub()->bindClient($client); + + withScope(function (Scope $scope) use ($event, $expectedEventToBeDropped): void { + $event = $scope->applyToEvent($event, []); + + if ($expectedEventToBeDropped) { + $this->assertNull($event); + } else { + $this->assertNotNull($event); + } + }); + } + + public function invokeDataProvider(): \Generator + { + $event = new Event(); + $event->setExceptions([ + ['type' => \RuntimeException::class], + ]); + + yield 'Integration disabled' => [ + new Event(), + false, + [ + 'ignore_exceptions' => [], + ], + false, + ]; + + $event = new Event(); + $event->setExceptions([ + ['type' => \RuntimeException::class], + ]); + + yield 'No exceptions to check' => [ + new Event(), + true, + [ + 'ignore_exceptions' => [], + ], + false, + ]; + + $event = new Event(); + $event->setExceptions([ + ['type' => \RuntimeException::class], + ]); + + yield 'The exception is matching exactly the "ignore_exceptions" option' => [ + $event, + true, + [ + 'ignore_exceptions' => [ + \RuntimeException::class, + ], + ], + true, + ]; + + $event = new Event(); + $event->setExceptions([ + ['type' => \RuntimeException::class], + ]); + + yield 'The exception is matching the "ignore_exceptions" option' => [ + $event, + true, + [ + 'ignore_exceptions' => [ + \Exception::class, + ], + ], + true, + ]; + } +} diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 1c4c6d7c8..650d48a24 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -17,6 +17,8 @@ final class OptionsTest extends TestCase { /** + * @group legacy + * * @dataProvider optionsDataProvider */ public function testConstructor($option, $value, $getterMethod): void @@ -27,6 +29,8 @@ public function testConstructor($option, $value, $getterMethod): void } /** + * @group legacy + * * @dataProvider optionsDataProvider */ public function testGettersAndSetters(string $option, $value, string $getterMethod, ?string $setterMethod = null): void @@ -206,7 +210,7 @@ public function testIsExcludedException($excludedExceptions, $exception, $result { $configuration = new Options(['excluded_exceptions' => $excludedExceptions]); - $this->assertSame($result, $configuration->isExcludedException($exception)); + $this->assertSame($result, $configuration->isExcludedException($exception, false)); } public function excludedExceptionsDataProvider() diff --git a/tests/SentrySdkExtension.php b/tests/SentrySdkExtension.php index 946a4f870..f6d1b9bc3 100644 --- a/tests/SentrySdkExtension.php +++ b/tests/SentrySdkExtension.php @@ -5,6 +5,7 @@ namespace Sentry\Tests; use PHPUnit\Runner\BeforeTestHook as BeforeTestHookInterface; +use Sentry\Integration\Handler; use Sentry\SentrySdk; use Sentry\State\Scope; @@ -21,5 +22,10 @@ public function executeBeforeTest(string $test): void $reflectionProperty->setAccessible(true); $reflectionProperty->setValue(null, []); $reflectionProperty->setAccessible(false); + + $reflectionProperty = new \ReflectionProperty(Handler::class, 'integrations'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue(null, []); + $reflectionProperty->setAccessible(false); } } diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index 52293c5d0..efeae34db 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -10,8 +10,10 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\ErrorHandler; use Sentry\Event; +use Sentry\Options; use Sentry\SentrySdk; -use Sentry\Transport\TransportInterface;; +use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -21,17 +23,22 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transport = new class implements TransportInterface { - public function send(Event $event): ?string +$transportFactory = new class implements TransportFactoryInterface { + public function create(Options $options): TransportInterface { - echo 'Transport called' . PHP_EOL; + return new class implements TransportInterface { + public function send(Event $event): ?string + { + echo 'Transport called' . PHP_EOL; - return null; + return null; + } + }; } }; $client = ClientBuilder::create([]) - ->setTransport($transport) + ->setTransportFactory($transportFactory) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_captures_rethrown_exception_once.phpt b/tests/phpt/error_handler_captures_rethrown_exception_once.phpt index 0f9913130..0e713876c 100644 --- a/tests/phpt/error_handler_captures_rethrown_exception_once.phpt +++ b/tests/phpt/error_handler_captures_rethrown_exception_once.phpt @@ -9,7 +9,9 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\Options; use Sentry\SentrySdk; +use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -26,17 +28,22 @@ set_exception_handler(static function (\Exception $exception): void { throw $exception; }); -$transport = new class implements TransportInterface { - public function send(Event $event): ?string +$transportFactory = new class implements TransportFactoryInterface { + public function create(Options $options): TransportInterface { - echo 'Transport called' . PHP_EOL; - - return null; + return new class implements TransportInterface { + public function send(Event $event): ?string + { + echo 'Transport called' . PHP_EOL; + + return null; + } + }; } }; $client = ClientBuilder::create([]) - ->setTransport($transport) + ->setTransportFactory($transportFactory) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt index 146a7c335..0b6dc3420 100644 --- a/tests/phpt/error_handler_respects_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -9,7 +9,9 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\Options; use Sentry\SentrySdk; +use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -20,17 +22,22 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transport = new class implements TransportInterface { - public function send(Event $event): ?string +$transportFactory = new class implements TransportFactoryInterface { + public function create(Options $options): TransportInterface { - echo 'Transport called' . PHP_EOL; - - return null; + return new class implements TransportInterface { + public function send(Event $event): ?string + { + echo 'Transport called' . PHP_EOL; + + return null; + } + }; } }; $client = ClientBuilder::create(['capture_silenced_errors' => true]) - ->setTransport($transport) + ->setTransportFactory($transportFactory) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt index c4f3b6198..3d3873396 100644 --- a/tests/phpt/error_listener_integration_respects_error_types_option.phpt +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -12,6 +12,7 @@ use Sentry\Event; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Options; use Sentry\SentrySdk; +use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -22,24 +23,30 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transport = new class implements TransportInterface { - public function send(Event $event): ?string +$transportFactory = new class implements TransportFactoryInterface { + public function create(Options $options): TransportInterface { - echo 'Transport called'; - - return null; + return new class implements TransportInterface { + public function send(Event $event): ?string + { + echo 'Transport called'; + + return null; + } + }; } }; -$options = new Options(); -$options->setErrorTypes(E_ALL & ~E_USER_WARNING); -$options->setDefaultIntegrations(false); -$options->setIntegrations([ - new ErrorListenerIntegration($options), +$options = new Options([ + 'error_types' => E_ALL & ~E_USER_WARNING, + 'default_integrations' => false, + 'integrations' => [ + new ErrorListenerIntegration(), + ], ]); $client = (new ClientBuilder($options)) - ->setTransport($transport) + ->setTransportFactory($transportFactory) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt b/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt index 836ca33ab..1e5ee3211 100644 --- a/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt +++ b/tests/phpt/error_listener_integration_skips_fatal_errors_if_configured.phpt @@ -12,6 +12,7 @@ use Sentry\Event; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Options; use Sentry\SentrySdk; +use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -22,23 +23,29 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transport = new class implements TransportInterface { - public function send(Event $event): ?string +$transportFactory = new class implements TransportFactoryInterface { + public function create(Options $options): TransportInterface { - echo 'Transport called (it should not have been)'; - - return null; + return new class implements TransportInterface { + public function send(Event $event): ?string + { + echo 'Transport called' . PHP_EOL; + + return null; + } + }; } }; -$options = new Options(); -$options->setDefaultIntegrations(false); -$options->setIntegrations([ - new ErrorListenerIntegration($options, false), +$options = new Options([ + 'default_integrations' => false, + 'integrations' => [ + new ErrorListenerIntegration(null, false), + ], ]); $client = (new ClientBuilder($options)) - ->setTransport($transport) + ->setTransportFactory($transportFactory) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index df5854d8a..d7468bf7c 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -12,6 +12,7 @@ use Sentry\Event; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; use Sentry\SentrySdk; +use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -22,23 +23,29 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transport = new class implements TransportInterface { - public function send(Event $event): ?string +$transportFactory = new class implements TransportFactoryInterface { + public function create(Options $options): TransportInterface { - echo 'Transport called' . PHP_EOL; + return new class implements TransportInterface { + public function send(Event $event): ?string + { + echo 'Transport called' . PHP_EOL; - return null; + return null; + } + }; } }; -$options = new Options(); -$options->setDefaultIntegrations(false); -$options->setIntegrations([ - new FatalErrorListenerIntegration($options), +$options = new Options([ + 'default_integrations' => false, + 'integrations' => [ + new FatalErrorListenerIntegration(), + ], ]); $client = (new ClientBuilder($options)) - ->setTransport($transport) + ->setTransportFactory($transportFactory) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/spool_drain.phpt b/tests/phpt/spool_drain.phpt index efe685200..882361f9f 100644 --- a/tests/phpt/spool_drain.phpt +++ b/tests/phpt/spool_drain.phpt @@ -8,11 +8,15 @@ declare(strict_types=1); namespace Sentry\Tests; use PHPUnit\Framework\Assert; +use Sentry\ClientBuilder; +use Sentry\Options; use Sentry\SentrySdk; use Sentry\Spool\MemorySpool; -use Sentry\Transport\SpoolTransport; +use Sentry\Spool\SpoolInterface; use Sentry\Transport\NullTransport; -use Sentry\ClientBuilder; +use Sentry\Transport\SpoolTransport; +use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -23,11 +27,24 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; $spool = new MemorySpool(); -$transport = new SpoolTransport($spool); $nullTransport = new NullTransport(); +$transportFactory = new class($spool) implements TransportFactoryInterface { + private $spool; + + public function __construct(SpoolInterface $spool) + { + $this->spool = $spool; + } + + public function create(Options $options): TransportInterface + { + return new SpoolTransport($this->spool); + } +}; + $client = ClientBuilder::create() - ->setTransport($transport) + ->setTransportFactory($transportFactory) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); From 910a255c577509403a4c1cecbfc627f531170d74 Mon Sep 17 00:00:00 2001 From: Bruno Wowk Date: Mon, 16 Dec 2019 10:14:51 -0300 Subject: [PATCH 0519/1161] Deprecate the replacement of all data of the scope's user context and allow merging instead (#931) --- CHANGELOG.md | 2 ++ src/State/Scope.php | 13 +++++++++++-- tests/State/ScopeTest.php | 39 ++++++++++++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 397a9caf5..a92aeb81a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Deprecate `Scope::setUser` behaviour of replacing user data. (#929) +- Add the `$merge` parameter on `Scope::setUser` to allow merging user context. (#929) - Fix remaining PHP 7.4 deprecations (#930) - Make the `integrations` option accept a `callable` that will receive the list of default integrations and returns a customized list (#919) - Add the `IgnoreErrorsIntegration` integration to deprecate and replace the `exclude_exceptions` option (#928) diff --git a/src/State/Scope.php b/src/State/Scope.php index a3764b63c..e61d187bd 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -130,12 +130,21 @@ public function setExtras(array $extras): self /** * Sets the given data in the user context. * - * @param array $data The data + * @param array $data The data + * @param bool $merge If true, $data will be merged into user context instead of replacing it * * @return $this */ - public function setUser(array $data): self + public function setUser(array $data, bool $merge = false): self { + if ($merge) { + $this->user->merge($data); + + return $this; + } + + @trigger_error('Replacing the data is deprecated since version 2.3 and will stop working from version 3.0. Set the second argument to `true` to merge the data instead.', E_USER_DEPRECATED); + $this->user->replaceData($data); return $this; diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 57d2d7c92..e8c31c60e 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -29,7 +29,7 @@ public function testSetTag(): void $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTagsContext()->toArray()); } - public function setTags(): void + public function testSetTags(): void { $scope = new Scope(); $scope->setTags(['foo' => 'bar']); @@ -82,7 +82,12 @@ public function testSetExtras(): void $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtraContext()->toArray()); } - public function testSetUser(): void + /** + * @group legacy + * + * @expectedDeprecation Replacing the data is deprecated since version 2.3 and will stop working from version 3.0. Set the second argument to `true` to merge the data instead. + */ + public function testSetUserThrowsDeprecation(): void { $scope = new Scope(); @@ -106,6 +111,30 @@ public function testSetUser(): void $this->assertSame(['bar' => 'baz'], $event->getUserContext()->toArray()); } + public function testSetUser(): void + { + $scope = new Scope(); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([], $event->getUserContext()->toArray()); + + $scope->setUser(['foo' => 'bar'], true); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar'], $event->getUserContext()->toArray()); + + $scope->setUser(['bar' => 'baz'], true); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getUserContext()->toArray()); + } + public function testSetFingerprint(): void { $scope = new Scope(); @@ -237,7 +266,7 @@ public function testClear(): void $scope->setFingerprint(['foo']); $scope->setExtras(['foo' => 'bar']); $scope->setTags(['bar' => 'foo']); - $scope->setUser(['foobar' => 'barfoo']); + $scope->setUser(['foobar' => 'barfoo'], true); $event = $scope->applyToEvent(new Event(), []); @@ -273,7 +302,7 @@ public function testApplyToEvent(): void $scope->addBreadcrumb($breadcrumb); $scope->setTag('foo', 'bar'); $scope->setExtra('bar', 'foo'); - $scope->setUser(['foo' => 'baz']); + $scope->setUser(['foo' => 'baz'], true); $event = $scope->applyToEvent($event, []); @@ -290,7 +319,7 @@ public function testApplyToEvent(): void $scope->setLevel(Severity::fatal()); $scope->setTag('bar', 'foo'); $scope->setExtra('foo', 'bar'); - $scope->setUser(['baz' => 'foo']); + $scope->setUser(['baz' => 'foo'], true); $event = $scope->applyToEvent($event, []); From 3433c6e904fcd0bc8926e2430aaa96baf91de4c8 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 17 Dec 2019 18:42:15 +0100 Subject: [PATCH 0520/1161] Fix error thrown when serializing an event containing invalid UTF-8 strings (#934) --- CHANGELOG.md | 1 + composer.json | 2 +- phpstan.neon | 6 ++ src/Transport/HttpTransport.php | 2 +- src/Util/JSON.php | 97 ++++++++++++++++- tests/Util/JSONTest.php | 185 ++++++++++++++++++++++++-------- 6 files changed, 244 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83832626c..91ecc3f39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix remaining PHP 7.4 deprecations (#930) +- Fix error thrown during JSON encoding if a string contains invalid UTF-8 characters (#934) ## 2.2.5 (2019-11-27) diff --git a/composer.json b/composer.json index e55c313ee..083f2ac35 100644 --- a/composer.json +++ b/composer.json @@ -77,7 +77,7 @@ "vendor/bin/phpstan analyse" ], "psalm": [ - "vendor/bin/psalm --config=psalm.xml.dist" + "vendor/bin/psalm" ] }, "config": { diff --git a/phpstan.neon b/phpstan.neon index 253c33e78..734be8bca 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,6 +14,12 @@ parameters: - message: "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" path: src/Transport/HttpTransport.php + - + message: '/^Argument of an invalid type array\|object supplied for foreach, only iterables are supported\.$/' + path: src/Util/JSON.php + - + message: '/^Constant JSON_INVALID_UTF8_SUBSTITUTE not found\.$/' + path: src/Util/JSON.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 19bd948a6..fabee43a8 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -109,7 +109,7 @@ public function send(Event $event): ?string 'POST', sprintf('/api/%d/store/', $projectId), ['Content-Type' => 'application/json'], - JSON::encode($event) + JSON::encode($event->toArray()) ); $promise = $this->httpClient->sendAsyncRequest($request); diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 5a1b3ca3d..03b435f7d 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -16,15 +16,34 @@ final class JSON /** * Encodes the given data into JSON. * - * @param mixed $data The data to encode + * @param mixed $data The data to encode + * @param int $options Bitmask consisting of JSON_* constants + * @param int $maxDepth The maximum depth allowed for serializing $data + * + * @return mixed * * @throws JsonException If the encoding failed */ - public static function encode($data): string + public static function encode($data, int $options = 0, int $maxDepth = 512) { - $encodedData = json_encode($data, JSON_UNESCAPED_UNICODE); + $options |= JSON_UNESCAPED_UNICODE; + + if (\PHP_VERSION_ID >= 70200) { + /** @psalm-suppress UndefinedConstant */ + $options |= JSON_INVALID_UTF8_SUBSTITUTE; + } + + $encodedData = json_encode($data, $options); - if (JSON_ERROR_NONE !== json_last_error() || false === $encodedData) { + // This should never happen on PHP >= 7.2 as the substitution of invalid + // UTF-8 characters is done internally. On lower versions instead, we + // try to sanitize the data ourselves before retrying encoding. If it + // fails again we throw an exception as usual. + if (JSON_ERROR_UTF8 === json_last_error()) { + $encodedData = json_encode(self::sanitizeData($data, $maxDepth - 1), $options); + } + + if (JSON_ERROR_NONE !== json_last_error()) { throw new JsonException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); } @@ -50,4 +69,74 @@ public static function decode(string $data) return $decodedData; } + + /** + * Performs sanity checks on data that shall be encoded to JSON. + * + * @param mixed $data The data to sanitize + * @param int $maxDepth The maximum depth to walk through `$data` + * + * @return mixed + * + * @throws JsonException If the value of $maxDepth is less than 0 + */ + private static function sanitizeData($data, int $maxDepth) + { + if ($maxDepth < 0) { + throw new JsonException('Reached the maximum depth limit while sanitizing the data.'); + } + + if (\is_string($data)) { + return self::convertStringToUtf8($data); + } elseif (\is_array($data) || \is_object($data)) { + $output = []; + + foreach ($data as $key => $value) { + if (\is_string($key)) { + $key = self::convertStringToUtf8($key); + } + + if (\is_string($value)) { + $value = self::convertStringToUtf8($value); + } elseif (\is_array($value) || \is_object($value)) { + // This check is here because the `Event::toArray()` method + // is broken and doesn't return all child items as scalars + // or objects/arrays, so the sanitification would fail (e.g. + // on breadcrumb objects which do not expose public properties + // to iterate on) + if (\is_object($value) && method_exists($value, 'toArray')) { + $value = $value->toArray(); + } + + $value = self::sanitizeData($value, $maxDepth - 1); + } + + $output[$key] = $value; + } + + return \is_array($data) ? $output : (object) $output; + } else { + return $data; + } + } + + /** + * Converts a string to UTF-8 to avoid errors during its encoding to + * the JSON format. + * + * @param string $value The text to convert to UTF-8 + */ + private static function convertStringToUtf8(string $value): string + { + $previousSubstituteCharacter = mb_substitute_character(); + $encoding = mb_detect_encoding($value, mb_detect_order(), true); + + mb_substitute_character(0xfffd); + + $value = mb_convert_encoding($value, 'UTF-8', $encoding ?: 'UTF-8'); + + mb_substitute_character($previousSubstituteCharacter); + + return $value; + } } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index 4b9f7caaf..f8c60c2d8 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -19,41 +19,123 @@ public function testEncode($value, string $expectedResult): void $this->assertSame($expectedResult, JSON::encode($value)); } - public function encodeDataProvider(): array + public function encodeDataProvider(): \Generator { - return [ + yield [ [ - [ - 'key' => 'value', - ], - '{"key":"value"}', + 'key' => 'value', ], - [ - 'string', - '"string"', + '{"key":"value"}', + ]; + + yield [ + 'string', + '"string"', + ]; + + yield [ + 123.45, + '123.45', + ]; + + yield [ + null, + 'null', + ]; + + yield [ + (object) [ + 'key' => 'value', ], + '{"key":"value"}', + ]; + + yield [ + new SimpleClass(), + '{"keyPublic":"public"}', + ]; + + yield [ + new JsonSerializableClass(), + '{"key":"value"}', + ]; + } + + /** + * @requires PHP >= 7.2 + * + * @dataProvider encodeSubstitutesInvalidUtf8CharactersOnPhp72OrGreaterDataProvider + */ + public function testEncodeSubstitutesInvalidUtf8CharactersOnPhp72OrGreater($value, string $expectedResult): void + { + $this->assertSame($expectedResult, JSON::encode($value)); + } + + public function encodeSubstitutesInvalidUtf8CharactersOnPhp72OrGreaterDataProvider(): \Generator + { + yield [ + "\x61\xb0\x62", + '"a�b"', + ]; + + yield [ + "\x61\xf0\x80\x80\x41", + '"a�A"', + ]; + + yield [ [ 123.45, - '123.45', - ], - [ - null, - 'null', - ], - [ - (object) [ - 'key' => 'value', + 'foo', + "\x61\xb0\x62", + [ + 'bar' => "\x61\xf0\x80\x80\x41", + "\x61\xf0\x80\x80\x41" => (object) [ + "\x61\xb0\x62", + "\x61\xf0\x80\x80\x41" => 'baz', + ], ], - '{"key":"value"}', - ], - [ - new SimpleClass(), - '{"keyPublic":"public"}', ], + '[123.45,"foo","a�b",{"bar":"a�A","a�A":{"0":"a�b","a�A":"baz"}}]', + ]; + } + + /** + * @requires PHP < 7.2 + * + * @dataProvider encodeSubstitutesInvalidUtf8CharactersOnPhp71OrLowerDataProvider + */ + public function testEncodeSubstitutesInvalidUtf8CharactersOnPhp71OrLower($value, string $expectedResult): void + { + $this->assertSame($expectedResult, JSON::encode($value)); + } + + public function encodeSubstitutesInvalidUtf8CharactersOnPhp71OrLowerDataProvider(): \Generator + { + yield [ + "\x61\xb0\x62", + '"a�b"', + ]; + + yield [ + "\x61\xf0\x80\x80\x41", + '"a���A"', + ]; + + yield [ [ - new JsonSerializableClass(), - '{"key":"value"}', + 123.45, + 'foo', + "\x61\xb0\x62", + [ + 'bar' => "\x61\xf0\x80\x80\x41", + "\x61\xf0\x80\x80\x41" => (object) [ + "\x61\xb0\x62", + "\x61\xf0\x80\x80\x41" => 'baz', + ], + ], ], + '[123.45,"foo","a�b",{"bar":"a���A","a���A":{"0":"a�b","a���A":"baz"}}]', ]; } @@ -72,6 +154,22 @@ public function testEncodeThrowsIfValueIsResource(): void JSON::encode($resource); } + public function testEncodeRespectsOptionsArgument(): void + { + $this->assertSame('{}', JSON::encode([], JSON_FORCE_OBJECT)); + } + + /** + * @requires PHP < 7.2 + * + * @expectedException \Sentry\Exception\JsonException + * @expectedExceptionMessage Reached the maximum depth limit while sanitizing the data. + */ + public function testEncodeThrowsOnPhp71OrLowerWhenSanitizationReachesMaxDepthLimit(): void + { + JSON::encode([[["\x61\xb0\x62"]]], 0, 2); + } + /** * @dataProvider decodeDataProvider */ @@ -80,28 +178,29 @@ public function testDecode(string $value, $expectedResult): void $this->assertSame($expectedResult, JSON::decode($value)); } - public function decodeDataProvider(): array + public function decodeDataProvider(): \Generator { - return [ - [ - '{"key":"value"}', - [ - 'key' => 'value', - ], - ], + yield [ + '{"key":"value"}', [ - '"string"', - 'string', - ], - [ - '123.45', - 123.45, - ], - [ - 'null', - null, + 'key' => 'value', ], ]; + + yield [ + '"string"', + 'string', + ]; + + yield [ + '123.45', + 123.45, + ]; + + yield [ + 'null', + null, + ]; } /** From c9b253229b95f8e5bbf6a3661a170a0be0f80563 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 18 Dec 2019 09:56:34 +0100 Subject: [PATCH 0521/1161] meta: Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ecc3f39..ab534a060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.2.6 (2019-12-18) + - Fix remaining PHP 7.4 deprecations (#930) - Fix error thrown during JSON encoding if a string contains invalid UTF-8 characters (#934) From b834fb50a1ae35535c88e2f50b707d6be5ee8e62 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 20 Dec 2019 12:53:38 +0100 Subject: [PATCH 0522/1161] Use Chocolatey to install PHP on AppVeyor (#938) --- .appveyor.yml | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 47c96285a..f7abedfed 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -12,21 +12,21 @@ branches: environment: matrix: - - PHP_VERSION: 7.1-Win32-VC14 + - PHP_VERSION: 7.1 DEPENDENCIES: lowest - - PHP_VERSION: 7.1-Win32-VC14 + - PHP_VERSION: 7.1 DEPENDENCIES: highest - - PHP_VERSION: 7.2-Win32-VC15 + - PHP_VERSION: 7.2 DEPENDENCIES: lowest - - PHP_VERSION: 7.2-Win32-VC15 + - PHP_VERSION: 7.2 DEPENDENCIES: highest - - PHP_VERSION: 7.3-Win32-VC15 + - PHP_VERSION: 7.3 DEPENDENCIES: lowest - - PHP_VERSION: 7.3-Win32-VC15 + - PHP_VERSION: 7.3 DEPENDENCIES: highest - - PHP_VERSION: 7.4-Win32-VC15 + - PHP_VERSION: 7.4 DEPENDENCIES: lowest - - PHP_VERSION: 7.4-Win32-VC15 + - PHP_VERSION: 7.4 DEPENDENCIES: highest matrix: @@ -34,32 +34,32 @@ matrix: cache: - composer.phar - - '%LocalAppData%\Composer\files' - - 'c:\php -> .appveyor.yml' + - '%LOCALAPPDATA%\Composer\files' + - '%PROGRAMDATA%\chocolatey\bin -> .appveyor.yml' + - '%PROGRAMDATA%\chocolatey\lib -> .appveyor.yml' + - C:\php -> .appveyor.yml init: - - SET PATH=c:\php\%PHP_VERSION%;%PATH% + - SET PATH=C:\php;%PATH% - SET ANSICON=121x90 (121x90) - SET INSTALL_PHP=1 install: - - IF NOT EXIST c:\php mkdir c:\php - - IF NOT EXIST c:\php\%PHP_VERSION% mkdir c:\php\%PHP_VERSION% ELSE SET INSTALL_PHP=0 - - IF %INSTALL_PHP%==1 cd c:\php\%PHP_VERSION% - - IF %INSTALL_PHP%==1 curl --fail --location --silent --show-error -o php-%PHP_VERSION%-x64-latest.zip https://windows.php.net/downloads/releases/latest/php-%PHP_VERSION%-x64-latest.zip - - IF %INSTALL_PHP%==1 7z x php-%PHP_VERSION%-x64-latest.zip -y >nul - - IF %INSTALL_PHP%==1 del /Q php-%PHP_VERSION%-x64-latest.zip - - IF %INSTALL_PHP%==1 copy /Y php.ini-development php.ini >nul - - IF %INSTALL_PHP%==1 echo extension_dir=c:\php\%PHP_VERSION%\ext >> php.ini + - IF EXIST C:\php SET INSTALL_PHP=0 + - ps: choco upgrade chocolatey --confirm --no-progress --allow-downgrade --version 0.10.13 + - ps: choco install php --confirm --no-progress --package-parameters '""/InstallDir:C:\php""' --version (choco search php --exact --all-versions --limit-output | Select-String -Pattern $env:PHP_VERSION | ForEach-Object {$_ -Replace "php\|", ""} | Sort {[version] $_} -Descending | Select-Object -First 1) + - cd C:\php + - IF %INSTALL_PHP%==1 copy /Y php.ini-production php.ini + - IF %INSTALL_PHP%==1 echo extension_dir=C:\php\ext >> php.ini - IF %INSTALL_PHP%==1 echo extension=php_curl.dll >> php.ini - IF %INSTALL_PHP%==1 echo extension=php_mbstring.dll >> php.ini - IF %INSTALL_PHP%==1 echo extension=php_openssl.dll >> php.ini - - cd c:\projects\sentry-php + - cd C:\projects\sentry-php - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/1.9.1/composer.phar - php composer.phar self-update - IF %DEPENDENCIES%==lowest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-lowest --prefer-dist - IF %DEPENDENCIES%==highest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-dist test_script: - - cd c:\projects\sentry-php + - cd C:\projects\sentry-php - vendor\bin\phpunit.bat From 6412b841bf2edf40c3f6586bbefa542150748307 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 21 Dec 2019 01:29:30 +0100 Subject: [PATCH 0523/1161] Fix Client::captureEvent not considering the attach_stacktrace option (#940) --- .appveyor.yml | 2 -- CHANGELOG.md | 2 ++ src/Client.php | 2 +- tests/ClientTest.php | 72 ++++++++++++++++++++++++-------------------- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index f7abedfed..43f166f4b 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -35,8 +35,6 @@ matrix: cache: - composer.phar - '%LOCALAPPDATA%\Composer\files' - - '%PROGRAMDATA%\chocolatey\bin -> .appveyor.yml' - - '%PROGRAMDATA%\chocolatey\lib -> .appveyor.yml' - C:\php -> .appveyor.yml init: diff --git a/CHANGELOG.md b/CHANGELOG.md index ab534a060..60370f5a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix `Client::captureEvent` not considering the `attach_stacktrace` option (#940) + ## 2.2.6 (2019-12-18) - Fix remaining PHP 7.4 deprecations (#930) diff --git a/src/Client.php b/src/Client.php index 5a125e33a..f19595cca 100644 --- a/src/Client.php +++ b/src/Client.php @@ -108,7 +108,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? */ public function captureEvent(array $payload, ?Scope $scope = null): ?string { - $event = $this->prepareEvent($payload, $scope); + $event = $this->prepareEvent($payload, $scope, $this->options->shouldAttachStacktrace()); if (null === $event) { return null; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index e12e90c9c..32961a9f6 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -111,9 +111,9 @@ public function captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesData ]; } - public function testCapture(): void + public function testCaptureEvent(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') @@ -135,6 +135,43 @@ public function testCapture(): void $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent($inputData)); } + /** + * @dataProvider captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider + */ + public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOption(bool $shouldAttachStacktrace): void + { + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(static function (Event $event) use ($shouldAttachStacktrace): bool { + if ($shouldAttachStacktrace && null === $event->getStacktrace()) { + return false; + } + + if (!$shouldAttachStacktrace && null !== $event->getStacktrace()) { + return false; + } + + return true; + })) + ->willReturn('500a339f3ab2450b96dee542adf36ba7'); + + $client = ClientBuilder::create(['attach_stacktrace' => $shouldAttachStacktrace]) + ->setTransport($transport) + ->getClient(); + + $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent([])); + } + + public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider(): array + { + return [ + [true], + [false], + ]; + } + public function testCaptureLastError(): void { /** @var TransportInterface|MockObject $transport */ @@ -372,17 +409,7 @@ public function testAttachStacktrace(): void $transport->expects($this->once()) ->method('send') ->with($this->callback(function (Event $event): bool { - $result = $event->getStacktrace(); - - $this->assertInstanceOf(Stacktrace::class, $result); - - $frames = $result->getFrames(); - - $this->assertNotEmpty($frames); - $this->assertTrue($frames[0]->isInApp()); - $this->assertFalse($frames[\count($frames) - 1]->isInApp()); - - return true; + return null !== $event->getStacktrace(); })); $client = ClientBuilder::create(['attach_stacktrace' => true]) @@ -392,25 +419,6 @@ public function testAttachStacktrace(): void $client->captureMessage('test'); } - public function testAttachStacktraceShouldNotWorkWithCaptureEvent(): void - { - /** @var TransportInterface|MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $this->assertNull($event->getStacktrace()); - - return true; - })); - - $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransport($transport) - ->getClient(); - - $client->captureEvent([]); - } - /** * @see https://github.com/symfony/polyfill/blob/52332f49d18c413699d2dccf465234356f8e0b2c/src/Php70/Php70.php#L52-L61 */ From bfb2a40dc3d91718a9155bfdeb6c66935718369e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 21 Dec 2019 10:57:34 +0100 Subject: [PATCH 0524/1161] Replace ramsey/uuid dependency with symfony/polyfill-uuid (#937) --- CHANGELOG.md | 1 + composer.json | 2 +- src/Event.php | 12 +++----- tests/EventTest.php | 70 ++++----------------------------------------- 4 files changed, 12 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da94ac64..7ef710fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix `Client::captureEvent` not considering the `attach_stacktrace` option (#940) +- Replace `ramsey/uuid` dependency with `uuid_create` from the PECL [`uuid`](https://pecl.php.net/package/uuid) extension or [`symfony/polyfill-uuid`](https://github.com/symfony/polyfill-uuid) (#937) - Deprecate `Scope::setUser` behaviour of replacing user data. (#929) - Add the `$merge` parameter on `Scope::setUser` to allow merging user context. (#929) - Make the `integrations` option accept a `callable` that will receive the list of default integrations and returns a customized list (#919) diff --git a/composer.json b/composer.json index 801146ad2..808895134 100644 --- a/composer.json +++ b/composer.json @@ -31,8 +31,8 @@ "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", "psr/http-message-implementation": "^1.0", - "ramsey/uuid": "^3.3", "symfony/options-resolver": "^2.7|^3.0|^4.0|^5.0", + "symfony/polyfill-uuid": "^1.13.1", "zendframework/zend-diactoros": "^1.7.1|^2.0" }, "require-dev": { diff --git a/src/Event.php b/src/Event.php index c0c8fe3e0..af80fc2c9 100644 --- a/src/Event.php +++ b/src/Event.php @@ -5,9 +5,6 @@ namespace Sentry; use Jean85\PrettyVersions; -use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; use Sentry\Context\Context; use Sentry\Context\RuntimeContext; use Sentry\Context\ServerOsContext; @@ -22,7 +19,7 @@ final class Event implements \JsonSerializable { /** - * @var UuidInterface The UUID + * @var string The UUID */ private $id; @@ -150,13 +147,12 @@ final class Event implements \JsonSerializable /** * Event constructor. * - * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present * @throws \InvalidArgumentException * @throws \Exception */ public function __construct() { - $this->id = Uuid::uuid4(); + $this->id = str_replace('-', '', uuid_create(UUID_TYPE_RANDOM)); $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); $this->level = Severity::error(); $this->serverOsContext = new ServerOsContext(); @@ -172,7 +168,7 @@ public function __construct() */ public function getId(): string { - return str_replace('-', '', $this->id->toString()); + return $this->id; } /** @@ -584,7 +580,7 @@ public function setStacktrace(Stacktrace $stacktrace): void public function toArray(): array { $data = [ - 'event_id' => str_replace('-', '', $this->id->toString()), + 'event_id' => $this->id, 'timestamp' => $this->timestamp, 'level' => (string) $this->level, 'platform' => 'php', diff --git a/tests/EventTest.php b/tests/EventTest.php index fa6bf54d1..1bd48ce31 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -5,21 +5,14 @@ namespace Sentry\Tests; use Jean85\PrettyVersions; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidFactory; -use Ramsey\Uuid\UuidFactoryInterface; use Sentry\Breadcrumb; use Sentry\Client; -use Sentry\ClientBuilder; -use Sentry\ClientInterface; use Sentry\Context\Context; use Sentry\Context\RuntimeContext; use Sentry\Context\ServerOsContext; use Sentry\Context\TagsContext; use Sentry\Event; -use Sentry\Options; use Sentry\Severity; use Sentry\Util\PHPVersion; @@ -28,71 +21,22 @@ */ final class EventTest extends TestCase { - private const GENERATED_UUID = [ - '4d310518-9e9d-463c-8161-bd46416f7817', - '431a2537-d1de-49da-80b6-b7861954c9cf', - ]; - - /** - * @var int - */ - protected $uuidGeneratorInvokationCount; - - /** - * @var UuidFactoryInterface - */ - protected $originalUuidFactory; - - /** - * @var Options - */ - protected $options; - - /** - * @var ClientInterface - */ - protected $client; - - protected function setUp(): void - { - $this->uuidGeneratorInvokationCount = 0; - $this->originalUuidFactory = new UuidFactory(); - $this->client = ClientBuilder::create()->getClient(); - $this->options = $this->client->getOptions(); - - /** @var UuidFactoryInterface|MockObject $uuidFactory */ - $uuidFactory = $this->getMockBuilder(UuidFactoryInterface::class) - ->getMock(); - - $uuidFactory->expects($this->any()) - ->method('uuid4') - ->willReturnCallback(function () { - $uuid = static::GENERATED_UUID[$this->uuidGeneratorInvokationCount++]; - - return $this->originalUuidFactory->fromString($uuid); - }); - - Uuid::setFactory($uuidFactory); - } - - protected function tearDown(): void - { - Uuid::setFactory($this->originalUuidFactory); - } - public function testEventIsGeneratedWithUniqueIdentifier(): void { $event1 = new Event(); $event2 = new Event(); - $this->assertEquals(str_replace('-', '', static::GENERATED_UUID[0]), $event1->getId()); - $this->assertEquals(str_replace('-', '', static::GENERATED_UUID[1]), $event2->getId()); + $this->assertRegExp('/^[a-z0-9]{32}$/', $event1->getId()); + $this->assertRegExp('/^[a-z0-9]{32}$/', $event2->getId()); + $this->assertNotEquals($event1->getId(), $event2->getId()); } public function testToArray(): void { + $event = new Event(); + $expected = [ - 'event_id' => str_replace('-', '', static::GENERATED_UUID[0]), + 'event_id' => $event->getId(), 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), 'level' => 'error', 'platform' => 'php', @@ -114,8 +58,6 @@ public function testToArray(): void ], ]; - $event = new Event(); - $this->assertEquals($expected, $event->toArray()); } From f1196dd363a8351935c95fa1b6973d6952761a7f Mon Sep 17 00:00:00 2001 From: Christoph Lehmann Date: Mon, 23 Dec 2019 16:17:41 +0100 Subject: [PATCH 0525/1161] Update Readme (#942) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bf81bc5a..8224fad82 100644 --- a/README.md +++ b/README.md @@ -80,13 +80,13 @@ The following integrations are available and maintained by members of the Sentry - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [ZendFramework](https://github.com/facile-it/sentry-module) - [SilverStripe](https://github.com/phptek/silverstripe-sentry) +- [TYPO3](https://github.com/networkteam/sentry_client) - ... feel free to be famous, create a port to your favourite platform! ### 3rd party integrations using old SDK 1.x - [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient) - [OpenCart](https://github.com/BurdaPraha/oc_sentry) -- [TYPO3](https://github.com/networkteam/sentry_client) ## Community From 5407481ac4fc6ed9f248c1c0e0af99d811a4f261 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 23 Dec 2019 16:48:12 +0100 Subject: [PATCH 0526/1161] Support setting arbitrary contexts in the scope and event (#941) Co-authored-by: William Desportes --- CHANGELOG.md | 1 + src/Context/RuntimeContext.php | 2 + src/Context/ServerOsContext.php | 2 + src/Context/TagsContext.php | 2 + src/Event.php | 37 +++++++++++++-- src/State/Scope.php | 40 +++++++++++++++++ tests/EventTest.php | 79 ++++++++++++++++----------------- tests/State/ScopeTest.php | 54 +++++++++++----------- 8 files changed, 147 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef710fa6..d6f73a7e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Add the `$merge` parameter on `Scope::setUser` to allow merging user context. (#929) - Make the `integrations` option accept a `callable` that will receive the list of default integrations and returns a customized list (#919) - Add the `IgnoreErrorsIntegration` integration to deprecate and replace the `exclude_exceptions` option (#928) +- Allow setting custom contexts on the scope and on the event (#839) ## 2.2.6 (2019-12-18) diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 91dd25860..15ae3690f 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -14,6 +14,8 @@ * stores information about the current runtime. * * @author Stefano Arlandini + * + * @final since version 2.3 */ class RuntimeContext extends Context { diff --git a/src/Context/ServerOsContext.php b/src/Context/ServerOsContext.php index 41bd04c97..b751eaec6 100644 --- a/src/Context/ServerOsContext.php +++ b/src/Context/ServerOsContext.php @@ -13,6 +13,8 @@ * stores information about the operating system of the server. * * @author Stefano Arlandini + * + * @final since version 2.3 */ class ServerOsContext extends Context { diff --git a/src/Context/TagsContext.php b/src/Context/TagsContext.php index 56bb8ab04..32349bd0e 100644 --- a/src/Context/TagsContext.php +++ b/src/Context/TagsContext.php @@ -9,6 +9,8 @@ * for the tags context. * * @author Stefano Arlandini + * + * @final since version 2.3 */ class TagsContext extends Context { diff --git a/src/Event.php b/src/Event.php index af80fc2c9..f4bdd3643 100644 --- a/src/Event.php +++ b/src/Event.php @@ -98,6 +98,11 @@ final class Event implements \JsonSerializable */ private $userContext; + /** + * @var array> An arbitrary mapping of additional contexts associated to this event + */ + private $contexts = []; + /** * @var Context An arbitrary mapping of additional metadata */ @@ -389,6 +394,29 @@ public function setRequest(array $request): void $this->request = $request; } + /** + * Gets an arbitrary mapping of additional contexts. + * + * @return array> + */ + public function getContexts(): array + { + return $this->contexts; + } + + /** + * Sets the data of the context with the given name. + * + * @param string $name The name that uniquely identifies the context + * @param array $data The data of the context + */ + public function setContext(string $name, array $data): self + { + $this->contexts[$name] = $data; + + return $this; + } + /** * Gets an arbitrary mapping of additional metadata. */ @@ -553,10 +581,7 @@ public function setStacktrace(Stacktrace $stacktrace): void * extra?: mixed[], * tags?: mixed[], * user?: mixed[], - * contexts?: array{ - * os?: mixed[], - * runtime?: mixed[] - * }, + * contexts?: mixed[], * breadcrumbs?: array{ * values: Breadcrumb[] * }, @@ -638,6 +663,10 @@ public function toArray(): array $data['contexts']['runtime'] = $this->runtimeContext->toArray(); } + if (!empty($this->contexts)) { + $data['contexts'] = array_merge($data['contexts'] ?? [], $this->contexts); + } + if (!empty($this->breadcrumbs)) { $data['breadcrumbs']['values'] = $this->breadcrumbs; } diff --git a/src/State/Scope.php b/src/State/Scope.php index e61d187bd..4d1acc489 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -27,6 +27,11 @@ final class Scope */ private $user; + /** + * @var array> The list of contexts associated to this scope + */ + private $contexts = []; + /** * @var TagsContext The list of tags associated to this scope */ @@ -67,6 +72,7 @@ public function __construct() $this->user = new UserContext(); $this->tags = new TagsContext(); $this->extra = new Context(); + $this->contexts = []; } /** @@ -98,6 +104,35 @@ public function setTags(array $tags): self return $this; } + /** + * Sets context data with the given name. + * + * @param string $name The name that uniquely identifies the context + * @param array $value The value + * + * @return $this + */ + public function setContext(string $name, array $value): self + { + $this->contexts[$name] = $value; + + return $this; + } + + /** + * Removes the context from the scope. + * + * @param string $name The name that uniquely identifies the context + * + * @return $this + */ + public function removeContext(string $name): self + { + unset($this->contexts[$name]); + + return $this; + } + /** * Sets a new information in the extra context. * @@ -246,6 +281,7 @@ public function clear(): self $this->level = null; $this->fingerprint = []; $this->breadcrumbs = []; + $this->contexts = []; return $this; } @@ -275,6 +311,10 @@ public function applyToEvent(Event $event, array $payload): ?Event $event->getExtraContext()->merge($this->extra->toArray()); $event->getUserContext()->merge($this->user->toArray()); + foreach (array_merge($this->contexts, $event->getContexts()) as $name => $data) { + $event->setContext($name, $data); + } + foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) { $event = \call_user_func($processor, $event, $payload); diff --git a/tests/EventTest.php b/tests/EventTest.php index 1bd48ce31..37d138233 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -8,10 +8,6 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; use Sentry\Client; -use Sentry\Context\Context; -use Sentry\Context\RuntimeContext; -use Sentry\Context\ServerOsContext; -use Sentry\Context\TagsContext; use Sentry\Event; use Sentry\Severity; use Sentry\Util\PHPVersion; @@ -58,7 +54,45 @@ public function testToArray(): void ], ]; - $this->assertEquals($expected, $event->toArray()); + $this->assertSame($expected, $event->toArray()); + } + + public function testToArrayMergesCustomContextsWithDefaultContexts(): void + { + $event = new Event(); + $event->setContext('foo', ['foo' => 'bar']); + $event->setContext('bar', ['bar' => 'foo']); + $event->setContext('runtime', ['baz' => 'baz']); + + $expected = [ + 'event_id' => $event->getId(), + 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), + 'level' => 'error', + 'platform' => 'php', + 'sdk' => [ + 'name' => Client::SDK_IDENTIFIER, + 'version' => PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(), + ], + 'contexts' => [ + 'os' => [ + 'name' => php_uname('s'), + 'version' => php_uname('r'), + 'build' => php_uname('v'), + 'kernel_version' => php_uname('a'), + ], + 'runtime' => [ + 'baz' => 'baz', + ], + 'foo' => [ + 'foo' => 'bar', + ], + 'bar' => [ + 'bar' => 'foo', + ], + ], + ]; + + $this->assertSame($expected, $event->toArray()); } /** @@ -188,41 +222,6 @@ public function getMessageDataProvider(): array ]; } - public function testGetServerOsContext(): void - { - $event = new Event(); - - $this->assertInstanceOf(ServerOsContext::class, $event->getServerOsContext()); - } - - public function testGetRuntimeContext(): void - { - $event = new Event(); - - $this->assertInstanceOf(RuntimeContext::class, $event->getRuntimeContext()); - } - - public function testGetUserContext(): void - { - $event = new Event(); - - $this->assertInstanceOf(Context::class, $event->getUserContext()); - } - - public function testGetExtraContext(): void - { - $event = new Event(); - - $this->assertInstanceOf(Context::class, $event->getExtraContext()); - } - - public function getTagsContext(): void - { - $event = new Event(); - - $this->assertInstanceOf(TagsContext::class, $event->getTagsContext()); - } - /** * @dataProvider gettersAndSettersDataProvider */ diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index e8c31c60e..a7188388e 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -47,6 +47,24 @@ public function testSetTags(): void $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTagsContext()->toArray()); } + public function testSetAndRemoveContext(): void + { + $scope = new Scope(); + $scope->setContext('foo', ['foo' => 'bar']); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame(['foo' => ['foo' => 'bar']], $event->getContexts()); + + $scope->removeContext('foo'); + + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertSame([], $event->getContexts()); + } + public function testSetExtra(): void { $scope = new Scope(); @@ -293,9 +311,11 @@ public function testClear(): void public function testApplyToEvent(): void { - $event = new Event(); $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + $event = new Event(); + $event->setContext('foocontext', ['foo' => 'foo', 'bar' => 'bar']); + $scope = new Scope(); $scope->setLevel(Severity::warning()); $scope->setFingerprint(['foo']); @@ -303,34 +323,16 @@ public function testApplyToEvent(): void $scope->setTag('foo', 'bar'); $scope->setExtra('bar', 'foo'); $scope->setUser(['foo' => 'baz'], true); + $scope->setContext('foocontext', ['foo' => 'bar']); + $scope->setContext('barcontext', ['bar' => 'foo']); - $event = $scope->applyToEvent($event, []); - - $this->assertNotNull($event); + $this->assertSame($event, $scope->applyToEvent($event, [])); $this->assertTrue($event->getLevel()->isEqualTo(Severity::warning())); $this->assertSame(['foo'], $event->getFingerprint()); $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); - $this->assertEquals(['foo' => 'bar'], $event->getTagsContext()->toArray()); - $this->assertEquals(['bar' => 'foo'], $event->getExtraContext()->toArray()); - $this->assertEquals(['foo' => 'baz'], $event->getUserContext()->toArray()); - - $scope->setFingerprint(['foo', 'bar']); - $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_FATAL, Breadcrumb::TYPE_ERROR, 'error_reporting')); - $scope->setLevel(Severity::fatal()); - $scope->setTag('bar', 'foo'); - $scope->setExtra('foo', 'bar'); - $scope->setUser(['baz' => 'foo'], true); - - $event = $scope->applyToEvent($event, []); - - $this->assertNotNull($event); - $this->assertTrue($event->getLevel()->isEqualTo(Severity::fatal())); - $this->assertSame(['foo'], $event->getFingerprint()); - $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); - $this->assertEquals(['foo' => 'bar', 'bar' => 'foo'], $event->getTagsContext()->toArray()); - $this->assertEquals(['bar' => 'foo', 'foo' => 'bar'], $event->getExtraContext()->toArray()); - $this->assertEquals(['foo' => 'baz', 'baz' => 'foo'], $event->getUserContext()->toArray()); - - $this->assertSame($event, $scope->applyToEvent($event, [])); + $this->assertSame(['foo' => 'bar'], $event->getTagsContext()->toArray()); + $this->assertSame(['bar' => 'foo'], $event->getExtraContext()->toArray()); + $this->assertSame(['foo' => 'baz'], $event->getUserContext()->toArray()); + $this->assertSame(['foocontext' => ['foo' => 'foo', 'bar' => 'bar'], 'barcontext' => ['bar' => 'foo']], $event->getContexts()); } } From 37db6763b6e717f2a30c86c89196cf695b9200b6 Mon Sep 17 00:00:00 2001 From: Dawid 'DeyV' Polak Date: Wed, 1 Jan 2020 16:48:32 +0100 Subject: [PATCH 0527/1161] Replace zendframework/zend-diactoros with guzzlehttp/psr7 (#945) * Zend framework is in stagnation. Move from zendframework/zend-diactoros to guzzlehttp/psr7 * merge request fixes --- composer.json | 4 +- src/Integration/RequestIntegration.php | 4 +- tests/Integration/RequestIntegrationTest.php | 82 ++++++-------------- 3 files changed, 29 insertions(+), 61 deletions(-) diff --git a/composer.json b/composer.json index 808895134..2388c55f7 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "ext-json": "*", "ext-mbstring": "*", "guzzlehttp/promises": "^1.3", + "guzzlehttp/psr7": "^1.6", "jean85/pretty-package-versions": "^1.2", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", @@ -32,8 +33,7 @@ "php-http/message": "^1.5", "psr/http-message-implementation": "^1.0", "symfony/options-resolver": "^2.7|^3.0|^4.0|^5.0", - "symfony/polyfill-uuid": "^1.13.1", - "zendframework/zend-diactoros": "^1.7.1|^2.0" + "symfony/polyfill-uuid": "^1.13.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index cde8c60bd..e7aed7182 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -4,6 +4,7 @@ namespace Sentry\Integration; +use GuzzleHttp\Psr7\ServerRequest; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; use Sentry\Event; @@ -12,7 +13,6 @@ use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\Util\JSON; -use Zend\Diactoros\ServerRequestFactory; /** * This integration collects information from the request and attaches them to @@ -98,7 +98,7 @@ public static function applyToEvent(self $self, Event $event, ?ServerRequestInte private function processEvent(Event $event, Options $options, ?ServerRequestInterface $request = null): void { if (null === $request) { - $request = isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequestFactory::fromGlobals() : null; + $request = isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequest::fromGlobals() : null; } if (null === $request) { diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 7eec01f25..07a0b510b 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -4,6 +4,9 @@ namespace Sentry\Tests\Integration; +use GuzzleHttp\Psr7\ServerRequest; +use GuzzleHttp\Psr7\UploadedFile; +use GuzzleHttp\Psr7\Uri; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; @@ -11,9 +14,6 @@ use Sentry\Event; use Sentry\Integration\RequestIntegration; use Sentry\Options; -use Zend\Diactoros\ServerRequest; -use Zend\Diactoros\UploadedFile; -use Zend\Diactoros\Uri; final class RequestIntegrationTest extends TestCase { @@ -39,7 +39,7 @@ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $event = new Event(); $event->getUserContext()->setData(['foo' => 'bar']); - $request = new ServerRequest(['REMOTE_ADDR' => '127.0.0.1']); + $request = new ServerRequest('GET', new Uri('http://www.example.com/fo'), [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1']); $integration = new RequestIntegration(new Options(['send_default_pii' => $shouldSendPii])); RequestIntegration::applyToEvent($integration, $event, $request); @@ -87,10 +87,8 @@ public function applyToEventDataProvider(): \Generator [ 'send_default_pii' => true, ], - (new ServerRequest()) - ->withCookieParams(['foo' => 'bar']) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('GET'), + (new ServerRequest('GET', new Uri('http://www.example.com/foo'))) + ->withCookieParams(['foo' => 'bar']), [ 'url' => 'http://www.example.com/foo', 'method' => 'GET', @@ -107,10 +105,8 @@ public function applyToEventDataProvider(): \Generator [ 'send_default_pii' => false, ], - (new ServerRequest()) - ->withCookieParams(['foo' => 'bar']) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('GET'), + (new ServerRequest('GET', new Uri('http://www.example.com/foo'))) + ->withCookieParams(['foo' => 'bar']), [ 'url' => 'http://www.example.com/foo', 'method' => 'GET', @@ -124,9 +120,7 @@ public function applyToEventDataProvider(): \Generator [ 'send_default_pii' => true, ], - (new ServerRequest()) - ->withUri(new Uri('http://www.example.com:1234/foo')) - ->withMethod('GET'), + (new ServerRequest('GET', new Uri('http://www.example.com:1234/foo'))), [ 'url' => 'http://www.example.com:1234/foo', 'method' => 'GET', @@ -141,9 +135,7 @@ public function applyToEventDataProvider(): \Generator [ 'send_default_pii' => false, ], - (new ServerRequest()) - ->withUri(new Uri('http://www.example.com:1234/foo')) - ->withMethod('GET'), + (new ServerRequest('GET', new Uri('http://www.example.com:1234/foo'))), [ 'url' => 'http://www.example.com:1234/foo', 'method' => 'GET', @@ -157,9 +149,7 @@ public function applyToEventDataProvider(): \Generator [ 'send_default_pii' => true, ], - (new ServerRequest(['REMOTE_ADDR' => '127.0.0.1'])) - ->withUri(new Uri('http://www.example.com/foo?foo=bar&bar=baz')) - ->withMethod('GET') + (new ServerRequest('GET', new Uri('http://www.example.com/foo?foo=bar&bar=baz'), [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1'])) ->withHeader('Host', 'www.example.com') ->withHeader('Authorization', 'foo') ->withHeader('Cookie', 'bar') @@ -185,9 +175,7 @@ public function applyToEventDataProvider(): \Generator [ 'send_default_pii' => false, ], - (new ServerRequest(['REMOTE_ADDR' => '127.0.0.1'])) - ->withUri(new Uri('http://www.example.com/foo?foo=bar&bar=baz')) - ->withMethod('GET') + (new ServerRequest('GET', new Uri('http://www.example.com/foo?foo=bar&bar=baz'), ['REMOTE_ADDR' => '127.0.0.1'])) ->withHeader('Host', 'www.example.com') ->withHeader('Authorization', 'foo') ->withHeader('Cookie', 'bar') @@ -206,13 +194,11 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'none', ], - (new ServerRequest()) + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withParsedBody([ 'foo' => 'foo value', 'bar' => 'bar value', ]) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST') ->withBody($this->getStreamMock(1)), [ 'url' => 'http://www.example.com/foo', @@ -227,13 +213,11 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'small', ], - (new ServerRequest()) + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withParsedBody([ 'foo' => 'foo value', 'bar' => 'bar value', ]) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST') ->withBody($this->getStreamMock(10 ** 3)), [ 'url' => 'http://www.example.com/foo', @@ -252,13 +236,11 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'small', ], - (new ServerRequest()) + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withParsedBody([ 'foo' => 'foo value', 'bar' => 'bar value', ]) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST') ->withBody($this->getStreamMock(10 ** 3 + 1)), [ 'url' => 'http://www.example.com/foo', @@ -273,13 +255,11 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'medium', ], - (new ServerRequest()) + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withParsedBody([ 'foo' => 'foo value', 'bar' => 'bar value', ]) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST') ->withBody($this->getStreamMock(10 ** 4)), [ 'url' => 'http://www.example.com/foo', @@ -298,13 +278,11 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'medium', ], - (new ServerRequest()) + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withParsedBody([ 'foo' => 'foo value', 'bar' => 'bar value', ]) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST') ->withBody($this->getStreamMock(10 ** 4 + 1)), [ 'url' => 'http://www.example.com/foo', @@ -319,12 +297,10 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'always', ], - (new ServerRequest()) + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withUploadedFiles([ 'foo' => new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), - ]) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST'), + ]), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', @@ -345,15 +321,13 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'always', ], - (new ServerRequest()) + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withUploadedFiles([ 'foo' => [ new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), ], - ]) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST'), + ]), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', @@ -381,7 +355,7 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'always', ], - (new ServerRequest()) + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withUploadedFiles([ 'foo' => [ 'bar' => [ @@ -389,9 +363,7 @@ public function applyToEventDataProvider(): \Generator new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), ], ], - ]) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST'), + ]), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', @@ -421,9 +393,7 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'always', ], - (new ServerRequest()) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST') + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withHeader('Content-Type', 'application/json') ->withBody($this->getStreamMock(13, '{"foo":"bar"}')), [ @@ -443,9 +413,7 @@ public function applyToEventDataProvider(): \Generator [ 'max_request_body_size' => 'always', ], - (new ServerRequest()) - ->withUri(new Uri('http://www.example.com/foo')) - ->withMethod('POST') + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withHeader('Content-Type', 'application/json') ->withBody($this->getStreamMock(1, '{')), [ From 9e9a5cd7a81228eef5213c26aa29d201975e4cb5 Mon Sep 17 00:00:00 2001 From: Michael Frischbutter <33195197+mfrischbutter@users.noreply.github.com> Date: Thu, 2 Jan 2020 17:58:19 +0100 Subject: [PATCH 0528/1161] Add option to whitelist specific paths as 'in app' (#909) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + src/Options.php | 30 +++++++ src/Stacktrace.php | 83 +++++++++++++----- tests/ClientTest.php | 9 +- tests/OptionsTest.php | 21 +++++ tests/StacktraceTest.php | 179 ++++++++++++++++++++++++++++++++------- 6 files changed, 270 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f73a7e1..6aed1f037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add `in_app_include` option to whitelist paths that should be marked as part of the app (#909) - Fix `Client::captureEvent` not considering the `attach_stacktrace` option (#940) - Replace `ramsey/uuid` dependency with `uuid_create` from the PECL [`uuid`](https://pecl.php.net/package/uuid) extension or [`symfony/polyfill-uuid`](https://github.com/symfony/polyfill-uuid) (#937) - Deprecate `Scope::setUser` behaviour of replacing user data. (#929) diff --git a/src/Options.php b/src/Options.php index e9679cadd..c39dc37c9 100644 --- a/src/Options.php +++ b/src/Options.php @@ -300,6 +300,28 @@ public function setInAppExcludedPaths(array $paths): void $this->options = $this->resolver->resolve($options); } + /** + * Gets the list of paths which has to be identified as in_app. + * + * @return string[] + */ + public function getInAppIncludedPaths(): array + { + return $this->options['in_app_include']; + } + + /** + * Set the list of paths to include in in_app detection. + * + * @param string[] $paths The list of paths + */ + public function setInAppIncludedPaths(array $paths): void + { + $options = array_merge($this->options, ['in_app_include' => $paths]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets the project ID number to send to the Sentry server. */ @@ -769,6 +791,7 @@ private function configureOptions(OptionsResolver $resolver): void }, 'excluded_exceptions' => [], 'in_app_exclude' => [], + 'in_app_include' => [], 'send_default_pii' => false, 'max_value_length' => 1024, 'http_proxy' => null, @@ -786,6 +809,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('excluded_exceptions', 'array'); $resolver->setAllowedTypes('in_app_exclude', 'array'); + $resolver->setAllowedTypes('in_app_include', 'array'); $resolver->setAllowedTypes('project_root', ['null', 'string']); $resolver->setAllowedTypes('logger', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); @@ -818,6 +842,8 @@ private function configureOptions(OptionsResolver $resolver): void return null; } + @trigger_error('The option "project_root" is deprecated. Please use the "in_app_include" option instead.', E_USER_DEPRECATED); + return $this->normalizeAbsolutePath($value); }); @@ -828,6 +854,10 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setNormalizer('in_app_exclude', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); + + $resolver->setNormalizer('in_app_include', function (SymfonyOptions $options, array $value) { + return array_map([$this, 'normalizeAbsolutePath'], $value); + }); } /** diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 202129c4c..f3bd0643c 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -11,9 +11,13 @@ * This class contains all the information about an error stacktrace. * * @author Stefano Arlandini + * + * @final since version 2.3 */ class Stacktrace implements \JsonSerializable { + private const INTERNAL_FRAME_FILENAME = '[internal]'; + /** * @var Options The client options */ @@ -77,7 +81,7 @@ public static function createFromBacktrace(Options $options, SerializerInterface foreach ($backtrace as $frame) { $stacktrace->addFrame($file, $line, $frame); - $file = $frame['file'] ?? '[internal]'; + $file = $frame['file'] ?? self::INTERNAL_FRAME_FILENAME; $line = $frame['line'] ?? 0; } @@ -97,6 +101,15 @@ public function getFrames(): array return $this->frames; } + public function getFrame(int $index): Frame + { + if ($index < 0 || $index >= \count($this->frames)) { + throw new \OutOfBoundsException(); + } + + return $this->frames[$index]; + } + /** * Adds a new frame to the stacktrace. * @@ -138,28 +151,7 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void $frame->setPostContext($sourceCodeExcerpt['post_context']); } - // In case it's an Sentry internal frame, we mark it as in_app false - if (null !== $functionName && 0 === strpos($functionName, 'Sentry\\')) { - $frame->setIsInApp(false); - } - - if (null !== $this->options->getProjectRoot()) { - $excludedAppPaths = $this->options->getInAppExcludedPaths(); - $absoluteFilePath = @realpath($file) ?: $file; - $isApplicationFile = 0 === strpos($absoluteFilePath, $this->options->getProjectRoot()); - - if (!$isApplicationFile) { - $frame->setIsInApp(false); - } elseif (!empty($excludedAppPaths)) { - foreach ($excludedAppPaths as $path) { - if (0 === mb_strpos($absoluteFilePath, $path)) { - $frame->setIsInApp(false); - - break; - } - } - } - } + $frame->setIsInApp($this->isFrameInApp($file, $functionName)); $frameArguments = $this->getFrameArguments($backtraceFrame); @@ -418,4 +410,49 @@ protected function serializeArgument($arg) return $arg; } } + + /** + * Checks whether a certain frame should be marked as "in app" or not. + * + * @param string $file The file to check + * @param string|null $functionName The name of the function + */ + private function isFrameInApp(string $file, ?string $functionName): bool + { + if (self::INTERNAL_FRAME_FILENAME === $file) { + return false; + } + + if (null !== $functionName && 0 === strpos($functionName, 'Sentry\\')) { + return false; + } + + $projectRoot = $this->options->getProjectRoot(); + $excludedAppPaths = $this->options->getInAppExcludedPaths(); + $includedAppPaths = $this->options->getInAppIncludedPaths(); + $absoluteFilePath = @realpath($file) ?: $file; + $isInApp = false; + + if (null !== $projectRoot) { + $isInApp = 0 === mb_strpos($absoluteFilePath, $projectRoot); + } + + foreach ($excludedAppPaths as $excludedAppPath) { + if (0 === mb_strpos($absoluteFilePath, $excludedAppPath)) { + $isInApp = false; + + break; + } + } + + foreach ($includedAppPaths as $includedAppPath) { + if (0 === mb_strpos($absoluteFilePath, $includedAppPath)) { + $isInApp = true; + + break; + } + } + + return $isInApp; + } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 35f7c10ae..ae4699164 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -217,6 +217,8 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void } /** + * @group legacy + * * @requires OSFAMILY Linux */ public function testAppPathLinux(): void @@ -230,6 +232,9 @@ public function testAppPathLinux(): void $this->assertEquals('/foo/baz/', $client->getOptions()->getProjectRoot()); } + /** + * @group legacy + */ public function testAppPathWindows(): void { $client = ClientBuilder::create(['project_root' => 'C:\\foo\\bar\\'])->getClient(); @@ -412,7 +417,9 @@ public function testAttachStacktrace(): void $transport->expects($this->once()) ->method('send') ->with($this->callback(function (Event $event): bool { - return null !== $event->getStacktrace(); + $result = $event->getStacktrace(); + + return null !== $result; })); $client = ClientBuilder::create(['attach_stacktrace' => true]) diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 650d48a24..7abc1f428 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -56,6 +56,7 @@ public function optionsDataProvider(): array ['environment', 'foo', 'getEnvironment', 'setEnvironment'], ['excluded_exceptions', ['foo', 'bar', 'baz'], 'getExcludedExceptions', 'setExcludedExceptions'], ['in_app_exclude', ['foo', 'bar'], 'getInAppExcludedPaths', 'setInAppExcludedPaths'], + ['in_app_include', ['foo', 'bar'], 'getInAppIncludedPaths', 'setInAppIncludedPaths'], ['project_root', 'baz', 'getProjectRoot', 'setProjectRoot'], ['logger', 'foo', 'getLogger', 'setLogger'], ['release', 'dev', 'getRelease', 'setRelease'], @@ -257,6 +258,26 @@ public function excludedPathProviders() ]; } + /** + * @dataProvider includedPathProviders + */ + public function testIncludedAppPathsOverrideExcludedAppPaths(string $value, string $expected) + { + $configuration = new Options(['in_app_include' => [$value]]); + + $this->assertSame([$expected], $configuration->getInAppIncludedPaths()); + } + + public function includedPathProviders(): array + { + return [ + ['some/path', 'some/path'], + ['some/specific/file.php', 'some/specific/file.php'], + [__DIR__, __DIR__], + [__FILE__, __FILE__], + ]; + } + /** * @dataProvider maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider */ diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index ba60068ec..0cf19135e 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -127,22 +127,161 @@ public function testAddFrameStripsPath(): void $this->assertFrameEquals($frames[3], 'test_function_parent_parent_parent', 'app/file', 12); } - public function testAddFrameMarksAsInApp(): void + /** + * @group legacy + * + * @dataProvider addFrameSetsInAppFlagCorrectlyDataProvider + */ + public function testAddFrameSetsInAppFlagCorrectly(array $options, string $file, string $functionName, bool $expectedResult): void { - $this->options->setProjectRoot('path/to'); - $this->options->setInAppExcludedPaths(['path/to/excluded/path']); + $options = new Options($options); + $stacktrace = new Stacktrace( + $options, + new Serializer($options), + new RepresentationSerializer($options) + ); - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $stacktrace->addFrame($file, 0, ['function' => $functionName]); - $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function']); - $stacktrace->addFrame('path/to/excluded/path/to/file', 12, ['function' => 'test_function']); - $stacktrace->addFrame('path/elsewhere', 12, ['function' => 'test_function']); + $this->assertSame($expectedResult, $stacktrace->getFrame(0)->isInApp()); + } - $frames = $stacktrace->getFrames(); + public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator + { + yield 'No config specified' => [ + [ + 'project_root' => null, + 'in_app_exclude' => [], + 'in_app_include' => [], + ], + '[internal]', + 'test_function', + false, + ]; + + yield 'project_root specified && file path matching project_root' => [ + [ + 'project_root' => 'path/to', + 'in_app_exclude' => [], + 'in_app_include' => [], + ], + 'path/to/file', + 'test_function', + true, + ]; + + yield 'project_root specified && file path matching project_root && file path matching in_app_exclude' => [ + [ + 'project_root' => 'path/to/file', + 'in_app_exclude' => [ + 'path/to', + ], + 'in_app_include' => [], + ], + 'path/to/file', + 'test_function', + false, + ]; + + yield 'project_root specified && file path not maching project_root && file path matching in_app_include' => [ + [ + 'project_root' => 'nested/path/to', + 'in_app_exclude' => [], + 'in_app_include' => [ + 'path/to', + ], + ], + 'path/to/file', + 'test_function', + true, + ]; - $this->assertFalse($frames[0]->isInApp()); - $this->assertFalse($frames[1]->isInApp()); - $this->assertTrue($frames[2]->isInApp()); + yield 'in_app_include specified && file path not matching' => [ + [ + 'project_root' => null, + 'in_app_exclude' => [], + 'in_app_include' => [ + 'path/to/nested/file', + ], + ], + 'path/to/file', + 'test_function', + false, + ]; + + yield 'in_app_include specified && file path matching' => [ + [ + 'project_root' => null, + 'in_app_exclude' => [], + 'in_app_include' => [ + 'path/to/nested/file', + 'path/to/file', + ], + ], + 'path/to/file', + 'test_function', + true, + ]; + + yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_include' => [ + [ + 'project_root' => null, + 'in_app_exclude' => [ + 'path/to/nested/file', + ], + 'in_app_include' => [ + 'path/to/file', + ], + ], + 'path/to/file', + 'test_function', + true, + ]; + + yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_exclude' => [ + [ + 'project_root' => null, + 'in_app_exclude' => [ + 'path/to/nested/file', + ], + 'in_app_include' => [ + 'path/to/file', + ], + ], + 'path/to/nested/file', + 'test_function', + false, + ]; + + yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_include && in_app_include prioritized over in_app_exclude' => [ + [ + 'project_root' => null, + 'in_app_exclude' => [ + 'path/to/file', + ], + 'in_app_include' => [ + 'path/to/file/nested', + ], + ], + 'path/to/file/nested', + 'test_function', + true, + ]; + + yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_exclude && in_app_exclude prioritized over in_app_include' => [ + [ + 'project_root' => null, + 'in_app_exclude' => [ + 'path/to/file', + ], + 'in_app_include' => [ + 'path/to/file/nested', + ], + ], + 'path/to/file', + 'test_function', + false, + ]; } /** @@ -247,24 +386,6 @@ public function testFromBacktraceWithAnonymousFrame(): void $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); } - public function testInAppWithEmptyFrame(): void - { - $stack = [ - [ - 'function' => '{closure}', - ], - null, - ]; - - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); - $stacktrace->addFrame('/some/file', 123, $stack); - $frames = $stacktrace->getFrames(); - - $this->assertCount(1, $frames); - $this->assertContainsOnlyInstancesOf(Frame::class, $frames); - $this->assertTrue($frames[0]->isInApp()); - } - public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void { // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. From a3593266c8f53eda19fc42e59d509ea12d6d41da Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 3 Jan 2020 12:48:05 +0100 Subject: [PATCH 0529/1161] Update CHANGELOG.md Add changelog entry for #945 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aed1f037..1b03e7828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Make the `integrations` option accept a `callable` that will receive the list of default integrations and returns a customized list (#919) - Add the `IgnoreErrorsIntegration` integration to deprecate and replace the `exclude_exceptions` option (#928) - Allow setting custom contexts on the scope and on the event (#839) +- Replace dependency to `zendframework/zend-diactoros` with `guzzlehttp/psr7` (#945) ## 2.2.6 (2019-12-18) From 161bd50dbae1468a165c1b3b14eda47f91f7f746 Mon Sep 17 00:00:00 2001 From: Marcin Michalski <57528542+marmichalski@users.noreply.github.com> Date: Thu, 9 Jan 2020 12:08:07 +0100 Subject: [PATCH 0530/1161] Fix exception thrown even if the HTTP client was instantiated successfully (#951) --- CHANGELOG.md | 2 ++ src/HttpClient/HttpClientFactory.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ca8a3a2..c9d181e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix client creation with proxy (#951) + ## 2.3.0 (2020-01-08) - Add `in_app_include` option to whitelist paths that should be marked as part of the app (#909) diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 898bec81d..6b89ae62b 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -111,9 +111,9 @@ public function create(Options $options): HttpAsyncClientInterface $this->httpClient = new CurlHttpClient($this->responseFactory, $this->streamFactory, [ CURLOPT_PROXY => $options->getHttpProxy(), ]); + } else { + throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); } - - throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); } if (null === $httpClient) { From b148cf14011a1a448ab8c0503aeeea20b33d2ee0 Mon Sep 17 00:00:00 2001 From: Philip Hofstetter Date: Fri, 17 Jan 2020 10:27:33 +0100 Subject: [PATCH 0531/1161] Allow unsetting the stacktrace of an event (#961) --- CHANGELOG.md | 1 + src/Event.php | 4 ++-- tests/EventTest.php | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9d181e06..5fa51aa32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix client creation with proxy (#951) +- Allow unsetting the stack trace on an `Event` by calling `Event::setStacktrace(null)` (#961) ## 2.3.0 (2020-01-08) diff --git a/src/Event.php b/src/Event.php index f4bdd3643..7c476d795 100644 --- a/src/Event.php +++ b/src/Event.php @@ -552,9 +552,9 @@ public function getStacktrace(): ?Stacktrace /** * Sets the stacktrace that generated this event. * - * @param Stacktrace $stacktrace The stacktrace instance + * @param Stacktrace|null $stacktrace The stacktrace instance */ - public function setStacktrace(Stacktrace $stacktrace): void + public function setStacktrace(?Stacktrace $stacktrace): void { $this->stacktrace = $stacktrace; } diff --git a/tests/EventTest.php b/tests/EventTest.php index 37d138233..05b15381b 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -10,6 +10,7 @@ use Sentry\Client; use Sentry\Event; use Sentry\Severity; +use Sentry\Stacktrace; use Sentry\Util\PHPVersion; /** @@ -253,6 +254,20 @@ public function gettersAndSettersDataProvider(): array ]; } + public function testSetStacktrace(): void + { + $stacktrace = $this->createMock(Stacktrace::class); + + $event = new Event(); + $event->setStacktrace($stacktrace); + + $this->assertSame($stacktrace, $event->getStacktrace()); + + $event->setStacktrace(null); + + $this->assertNull($event->getStacktrace()); + } + public function testEventJsonSerialization(): void { $event = new Event(); From f28c0e1942de6621d75402f69a2c932d0c413a9c Mon Sep 17 00:00:00 2001 From: Philip Hofstetter Date: Fri, 17 Jan 2020 21:54:21 +0100 Subject: [PATCH 0532/1161] Do not send the stacktrace along with the exception when attach_stacktrace = true (#960) --- CHANGELOG.md | 1 + src/Client.php | 19 +++++---------- tests/ClientTest.php | 58 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa51aa32..eb4cba3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix client creation with proxy (#951) - Allow unsetting the stack trace on an `Event` by calling `Event::setStacktrace(null)` (#961) +- Fix sending of both `event.stacktrace` and `event.exceptions` when `attach_stacktrace = true` (#960) ## 2.3.0 (2020-01-08) diff --git a/src/Client.php b/src/Client.php index 2147258e3..86c07153b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -85,13 +85,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope 'level' => $level, ]; - $event = $this->prepareEvent($payload, $scope, $this->options->shouldAttachStacktrace()); - - if (null === $event) { - return null; - } - - return $this->transport->send($event); + return $this->captureEvent($payload, $scope); } /** @@ -111,7 +105,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? */ public function captureEvent(array $payload, ?Scope $scope = null): ?string { - $event = $this->prepareEvent($payload, $scope, $this->options->shouldAttachStacktrace()); + $event = $this->prepareEvent($payload, $scope); if (null === $event) { return null; @@ -162,13 +156,12 @@ public function flush(?int $timeout = null): PromiseInterface /** * Assembles an event and prepares it to be sent of to Sentry. * - * @param array $payload the payload that will be converted to an Event - * @param Scope|null $scope optional scope which enriches the Event - * @param bool $withStacktrace True if the event should have and attached stacktrace + * @param array $payload the payload that will be converted to an Event + * @param Scope|null $scope optional scope which enriches the Event * * @return Event|null returns ready to send Event, however depending on options it can be discarded */ - private function prepareEvent(array $payload, ?Scope $scope = null, bool $withStacktrace = false): ?Event + private function prepareEvent(array $payload, ?Scope $scope = null): ?Event { $sampleRate = $this->getOptions()->getSampleRate(); @@ -176,7 +169,7 @@ private function prepareEvent(array $payload, ?Scope $scope = null, bool $withSt return null; } - if ($withStacktrace) { + if ($this->getOptions()->shouldAttachStacktrace() && !isset($payload['exception']) && !isset($payload['stacktrace'])) { $event = $this->eventFactory->createWithStacktrace($payload); } else { $event = $this->eventFactory->create($payload); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index ae4699164..0c5e85bb6 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -140,7 +140,7 @@ public function testCaptureEvent(): void /** * @dataProvider captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider */ - public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOption(bool $shouldAttachStacktrace): void + public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOption(bool $attachStacktraceOption, array $payload, bool $shouldAttachStacktrace): void { /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -159,19 +159,63 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt })) ->willReturn('500a339f3ab2450b96dee542adf36ba7'); - $client = ClientBuilder::create(['attach_stacktrace' => $shouldAttachStacktrace]) + $client = ClientBuilder::create(['attach_stacktrace' => $attachStacktraceOption]) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent([])); + $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent($payload)); } - public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider(): array + public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider(): \Generator { - return [ - [true], - [false], + yield 'Stacktrace attached when attach_stacktrace = true and both payload.exception and payload.stacktrace are unset' => [ + true, + [], + true, + ]; + + yield 'No stacktrace attached when attach_stacktrace = false' => [ + false, + [], + false, + ]; + + yield 'No stacktrace attached when attach_stacktrace = true and payload.exception is set' => [ + true, + [ + 'exception' => new \Exception(), + ], + false, ]; + + yield 'No stacktrace attached when attach_stacktrace = false and payload.exception is set' => [ + true, + [ + 'exception' => new \Exception(), + ], + false, + ]; + } + + public function testCaptureEventPrefersExplicitStacktrace(): void + { + $explicitStacktrace = $this->createMock(Stacktrace::class); + $payload = ['stacktrace' => $explicitStacktrace]; + + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(static function (Event $event) use ($explicitStacktrace): bool { + return $explicitStacktrace === $event->getStacktrace(); + })) + ->willReturn('500a339f3ab2450b96dee542adf36ba7'); + + $client = ClientBuilder::create(['attach_stacktrace' => true]) + ->setTransportFactory($this->createTransportFactory($transport)) + ->getClient(); + + $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent($payload)); } public function testCaptureLastError(): void From ee5d6af85c735c6882a71c674ee6b6707bf25d19 Mon Sep 17 00:00:00 2001 From: Ken Koch Date: Mon, 20 Jan 2020 15:48:47 -0500 Subject: [PATCH 0533/1161] Strip the memory address of the function from the frames of the stacktrace (#956) --- CHANGELOG.md | 1 + src/Stacktrace.php | 6 +++++- .../anonymous_frame_with_memory_address.json | 11 +++++++++++ tests/StacktraceTest.php | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json diff --git a/CHANGELOG.md b/CHANGELOG.md index eb4cba3c3..19bba9daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix client creation with proxy (#951) - Allow unsetting the stack trace on an `Event` by calling `Event::setStacktrace(null)` (#961) - Fix sending of both `event.stacktrace` and `event.exceptions` when `attach_stacktrace = true` (#960) +- Fix issues with memory addresses in anonymous class stack traces (#956) ## 2.3.0 (2020-01-08) diff --git a/src/Stacktrace.php b/src/Stacktrace.php index f3bd0643c..28bc974bc 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -129,7 +129,11 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void } if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { - $functionName = sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); + $functionName = sprintf( + '%s::%s', + preg_replace('/0x[a-fA-F0-9]+$/', '', $backtraceFrame['class']), + $backtraceFrame['function'] + ); } elseif (isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['function']; } else { diff --git a/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json b/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json new file mode 100644 index 000000000..7181588b6 --- /dev/null +++ b/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json @@ -0,0 +1,11 @@ +{ + "file": "path/to/file", + "line": 12, + "backtrace": [ + { + "class": "class@anonymous/path/to/app/consumer.php0x7fc3bc369418", + "function": "messageCallback", + "type": "->" + } + ] +} diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 0cf19135e..35011ac2c 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -386,6 +386,25 @@ public function testFromBacktraceWithAnonymousFrame(): void $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); } + public function testFromBacktraceWithAnonymousClass(): void + { + $fixture = $this->getJsonFixture('backtraces/anonymous_frame_with_memory_address.json'); + $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + + $this->assertFrameEquals( + $frames[0], + null, + '[internal]', + 0 + ); + $this->assertFrameEquals( + $frames[1], + 'class@anonymous/path/to/app/consumer.php::messageCallback', + 'path/to/file', + 12 + ); + } + public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void { // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. From 15bfd0eb7df143b9760dda5cd748308f992c2e26 Mon Sep 17 00:00:00 2001 From: tpdrs <58810769+tpdrs@users.noreply.github.com> Date: Thu, 23 Jan 2020 12:12:12 +0300 Subject: [PATCH 0534/1161] Fixed check for exclude (#958) --- CHANGELOG.md | 1 + src/Stacktrace.php | 2 +- tests/StacktraceTest.php | 13 ++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19bba9daa..4e477845d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Allow unsetting the stack trace on an `Event` by calling `Event::setStacktrace(null)` (#961) - Fix sending of both `event.stacktrace` and `event.exceptions` when `attach_stacktrace = true` (#960) - Fix issues with memory addresses in anonymous class stack traces (#956) +- By default all paths are included in stacktrace (#958) ## 2.3.0 (2020-01-08) diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 28bc974bc..88677f345 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -435,7 +435,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool $excludedAppPaths = $this->options->getInAppExcludedPaths(); $includedAppPaths = $this->options->getInAppIncludedPaths(); $absoluteFilePath = @realpath($file) ?: $file; - $isInApp = false; + $isInApp = true; if (null !== $projectRoot) { $isInApp = 0 === mb_strpos($absoluteFilePath, $projectRoot); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 35011ac2c..fb8dc5e11 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -206,7 +206,18 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator ], 'path/to/file', 'test_function', - false, + true, + ]; + + yield 'in_app_include not specified && file path not matching' => [ + [ + 'project_root' => null, + 'in_app_exclude' => [], + 'in_app_include' => [], + ], + 'path/to/file', + 'test_function', + true, ]; yield 'in_app_include specified && file path matching' => [ From 6d736f8cefa989f6171e30e1d1bfa214f7f5ab58 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 23 Jan 2020 10:40:22 +0100 Subject: [PATCH 0535/1161] Prepare changelog for 2.3.1 release (#966) --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e477845d..65f5080f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,13 @@ ## Unreleased -- Fix client creation with proxy (#951) +## 2.3.1 (2020-01-23) + - Allow unsetting the stack trace on an `Event` by calling `Event::setStacktrace(null)` (#961) - Fix sending of both `event.stacktrace` and `event.exceptions` when `attach_stacktrace = true` (#960) +- Fix regression that set all frames of a stacktrace as not in app by default (#958) - Fix issues with memory addresses in anonymous class stack traces (#956) -- By default all paths are included in stacktrace (#958) +- Fix exception thrown regardless of whether the HTTP client was instantiated when using the `http_proxy option` (#951) ## 2.3.0 (2020-01-08) From 7acdcdd1d507e29a38b4c282527a2ae3c9fca130 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 27 Jan 2020 11:03:07 +0100 Subject: [PATCH 0536/1161] meta: Craft update --- .craft.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.craft.yml b/.craft.yml index 4e96626e6..a492a4e02 100644 --- a/.craft.yml +++ b/.craft.yml @@ -1,11 +1,14 @@ -minVersion: '0.7.0' +minVersion: '0.9.0' github: owner: getsentry repo: sentry-php changelogPolicy: simple +statusProvider: + name: github preReleaseCommand: "" targets: - name: github + includeNames: /none/ - name: registry type: sdk config: From 516fb25bf8e2c2f6040c45768902e744ca1c528c Mon Sep 17 00:00:00 2001 From: John-Junior Murray Date: Fri, 14 Feb 2020 08:50:39 +0000 Subject: [PATCH 0537/1161] Fix the http_proxy option not being applied correctly (#978) * Store created httpClient to local variable * Remove unneeded phpstan ignored error * Add changelog entry Co-authored-by: Alex Bouma --- CHANGELOG.md | 2 ++ phpstan.neon | 3 --- src/HttpClient/HttpClientFactory.php | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f5080f0..0fe995d57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix `http_proxy` option not being applied (#978) + ## 2.3.1 (2020-01-23) - Allow unsetting the stack trace on an `Event` by calling `Event::setStacktrace(null)` (#961) diff --git a/phpstan.neon b/phpstan.neon index 490765929..211d3ad4c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -34,9 +34,6 @@ parameters: - message: '/^Access to constant PROXY on an unknown class GuzzleHttp\\RequestOptions\.$/' path: src/HttpClient/HttpClientFactory.php - - - message: '/^Property Sentry\\HttpClient\\HttpClientFactory::\$httpClient \(Http\\Client\\HttpAsyncClient\|null\) does not accept Http\\Client\\Curl\\Client.$/' - path: src/HttpClient/HttpClientFactory.php - message: '/^Access to an undefined property Sentry\\Integration\\IntegrationInterface::\$options\.$/' path: src/Integration/IgnoreErrorsIntegration.php diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 6b89ae62b..c67239b2b 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -103,12 +103,12 @@ public function create(Options $options): HttpAsyncClientInterface if (null === $httpClient && null !== $options->getHttpProxy()) { if (class_exists(GuzzleHttpClient::class)) { /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->httpClient = GuzzleHttpClient::createWithConfig([ + $httpClient = GuzzleHttpClient::createWithConfig([ GuzzleHttpClientOptions::PROXY => $options->getHttpProxy(), ]); } elseif (class_exists(CurlHttpClient::class)) { /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->httpClient = new CurlHttpClient($this->responseFactory, $this->streamFactory, [ + $httpClient = new CurlHttpClient($this->responseFactory, $this->streamFactory, [ CURLOPT_PROXY => $options->getHttpProxy(), ]); } else { From 4570ae3931eec869420a9289edad00bb9623c497 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 14 Feb 2020 09:51:11 +0100 Subject: [PATCH 0538/1161] =?UTF-8?q?Enforce=20a=20timeout=20for=20connect?= =?UTF-8?q?ing=20to=20the=20server=20and=20for=20the=20reque=E2=80=A6=20(#?= =?UTF-8?q?979)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enforce a timeout for connecting to the server and for the requests instead of waiting indefinitely * Fix PHPStan ignoreErrors rules * Add CHANGELOG entry --- CHANGELOG.md | 2 ++ phpstan.neon | 2 +- src/HttpClient/HttpClientFactory.php | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f5080f0..1d49f012f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Enforce a timeout for connecting to the server and for the requests instead of waiting indefinitely (#979) + ## 2.3.1 (2020-01-23) - Allow unsetting the stack trace on an `Event` by calling `Event::setStacktrace(null)` (#961) diff --git a/phpstan.neon b/phpstan.neon index 490765929..33292fb7a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -32,7 +32,7 @@ parameters: message: '/^Call to static method createWithConfig\(\) on an unknown class Http\\Adapter\\Guzzle6\\Client\.$/' path: src/HttpClient/HttpClientFactory.php - - message: '/^Access to constant PROXY on an unknown class GuzzleHttp\\RequestOptions\.$/' + message: '/^Access to constant (?:PROXY|TIMEOUT|CONNECT_TIMEOUT) on an unknown class GuzzleHttp\\RequestOptions\.$/' path: src/HttpClient/HttpClientFactory.php - message: '/^Property Sentry\\HttpClient\\HttpClientFactory::\$httpClient \(Http\\Client\\HttpAsyncClient\|null\) does not accept Http\\Client\\Curl\\Client.$/' diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 6b89ae62b..8f2181276 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -29,6 +29,17 @@ */ final class HttpClientFactory implements HttpClientFactoryInterface { + /** + * @var int The timeout of the request in seconds + */ + private const DEFAULT_HTTP_TIMEOUT = 5; + + /** + * @var int The default number of seconds to wait while trying to connect + * to a server + */ + private const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; + /** * @var UriFactoryInterface The PSR-7 URI factory */ @@ -105,11 +116,15 @@ public function create(Options $options): HttpAsyncClientInterface /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->httpClient = GuzzleHttpClient::createWithConfig([ GuzzleHttpClientOptions::PROXY => $options->getHttpProxy(), + GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, + GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, ]); } elseif (class_exists(CurlHttpClient::class)) { /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->httpClient = new CurlHttpClient($this->responseFactory, $this->streamFactory, [ CURLOPT_PROXY => $options->getHttpProxy(), + CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, + CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, ]); } else { throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); From 9ec2815f5acbed81490463297ba5dc86661860ba Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 17 Feb 2020 11:58:58 +0100 Subject: [PATCH 0539/1161] Add Codecov and remove Scrutinizer (#980) --- .appveyor.yml | 21 +++++++++++++++++++-- .scrutinizer.yml | 21 --------------------- .travis.yml | 26 ++++++++++++-------------- README.md | 23 ++++++++++++++++++----- codecov.yml | 5 +++++ phpstan.neon | 3 --- 6 files changed, 54 insertions(+), 45 deletions(-) delete mode 100644 .scrutinizer.yml create mode 100644 codecov.yml diff --git a/.appveyor.yml b/.appveyor.yml index 43f166f4b..33b3eecb3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,20 +13,28 @@ branches: environment: matrix: - PHP_VERSION: 7.1 + XDEBUG_VERSION: 2.9.2-7.1-vc14-nts DEPENDENCIES: lowest - PHP_VERSION: 7.1 + XDEBUG_VERSION: 2.9.2-7.1-vc14-nts DEPENDENCIES: highest - PHP_VERSION: 7.2 + XDEBUG_VERSION: 2.9.2-7.2-vc15-nts DEPENDENCIES: lowest - PHP_VERSION: 7.2 + XDEBUG_VERSION: 2.9.2-7.2-vc15-nts DEPENDENCIES: highest - PHP_VERSION: 7.3 + XDEBUG_VERSION: 2.9.2-7.3-vc15-nts DEPENDENCIES: lowest - PHP_VERSION: 7.3 + XDEBUG_VERSION: 2.9.2-7.3-vc15-nts DEPENDENCIES: highest - PHP_VERSION: 7.4 + XDEBUG_VERSION: 2.9.2-7.4-vc15-nts DEPENDENCIES: lowest - PHP_VERSION: 7.4 + XDEBUG_VERSION: 2.9.2-7.4-vc15-nts DEPENDENCIES: highest matrix: @@ -45,19 +53,28 @@ init: install: - IF EXIST C:\php SET INSTALL_PHP=0 - ps: choco upgrade chocolatey --confirm --no-progress --allow-downgrade --version 0.10.13 + - ps: choco install codecov --confirm --no-progress - ps: choco install php --confirm --no-progress --package-parameters '""/InstallDir:C:\php""' --version (choco search php --exact --all-versions --limit-output | Select-String -Pattern $env:PHP_VERSION | ForEach-Object {$_ -Replace "php\|", ""} | Sort {[version] $_} -Descending | Select-Object -First 1) - cd C:\php + - ps: if ($env:INSTALL_PHP -imatch 1) { appveyor-retry appveyor DownloadFile "https://xdebug.org/files/php_xdebug-$env:XDEBUG_VERSION-x86_64.dll" -FileName C:\php\ext\php_xdebug.dll } - IF %INSTALL_PHP%==1 copy /Y php.ini-production php.ini - IF %INSTALL_PHP%==1 echo extension_dir=C:\php\ext >> php.ini - IF %INSTALL_PHP%==1 echo extension=php_curl.dll >> php.ini - IF %INSTALL_PHP%==1 echo extension=php_mbstring.dll >> php.ini - IF %INSTALL_PHP%==1 echo extension=php_openssl.dll >> php.ini + - IF %INSTALL_PHP%==1 echo zend_extension=C:\php\ext\php_xdebug.dll >> php.ini + - IF %INSTALL_PHP%==1 echo xdebug.overload_var_dump=0 >> php.ini + - IF %INSTALL_PHP%==1 echo xdebug.collect_includes=0 >> php.ini + - IF %INSTALL_PHP%==1 echo xdebug.dump_globals=0 >> php.ini + - IF %INSTALL_PHP%==1 echo xdebug.collect_vars=0 >> php.ini + - IF %INSTALL_PHP%==1 echo xdebug.extended_info=0 >> php.ini - cd C:\projects\sentry-php - - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/1.9.1/composer.phar + - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/1.9.3/composer.phar - php composer.phar self-update - IF %DEPENDENCIES%==lowest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-lowest --prefer-dist - IF %DEPENDENCIES%==highest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-dist test_script: - cd C:\projects\sentry-php - - vendor\bin\phpunit.bat + - vendor\bin\phpunit.bat --coverage-clover=build/coverage-report.xml + - codecov -f build/coverage-report.xml diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index ac4e2eeee..000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,21 +0,0 @@ -tools: - external_code_coverage: - timeout: 600 - -build: - nodes: - analysis: - tests: - override: - - php-scrutinizer-run - -filter: - paths: - - src/ - - tests/ - -coding_style: - php: - spaces: - around_operators: - concatenation: true diff --git a/.travis.yml b/.travis.yml index d4b8f0190..9198d1364 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,32 +26,30 @@ cache: stages: - Code style & static analysis - Test - - Code coverage jobs: include: - - stage: Code style & static analysis - name: PHP CS Fixer + - &code-style-static-analysis + stage: Code style & static analysis + name: PHP-CS-Fixer + php: 7.4 env: dependencies=highest script: composer phpcs - - name: PHPStan - env: dependencies=highest + - <<: *code-style-static-analysis + name: PHPStan script: composer phpstan - - name: Psalm - env: dependencies=highest + - <<: *code-style-static-analysis + name: Psalm script: composer psalm - - stage: Code coverage - php: 7.3 - env: dependencies=highest - script: - - vendor/bin/phpunit --verbose --coverage-clover=build/logs/clover.xml - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml --revision=$TRAVIS_COMMIT install: - if [ "$dependencies" = "lowest" ]; then composer update --no-interaction --no-suggest --prefer-lowest --prefer-dist; fi; - if [ "$dependencies" = "highest" ]; then composer update --no-interaction --no-suggest --prefer-dist; fi; +script: >- + vendor/bin/phpunit --coverage-clover=build/coverage-report.xml && + bash <(curl -s https://codecov.io/bash) -f build/coverage-report.xml + notifications: webhooks: urls: diff --git a/README.md b/README.md index 4258ba6f4..5705a3090 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,18 @@

-# Sentry for PHP +# Sentry SDK for PHP -[![Build Status](https://img.shields.io/travis/getsentry/sentry-php/master?logo=travis)](http://travis-ci.org/getsentry/sentry-php) -[![AppVeyor Build Status](https://img.shields.io/appveyor/ci/sentry/sentry-php/master?logo=appveyor)](https://ci.appveyor.com/project/sentry/sentry-php) [![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) [![Monthly Downloads](https://poser.pugx.org/sentry/sentry/d/monthly)](https://packagist.org/packages/sentry/sentry) [![Latest Stable Version](https://poser.pugx.org/sentry/sentry/v/stable)](https://packagist.org/packages/sentry/sentry) [![License](https://poser.pugx.org/sentry/sentry/license)](https://packagist.org/packages/sentry/sentry) -[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) -[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) +[![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/cWnMQeA) + +| Version | Build Status | Code Coverage | +|:---------:|:-------------:|:-----:| +| `master`| [![Build Status][Travis Master Build Status Image]][Travis Build Status] [![Build Status][AppVeyor Master Build Status Image]][AppVeyor Build Status] | [![Coverage Status][Master Code Coverage Image]][Master Code Coverage] | +| `develop` | [![Build Status][Travis Develop Build Status Image]][Travis Build Status] [![Build Status][AppVeyor Develop Build Status Image]][AppVeyor Build Status] | [![Coverage Status][Develop Code Coverage Image]][Develop Code Coverage] | The Sentry PHP error reporter tracks errors and exceptions that happen during the execution of your application and provides instant notification with detailed @@ -107,3 +109,14 @@ Tests can then be run via phpunit: ``` $ vendor/bin/phpunit ``` + +[Travis Build Status]: http://travis-ci.org/getsentry/sentry-php +[Travis Master Build Status Image]: https://img.shields.io/travis/getsentry/sentry-php/master?logo=travis +[Travis Develop Build Status Image]: https://img.shields.io/travis/getsentry/sentry-php/develop?logo=travis +[AppVeyor Build Status]: https://ci.appveyor.com/project/sentry/sentry-php +[AppVeyor Master Build Status Image]: https://img.shields.io/appveyor/ci/sentry/sentry-php/master?logo=appveyor +[AppVeyor Develop Build Status Image]: https://img.shields.io/appveyor/ci/sentry/sentry-php/develop?logo=appveyor +[Master Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/master +[Master Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/master?logo=codecov +[Develop Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/develop +[Develop Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/develop?logo=codecov diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..0d992425c --- /dev/null +++ b/codecov.yml @@ -0,0 +1,5 @@ +comment: false +ignore: + - tests/data + - tests/resources + - tests/Fixtures diff --git a/phpstan.neon b/phpstan.neon index 211d3ad4c..53e69dca4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -16,9 +16,6 @@ parameters: - message: '/^Argument of an invalid type array\|object supplied for foreach, only iterables are supported\.$/' path: src/Util/JSON.php - - - message: '/^Constant JSON_INVALID_UTF8_SUBSTITUTE not found\.$/' - path: src/Util/JSON.php - message: '/^Class Http\\Client\\Curl\\Client not found\.$/' path: src/HttpClient/HttpClientFactory.php From 9ea19135d1943593a1b9903dee069f15fc503301 Mon Sep 17 00:00:00 2001 From: Mark Plomer Date: Thu, 20 Feb 2020 09:00:09 +0100 Subject: [PATCH 0540/1161] Hard-limit concurrent requests in HttpTransport and removed pre-init of promises (fixes "too many open files" errors) (#981) (#981) --- CHANGELOG.md | 1 + src/Transport/HttpTransport.php | 35 +++++++++++++++++++-------------- tests/ClientTest.php | 23 ++++++---------------- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fe995d57..64f5986c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Hard-limit concurrent requests in `HttpTransport` and removed pre-init of promises (fixes "too many open files" errors) (#981) - Fix `http_proxy` option not being applied (#978) ## 2.3.1 (2020-01-23) diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index fabee43a8..9e0619f99 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -4,11 +4,12 @@ namespace Sentry\Transport; +use GuzzleHttp\Promise\EachPromise; use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Message\RequestFactory as RequestFactoryInterface; -use Http\Promise\Promise as HttpPromiseInterface; +use Psr\Http\Message\RequestInterface; use Sentry\Event; use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; @@ -38,7 +39,7 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterf private $requestFactory; /** - * @var HttpPromiseInterface[] The list of pending promises + * @var RequestInterface[] The list of pending requests */ private $pendingRequests = []; @@ -112,13 +113,11 @@ public function send(Event $event): ?string JSON::encode($event->toArray()) ); - $promise = $this->httpClient->sendAsyncRequest($request); - if ($this->delaySendingUntilShutdown) { - $this->pendingRequests[] = $promise; + $this->pendingRequests[] = $request; } else { try { - $promise->wait(); + $this->httpClient->sendAsyncRequest($request)->wait(); } catch (\Throwable $exception) { return null; } @@ -138,8 +137,7 @@ public function close(?int $timeout = null): PromiseInterface } /** - * Cleanups the pending promises by awaiting for them. Any error that occurs - * will be ignored. + * Sends the pending requests. Any error that occurs will be ignored. * * @deprecated since version 2.2.3, to be removed in 3.0. Even though this * method is `private` we cannot delete it because it's used @@ -148,13 +146,20 @@ public function close(?int $timeout = null): PromiseInterface */ private function cleanupPendingRequests(): void { - while ($promise = array_pop($this->pendingRequests)) { - try { - $promise->wait(); - } catch (\Throwable $exception) { - // Do nothing because we don't want to break applications while - // trying to send events - } + try { + $requestGenerator = function (): \Generator { + foreach ($this->pendingRequests as $key => $request) { + yield $key => $this->httpClient->sendAsyncRequest($request); + } + }; + + $eachPromise = new EachPromise($requestGenerator(), ['concurrency' => 30]); + $eachPromise->promise()->wait(); + } catch (\Throwable $exception) { + // Do nothing because we don't want to break applications while + // trying to send events } + + $this->pendingRequests = []; } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 0c5e85bb6..3427eaa79 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -4,8 +4,6 @@ namespace Sentry\Tests; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Mock\Client as MockClient; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\ClientBuilder; @@ -15,7 +13,6 @@ use Sentry\Serializer\Serializer; use Sentry\Severity; use Sentry\Stacktrace; -use Sentry\Transport\HttpTransport; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; @@ -316,27 +313,19 @@ public function testSendChecksBeforeSendOption(): void */ public function testSampleRateAbsolute(float $sampleRate): void { - $httpClient = new MockClient(); - $options = new Options(['dsn' => 'http://public:secret@example.com/1']); - $options->setSampleRate($sampleRate); - $transportFactory = $this->createTransportFactory(new HttpTransport($options, $httpClient, MessageFactoryDiscovery::find(), true, false)); + $transport = $this->createMock(TransportInterface::class); + $transport->expects(0 == $sampleRate ? $this->never() : $this->exactly(10)) + ->method('send'); - $client = (new ClientBuilder($options)) + $transportFactory = $this->createTransportFactory($transport); + + $client = (new ClientBuilder(new Options(['sample_rate' => $sampleRate]))) ->setTransportFactory($transportFactory) ->getClient(); for ($i = 0; $i < 10; ++$i) { $client->captureMessage('foobar'); } - - switch ($sampleRate) { - case 0: - $this->assertEmpty($httpClient->getRequests()); - break; - case 1: - $this->assertNotEmpty($httpClient->getRequests()); - break; - } } public function sampleRateAbsoluteDataProvider(): array From 8c7b99890cb47dbcd358251efb3f7c1d5ec2adad Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Fri, 6 Mar 2020 09:57:51 +0100 Subject: [PATCH 0541/1161] Avoid re-throwing the captured exception if the previous listener doesn't (#974) Co-authored-by: Stefano Arlandini --- .travis.yml | 4 +- phpstan.neon | 4 +- src/ErrorHandler.php | 46 +++++++-------- ...eption_rethrown_from_previous_handler.phpt | 45 ++++++++++++++ ...xception_thrown_from_previous_handler.phpt | 15 ++++- ...es_exception_with_no_previous_handler.phpt | 43 ++++++++++++++ ...ures_exception_with_previous_handler.phpt} | 7 +-- ...er_captures_out_of_memory_fatal_error.phpt | 7 +-- ...dler_captures_rethrown_exception_once.phpt | 58 ------------------- ...er_gets_registered_with_legacy_method.phpt | 27 +++++++++ ...er_gets_registered_with_legacy_method.phpt | 29 ++++++++++ ...er_gets_registered_with_legacy_method.phpt | 29 ++++++++++ 12 files changed, 214 insertions(+), 100 deletions(-) create mode 100644 tests/phpt/error_handler_captures_exception_rethrown_from_previous_handler.phpt create mode 100644 tests/phpt/error_handler_captures_exception_with_no_previous_handler.phpt rename tests/phpt/{error_handler_captures_exception.phpt => error_handler_captures_exception_with_previous_handler.phpt} (90%) delete mode 100644 tests/phpt/error_handler_captures_rethrown_exception_once.phpt create mode 100644 tests/phpt/error_handler_error_listener_gets_registered_with_legacy_method.phpt create mode 100644 tests/phpt/error_handler_exception_listener_gets_registered_with_legacy_method.phpt create mode 100644 tests/phpt/error_handler_fatal_error_listener_gets_registered_with_legacy_method.phpt diff --git a/.travis.yml b/.travis.yml index 9198d1364..d98bd6a2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,6 @@ env: - dependencies=highest - dependencies=lowest -matrix: - fast_finish: true - cache: directories: - $HOME/.composer/cache @@ -28,6 +25,7 @@ stages: - Test jobs: + fast_finish: true include: - &code-style-static-analysis stage: Code style & static analysis diff --git a/phpstan.neon b/phpstan.neon index 53e69dca4..71c530cbc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,12 +7,10 @@ parameters: ignoreErrors: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - '/^Parameter #1 \$object of method ReflectionProperty::setValue\(\) expects object, null given\.$/' # https://github.com/phpstan/phpstan/pull/2340 + - "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" - message: /^Cannot assign offset 'os' to array\|string\.$/ path: src/Event.php - - - message: "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" - path: src/Transport/HttpTransport.php - message: '/^Argument of an invalid type array\|object supplied for foreach, only iterables are supported\.$/' path: src/Util/JSON.php diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 8df4a7638..38c2189bb 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -9,11 +9,9 @@ /** * This class implements a simple error handler that catches all configured - * error types and logs them using a certain Raven client. Registering more - * than once this error handler is not supported and will lead to nasty problems. - * The code is based on the Symfony Debug component. - * - * @author Stefano Arlandini + * error types and relays them to all configured listeners. Registering this + * error handler more than once is not supported and will lead to nasty + * problems. The code is based on the Symfony ErrorHandler component. */ final class ErrorHandler { @@ -63,6 +61,8 @@ final class ErrorHandler /** * @var callable|null The previous exception handler, if any + * + * @psalm-var null|callable(\Throwable): void */ private $previousExceptionHandler; @@ -346,8 +346,8 @@ public function addExceptionHandlerListener(callable $listener): void } /** - * Handles errors by capturing them through the Raven client according to - * the configured bit field. + * Handles errors by capturing them through the client according to the + * configured bit field. * * @param int $level The level of the error raised, represented by * one of the E_* constants @@ -376,19 +376,18 @@ private function handleError(int $level, string $message, string $file, int $lin $this->invokeListeners($this->errorListeners, $errorAsException); if (null !== $this->previousErrorHandler) { - return false !== \call_user_func($this->previousErrorHandler, $level, $message, $file, $line, $errcontext); + return false !== ($this->previousErrorHandler)($level, $message, $file, $line, $errcontext); } return false; } /** - * Handles fatal errors by capturing them through the Raven client. This - * method is used as callback of a shutdown function. - * - * @param array|null $error The error details as returned by error_get_last() + * Tries to handle a fatal error if any and relay them to the listeners. + * It only tries to do this if we still have some reserved memory at + * disposal. This method is used as callback of a shutdown function. */ - private function handleFatalError(array $error = null): void + private function handleFatalError(): void { // If there is not enough memory that can be used to handle the error // do nothing @@ -397,10 +396,7 @@ private function handleFatalError(array $error = null): void } self::$reservedMemory = null; - - if (null === $error) { - $error = error_get_last(); - } + $error = error_get_last(); if (!empty($error) && $error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)) { $errorAsException = new FatalErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); @@ -434,16 +430,18 @@ private function handleException(\Throwable $exception): void try { if (null !== $previousExceptionHandler) { $previousExceptionHandler($exception); + + return; } } catch (\Throwable $previousExceptionHandlerException) { - // Do nothing, we just need to set the $previousExceptionHandlerException - // variable to the exception we just catched to compare it later - // with the original object instance + // This `catch` statement is here to forcefully override the + // $previousExceptionHandlerException variable with the exception + // we just catched } - // If the exception object instance is the same as the one catched from - // the previous exception handler, if any, give it back to the native - // PHP handler to prevent infinite circular loop + // If the instance of the exception we're handling is the same as the one + // catched from the previous exception handler then we give it back to the + // native PHP handler to prevent an infinite loop if ($exception === $previousExceptionHandlerException) { // Disable the fatal error handler or the error will be reported twice self::$reservedMemory = null; @@ -490,7 +488,7 @@ private function invokeListeners(array $listeners, \Throwable $throwable): void { foreach ($listeners as $listener) { try { - \call_user_func($listener, $throwable); + $listener($throwable); } catch (\Throwable $exception) { // Do nothing as this should be as transparent as possible } diff --git a/tests/phpt/error_handler_captures_exception_rethrown_from_previous_handler.phpt b/tests/phpt/error_handler_captures_exception_rethrown_from_previous_handler.phpt new file mode 100644 index 000000000..dda9b6e9f --- /dev/null +++ b/tests/phpt/error_handler_captures_exception_rethrown_from_previous_handler.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test that the exception being handled is captured only once even if it's +rethrown from a previous exception handler +--FILE-- +addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called (it should not have been)' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { + echo 'Exception listener called' . PHP_EOL; +}); + +throw new \Exception('foo bar'); +?> +--EXPECTF-- +Exception listener called +Custom exception handler called + +Fatal error: Uncaught Exception: foo bar in %s:%d +Stack trace: +%a diff --git a/tests/phpt/error_handler_captures_exception_thrown_from_previous_handler.phpt b/tests/phpt/error_handler_captures_exception_thrown_from_previous_handler.phpt index 602d4b834..ec11e2a10 100644 --- a/tests/phpt/error_handler_captures_exception_thrown_from_previous_handler.phpt +++ b/tests/phpt/error_handler_captures_exception_thrown_from_previous_handler.phpt @@ -1,5 +1,5 @@ --TEST-- -Test catching exceptions +Test that an exception thrown from the previous handler is captured --FILE-- addErrorHandlerListener(static function (): void { + echo 'Error listener called (it should not have been)' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called (it should not have been)' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { echo 'Exception listener called' . PHP_EOL; }); diff --git a/tests/phpt/error_handler_captures_exception_with_no_previous_handler.phpt b/tests/phpt/error_handler_captures_exception_with_no_previous_handler.phpt new file mode 100644 index 000000000..4d21c2076 --- /dev/null +++ b/tests/phpt/error_handler_captures_exception_with_no_previous_handler.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test that the handler captures the thrown exception when no previous handler is set +and that the details of the exception are printed on the screen +--FILE-- +addErrorHandlerListener(static function (): void { + echo 'Error listener called (it should not have been)' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called (it should not have been)' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { + echo 'Exception listener called' . PHP_EOL; +}); + +throw new \Exception('foo bar'); +?> +--EXPECTF-- +Exception listener called + +Fatal error: Uncaught Exception: foo bar in %s:%d +Stack trace: +%a diff --git a/tests/phpt/error_handler_captures_exception.phpt b/tests/phpt/error_handler_captures_exception_with_previous_handler.phpt similarity index 90% rename from tests/phpt/error_handler_captures_exception.phpt rename to tests/phpt/error_handler_captures_exception_with_previous_handler.phpt index 5365d6b84..5d3f68875 100644 --- a/tests/phpt/error_handler_captures_exception.phpt +++ b/tests/phpt/error_handler_captures_exception_with_previous_handler.phpt @@ -18,7 +18,7 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; set_exception_handler(static function (): void { - echo 'Custom exception handler called'; + echo 'Custom exception handler called' . PHP_EOL; }); $errorHandler = ErrorHandler::registerOnceErrorHandler(); @@ -28,7 +28,7 @@ $errorHandler->addErrorHandlerListener(static function (): void { $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); $errorHandler->addFatalErrorHandlerListener(static function (): void { - echo 'Fatal error listener called (it should not have been)'; + echo 'Fatal error listener called (it should not have been)' . PHP_EOL; }); $errorHandler = ErrorHandler::registerOnceExceptionHandler(); @@ -41,6 +41,3 @@ throw new \Exception('foo bar'); --EXPECTF-- Exception listener called Custom exception handler called -Fatal error: Uncaught Exception: foo bar in %s:%d -Stack trace: -%a diff --git a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt index 961faa654..82f2fc2b5 100644 --- a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt @@ -1,5 +1,7 @@ --TEST-- Test catching out of memory fatal error +--INI-- +memory_limit=20M --FILE-- setTransportFactory($transportFactory) - ->getClient(); - -SentrySdk::getCurrentHub()->bindClient($client); - -throw new \Exception('foo bar'); -?> ---EXPECTF-- -Transport called -Custom exception handler called -Fatal error: Uncaught Exception: foo bar in %s:%d -Stack trace: -%a diff --git a/tests/phpt/error_handler_error_listener_gets_registered_with_legacy_method.phpt b/tests/phpt/error_handler_error_listener_gets_registered_with_legacy_method.phpt new file mode 100644 index 000000000..539b50e12 --- /dev/null +++ b/tests/phpt/error_handler_error_listener_gets_registered_with_legacy_method.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test that the error listener gets registered using the deprecated method +--FILE-- + Date: Fri, 6 Mar 2020 10:24:53 +0100 Subject: [PATCH 0542/1161] Prepare changelog for 2.3.2 release --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f5986c7..6a0407444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ ## Unreleased +## 2.3.2 (2020-03-06) + - Hard-limit concurrent requests in `HttpTransport` and removed pre-init of promises (fixes "too many open files" errors) (#981) - Fix `http_proxy` option not being applied (#978) +- Fix the error handler rethrowing the captured exception when previous handler didn't (#974) ## 2.3.1 (2020-01-23) From 4bb4e540748d99ff3d33aa4f8d469de01fd306d8 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 19 Mar 2020 21:35:35 +0100 Subject: [PATCH 0543/1161] Allow passing a custom request fetcher to the RequestIntegration integration (#984) --- .php_cs.dist | 5 ++ CHANGELOG.md | 1 + composer.json | 2 +- src/Integration/RequestFetcher.php | 27 ++++++++++ src/Integration/RequestFetcherInterface.php | 19 +++++++ src/Integration/RequestIntegration.php | 13 +++-- src/Serializer/RepresentationSerializer.php | 2 + tests/Integration/RequestIntegrationTest.php | 52 +++++++++++++++++--- 8 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 src/Integration/RequestFetcher.php create mode 100644 src/Integration/RequestFetcherInterface.php diff --git a/.php_cs.dist b/.php_cs.dist index 80ed159fd..0a411e3e2 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -21,6 +21,11 @@ return PhpCsFixer\Config::create() 'phpdoc_align' => [ 'tags' => ['param', 'return', 'throws', 'type', 'var'], ], + 'phpdoc_line_span' => [ + 'const' => 'multi', + 'method' => 'multi', + 'property' => 'multi', + ], ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/CHANGELOG.md b/CHANGELOG.md index 70baddea3..8e55c5d92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Enforce a timeout for connecting to the server and for the requests instead of waiting indefinitely (#979) +- Add `RequestFetcherInterface` to allow customizing the request data attached to the logged event (#984) ## 2.3.2 (2020-03-06) diff --git a/composer.json b/composer.json index 6cbeb1fd5..b5037c19c 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "symfony/polyfill-uuid": "^1.13.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.13", + "friendsofphp/php-cs-fixer": "^2.16", "monolog/monolog": "^1.3|^2.0", "php-http/mock-client": "^1.3", "phpstan/extension-installer": "^1.0", diff --git a/src/Integration/RequestFetcher.php b/src/Integration/RequestFetcher.php new file mode 100644 index 000000000..333205b5f --- /dev/null +++ b/src/Integration/RequestFetcher.php @@ -0,0 +1,27 @@ +options = $options; + $this->requestFetcher = $requestFetcher ?? new RequestFetcher(); } /** @@ -98,7 +105,7 @@ public static function applyToEvent(self $self, Event $event, ?ServerRequestInte private function processEvent(Event $event, Options $options, ?ServerRequestInterface $request = null): void { if (null === $request) { - $request = isset($_SERVER['REQUEST_METHOD']) && \PHP_SAPI !== 'cli' ? ServerRequest::fromGlobals() : null; + $request = $this->requestFetcher->fetchRequest(); } if (null === $request) { diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index f8b9e07e2..e4b6e5c0c 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -12,6 +12,8 @@ class RepresentationSerializer extends AbstractSerializer implements Representat { /** * {@inheritdoc} + * + * @psalm-suppress InvalidReturnType */ public function representationSerialize($value) { diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 07a0b510b..cf2bfc5aa 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -11,9 +11,14 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; +use Sentry\ClientInterface; use Sentry\Event; +use Sentry\Integration\RequestFetcherInterface; use Sentry\Integration\RequestIntegration; use Sentry\Options; +use Sentry\SentrySdk; +use Sentry\State\Scope; +use function Sentry\withScope; final class RequestIntegrationTest extends TestCase { @@ -28,11 +33,7 @@ public function testConstructorThrowsDeprecationIfPassingOptions(): void } /** - * @group legacy - * * @dataProvider applyToEventWithRequestHavingIpAddressDataProvider - * - * @expectedDeprecation The "Sentry\Integration\RequestIntegration::applyToEvent" method is deprecated since version 2.1 and will be removed in 3.0. */ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $expectedValue): void { @@ -40,11 +41,28 @@ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $event->getUserContext()->setData(['foo' => 'bar']); $request = new ServerRequest('GET', new Uri('http://www.example.com/fo'), [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1']); - $integration = new RequestIntegration(new Options(['send_default_pii' => $shouldSendPii])); + $integration = new RequestIntegration(null, $this->createRequestFetcher($request)); + $integration->setupOnce(); - RequestIntegration::applyToEvent($integration, $event, $request); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + + $client->expects($this->once()) + ->method('getIntegration') + ->willReturn($integration); + + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['send_default_pii' => $shouldSendPii])); - $this->assertEquals($expectedValue, $event->getUserContext()->toArray()); + SentrySdk::getCurrentHub()->bindClient($client); + + withScope(function (Scope $scope) use ($event, $expectedValue): void { + $event = $scope->applyToEvent($event, []); + + $this->assertNotNull($event); + $this->assertEquals($expectedValue, $event->getUserContext()->toArray()); + }); } public function applyToEventWithRequestHavingIpAddressDataProvider(): array @@ -442,4 +460,24 @@ private function getStreamMock(int $size, string $content = ''): StreamInterface return $stream; } + + private function createRequestFetcher(ServerRequestInterface $request): RequestFetcherInterface + { + return new class($request) implements RequestFetcherInterface { + /** + * @var ServerRequestInterface + */ + private $request; + + public function __construct(ServerRequestInterface $request) + { + $this->request = $request; + } + + public function fetchRequest(): ServerRequestInterface + { + return $this->request; + } + }; + } } From ca1dffeb2af85a7684adf5b2e30d2ba5714bcf22 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 23 Mar 2020 10:44:19 +0100 Subject: [PATCH 0544/1161] Log internal debug and error messages to a PSR-3 compatible logger (#989) --- CHANGELOG.md | 1 + composer.json | 1 + src/Client.php | 43 ++++-- src/ClientBuilder.php | 20 ++- src/ClientBuilderInterface.php | 2 + src/HttpClient/Plugin/GzipEncoderPlugin.php | 2 + src/Integration/Handler.php | 2 + src/Monolog/Handler.php | 6 +- src/State/Hub.php | 2 +- src/State/Scope.php | 2 +- src/Transport/DefaultTransportFactory.php | 13 +- src/Transport/HttpTransport.php | 57 ++++++-- src/Util/PHPVersion.php | 6 + tests/ClientTest.php | 85 ++++++++++-- tests/Transport/HttpTransportTest.php | 140 +++++++++++++++++++- 15 files changed, 332 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e55c5d92..65ac79fc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Enforce a timeout for connecting to the server and for the requests instead of waiting indefinitely (#979) - Add `RequestFetcherInterface` to allow customizing the request data attached to the logged event (#984) +- Log internal debug and error messages to a PSR-3 compatible logger (#989) ## 2.3.2 (2020-03-06) diff --git a/composer.json b/composer.json index b5037c19c..e8ff9ef17 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", "psr/http-message-implementation": "^1.0", + "psr/log": "^1.0", "symfony/options-resolver": "^2.7|^3.0|^4.0|^5.0", "symfony/polyfill-uuid": "^1.13.1" }, diff --git a/src/Client.php b/src/Client.php index 86c07153b..09a9e77f6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -6,6 +6,8 @@ use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Sentry\Integration\Handler; use Sentry\Integration\IgnoreErrorsIntegration; use Sentry\Integration\IntegrationInterface; @@ -45,6 +47,11 @@ final class Client implements FlushableClientInterface */ private $eventFactory; + /** + * @var LoggerInterface The PSR-3 logger + */ + private $logger; + /** * @var array The stack of integrations * @@ -58,13 +65,15 @@ final class Client implements FlushableClientInterface * @param Options $options The client configuration * @param TransportInterface $transport The transport * @param EventFactoryInterface $eventFactory The factory for events + * @param LoggerInterface|null $logger The PSR-3 logger */ - public function __construct(Options $options, TransportInterface $transport, EventFactoryInterface $eventFactory) + public function __construct(Options $options, TransportInterface $transport, EventFactoryInterface $eventFactory, ?LoggerInterface $logger = null) { $this->options = $options; $this->transport = $transport; $this->eventFactory = $eventFactory; $this->integrations = Handler::setupIntegrations($options->getIntegrations()); + $this->logger = $logger ?? new NullLogger(); } /** @@ -156,33 +165,45 @@ public function flush(?int $timeout = null): PromiseInterface /** * Assembles an event and prepares it to be sent of to Sentry. * - * @param array $payload the payload that will be converted to an Event - * @param Scope|null $scope optional scope which enriches the Event + * @param array $payload The payload that will be converted to an Event + * @param Scope|null $scope Optional scope which enriches the Event * * @return Event|null returns ready to send Event, however depending on options it can be discarded */ private function prepareEvent(array $payload, ?Scope $scope = null): ?Event { - $sampleRate = $this->getOptions()->getSampleRate(); - - if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { - return null; - } - - if ($this->getOptions()->shouldAttachStacktrace() && !isset($payload['exception']) && !isset($payload['stacktrace'])) { + if ($this->options->shouldAttachStacktrace() && !isset($payload['exception']) && !isset($payload['stacktrace'])) { $event = $this->eventFactory->createWithStacktrace($payload); } else { $event = $this->eventFactory->create($payload); } + $sampleRate = $this->options->getSampleRate(); + + if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { + $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]); + + return null; + } + if (null !== $scope) { + $previousEvent = $event; $event = $scope->applyToEvent($event, $payload); if (null === $event) { + $this->logger->info('The event will be discarded because one of the event processors returned `null`.', ['event' => $previousEvent]); + return null; } } - return \call_user_func($this->options->getBeforeSendCallback(), $event); + $previousEvent = $event; + $event = ($this->options->getBeforeSendCallback())($event); + + if (null === $event) { + $this->logger->info('The event will be discarded because the "before_send" callback returned `null`.', ['event' => $previousEvent]); + } + + return $event; } } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index c3b512f6f..d4982b179 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -13,6 +13,7 @@ use Http\Message\StreamFactory as StreamFactoryInterface; use Http\Message\UriFactory as UriFactoryInterface; use Jean85\PrettyVersions; +use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; use Sentry\HttpClient\PluggableHttpClientFactory; use Sentry\Serializer\RepresentationSerializer; @@ -80,6 +81,11 @@ final class ClientBuilder implements ClientBuilderInterface */ private $representationSerializer; + /** + * @var LoggerInterface|null A PSR-3 logger to log internal errors and debug messages + */ + private $logger; + /** * @var string The SDK identifier, to be used in {@see Event} and {@see SentryAuth} */ @@ -215,6 +221,16 @@ public function setRepresentationSerializer(RepresentationSerializerInterface $r return $this; } + /** + * {@inheritdoc} + */ + public function setLogger(LoggerInterface $logger): ClientBuilderInterface + { + $this->logger = $logger; + + return $this; + } + /** * {@inheritdoc} */ @@ -260,7 +276,7 @@ public function getClient(): ClientInterface { $this->transport = $this->transport ?? $this->createTransportInstance(); - return new Client($this->options, $this->transport, $this->createEventFactory()); + return new Client($this->options, $this->transport, $this->createEventFactory(), $this->logger); } /** @@ -324,6 +340,6 @@ private function createDefaultTransportFactory(): DefaultTransportFactory $httpClientFactory = new PluggableHttpClientFactory($httpClientFactory, $this->httpClientPlugins); } - return new DefaultTransportFactory($this->messageFactory, $httpClientFactory); + return new DefaultTransportFactory($this->messageFactory, $httpClientFactory, $this->logger); } } diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 7e90a3199..010f20282 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -8,6 +8,7 @@ use Http\Client\HttpAsyncClient; use Http\Message\MessageFactory as MessageFactoryInterface; use Http\Message\UriFactory as UriFactoryInterface; +use Psr\Log\LoggerInterface; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\SerializerInterface; use Sentry\Transport\TransportFactoryInterface; @@ -19,6 +20,7 @@ * @author Stefano Arlandini * * @method self setTransportFactory(TransportFactoryInterface $transportFactory) + * @method self setLogger(LoggerInterface $logger) */ interface ClientBuilderInterface { diff --git a/src/HttpClient/Plugin/GzipEncoderPlugin.php b/src/HttpClient/Plugin/GzipEncoderPlugin.php index a79680b12..558ff9543 100644 --- a/src/HttpClient/Plugin/GzipEncoderPlugin.php +++ b/src/HttpClient/Plugin/GzipEncoderPlugin.php @@ -53,6 +53,8 @@ public function handleRequest(RequestInterface $request, callable $next, callabl $requestBody->rewind(); } + // Instead of using a stream filter we have to compress the whole request + // body in one go to work around a PHP bug. See https://github.com/getsentry/sentry-php/pull/877 $encodedBody = gzcompress($requestBody->getContents(), -1, ZLIB_ENCODING_GZIP); if (false === $encodedBody) { diff --git a/src/Integration/Handler.php b/src/Integration/Handler.php index 3b7c35fdb..afd15420f 100644 --- a/src/Integration/Handler.php +++ b/src/Integration/Handler.php @@ -7,6 +7,8 @@ /** * This class handles the state of already installed integrations. * It makes sure to call {@link IntegrationInterface::setupOnce} only once per integration. + * + * @internal since version 2.4 */ final class Handler { diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 7d62beb57..ed3c02e37 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -27,7 +27,7 @@ final class Handler extends AbstractProcessingHandler * Constructor. * * @param HubInterface $hub The hub to which errors are reported - * @param int $level The minimum logging level at which this + * @param int|string $level The minimum logging level at which this * handler will be triggered * @param bool $bubble Whether the messages that are handled can * bubble up the stack or not @@ -45,7 +45,7 @@ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bub protected function write(array $record): void { $payload = [ - 'level' => $this->getSeverityFromLevel($record['level']), + 'level' => self::getSeverityFromLevel($record['level']), 'message' => $record['message'], 'logger' => 'monolog.' . $record['channel'], ]; @@ -79,7 +79,7 @@ protected function write(array $record): void * * @param int $level The Monolog log level */ - private function getSeverityFromLevel(int $level): Severity + private static function getSeverityFromLevel(int $level): Severity { switch ($level) { case Logger::DEBUG: diff --git a/src/State/Hub.php b/src/State/Hub.php index eaf7a0ab6..2814633e4 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -182,7 +182,7 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool return false; } - $breadcrumb = \call_user_func($beforeBreadcrumbCallback, $breadcrumb); + $breadcrumb = $beforeBreadcrumbCallback($breadcrumb); if (null !== $breadcrumb) { $this->getScope()->addBreadcrumb($breadcrumb, $maxBreadcrumbs); diff --git a/src/State/Scope.php b/src/State/Scope.php index 4d1acc489..402387416 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -316,7 +316,7 @@ public function applyToEvent(Event $event, array $payload): ?Event } foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) { - $event = \call_user_func($processor, $event, $payload); + $event = $processor($event, $payload); if (null === $event) { return null; diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php index 5d42a03dd..73c45c3fb 100644 --- a/src/Transport/DefaultTransportFactory.php +++ b/src/Transport/DefaultTransportFactory.php @@ -5,6 +5,7 @@ namespace Sentry\Transport; use Http\Message\MessageFactory as MessageFactoryInterface; +use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactoryInterface; use Sentry\Options; @@ -24,16 +25,23 @@ final class DefaultTransportFactory implements TransportFactoryInterface */ private $httpClientFactory; + /** + * @var LoggerInterface|null A PSR-3 logger + */ + private $logger; + /** * Constructor. * * @param MessageFactoryInterface $messageFactory The PSR-7 message factory * @param HttpClientFactoryInterface $httpClientFactory The HTTP client factory + * @param LoggerInterface|null $logger A PSR-3 logger */ - public function __construct(MessageFactoryInterface $messageFactory, HttpClientFactoryInterface $httpClientFactory) + public function __construct(MessageFactoryInterface $messageFactory, HttpClientFactoryInterface $httpClientFactory, ?LoggerInterface $logger = null) { $this->messageFactory = $messageFactory; $this->httpClientFactory = $httpClientFactory; + $this->logger = $logger; } /** @@ -50,7 +58,8 @@ public function create(Options $options): TransportInterface $this->httpClientFactory->create($options), $this->messageFactory, true, - false + false, + $this->logger ); } } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 9e0619f99..706848814 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -9,7 +9,8 @@ use GuzzleHttp\Promise\PromiseInterface; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Message\RequestFactory as RequestFactoryInterface; -use Psr\Http\Message\RequestInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Sentry\Event; use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; @@ -39,7 +40,9 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterf private $requestFactory; /** - * @var RequestInterface[] The list of pending requests + * @var array> The list of pending requests + * + * @psalm-var array */ private $pendingRequests = []; @@ -49,6 +52,11 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterf */ private $delaySendingUntilShutdown = false; + /** + * @var LoggerInterface A PSR-3 logger + */ + private $logger; + /** * Constructor. * @@ -63,13 +71,15 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterf * used relying on the deprecated behavior * of delaying the sending of the events * until the shutdown of the application + * @param LoggerInterface|null $logger An instance of a PSR-3 logger */ public function __construct( Options $config, HttpAsyncClientInterface $httpClient, RequestFactoryInterface $requestFactory, bool $delaySendingUntilShutdown = true, - bool $triggerDeprecation = true + bool $triggerDeprecation = true, + ?LoggerInterface $logger = null ) { if ($delaySendingUntilShutdown && $triggerDeprecation) { @trigger_error(sprintf('Delaying the sending of the events using the "%s" class is deprecated since version 2.2 and will not work in 3.0.', __CLASS__), E_USER_DEPRECATED); @@ -79,6 +89,7 @@ public function __construct( $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; $this->delaySendingUntilShutdown = $delaySendingUntilShutdown; + $this->logger = $logger ?? new NullLogger(); // By calling the cleanupPendingRequests function from a shutdown function // registered inside another shutdown function we can be confident that it @@ -114,11 +125,19 @@ public function send(Event $event): ?string ); if ($this->delaySendingUntilShutdown) { - $this->pendingRequests[] = $request; + $this->pendingRequests[] = [$request, $event]; } else { try { $this->httpClient->sendAsyncRequest($request)->wait(); } catch (\Throwable $exception) { + $this->logger->error( + sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), + [ + 'exception' => $exception, + 'event' => $event, + ] + ); + return null; } } @@ -146,18 +165,32 @@ public function close(?int $timeout = null): PromiseInterface */ private function cleanupPendingRequests(): void { + $requestGenerator = function (): \Generator { + foreach ($this->pendingRequests as $key => $data) { + yield $key => $this->httpClient->sendAsyncRequest($data[0]); + } + }; + try { - $requestGenerator = function (): \Generator { - foreach ($this->pendingRequests as $key => $request) { - yield $key => $this->httpClient->sendAsyncRequest($request); - } - }; + $eachPromise = new EachPromise($requestGenerator(), [ + 'concurrency' => 30, + 'rejected' => function (\Throwable $exception, int $requestIndex): void { + $this->logger->error( + sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), + [ + 'exception' => $exception, + 'event' => $this->pendingRequests[$requestIndex][1], + ] + ); + }, + ]); - $eachPromise = new EachPromise($requestGenerator(), ['concurrency' => 30]); $eachPromise->promise()->wait(); } catch (\Throwable $exception) { - // Do nothing because we don't want to break applications while - // trying to send events + $this->logger->error( + sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), + ['exception' => $exception] + ); } $this->pendingRequests = []; diff --git a/src/Util/PHPVersion.php b/src/Util/PHPVersion.php index 708d98369..22b32ff0d 100644 --- a/src/Util/PHPVersion.php +++ b/src/Util/PHPVersion.php @@ -4,6 +4,12 @@ namespace Sentry\Util; +/** + * This class is an helper utility to parse the version of PHP and convert it + * to a normalized form. + * + * @internal since version 2.4 + */ final class PHPVersion { private const VERSION_PARSING_REGEX = '/^(?\d\.\d\.\d{1,2})(?-(beta|rc)-?(\d+)?(-dev)?)?/i'; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 3427eaa79..41b630d8b 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -4,8 +4,10 @@ namespace Sentry\Tests; +use PHPUnit\Framework\MockObject\Matcher\Invocation; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; @@ -13,6 +15,7 @@ use Sentry\Serializer\Serializer; use Sentry\Severity; use Sentry\Stacktrace; +use Sentry\State\Scope; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; @@ -309,31 +312,91 @@ public function testSendChecksBeforeSendOption(): void } /** - * @dataProvider sampleRateAbsoluteDataProvider + * @dataProvider processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDataProvider */ - public function testSampleRateAbsolute(float $sampleRate): void + public function testProcessEventDiscardsEventWhenItIsSampledDueToSampleRateOption(float $sampleRate, Invocation $transportCallInvocationMatcher, Invocation $loggerCallInvocationMatcher): void { + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); - $transport->expects(0 == $sampleRate ? $this->never() : $this->exactly(10)) + $transport->expects($transportCallInvocationMatcher) ->method('send'); - $transportFactory = $this->createTransportFactory($transport); + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($loggerCallInvocationMatcher) + ->method('info') + ->with('The event will be discarded because it has been sampled.', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); - $client = (new ClientBuilder(new Options(['sample_rate' => $sampleRate]))) - ->setTransportFactory($transportFactory) + $client = ClientBuilder::create(['sample_rate' => $sampleRate]) + ->setTransportFactory($this->createTransportFactory($transport)) + ->setLogger($logger) ->getClient(); for ($i = 0; $i < 10; ++$i) { - $client->captureMessage('foobar'); + $client->captureMessage('foo'); } } - public function sampleRateAbsoluteDataProvider(): array + public function processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDataProvider(): \Generator { - return [ - 'sample rate 0' => [0], - 'sample rate 1' => [1], + yield [ + 0, + $this->never(), + $this->exactly(10), + ]; + + yield [ + 1, + $this->exactly(10), + $this->never(), + ]; + } + + public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull(): void + { + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because the "before_send" callback returned `null`.', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'before_send' => static function () { + return null; + }, ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureMessage('foo'); + } + + public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): void + { + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because one of the event processors returned `null`.', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $client = ClientBuilder::create([]) + ->setLogger($logger) + ->getClient(); + + $scope = new Scope(); + $scope->addEventProcessor(static function () { + return null; + }); + + $client->captureMessage('foo', Severity::debug(), $scope); } /** diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 224f63ae3..9f8d3df99 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -10,6 +10,7 @@ use Http\Promise\RejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; use Sentry\Event; use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; @@ -26,14 +27,18 @@ public function testSendDelaysExecutionUntilShutdown(): void { $promise = new FulfilledPromise('foo'); - /** @var HttpAsyncClient|MockObject $httpClient */ + /** @var HttpAsyncClient&MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); $httpClient->expects($this->once()) ->method('sendAsyncRequest') ->willReturn($promise); - $config = new Options(['dsn' => 'http://public@example.com/sentry/1']); - $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find(), true, false); + $transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/sentry/1']), + $httpClient, + MessageFactoryDiscovery::find(), + true + ); $this->assertAttributeEmpty('pendingRequests', $transport); @@ -50,14 +55,18 @@ public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoI { $promise = new RejectedPromise(new \Exception()); - /** @var HttpAsyncClient|MockObject $httpClient */ + /** @var HttpAsyncClient&MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); $httpClient->expects($this->once()) ->method('sendAsyncRequest') ->willReturn($promise); - $config = new Options(['dsn' => 'http://public@example.com/sentry/1']); - $transport = new HttpTransport($config, $httpClient, MessageFactoryDiscovery::find(), false, false); + $transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/sentry/1']), + $httpClient, + MessageFactoryDiscovery::find(), + false + ); $transport->send(new Event()); @@ -70,8 +79,125 @@ public function testSendThrowsOnMissingProjectIdCredential(): void /** @var HttpAsyncClient&MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClient::class); - $transport = new HttpTransport(new Options(), $httpClient, MessageFactoryDiscovery::find(), true, false); + $transport = new HttpTransport( + new Options(), + $httpClient, + MessageFactoryDiscovery::find(), + false + ); + + $transport->send(new Event()); + } + + public function testSendLogsErrorMessageIfSendingFailed(): void + { + $exception = new \Exception('foo'); + $event = new Event(); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('error') + ->with('Failed to send the event to Sentry. Reason: "foo".', ['exception' => $exception, 'event' => $event]); + + /** @var HttpAsyncClient&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willReturn(new RejectedPromise($exception)); + + $transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/sentry/1']), + $httpClient, + MessageFactoryDiscovery::find(), + false, + true, + $logger + ); + + $transport->send($event); + } + + /** + * @group legacy + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ + public function testCloseLogsErrorMessageIfSendingFailed(): void + { + $exception = new \Exception('foo'); + $event1 = new Event(); + $event2 = new Event(); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->exactly(2)) + ->method('error') + ->withConsecutive([ + 'Failed to send the event to Sentry. Reason: "foo".', + ['exception' => $exception, 'event' => $event1], + ], + [ + 'Failed to send the event to Sentry. Reason: "foo".', + ['exception' => $exception, 'event' => $event2], + ]); + + /** @var HttpAsyncClient&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $httpClient->expects($this->exactly(2)) + ->method('sendAsyncRequest') + ->willReturnOnConsecutiveCalls( + new RejectedPromise($exception), + new RejectedPromise($exception) + ); + + $transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/sentry/1']), + $httpClient, + MessageFactoryDiscovery::find(), + true, + true, + $logger + ); + + // Send multiple events to assert that they all gets the chance of + // being sent regardless of which fails + $transport->send($event1); + $transport->send($event2); + $transport->close(); + } + + /** + * @group legacy + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ + public function testCloseLogsErrorMessageIfExceptionIsThrownWhileProcessingTheHttpRequest(): void + { + $exception = new \Exception('foo'); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('error') + ->with('Failed to send the event to Sentry. Reason: "foo".', ['exception' => $exception]); + + /** @var HttpAsyncClient&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClient::class); + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willThrowException($exception); + + $transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/sentry/1']), + $httpClient, + MessageFactoryDiscovery::find(), + true, + true, + $logger + ); $transport->send(new Event()); + $transport->close(); } } From 498a41cc1dbf3c403c10a098d8994af6848f3685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20P=C3=A9rez=20Batanero?= Date: Tue, 24 Mar 2020 14:01:00 +0100 Subject: [PATCH 0545/1161] Add support for iterables in the serializer (#991) --- CHANGELOG.md | 1 + src/Serializer/AbstractSerializer.php | 2 +- tests/Serializer/SerializerTest.php | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ac79fc8..be1c8f6bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Enforce a timeout for connecting to the server and for the requests instead of waiting indefinitely (#979) - Add `RequestFetcherInterface` to allow customizing the request data attached to the logged event (#984) - Log internal debug and error messages to a PSR-3 compatible logger (#989) +- Make `AbstractSerializer` to accept `Traversable` values using `is_iterable` instead of `is_array` (#991) ## 2.3.2 (2020-03-06) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index fe7af7ec1..edc394249 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -103,7 +103,7 @@ protected function serializeRecursively($value, int $_depth = 0) return $this->serializeCallableWithoutTypeHint($value); } - if (\is_array($value)) { + if (is_iterable($value)) { $serializedArray = []; foreach ($value as $k => $v) { diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index 228a854f7..bbf3bf702 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -27,6 +27,24 @@ public function testArraysAreArrays(bool $serializeAllObjects): void $this->assertSame([1, 2, 3], $result); } + /** + * @dataProvider serializeAllObjectsDataProvider + */ + public function testTraversablesAreArrays(bool $serializeAllObjects): void + { + $serializer = $this->createSerializer(); + + if ($serializeAllObjects) { + $serializer->setSerializeAllObjects(true); + } + + $content = [1, 2, 3]; + $traversable = new \ArrayIterator($content); + $result = $this->invokeSerialization($serializer, $traversable); + + $this->assertSame([1, 2, 3], $result); + } + /** * @dataProvider serializeAllObjectsDataProvider */ From 7219958c49e066e058ff426eef70e9dc64a634d9 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 25 Mar 2020 10:09:12 +0100 Subject: [PATCH 0546/1161] Refactor the ModulesIntegration integration and improve its unit tests (#990) --- CHANGELOG.md | 1 + phpstan.neon | 3 + src/Integration/IgnoreErrorsIntegration.php | 5 +- src/Integration/ModulesIntegration.php | 23 ++++++-- tests/Integration/ModulesIntegrationTest.php | 62 +++++++++++++++++--- 5 files changed, 79 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be1c8f6bf..96aec3f0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add `RequestFetcherInterface` to allow customizing the request data attached to the logged event (#984) - Log internal debug and error messages to a PSR-3 compatible logger (#989) - Make `AbstractSerializer` to accept `Traversable` values using `is_iterable` instead of `is_array` (#991) +- Refactor the `ModulesIntegration` integration to improve its code and its tests (#990) ## 2.3.2 (2020-03-06) diff --git a/phpstan.neon b/phpstan.neon index d6e3e33d4..b416bc0f6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -35,6 +35,9 @@ parameters: - message: '/^Call to an undefined method Sentry\\Integration\\IntegrationInterface::shouldDropEvent\(\)\.$/' path: src/Integration/IgnoreErrorsIntegration.php + - + message: '/^Call to an undefined method Sentry\\Integration\\IntegrationInterface::processEvent\(\)\.$/' + path: src/Integration/ModulesIntegration.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index d83dbe8a3..497411817 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -49,9 +49,8 @@ public function __construct(array $options = []) */ public function setupOnce(): void { - Scope::addGlobalEventProcessor(function (Event $event): ?Event { - $currentHub = SentrySdk::getCurrentHub(); - $integration = $currentHub->getIntegration(self::class); + Scope::addGlobalEventProcessor(static function (Event $event): ?Event { + $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); if (null !== $integration && $integration->shouldDropEvent($event, $integration->options)) { return null; diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index bda5a15c0..ecf20601d 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -17,7 +17,7 @@ final class ModulesIntegration implements IntegrationInterface { /** - * @var array The list of installed vendors + * @var array The list of installed vendors */ private static $loadedModules = []; @@ -26,13 +26,13 @@ final class ModulesIntegration implements IntegrationInterface */ public function setupOnce(): void { - Scope::addGlobalEventProcessor(function (Event $event) { + Scope::addGlobalEventProcessor(static function (Event $event): Event { $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); // The integration could be bound to a client that is not the one // attached to the current hub. If this is the case, bail out - if ($integration instanceof self) { - self::applyToEvent($integration, $event); + if (null !== $integration) { + $integration->processEvent($event); } return $event; @@ -44,8 +44,23 @@ public function setupOnce(): void * * @param self $self The instance of this integration * @param Event $event The event that will be enriched with the modules + * + * @deprecated since version 2.4, to be removed in 3.0 */ public static function applyToEvent(self $self, Event $event): void + { + @trigger_error(sprintf('The "%s" method is deprecated since version 2.4 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); + + $self->processEvent($event); + } + + /** + * Gathers information about the versions of the installed dependencies of + * the application and sets them on the event. + * + * @param Event $event The event + */ + private function processEvent(Event $event): void { if (empty(self::$loadedModules)) { foreach (Versions::VERSIONS as $package => $rawVersion) { diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index 7f189dea8..6f359b9f3 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -4,24 +4,70 @@ namespace Sentry\Tests\Integration; -use Jean85\PrettyVersions; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Sentry\ClientInterface; use Sentry\Event; use Sentry\Integration\ModulesIntegration; +use Sentry\SentrySdk; +use Sentry\State\Scope; +use function Sentry\withScope; final class ModulesIntegrationTest extends TestCase { - public function testInvoke(): void + /** + * @dataProvider invokeDataProvider + */ + public function testInvoke(bool $isIntegrationEnabled, bool $expectedEmptyModules): void { - $event = new Event(); $integration = new ModulesIntegration(); + $integration->setupOnce(); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getIntegration') + ->willReturn($isIntegrationEnabled ? $integration : null); + + SentrySdk::getCurrentHub()->bindClient($client); + + withScope(function (Scope $scope) use ($expectedEmptyModules): void { + $event = $scope->applyToEvent(new Event(), []); - ModulesIntegration::applyToEvent($integration, $event); + $this->assertNotNull($event); - $modules = $event->getModules(); + if ($expectedEmptyModules) { + $this->assertEmpty($event->getModules()); + } else { + $this->assertNotEmpty($event->getModules()); + } + }); + } + + public function invokeDataProvider(): \Generator + { + yield [ + false, + true, + ]; + + yield [ + true, + false, + ]; + } + + /** + * @group legacy + * + * @expectedDeprecationMessage The "Sentry\Integration\ModulesIntegration::applyToEvent" method is deprecated since version 2.4 and will be removed in 3.0. + */ + public function testApplyToEvent(): void + { + $event = new Event(); + $integration = new ModulesIntegration(); + $integration->applyToEvent($integration, $event); - $this->assertArrayHasKey('sentry/sentry', $modules, 'Root project missing'); - $this->assertArrayHasKey('ocramius/package-versions', $modules, 'Indirect dependency missing'); - $this->assertEquals(PrettyVersions::getVersion('sentry/sentry'), $modules['sentry/sentry']); + $this->assertNotEmpty($event->getModules()); } } From cc510f00537d882f566d7626cbd87c3bf962c32d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 7 Apr 2020 12:13:36 +0200 Subject: [PATCH 0547/1161] Stop using deprecated assertAttribute* methods of PHPUnit (#780) --- tests/ClientBuilderTest.php | 178 +++++++++--------- tests/Context/AbstractContextTest.php | 3 +- tests/Context/ContextTest.php | 55 +++--- tests/Transport/HttpTransportTest.php | 36 ---- tests/Transport/SpoolTransportTest.php | 22 ++- .../http_transport_send_event_delayed.phpt | 59 ++++++ tests/phpt/spool_drain.phpt | 68 ------- 7 files changed, 188 insertions(+), 233 deletions(-) create mode 100644 tests/phpt/http_transport_send_event_delayed.phpt delete mode 100644 tests/phpt/spool_drain.phpt diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 3c8eb5ce6..f9be538b9 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -4,12 +4,14 @@ namespace Sentry\Tests; -use Http\Client\Common\Plugin; -use Http\Client\Common\PluginClient; -use Http\Client\HttpAsyncClient; -use Http\Message\MessageFactory; -use Http\Message\UriFactory; -use Http\Promise\Promise; +use Http\Client\Common\Plugin as PluginInterface; +use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; +use Http\Discovery\MessageFactoryDiscovery; +use Http\Discovery\UriFactoryDiscovery; +use Http\Message\MessageFactory as MessageFactoryInterface; +use Http\Message\UriFactory as UriFactoryInterface; +use Http\Promise\FulfilledPromise; +use Http\Promise\Promise as PromiseInterface; use Jean85\PrettyVersions; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -17,6 +19,7 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\FlushableClientInterface; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -60,13 +63,17 @@ public function testNullTransportIsUsedWhenNoServerIsConfigured(): void */ public function testSetUriFactory(): void { - /** @var UriFactory|MockObject $uriFactory */ - $uriFactory = $this->createMock(UriFactory::class); + /** @var UriFactoryInterface&MockObject $uriFactory */ + $uriFactory = $this->createMock(UriFactoryInterface::class); + $uriFactory->expects($this->once()) + ->method('createUri') + ->willReturn(UriFactoryDiscovery::find()->createUri('http://www.example.com')); - $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); - $clientBuilder->setUriFactory($uriFactory); + $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) + ->setUriFactory($uriFactory) + ->getClient(); - $this->assertAttributeSame($uriFactory, 'uriFactory', $clientBuilder); + $client->captureMessage('foo'); } /** @@ -76,17 +83,17 @@ public function testSetUriFactory(): void */ public function testSetMessageFactory(): void { - /** @var MessageFactory|MockObject $messageFactory */ - $messageFactory = $this->createMock(MessageFactory::class); - - $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); - $clientBuilder->setMessageFactory($messageFactory); + /** @var MessageFactoryInterface&MockObject $messageFactory */ + $messageFactory = $this->createMock(MessageFactoryInterface::class); + $messageFactory->expects($this->once()) + ->method('createRequest') + ->willReturn(MessageFactoryDiscovery::find()->createRequest('POST', 'http://www.example.com')); - $this->assertAttributeSame($messageFactory, 'messageFactory', $clientBuilder); - - $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); + $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) + ->setMessageFactory($messageFactory) + ->getClient(); - $this->assertAttributeSame($messageFactory, 'requestFactory', $transport); + $client->captureMessage('foo'); } /** @@ -96,14 +103,17 @@ public function testSetMessageFactory(): void */ public function testSetTransport(): void { - /** @var TransportInterface|MockObject $transport */ + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->willReturn('ddb4a0b9ab1941bf92bd2520063663e3'); - $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); - $clientBuilder->setTransport($transport); + $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) + ->setTransport($transport) + ->getClient(); - $this->assertAttributeSame($transport, 'transport', $clientBuilder); - $this->assertAttributeSame($transport, 'transport', $clientBuilder->getClient()); + $this->assertSame('ddb4a0b9ab1941bf92bd2520063663e3', $client->captureMessage('foo')); } /** @@ -113,17 +123,19 @@ public function testSetTransport(): void */ public function testSetHttpClient(): void { - /** @var HttpAsyncClient|MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); - - $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); - $clientBuilder->setHttpClient($httpClient); - - $this->assertAttributeSame($httpClient, 'httpClient', $clientBuilder); - - $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); - - $this->assertAttributeSame($httpClient, 'client', $this->getObjectAttribute($transport, 'httpClient')); + /** @var HttpAsyncClientInterface&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClientInterface::class); + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willReturn(new FulfilledPromise(true)); + + /** @var FlushableClientInterface $client */ + $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) + ->setHttpClient($httpClient) + ->getClient(); + + $client->captureMessage('foo'); + $client->flush(); } /** @@ -133,16 +145,19 @@ public function testSetHttpClient(): void */ public function testAddHttpClientPlugin(): void { - /** @var Plugin|MockObject $plugin */ - $plugin = $this->createMock(Plugin::class); - - $clientBuilder = new ClientBuilder(); - $clientBuilder->addHttpClientPlugin($plugin); - - $plugins = $this->getObjectAttribute($clientBuilder, 'httpClientPlugins'); - - $this->assertCount(1, $plugins); - $this->assertSame($plugin, $plugins[0]); + /** @var PluginInterface&MockObject $plugin */ + $plugin = $this->createMock(PluginInterface::class); + $plugin->expects($this->once()) + ->method('handleRequest') + ->willReturn(new FulfilledPromise(true)); + + /** @var FlushableClientInterface $client */ + $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) + ->addHttpClientPlugin($plugin) + ->getClient(); + + $client->captureMessage('foo'); + $client->flush(); } /** @@ -153,36 +168,30 @@ public function testAddHttpClientPlugin(): void */ public function testRemoveHttpClientPlugin(): void { - $plugin = new PluginStub1(); - $plugin2 = new PluginStub2(); - - $clientBuilder = new ClientBuilder(); - $clientBuilder->addHttpClientPlugin($plugin); - $clientBuilder->addHttpClientPlugin($plugin); - $clientBuilder->addHttpClientPlugin($plugin2); - - $this->assertAttributeCount(3, 'httpClientPlugins', $clientBuilder); - - $clientBuilder->removeHttpClientPlugin(PluginStub1::class); - - $plugins = $this->getObjectAttribute($clientBuilder, 'httpClientPlugins'); - - $this->assertCount(1, $plugins); - $this->assertSame($plugin2, reset($plugins)); - } - - public function testGetClient(): void - { - $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); - $client = $clientBuilder->getClient(); - - $this->assertInstanceOf(Client::class, $client); - $this->assertAttributeInstanceOf(HttpTransport::class, 'transport', $client); - - $transport = $this->getObjectAttribute($client, 'transport'); - - $this->assertAttributeSame($this->getObjectAttribute($clientBuilder, 'messageFactory'), 'requestFactory', $transport); - $this->assertAttributeInstanceOf(PluginClient::class, 'httpClient', $transport); + $plugin = new class() implements PluginInterface { + public function handleRequest(RequestInterface $request, callable $next, callable $first): PromiseInterface + { + return new FulfilledPromise(true); + } + }; + + $plugin2 = new class() implements PluginInterface { + public function handleRequest(RequestInterface $request, callable $next, callable $first): PromiseInterface + { + return new FulfilledPromise(true); + } + }; + + /** @var FlushableClientInterface $client */ + $client = ClientBuilder::create() + ->addHttpClientPlugin($plugin) + ->addHttpClientPlugin($plugin) + ->addHttpClientPlugin($plugin2) + ->removeHttpClientPlugin(\get_class($plugin2)) + ->getClient(); + + $client->captureMessage('foo'); + $client->flush(); } /** @@ -194,8 +203,7 @@ public function testIntegrationsAreAddedToClientCorrectly(bool $defaultIntegrati $options->setDefaultIntegrations($defaultIntegrations); $options->setIntegrations($integrations); - $clientBuilder = new ClientBuilder($options); - $client = $clientBuilder->getClient(); + $client = (new ClientBuilder($options))->getClient(); $actualIntegrationsClassNames = array_map('\get_class', $client->getOptions()->getIntegrations()); @@ -299,17 +307,3 @@ public function setupOnce(): void { } } - -final class PluginStub1 implements Plugin -{ - public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise - { - } -} - -final class PluginStub2 implements Plugin -{ - public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise - { - } -} diff --git a/tests/Context/AbstractContextTest.php b/tests/Context/AbstractContextTest.php index 2237f9012..8e1b82fe7 100644 --- a/tests/Context/AbstractContextTest.php +++ b/tests/Context/AbstractContextTest.php @@ -100,7 +100,8 @@ public function testOffsetSet(string $key, $value, ?string $expectedExceptionCla $context = $this->createContext(); $context[$key] = $value; - $this->assertArraySubset([$key => $value], $context->toArray()); + $this->assertArrayHasKey($key, $context); + $this->assertSame($value, $context[$key]); } /** diff --git a/tests/Context/ContextTest.php b/tests/Context/ContextTest.php index 79085e214..76a418776 100644 --- a/tests/Context/ContextTest.php +++ b/tests/Context/ContextTest.php @@ -9,14 +9,14 @@ class ContextTest extends TestCase { - public function testConstructor() + public function testConstructor(): void { $context = new Context(['foo' => 'bar']); - $this->assertAttributeEquals(['foo' => 'bar'], 'data', $context); + $this->assertSame(['foo' => 'bar'], $context->toArray()); } - public function testMerge() + public function testMerge(): void { $context = new Context([ 'foo' => 'bar', @@ -27,45 +27,45 @@ public function testMerge() $context->merge(['bar' => ['barfoo' => 'foobar']], true); - $this->assertAttributeEquals(['foo' => 'bar', 'bar' => ['foobar' => 'barfoo', 'barfoo' => 'foobar']], 'data', $context); + $this->assertSame(['foo' => 'bar', 'bar' => ['foobar' => 'barfoo', 'barfoo' => 'foobar']], $context->toArray()); $context->merge(['bar' => 'foo']); - $this->assertAttributeEquals(['foo' => 'bar', 'bar' => 'foo'], 'data', $context); + $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $context->toArray()); } - public function testSetData() + public function testSetData(): void { $context = new Context(['foo' => 'bar']); $context->setData(['bar' => 'foo']); - $this->assertAttributeEquals(['foo' => 'bar', 'bar' => 'foo'], 'data', $context); + $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $context->toArray()); $context->setData(['foo' => ['bar' => 'baz']]); - $this->assertAttributeEquals(['foo' => ['bar' => 'baz'], 'bar' => 'foo'], 'data', $context); + $this->assertSame(['foo' => ['bar' => 'baz'], 'bar' => 'foo'], $context->toArray()); } - public function testReplaceData() + public function testReplaceData(): void { $context = new Context(['foo' => 'bar']); $context->replaceData(['bar' => 'foo']); - $this->assertAttributeEquals(['bar' => 'foo'], 'data', $context); + $this->assertSame(['bar' => 'foo'], $context->toArray()); } - public function testClear() + public function testClear(): void { $context = new Context(['foo' => 'bar']); - $this->assertAttributeEquals(['foo' => 'bar'], 'data', $context); + $this->assertSame(['foo' => 'bar'], $context->toArray()); $context->clear(); - $this->assertAttributeEmpty('data', $context); + $this->assertSame([], $context->toArray()); } - public function testIsEmpty() + public function testIsEmpty(): void { $context = new Context(); @@ -76,51 +76,40 @@ public function testIsEmpty() $this->assertFalse($context->isEmpty()); } - public function testToArray() + public function testJsonSerialize(): void { $context = new Context(['foo' => 'bar']); - $this->assertEquals(['foo' => 'bar'], $context->toArray()); + $this->assertSame('{"foo":"bar"}', json_encode($context)); } - public function testJsonSerialize() - { - $context = new Context(['foo' => 'bar']); - - $this->assertEquals('{"foo":"bar"}', json_encode($context)); - } - - public function testArrayLikeBehaviour() + public function testArrayLikeBehaviour(): void { $context = new Context(); - $this->assertAttributeEquals([], 'data', $context); - $this->assertArrayNotHasKey('foo', $context); - // Accessing a key that does not exists in the data object should behave // like accessing a non-existent key of an array @$context['foo']; $error = error_get_last(); - $this->assertInternalType('array', $error); - $this->assertEquals('Undefined index: foo', $error['message']); + $this->assertIsArray($error); + $this->assertSame('Undefined index: foo', $error['message']); $context['foo'] = 'bar'; - $this->assertAttributeEquals(['foo' => 'bar'], 'data', $context); $this->assertTrue(isset($context['foo'])); - $this->assertEquals('bar', $context['foo']); + $this->assertSame('bar', $context['foo']); unset($context['foo']); $this->assertArrayNotHasKey('foo', $context); } - public function testGetIterator() + public function testGetIterator(): void { $context = new Context(['foo' => 'bar', 'bar' => 'foo']); - $this->assertEquals($context->toArray(), iterator_to_array($context)); + $this->assertSame($context->toArray(), iterator_to_array($context)); } } diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 9f8d3df99..f5a8505ed 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -6,7 +6,6 @@ use Http\Client\HttpAsyncClient; use Http\Discovery\MessageFactoryDiscovery; -use Http\Promise\FulfilledPromise; use Http\Promise\RejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -18,39 +17,6 @@ final class HttpTransportTest extends TestCase { - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ - public function testSendDelaysExecutionUntilShutdown(): void - { - $promise = new FulfilledPromise('foo'); - - /** @var HttpAsyncClient&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); - $httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn($promise); - - $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $httpClient, - MessageFactoryDiscovery::find(), - true - ); - - $this->assertAttributeEmpty('pendingRequests', $transport); - - $transport->send(new Event()); - - $this->assertAttributeNotEmpty('pendingRequests', $transport); - - $transport->close(); - - $this->assertAttributeEmpty('pendingRequests', $transport); - } - public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoIt(): void { $promise = new RejectedPromise(new \Exception()); @@ -69,8 +35,6 @@ public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoI ); $transport->send(new Event()); - - $this->assertAttributeEmpty('pendingRequests', $transport); } public function testSendThrowsOnMissingProjectIdCredential(): void diff --git a/tests/Transport/SpoolTransportTest.php b/tests/Transport/SpoolTransportTest.php index 11b5e808d..501eefa61 100644 --- a/tests/Transport/SpoolTransportTest.php +++ b/tests/Transport/SpoolTransportTest.php @@ -33,14 +33,30 @@ public function testGetSpool(): void $this->assertSame($this->spool, $this->transport->getSpool()); } - public function testSend(): void + /** + * @dataProvider sendDataProvider + */ + public function testSend(bool $isSendingSuccessful): void { $event = new Event(); $this->spool->expects($this->once()) ->method('queueEvent') - ->with($event); + ->with($event) + ->willReturn($isSendingSuccessful); + + $eventId = $this->transport->send($event); - $this->transport->send($event); + if ($isSendingSuccessful) { + $this->assertSame($event->getId(), $eventId); + } else { + $this->assertNull($eventId); + } + } + + public function sendDataProvider(): \Generator + { + yield [true]; + yield [false]; } } diff --git a/tests/phpt/http_transport_send_event_delayed.phpt b/tests/phpt/http_transport_send_event_delayed.phpt new file mode 100644 index 000000000..b64a44028 --- /dev/null +++ b/tests/phpt/http_transport_send_event_delayed.phpt @@ -0,0 +1,59 @@ +--TEST-- +Test that the HttpTransport transport delays the event sending until the shutdown +--FILE-- +httpClientInvokationCount = &$httpClientInvokationCount; + } + + public function sendAsyncRequest(RequestInterface $request) + { + ++$this->httpClientInvokationCount; + + return new RejectedPromise(new \Exception()); + } +}; + +$transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/sentry/1']), + $httpClient, + MessageFactoryDiscovery::find() +); + +$transport->send(new Event()); + +var_dump($httpClientInvokationCount); + +register_shutdown_function('register_shutdown_function', static function () use (&$httpClientInvokationCount): void { + var_dump($httpClientInvokationCount); +}); +?> +--EXPECT-- +int(0) +int(1) diff --git a/tests/phpt/spool_drain.phpt b/tests/phpt/spool_drain.phpt deleted file mode 100644 index 882361f9f..000000000 --- a/tests/phpt/spool_drain.phpt +++ /dev/null @@ -1,68 +0,0 @@ ---TEST-- -Test emptying spool transport ---FILE-- -spool = $spool; - } - - public function create(Options $options): TransportInterface - { - return new SpoolTransport($this->spool); - } -}; - -$client = ClientBuilder::create() - ->setTransportFactory($transportFactory) - ->getClient(); - -SentrySdk::getCurrentHub()->bindClient($client); - -register_shutdown_function('register_shutdown_function', static function () use ($spool, $nullTransport): void { - Assert::assertAttributeCount(1, 'events', $spool); - - $spool->flushQueue($nullTransport); - - Assert::assertAttributeCount(0, 'events', $spool); - - echo 'Shutdown function called'; -}); - -\Foo\Bar::baz(); -?> ---EXPECTF-- -Fatal error: Uncaught Error: Class '%s' not found in %s:%d -Stack trace: -%a -Shutdown function called From f7645df23d00a1102db3f8ca5de997763e599a21 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 7 Apr 2020 12:53:26 +0200 Subject: [PATCH 0548/1161] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5705a3090..e5f01a1e1 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ The following integrations are available and maintained by members of the Sentry ## Contributing -Dependencies are managed through composer: +Dependencies are managed through `composer`: ``` $ composer install From b464c3430d4fc7e0cd513362e227a0079fbcf82d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 7 Apr 2020 19:13:39 +0200 Subject: [PATCH 0549/1161] Extract all the logic related to the parsing and validation of the DSN to a value object (#995) --- CHANGELOG.md | 1 + phpstan.neon | 27 ++ src/Dsn.php | 228 ++++++++++++++ .../MissingProjectIdCredentialException.php | 4 + .../MissingPublicKeyCredentialException.php | 4 + .../Authentication/SentryAuthentication.php | 26 +- src/HttpClient/HttpClientFactory.php | 4 +- src/HttpClient/PluggableHttpClientFactory.php | 2 + src/Options.php | 154 +++++---- src/Severity.php | 2 +- src/Transport/DefaultTransportFactory.php | 2 +- src/Transport/HttpTransport.php | 20 +- src/Transport/TransportInterface.php | 3 - tests/ClientBuilderTest.php | 22 -- tests/DsnTest.php | 233 ++++++++++++++ ...issingProjectIdCredentialExceptionTest.php | 4 + ...issingPublicKeyCredentialExceptionTest.php | 4 + .../SentryAuthenticationTest.php | 73 ++--- tests/HttpClient/HttpClientFactoryTest.php | 6 +- .../PluggableHttpClientFactoryTest.php | 3 + tests/OptionsTest.php | 295 ++++++++++-------- tests/Transport/HttpTransportTest.php | 68 ++-- 22 files changed, 845 insertions(+), 340 deletions(-) create mode 100644 src/Dsn.php create mode 100644 tests/DsnTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 96aec3f0b..3d237e80f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Log internal debug and error messages to a PSR-3 compatible logger (#989) - Make `AbstractSerializer` to accept `Traversable` values using `is_iterable` instead of `is_array` (#991) - Refactor the `ModulesIntegration` integration to improve its code and its tests (#990) +- Extract the parsing and validation logic of the DSN into its own value object (#995) ## 2.3.2 (2020-03-06) diff --git a/phpstan.neon b/phpstan.neon index b416bc0f6..4cdea32b8 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -38,6 +38,33 @@ parameters: - message: '/^Call to an undefined method Sentry\\Integration\\IntegrationInterface::processEvent\(\)\.$/' path: src/Integration/ModulesIntegration.php + - + message: '/^Parameter #2 \$str of function explode expects string, int\|string given\.$/' + path: src/Dsn.php + - + message: '/^Parameter #1 \$haystack of function strrpos expects string, int\|string given\.$/' + path: src/Dsn.php + - + message: '/^Parameter #1 \$str of function substr expects string, int\|string given\.$/' + path: src/Dsn.php + - + message: '/^Parameter #2 \$host of class Sentry\\Dsn constructor expects string, int\|string given\.$/' + path: src/Dsn.php + - + message: '/^Parameter #3 \$port of class Sentry\\Dsn constructor expects int, int\|string given\.$/' + path: src/Dsn.php + - + message: '/^Parameter #5 \$path of class Sentry\\Dsn constructor expects string, int\|string given\.$/' + path: src/Dsn.php + - + message: '/^Parameter #6 \$publicKey of class Sentry\\Dsn constructor expects string, int\|string given\.$/' + path: src/Dsn.php + - + message: '/^Parameter #7 \$secretKey of class Sentry\\Dsn constructor expects string\|null, int\|string\|null given\.$/' + path: src/Dsn.php + - + message: '/^Parameter #1 \$c of function ctype_digit expects int\|string, string\|null given\.$/' + path: src/Dsn.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Dsn.php b/src/Dsn.php new file mode 100644 index 000000000..02eaed8b0 --- /dev/null +++ b/src/Dsn.php @@ -0,0 +1,228 @@ + + */ +final class Dsn +{ + /** + * @var string The protocol to be used to access the resource + */ + private $scheme; + + /** + * @var string The host that holds the resource + */ + private $host; + + /** + * @var int The port on which the resource is exposed + */ + private $port; + + /** + * @var string The public key to authenticate the SDK + */ + private $publicKey; + + /** + * @var string|null The secret key to authenticate the SDK + */ + private $secretKey; + + /** + * @var int The ID of the resource to access + */ + private $projectId; + + /** + * @var string The specific resource that the web client wants to access + */ + private $path; + + /** + * Class constructor. + * + * @param string $scheme The protocol to be used to access the resource + * @param string $host The host that holds the resource + * @param int $port The port on which the resource is exposed + * @param int $projectId The ID of the resource to access + * @param string $path The specific resource that the web client wants to access + * @param string $publicKey The public key to authenticate the SDK + * @param string|null $secretKey The secret key to authenticate the SDK + */ + private function __construct(string $scheme, string $host, int $port, int $projectId, string $path, string $publicKey, ?string $secretKey) + { + $this->scheme = $scheme; + $this->host = $host; + $this->port = $port; + $this->publicKey = $publicKey; + $this->secretKey = $secretKey; + $this->path = $path; + $this->projectId = $projectId; + } + + /** + * Creates an instance of this class by parsing the given string. + * + * @param string $value The string to parse + */ + public static function createFromString(string $value): self + { + $parsedDsn = parse_url($value); + + if (false === $parsedDsn) { + throw new \InvalidArgumentException(sprintf('The "%s" DSN is invalid.', $value)); + } + + foreach (['scheme', 'host', 'path', 'user'] as $component) { + if (!isset($parsedDsn[$component]) || (isset($parsedDsn[$component]) && empty($parsedDsn[$component]))) { + throw new \InvalidArgumentException(sprintf('The "%s" DSN must contain a scheme, a host, a user and a path component.', $value)); + } + } + + if (isset($parsedDsn['pass']) && empty($parsedDsn['pass'])) { + throw new \InvalidArgumentException(sprintf('The "%s" DSN must contain a valid secret key.', $value)); + } + + /** @psalm-suppress PossiblyUndefinedArrayOffset */ + if (!\in_array($parsedDsn['scheme'], ['http', 'https'], true)) { + throw new \InvalidArgumentException(sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value)); + } + + /** @psalm-suppress PossiblyUndefinedArrayOffset */ + $segmentPaths = explode('/', $parsedDsn['path']); + $projectId = array_pop($segmentPaths); + + if (!ctype_digit($projectId)) { + throw new \InvalidArgumentException('"%s" DSN must contain a valid project ID.'); + } + + $lastSlashPosition = strrpos($parsedDsn['path'], '/'); + $path = $parsedDsn['path']; + + if (false !== $lastSlashPosition) { + $path = substr($parsedDsn['path'], 0, $lastSlashPosition); + } + + /** @psalm-suppress PossiblyUndefinedArrayOffset */ + return new self( + $parsedDsn['scheme'], + $parsedDsn['host'], + $parsedDsn['port'] ?? ('http' === $parsedDsn['scheme'] ? 80 : 443), + (int) $projectId, + $path, + $parsedDsn['user'], + $parsedDsn['pass'] ?? null + ); + } + + /** + * Gets the protocol to be used to access the resource. + */ + public function getScheme(): string + { + return $this->scheme; + } + + /** + * Gets the host that holds the resource. + */ + public function getHost(): string + { + return $this->host; + } + + /** + * Gets the port on which the resource is exposed. + */ + public function getPort(): int + { + return $this->port; + } + + /** + * Gets the specific resource that the web client wants to access. + */ + public function getPath(): string + { + return $this->path; + } + + /** + * Gets the ID of the resource to access. + */ + public function getProjectId(): int + { + return $this->projectId; + } + + /** + * Gets the public key to authenticate the SDK. + */ + public function getPublicKey(): string + { + return $this->publicKey; + } + + /** + * Gets the secret key to authenticate the SDK. + */ + public function getSecretKey(): ?string + { + return $this->secretKey; + } + + /** + * Gets the URL of the API endpoint to use to post an event to Sentry. + */ + public function getStoreApiEndpointUrl(): string + { + $url = $this->scheme . '://' . $this->host; + + if (('http' === $this->scheme && 80 !== $this->port) || ('https' === $this->scheme && 443 !== $this->port)) { + $url .= ':' . $this->port; + } + + if (null !== $this->path) { + $url .= $this->path; + } + + $url .= '/api/' . $this->projectId . '/store/'; + + return $url; + } + + /** + * @see https://www.php.net/manual/en/language.oop5.magic.php#object.tostring + */ + public function __toString(): string + { + $url = $this->scheme . '://' . $this->publicKey; + + if (null !== $this->secretKey) { + $url .= ':' . $this->secretKey; + } + + $url .= '@' . $this->host; + + if (('http' === $this->scheme && 80 !== $this->port) || ('https' === $this->scheme && 443 !== $this->port)) { + $url .= ':' . $this->port; + } + + if (null !== $this->path) { + $url .= $this->path; + } + + $url .= '/' . $this->projectId; + + return $url; + } +} diff --git a/src/Exception/MissingProjectIdCredentialException.php b/src/Exception/MissingProjectIdCredentialException.php index 79b6a5b88..f3adf3181 100644 --- a/src/Exception/MissingProjectIdCredentialException.php +++ b/src/Exception/MissingProjectIdCredentialException.php @@ -6,9 +6,13 @@ use Throwable; +@trigger_error(sprintf('The %s class is deprecated since version 2.4 and will be removed in 3.0.', MissingProjectIdCredentialException::class), E_USER_DEPRECATED); + /** * This exception is thrown during the sending of an event when the project ID * is not provided in the DSN. + * + * @deprecated since version 2.4, to be removed in 3.0 */ final class MissingProjectIdCredentialException extends \RuntimeException { diff --git a/src/Exception/MissingPublicKeyCredentialException.php b/src/Exception/MissingPublicKeyCredentialException.php index 9561c2658..f10957112 100644 --- a/src/Exception/MissingPublicKeyCredentialException.php +++ b/src/Exception/MissingPublicKeyCredentialException.php @@ -6,9 +6,13 @@ use Throwable; +@trigger_error(sprintf('The %s class is deprecated since version 2.4 and will be removed in 3.0.', MissingProjectIdCredentialException::class), E_USER_DEPRECATED); + /** * This exception is thrown during the sending of an event when the public key * is not provided in the DSN. + * + * @deprecated since version 2.4, to be removed in 3.0 */ final class MissingPublicKeyCredentialException extends \RuntimeException { diff --git a/src/HttpClient/Authentication/SentryAuthentication.php b/src/HttpClient/Authentication/SentryAuthentication.php index d28124c8c..a7a561cc0 100644 --- a/src/HttpClient/Authentication/SentryAuthentication.php +++ b/src/HttpClient/Authentication/SentryAuthentication.php @@ -4,10 +4,10 @@ namespace Sentry\HttpClient\Authentication; -use Http\Message\Authentication; +use Http\Message\Authentication as AuthenticationInterface; use Psr\Http\Message\RequestInterface; use Sentry\Client; -use Sentry\Exception\MissingPublicKeyCredentialException; +use Sentry\Dsn; use Sentry\Options; /** @@ -16,7 +16,7 @@ * * @author Stefano Arlandini */ -final class SentryAuthentication implements Authentication +final class SentryAuthentication implements AuthenticationInterface { /** * @var Options The Sentry client configuration @@ -49,26 +49,23 @@ public function __construct(Options $options, string $sdkIdentifier, string $sdk /** * {@inheritdoc} - * - * @throws MissingPublicKeyCredentialException If the public key is missing in the DSN */ public function authenticate(RequestInterface $request): RequestInterface { - $publicKey = $this->options->getPublicKey(); - $secretKey = $this->options->getSecretKey(); + $dsn = $this->options->getDsn(false); - if (null === $publicKey) { - throw new MissingPublicKeyCredentialException(); + if (!$dsn instanceof Dsn) { + return $request; } $data = [ 'sentry_version' => Client::PROTOCOL_VERSION, 'sentry_client' => $this->sdkIdentifier . '/' . $this->sdkVersion, - 'sentry_key' => $publicKey, + 'sentry_key' => $dsn->getPublicKey(), ]; - if (null !== $secretKey) { - $data['sentry_secret'] = $secretKey; + if (null !== $dsn->getSecretKey()) { + $data['sentry_secret'] = $dsn->getSecretKey(); } $headers = []; @@ -77,9 +74,6 @@ public function authenticate(RequestInterface $request): RequestInterface $headers[] = $headerKey . '=' . $headerValue; } - /** @var RequestInterface $request */ - $request = $request->withHeader('X-Sentry-Auth', 'Sentry ' . implode(', ', $headers)); - - return $request; + return $request->withHeader('X-Sentry-Auth', 'Sentry ' . implode(', ', $headers)); } } diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index b82df08ed..e35f1a95c 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -7,7 +7,6 @@ use GuzzleHttp\RequestOptions as GuzzleHttpClientOptions; use Http\Adapter\Guzzle6\Client as GuzzleHttpClient; use Http\Client\Common\Plugin\AuthenticationPlugin; -use Http\Client\Common\Plugin\BaseUriPlugin; use Http\Client\Common\Plugin\DecoderPlugin; use Http\Client\Common\Plugin\ErrorPlugin; use Http\Client\Common\Plugin\HeaderSetPlugin; @@ -101,7 +100,7 @@ public function __construct( */ public function create(Options $options): HttpAsyncClientInterface { - if (null === $options->getDsn()) { + if (null === $options->getDsn(false)) { throw new \RuntimeException('Cannot create an HTTP client without the Sentry DSN set in the options.'); } @@ -136,7 +135,6 @@ public function create(Options $options): HttpAsyncClientInterface } $httpClientPlugins = [ - new BaseUriPlugin($this->uriFactory->createUri($options->getDsn())), new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion]), new AuthenticationPlugin(new SentryAuthentication($options, $this->sdkIdentifier, $this->sdkVersion)), new RetryPlugin(['retries' => $options->getSendAttempts()]), diff --git a/src/HttpClient/PluggableHttpClientFactory.php b/src/HttpClient/PluggableHttpClientFactory.php index 577f84bc9..981eab5b6 100644 --- a/src/HttpClient/PluggableHttpClientFactory.php +++ b/src/HttpClient/PluggableHttpClientFactory.php @@ -36,6 +36,8 @@ final class PluggableHttpClientFactory implements HttpClientFactoryInterface */ public function __construct(HttpClientFactoryInterface $decoratedHttpClientFactory, array $httpClientPlugins) { + @trigger_error(sprintf('The "%s" class is deprecated since version 2.3 and will be removed in 3.0.', self::class), E_USER_DEPRECATED); + $this->decoratedHttpClientFactory = $decoratedHttpClientFactory; $this->httpClientPlugins = $httpClientPlugins; } diff --git a/src/Options.php b/src/Options.php index c39dc37c9..e5b0c056b 100644 --- a/src/Options.php +++ b/src/Options.php @@ -30,26 +30,6 @@ final class Options */ private $options = []; - /** - * @var string|null A simple server string, set to the DSN found on your Sentry settings - */ - private $dsn; - - /** - * @var string|null The project ID number to send to the Sentry server - */ - private $projectId; - - /** - * @var string|null The public key to authenticate the SDK - */ - private $publicKey; - - /** - * @var string|null The secret key to authenticate the SDK - */ - private $secretKey; - /** * @var OptionsResolver The options resolver */ @@ -324,10 +304,18 @@ public function setInAppIncludedPaths(array $paths): void /** * Gets the project ID number to send to the Sentry server. + * + * @deprecated since version 2.4, to be removed in 3.0 */ public function getProjectId(): ?string { - return $this->projectId; + @trigger_error(sprintf('Method %s() is deprecated since version 2.4 and will be removed in 3.0. Use the getDsn() method instead.', __METHOD__), E_USER_DEPRECATED); + + if (null === $this->options['dsn']) { + return null; + } + + return (string) $this->options['dsn']->getProjectId(); } /** @@ -352,18 +340,34 @@ public function setProjectRoot(?string $path): void /** * Gets the public key to authenticate the SDK. + * + * @deprecated since version 2.4, to be removed in 3.0 */ public function getPublicKey(): ?string { - return $this->publicKey; + @trigger_error(sprintf('Method %s() is deprecated since version 2.4 and will be removed in 3.0. Use the getDsn() method instead.', __METHOD__), E_USER_DEPRECATED); + + if (null === $this->options['dsn']) { + return null; + } + + return $this->options['dsn']->getPublicKey(); } /** * Gets the secret key to authenticate the SDK. + * + * @deprecated since version 2.4, to be removed in 3.0 */ public function getSecretKey(): ?string { - return $this->secretKey; + @trigger_error(sprintf('Method %s() is deprecated since version 2.4 and will be removed in 3.0. Use the getDsn() method instead.', __METHOD__), E_USER_DEPRECATED); + + if (null === $this->options['dsn']) { + return null; + } + + return $this->options['dsn']->getSecretKey(); } /** @@ -410,10 +414,35 @@ public function setRelease(?string $release): void /** * Gets the DSN of the Sentry server the authenticated user is bound to. + * + * @param bool $returnAsString Whether to return the DSN as a string or as an object + * + * @return string|Dsn|null */ - public function getDsn(): ?string + public function getDsn(bool $returnAsString = true) { - return $this->dsn; + /** @var Dsn|null $dsn */ + $dsn = $this->options['dsn']; + + if (null === $dsn) { + return null; + } + + if ($returnAsString) { + @trigger_error(sprintf('Calling the method %s() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0.', __METHOD__), E_USER_DEPRECATED); + + $url = $dsn->getScheme() . '://' . $dsn->getHost(); + + if (('http' === $dsn->getScheme() && 80 !== $dsn->getPort()) || ('https' === $dsn->getScheme() && 443 !== $dsn->getPort())) { + $url .= ':' . $dsn->getPort(); + } + + $url .= $dsn->getPath(); + + return $url; + } + + return $dsn; } /** @@ -813,7 +842,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('project_root', ['null', 'string']); $resolver->setAllowedTypes('logger', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); - $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool']); + $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('tags', 'array'); @@ -880,16 +909,20 @@ private function normalizeAbsolutePath(string $value): string * Normalizes the DSN option by parsing the host, public and secret keys and * an optional path. * - * @param SymfonyOptions$options The configuration options - * @param string|null $dsn The actual value of the option to normalize + * @param SymfonyOptions $options The configuration options + * @param string|bool|Dsn|null $value The actual value of the option to normalize */ - private function normalizeDsnOption(SymfonyOptions $options, ?string $dsn): ?string + private function normalizeDsnOption(SymfonyOptions $options, $value): ?Dsn { - if (empty($dsn)) { + if (null === $value || \is_bool($value)) { return null; } - switch (strtolower($dsn)) { + if ($value instanceof Dsn) { + return $value; + } + + switch (strtolower($value)) { case '': case 'false': case '(false)': @@ -900,48 +933,25 @@ private function normalizeDsnOption(SymfonyOptions $options, ?string $dsn): ?str return null; } - $parsed = @parse_url($dsn); - - if (false === $parsed || !isset($parsed['scheme'], $parsed['host'], $parsed['path'], $parsed['user'])) { - return null; - } - - $this->dsn = $parsed['scheme'] . '://' . $parsed['host']; - - if (isset($parsed['port']) && ((80 !== $parsed['port'] && 'http' === $parsed['scheme']) || (443 !== $parsed['port'] && 'https' === $parsed['scheme']))) { - $this->dsn .= ':' . $parsed['port']; - } - - $lastSlashPosition = strrpos($parsed['path'], '/'); - - if (false !== $lastSlashPosition) { - $this->dsn .= substr($parsed['path'], 0, $lastSlashPosition); - } else { - $this->dsn .= $parsed['path']; - } - - $this->publicKey = $parsed['user']; - $this->secretKey = $parsed['pass'] ?? null; - - $parts = explode('/', $parsed['path']); - - $this->projectId = array_pop($parts); - - return $dsn; + return Dsn::createFromString($value); } /** * Validates the DSN option ensuring that all required pieces are set and * that the URL is valid. * - * @param string|null $dsn The value of the option + * @param string|bool|Dsn|null $dsn The value of the option */ - private function validateDsnOption(?string $dsn): bool + private function validateDsnOption($dsn): bool { - if (null === $dsn) { + if (null === $dsn || $dsn instanceof Dsn) { return true; } + if (\is_bool($dsn)) { + return false === $dsn; + } + switch (strtolower($dsn)) { case '': case 'false': @@ -953,25 +963,13 @@ private function validateDsnOption(?string $dsn): bool return true; } - $parsed = @parse_url($dsn); - - if (false === $parsed) { - return false; - } - - if (!isset($parsed['scheme'], $parsed['user'], $parsed['host'], $parsed['path'])) { - return false; - } + try { + Dsn::createFromString($dsn); - if (empty($parsed['user']) || (isset($parsed['pass']) && empty($parsed['pass']))) { - return false; - } - - if (!\in_array(strtolower($parsed['scheme']), ['http', 'https'])) { + return true; + } catch (\InvalidArgumentException $exception) { return false; } - - return true; } /** diff --git a/src/Severity.php b/src/Severity.php index aad1c8000..b0bc7206d 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -164,7 +164,7 @@ public function isEqualTo(self $other): bool } /** - * {@inheritdoc} + * @see https://www.php.net/manual/en/language.oop5.magic.php#object.tostring */ public function __toString(): string { diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php index 73c45c3fb..16a5919d4 100644 --- a/src/Transport/DefaultTransportFactory.php +++ b/src/Transport/DefaultTransportFactory.php @@ -49,7 +49,7 @@ public function __construct(MessageFactoryInterface $messageFactory, HttpClientF */ public function create(Options $options): TransportInterface { - if (null === $options->getDsn()) { + if (null === $options->getDsn(false)) { return new NullTransport(); } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 706848814..aed2e0f5c 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -11,8 +11,8 @@ use Http\Message\RequestFactory as RequestFactoryInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Sentry\Dsn; use Sentry\Event; -use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; use Sentry\Util\JSON; @@ -25,9 +25,9 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterface { /** - * @var Options The Raven client configuration + * @var Options The Sentry client options */ - private $config; + private $options; /** * @var HttpAsyncClientInterface The HTTP client @@ -60,7 +60,7 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterf /** * Constructor. * - * @param Options $config The Raven client configuration + * @param Options $options The Sentry client configuration * @param HttpAsyncClientInterface $httpClient The HTTP client * @param RequestFactoryInterface $requestFactory The PSR-7 request factory * @param bool $delaySendingUntilShutdown This flag controls whether to delay @@ -74,7 +74,7 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterf * @param LoggerInterface|null $logger An instance of a PSR-3 logger */ public function __construct( - Options $config, + Options $options, HttpAsyncClientInterface $httpClient, RequestFactoryInterface $requestFactory, bool $delaySendingUntilShutdown = true, @@ -85,7 +85,7 @@ public function __construct( @trigger_error(sprintf('Delaying the sending of the events using the "%s" class is deprecated since version 2.2 and will not work in 3.0.', __CLASS__), E_USER_DEPRECATED); } - $this->config = $config; + $this->options = $options; $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; $this->delaySendingUntilShutdown = $delaySendingUntilShutdown; @@ -111,15 +111,15 @@ public function __destruct() */ public function send(Event $event): ?string { - $projectId = $this->config->getProjectId(); + $dsn = $this->options->getDsn(false); - if (null === $projectId) { - throw new MissingProjectIdCredentialException(); + if (!$dsn instanceof Dsn) { + throw new \RuntimeException(sprintf('The DSN option must be set to use the "%s" transport.', self::class)); } $request = $this->requestFactory->createRequest( 'POST', - sprintf('/api/%d/store/', $projectId), + $dsn->getStoreApiEndpointUrl(), ['Content-Type' => 'application/json'], JSON::encode($event->toArray()) ); diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index f7e10569e..b6d2a8567 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -5,7 +5,6 @@ namespace Sentry\Transport; use Sentry\Event; -use Sentry\Exception\MissingProjectIdCredentialException; /** * This interface must be implemented by all classes willing to provide a way @@ -21,8 +20,6 @@ interface TransportInterface * @param Event $event The event * * @return string|null Returns the ID of the event or `null` if it failed to be sent - * - * @throws MissingProjectIdCredentialException If the project ID is missing in the DSN */ public function send(Event $event): ?string; } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index f9be538b9..0e61ed983 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -7,9 +7,7 @@ use Http\Client\Common\Plugin as PluginInterface; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Discovery\MessageFactoryDiscovery; -use Http\Discovery\UriFactoryDiscovery; use Http\Message\MessageFactory as MessageFactoryInterface; -use Http\Message\UriFactory as UriFactoryInterface; use Http\Promise\FulfilledPromise; use Http\Promise\Promise as PromiseInterface; use Jean85\PrettyVersions; @@ -56,26 +54,6 @@ public function testNullTransportIsUsedWhenNoServerIsConfigured(): void $this->assertInstanceOf(NullTransport::class, $transport); } - /** - * @group legacy - * - * @expectedDeprecation Method Sentry\ClientBuilder::setUriFactory() is deprecated since version 2.3 and will be removed in 3.0. - */ - public function testSetUriFactory(): void - { - /** @var UriFactoryInterface&MockObject $uriFactory */ - $uriFactory = $this->createMock(UriFactoryInterface::class); - $uriFactory->expects($this->once()) - ->method('createUri') - ->willReturn(UriFactoryDiscovery::find()->createUri('http://www.example.com')); - - $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) - ->setUriFactory($uriFactory) - ->getClient(); - - $client->captureMessage('foo'); - } - /** * @group legacy * diff --git a/tests/DsnTest.php b/tests/DsnTest.php new file mode 100644 index 000000000..a551d540c --- /dev/null +++ b/tests/DsnTest.php @@ -0,0 +1,233 @@ +assertSame($expectedScheme, $dsn->getScheme()); + $this->assertSame($expectedHost, $dsn->getHost()); + $this->assertSame($expectedPort, $dsn->getPort()); + $this->assertSame($expectedPublicKey, $dsn->getPublicKey()); + $this->assertSame($expectedSecretKey, $dsn->getSecretKey()); + $this->assertSame($expectedProjectId, $dsn->getProjectId()); + $this->assertSame($expectedPath, $dsn->getPath()); + } + + public function createFromStringDataProvider(): \Generator + { + yield [ + 'http://public@example.com/sentry/1', + 'http', + 'example.com', + 80, + 'public', + null, + 1, + '/sentry', + ]; + + yield [ + 'http://public@example.com/1', + 'http', + 'example.com', + 80, + 'public', + null, + 1, + '', + ]; + + yield [ + 'http://public:secret@example.com/1', + 'http', + 'example.com', + 80, + 'public', + 'secret', + 1, + '', + ]; + + yield [ + 'http://public@example.com:80/1', + 'http', + 'example.com', + 80, + 'public', + null, + 1, + '', + ]; + + yield [ + 'http://public@example.com:8080/1', + 'http', + 'example.com', + 8080, + 'public', + null, + 1, + '', + ]; + + yield [ + 'https://public@example.com/1', + 'https', + 'example.com', + 443, + 'public', + null, + 1, + '', + ]; + + yield [ + 'https://public@example.com:443/1', + 'https', + 'example.com', + 443, + 'public', + null, + 1, + '', + ]; + + yield [ + 'https://public@example.com:4343/1', + 'https', + 'example.com', + 4343, + 'public', + null, + 1, + '', + ]; + } + + /** + * @dataProvider createFromStringThrowsExceptionIfValueIsInvalidDataProvider + */ + public function testCreateFromStringThrowsExceptionIfValueIsInvalid(string $value, string $expectedExceptionMessage): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + Dsn::createFromString($value); + } + + public function createFromStringThrowsExceptionIfValueIsInvalidDataProvider(): \Generator + { + yield 'invalid DSN' => [ + ':', + 'The ":" DSN is invalid.', + ]; + + yield 'missing scheme' => [ + '://public@example.com/sentry/1', + 'The "://public@example.com/sentry/1" DSN must contain a scheme, a host, a user and a path component.', + ]; + + yield 'missing public key' => [ + 'http://:secret@example.com/sentry/1', + 'The "http://:secret@example.com/sentry/1" DSN must contain a scheme, a host, a user and a path component.', + ]; + + yield 'missing secret key' => [ + 'http://public:@example.com/sentry/1', + 'The "http://public:@example.com/sentry/1" DSN must contain a valid secret key.', + ]; + + yield 'missing host' => [ + '/sentry/1', + 'The "/sentry/1" DSN must contain a scheme, a host, a user and a path component.', + ]; + + yield 'missing path' => [ + 'http://public@example.com', + 'The "http://public@example.com" DSN must contain a scheme, a host, a user and a path component.', + ]; + + yield 'unsupported scheme' => [ + 'tcp://public:secret@example.com/1', + 'The scheme of the "tcp://public:secret@example.com/1" DSN must be either "http" or "https".', + ]; + } + + /** + * @dataProvider getStoreApiEndpointUrlDataProvider + */ + public function testGetStoreApiEndpointUrl(string $value, string $expectedUrl): void + { + $dsn = Dsn::createFromString($value); + + $this->assertSame($expectedUrl, $dsn->getStoreApiEndpointUrl()); + } + + public function getStoreApiEndpointUrlDataProvider(): \Generator + { + yield [ + 'http://public@example.com/sentry/1', + 'http://example.com/sentry/api/1/store/', + ]; + + yield [ + 'http://public@example.com/1', + 'http://example.com/api/1/store/', + ]; + + yield [ + 'http://public@example.com:8080/sentry/1', + 'http://example.com:8080/sentry/api/1/store/', + ]; + + yield [ + 'https://public@example.com/sentry/1', + 'https://example.com/sentry/api/1/store/', + ]; + + yield [ + 'https://public@example.com:4343/sentry/1', + 'https://example.com:4343/sentry/api/1/store/', + ]; + } + + /** + * @dataProvider toStringDataProvider + */ + public function testToString(string $value): void + { + $this->assertSame($value, (string) Dsn::createFromString($value)); + } + + public function toStringDataProvider(): array + { + return [ + ['http://public@example.com/sentry/1'], + ['http://public:secret@example.com/sentry/1'], + ['http://public@example.com/1'], + ['http://public@example.com:8080/sentry/1'], + ['https://public@example.com/sentry/1'], + ['https://public@example.com:4343/sentry/1'], + ]; + } +} diff --git a/tests/Exception/MissingProjectIdCredentialExceptionTest.php b/tests/Exception/MissingProjectIdCredentialExceptionTest.php index 5386e6224..5a79607a1 100644 --- a/tests/Exception/MissingProjectIdCredentialExceptionTest.php +++ b/tests/Exception/MissingProjectIdCredentialExceptionTest.php @@ -9,6 +9,10 @@ final class MissingProjectIdCredentialExceptionTest extends TestCase { + /** + * @group legacy + * @expectedDeprecationMessage The Sentry\Exception\MissingProjectIdCredentialException class is deprecated since version 2.4 and will be removed in 3.0. + */ public function testGetMessage(): void { $exception = new MissingProjectIdCredentialException(); diff --git a/tests/Exception/MissingPublicKeyCredentialExceptionTest.php b/tests/Exception/MissingPublicKeyCredentialExceptionTest.php index 8da72b6b2..01b9a0e16 100644 --- a/tests/Exception/MissingPublicKeyCredentialExceptionTest.php +++ b/tests/Exception/MissingPublicKeyCredentialExceptionTest.php @@ -9,6 +9,10 @@ final class MissingPublicKeyCredentialExceptionTest extends TestCase { + /** + * @group legacy + * @expectedDeprecationMessage The Sentry\Exception\MissingPublicKeyCredentialExceptionTest class is deprecated since version 2.4 and will be removed in 3.0. + */ public function testGetMessage(): void { $exception = new MissingPublicKeyCredentialException(); diff --git a/tests/HttpClient/Authentication/SentryAuthenticationTest.php b/tests/HttpClient/Authentication/SentryAuthenticationTest.php index 153577adb..726903a10 100644 --- a/tests/HttpClient/Authentication/SentryAuthenticationTest.php +++ b/tests/HttpClient/Authentication/SentryAuthenticationTest.php @@ -4,79 +4,58 @@ namespace Sentry\Tests\HttpClient\Authentication; -use PHPUnit\Framework\MockObject\MockObject; +use GuzzleHttp\Psr7\Request; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestInterface; use Sentry\Client; -use Sentry\Exception\MissingPublicKeyCredentialException; use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\Options; final class SentryAuthenticationTest extends TestCase { - public function testAuthenticate(): void + public function testAuthenticateWithSecretKey(): void { - $configuration = new Options(['dsn' => 'http://public:secret@example.com/']); + $configuration = new Options(['dsn' => 'http://public:secret@example.com/sentry/1']); $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '1.2.3'); - - /** @var RequestInterface|MockObject $request */ - $request = $this->getMockBuilder(RequestInterface::class) - ->getMock(); - - /** @var RequestInterface|MockObject $newRequest */ - $newRequest = $this->getMockBuilder(RequestInterface::class) - ->getMock(); - - $headerValue = sprintf( + $request = new Request('POST', 'http://www.example.com', []); + $expectedHeader = sprintf( 'Sentry sentry_version=%s, sentry_client=%s, sentry_key=public, sentry_secret=secret', Client::PROTOCOL_VERSION, 'sentry.php.test/1.2.3' ); - $request->expects($this->once()) - ->method('withHeader') - ->with('X-Sentry-Auth', $headerValue) - ->willReturn($newRequest); + $this->assertFalse($request->hasHeader('X-Sentry-Auth')); - $this->assertSame($newRequest, $authentication->authenticate($request)); + $request = $authentication->authenticate($request); + + $this->assertTrue($request->hasHeader('X-Sentry-Auth')); + $this->assertSame($expectedHeader, $request->getHeaderLine('X-Sentry-Auth')); } - public function testAuthenticateWithNoSecretKey(): void + public function testAuthenticateWithoutSecretKey(): void { - $configuration = new Options(['dsn' => 'http://public@example.com/']); - $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '2.0.0'); - - /** @var RequestInterface|MockObject $request */ - $request = $this->getMockBuilder(RequestInterface::class) - ->getMock(); - - /** @var RequestInterface|MockObject $newRequest */ - $newRequest = $this->getMockBuilder(RequestInterface::class) - ->getMock(); - - $headerValue = sprintf( + $configuration = new Options(['dsn' => 'http://public@example.com/sentry/1']); + $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '1.2.3'); + $request = new Request('POST', 'http://www.example.com', []); + $expectedHeader = sprintf( 'Sentry sentry_version=%s, sentry_client=%s, sentry_key=public', Client::PROTOCOL_VERSION, - 'sentry.php.test/2.0.0' + 'sentry.php.test/1.2.3' ); - $request->expects($this->once()) - ->method('withHeader') - ->with('X-Sentry-Auth', $headerValue) - ->willReturn($newRequest); + $this->assertFalse($request->hasHeader('X-Sentry-Auth')); - $this->assertSame($newRequest, $authentication->authenticate($request)); + $request = $authentication->authenticate($request); + + $this->assertTrue($request->hasHeader('X-Sentry-Auth')); + $this->assertSame($expectedHeader, $request->getHeaderLine('X-Sentry-Auth')); } - public function testAuthenticateWithNoPublicKeyThrowsException(): void + public function testAuthenticateWithoutDsnOptionSet(): void { - $this->expectException(MissingPublicKeyCredentialException::class); - - $authentication = new SentryAuthentication(new Options(), 'sentry.php.test', '2.0.0'); - - /** @var RequestInterface&MockObject $request */ - $request = $this->createMock(RequestInterface::class); + $authentication = new SentryAuthentication(new Options(), 'sentry.php.test', '1.2.3'); + $request = new Request('POST', 'http://www.example.com', []); + $request = $authentication->authenticate($request); - $authentication->authenticate($request); + $this->assertFalse($request->hasHeader('X-Sentry-Auth')); } } diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php index f261efd2d..4c8c3c93d 100644 --- a/tests/HttpClient/HttpClientFactoryTest.php +++ b/tests/HttpClient/HttpClientFactoryTest.php @@ -37,15 +37,15 @@ public function testCreate(bool $isCompressionEnabled, string $expectedRequestBo 'enable_compression' => $isCompressionEnabled, ])); - $httpClient->sendAsyncRequest(MessageFactoryDiscovery::find()->createRequest('POST', '/foo', [], 'foo bar')); + $httpClient->sendAsyncRequest(MessageFactoryDiscovery::find()->createRequest('POST', 'http://example.com/sentry/foo', [], 'foo bar')); /** @var RequestInterface|bool $httpRequest */ $httpRequest = $mockHttpClient->getLastRequest(); $this->assertInstanceOf(RequestInterface::class, $httpRequest); $this->assertSame('http://example.com/sentry/foo', (string) $httpRequest->getUri()); - $this->assertSame('sentry.php.test/1.2.3', (string) $httpRequest->getHeaderLine('User-Agent')); - $this->assertSame('Sentry sentry_version=7, sentry_client=sentry.php.test/1.2.3, sentry_key=public', (string) $httpRequest->getHeaderLine('X-Sentry-Auth')); + $this->assertSame('sentry.php.test/1.2.3', $httpRequest->getHeaderLine('User-Agent')); + $this->assertSame('Sentry sentry_version=7, sentry_client=sentry.php.test/1.2.3, sentry_key=public', $httpRequest->getHeaderLine('X-Sentry-Auth')); $this->assertSame($expectedRequestBody, (string) $httpRequest->getBody()); } diff --git a/tests/HttpClient/PluggableHttpClientFactoryTest.php b/tests/HttpClient/PluggableHttpClientFactoryTest.php index 797850d52..0c34f318f 100644 --- a/tests/HttpClient/PluggableHttpClientFactoryTest.php +++ b/tests/HttpClient/PluggableHttpClientFactoryTest.php @@ -14,6 +14,9 @@ use Sentry\HttpClient\PluggableHttpClientFactory; use Sentry\Options; +/** + * @group legacy + */ final class PluggableHttpClientFactoryTest extends TestCase { public function testCreate(): void diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 7abc1f428..4deb76df8 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -5,6 +5,7 @@ namespace Sentry\Tests; use PHPUnit\Framework\TestCase; +use Sentry\Dsn; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -76,179 +77,191 @@ public function optionsDataProvider(): array } /** - * @dataProvider serverOptionDataProvider + * @group legacy + * + * @dataProvider dsnOptionDataProvider + * + * @expectedDeprecationMessage Calling the method getDsn() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0. */ - public function testServerOption(string $dsn, array $options): void + public function testDsnOption($value, ?string $expectedProjectId, ?string $expectedPublicKey, ?string $expectedSecretKey, ?string $expectedDsnAsString, ?Dsn $expectedDsnAsObject): void { - $configuration = new Options(['dsn' => $dsn]); + $options = new Options(['dsn' => $value]); - $this->assertEquals($options['project_id'], $configuration->getProjectId()); - $this->assertEquals($options['public_key'], $configuration->getPublicKey()); - $this->assertEquals($options['secret_key'], $configuration->getSecretKey()); - $this->assertEquals($options['server'], $configuration->getDsn()); + $this->assertSame($expectedProjectId, $options->getProjectId()); + $this->assertSame($expectedPublicKey, $options->getPublicKey()); + $this->assertSame($expectedSecretKey, $options->getSecretKey()); + $this->assertEquals($expectedDsnAsString, $options->getDsn()); + $this->assertEquals($expectedDsnAsObject, $options->getDsn(false)); } - public function serverOptionDataProvider(): array + public function dsnOptionDataProvider(): \Generator { - return [ - [ - 'http://public@example.com/1', - [ - 'project_id' => 1, - 'public_key' => 'public', - 'secret_key' => null, - 'server' => 'http://example.com', - ], - ], - [ - 'http://public:secret@example.com/1', - [ - 'project_id' => 1, - 'public_key' => 'public', - 'secret_key' => 'secret', - 'server' => 'http://example.com', - ], - ], - [ - 'http://public:secret@example.com:80/1', - [ - 'project_id' => 1, - 'public_key' => 'public', - 'secret_key' => 'secret', - 'server' => 'http://example.com', - ], - ], - [ - 'https://public:secret@example.com/1', - [ - 'project_id' => 1, - 'public_key' => 'public', - 'secret_key' => 'secret', - 'server' => 'https://example.com', - ], - ], - [ - 'https://public:secret@example.com:443/1', - [ - 'project_id' => 1, - 'public_key' => 'public', - 'secret_key' => 'secret', - 'server' => 'https://example.com', - ], - ], - [ - 'http://public:secret@example.com/sentry/1', - [ - 'project_id' => 1, - 'public_key' => 'public', - 'secret_key' => 'secret', - 'server' => 'http://example.com/sentry', - ], - ], - [ - 'http://public:secret@example.com:3000/sentry/1', - [ - 'project_id' => 1, - 'public_key' => 'public', - 'secret_key' => 'secret', - 'server' => 'http://example.com:3000/sentry', - ], - ], + yield [ + 'http://public:secret@example.com/sentry/1', + '1', + 'public', + 'secret', + 'http://example.com/sentry', + Dsn::createFromString('http://public:secret@example.com/sentry/1'), ]; - } - /** - * @dataProvider invalidServerOptionDataProvider - * - * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException - * @expectedExceptionMessageRegExp /^The option "dsn" with value "(.*)" is invalid.$/ - */ - public function testServerOptionsWithInvalidServer(string $dsn): void - { - new Options(['dsn' => $dsn]); - } + yield [ + Dsn::createFromString('http://public:secret@example.com/sentry/1'), + '1', + 'public', + 'secret', + 'http://example.com/sentry', + Dsn::createFromString('http://public:secret@example.com/sentry/1'), + ]; - public function invalidServerOptionDataProvider(): array - { - return [ - ['http://public:secret@/1'], - ['http://public:secret@example.com'], - ['http://:secret@example.com/1'], - ['http://public:@example.com'], - ['tcp://public:secret@example.com/1'], + yield [ + null, + null, + null, + null, + null, + null, + ]; + + yield [ + 'null', + null, + null, + null, + null, + null, + ]; + + yield [ + '(null)', + null, + null, + null, + null, + null, + ]; + + yield [ + false, + null, + null, + null, + null, + null, + ]; + + yield [ + 'false', + null, + null, + null, + null, + null, + ]; + + yield [ + '(false)', + null, + null, + null, + null, + null, + ]; + + yield [ + '', + null, + null, + null, + null, + null, + ]; + + yield [ + 'empty', + null, + null, + null, + null, + null, + ]; + + yield [ + '(empty)', + null, + null, + null, + null, + null, ]; } /** - * @dataProvider disabledDsnProvider + * @dataProvider dsnOptionThrowsOnInvalidValueDataProvider */ - public function testParseDSNWithDisabledValue($dsn) + public function testDsnOptionThrowsOnInvalidValue($value, string $expectedExceptionMessage): void { - $configuration = new Options(['dsn' => $dsn]); + $this->expectException(InvalidOptionsException::class); + $this->expectExceptionMessage($expectedExceptionMessage); - $this->assertNull($configuration->getProjectId()); - $this->assertNull($configuration->getPublicKey()); - $this->assertNull($configuration->getSecretKey()); - $this->assertNull($configuration->getDsn()); + new Options(['dsn' => $value]); } - public function disabledDsnProvider() + public function dsnOptionThrowsOnInvalidValueDataProvider(): \Generator { - return [ - [null], - ['null'], - [false], - ['false'], - [''], - ['empty'], + yield [ + true, + 'The option "dsn" with value true is invalid.', + ]; + + yield [ + 'foo', + 'The option "dsn" with value "foo" is invalid.', ]; } /** * @dataProvider excludedExceptionsDataProvider */ - public function testIsExcludedException($excludedExceptions, $exception, $result) + public function testIsExcludedException(array $excludedExceptions, \Throwable $exception, bool $result): void { $configuration = new Options(['excluded_exceptions' => $excludedExceptions]); $this->assertSame($result, $configuration->isExcludedException($exception, false)); } - public function excludedExceptionsDataProvider() + public function excludedExceptionsDataProvider(): \Generator { - return [ - [ - [\BadFunctionCallException::class, \BadMethodCallException::class], - new \BadMethodCallException(), - true, - ], - [ - [\BadFunctionCallException::class], - new \Exception(), - false, - ], - [ - [\Exception::class], - new \BadFunctionCallException(), - true, - ], + yield [ + [\BadFunctionCallException::class, \BadMethodCallException::class], + new \BadMethodCallException(), + true, + ]; + + yield [ + [\BadFunctionCallException::class], + new \Exception(), + false, + ]; + + yield [ + [\Exception::class], + new \BadFunctionCallException(), + true, ]; } /** * @dataProvider excludedPathProviders - * - * @param string $value - * @param string $expected */ - public function testExcludedAppPathsPathRegressionWithFileName($value, $expected) + public function testExcludedAppPathsPathRegressionWithFileName(string $value, string $expected): void { $configuration = new Options(['in_app_exclude' => [$value]]); $this->assertSame([$expected], $configuration->getInAppExcludedPaths()); } - public function excludedPathProviders() + public function excludedPathProviders(): array { return [ ['some/path', 'some/path'], @@ -305,38 +318,44 @@ public function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array ]; } - public function testDsnOptionSupportsEnvironmentVariable(): void + /** + * @group legacy + * @backupGlobals enabled + * + * @expectedDeprecationMessage Calling the method getDsn() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0. + */ + public function testDsnOptionDefaultValueIsGotFromEnvironmentVariable(): void { $_SERVER['SENTRY_DSN'] = 'http://public@example.com/1'; $options = new Options(); - unset($_SERVER['SENTRY_DSN']); - $this->assertSame('http://example.com', $options->getDsn()); $this->assertSame('public', $options->getPublicKey()); $this->assertSame('1', $options->getProjectId()); } - public function testEnvironmentOptionSupportsEnvironmentVariable(): void + /** + * @backupGlobals enabled + */ + public function testEnvironmentOptionDefaultValueIsGotFromEnvironmentVariable(): void { $_SERVER['SENTRY_ENVIRONMENT'] = 'test_environment'; $options = new Options(); - unset($_SERVER['SENTRY_ENVIRONMENT']); - $this->assertSame('test_environment', $options->getEnvironment()); } - public function testReleaseOptionSupportsEnvironmentVariable(): void + /** + * @backupGlobals enabled + */ + public function testReleaseOptionDefaultValueIsGotFromEnvironmentVariable(): void { $_SERVER['SENTRY_RELEASE'] = '0.0.1'; $options = new Options(); - unset($_SERVER['SENTRY_RELEASE']); - $this->assertSame('0.0.1', $options->getRelease()); } diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index f5a8505ed..bdbb16de2 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -4,25 +4,45 @@ namespace Sentry\Tests\Transport; -use Http\Client\HttpAsyncClient; +use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Discovery\MessageFactoryDiscovery; +use Http\Promise\FulfilledPromise; use Http\Promise\RejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Sentry\Event; -use Sentry\Exception\MissingProjectIdCredentialException; use Sentry\Options; use Sentry\Transport\HttpTransport; final class HttpTransportTest extends TestCase { - public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoIt(): void + public function testSendThrowsIfDsnOptionIsNotSet(): void { - $promise = new RejectedPromise(new \Exception()); + $transport = new HttpTransport( + new Options(), + $this->createMock(HttpAsyncClientInterface::class), + MessageFactoryDiscovery::find(), + false + ); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The DSN option must be set to use the "Sentry\Transport\HttpTransport" transport.'); + + $transport->send(new Event()); + } + + /** + * @group legacy + * + * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + */ + public function testSendDelaysExecutionUntilShutdown(): void + { + $promise = new FulfilledPromise('foo'); - /** @var HttpAsyncClient&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); + /** @var HttpAsyncClientInterface&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClientInterface::class); $httpClient->expects($this->once()) ->method('sendAsyncRequest') ->willReturn($promise); @@ -31,20 +51,32 @@ public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoI new Options(['dsn' => 'http://public@example.com/sentry/1']), $httpClient, MessageFactoryDiscovery::find(), - false + true ); + $this->assertAttributeEmpty('pendingRequests', $transport); + $transport->send(new Event()); + + $this->assertAttributeNotEmpty('pendingRequests', $transport); + + $transport->close(); + + $this->assertAttributeEmpty('pendingRequests', $transport); } - public function testSendThrowsOnMissingProjectIdCredential(): void + public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoIt(): void { - $this->expectException(MissingProjectIdCredentialException::class); + $promise = new RejectedPromise(new \Exception()); + + /** @var HttpAsyncClientInterface&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClientInterface::class); + $httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willReturn($promise); - /** @var HttpAsyncClient&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); $transport = new HttpTransport( - new Options(), + new Options(['dsn' => 'http://public@example.com/sentry/1']), $httpClient, MessageFactoryDiscovery::find(), false @@ -64,8 +96,8 @@ public function testSendLogsErrorMessageIfSendingFailed(): void ->method('error') ->with('Failed to send the event to Sentry. Reason: "foo".', ['exception' => $exception, 'event' => $event]); - /** @var HttpAsyncClient&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); + /** @var HttpAsyncClientInterface&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClientInterface::class); $httpClient->expects($this->once()) ->method('sendAsyncRequest') ->willReturn(new RejectedPromise($exception)); @@ -106,8 +138,8 @@ public function testCloseLogsErrorMessageIfSendingFailed(): void ['exception' => $exception, 'event' => $event2], ]); - /** @var HttpAsyncClient&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); + /** @var HttpAsyncClientInterface&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClientInterface::class); $httpClient->expects($this->exactly(2)) ->method('sendAsyncRequest') ->willReturnOnConsecutiveCalls( @@ -146,8 +178,8 @@ public function testCloseLogsErrorMessageIfExceptionIsThrownWhileProcessingTheHt ->method('error') ->with('Failed to send the event to Sentry. Reason: "foo".', ['exception' => $exception]); - /** @var HttpAsyncClient&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClient::class); + /** @var HttpAsyncClientInterface&MockObject $httpClient */ + $httpClient = $this->createMock(HttpAsyncClientInterface::class); $httpClient->expects($this->once()) ->method('sendAsyncRequest') ->willThrowException($exception); From 0e8a0b3ab433d2d57bdd7e8f50182a634476e6ed Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Sun, 26 Apr 2020 11:38:09 +0200 Subject: [PATCH 0550/1161] Fix typos in DocBlocks (#1002) --- src/Breadcrumb.php | 2 +- src/ErrorHandler.php | 4 ++-- src/Integration/RequestIntegration.php | 1 - src/Serializer/RepresentationSerializer.php | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index c0a8c5973..191e0a268 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -7,7 +7,7 @@ use Sentry\Exception\InvalidArgumentException; /** - * This class stores all the informations about a breadcrumb. + * This class stores all the information about a breadcrumb. * * @author Stefano Arlandini */ diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 38c2189bb..1aed17f06 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -436,11 +436,11 @@ private function handleException(\Throwable $exception): void } catch (\Throwable $previousExceptionHandlerException) { // This `catch` statement is here to forcefully override the // $previousExceptionHandlerException variable with the exception - // we just catched + // we just caught } // If the instance of the exception we're handling is the same as the one - // catched from the previous exception handler then we give it back to the + // caught from the previous exception handler then we give it back to the // native PHP handler to prevent an infinite loop if ($exception === $previousExceptionHandlerException) { // Disable the fatal error handler or the error will be reported twice diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index e582fbd9f..e1a1a233d 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -4,7 +4,6 @@ namespace Sentry\Integration; -use GuzzleHttp\Psr7\ServerRequest; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; use Sentry\Event; diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index e4b6e5c0c..2d91446f6 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -14,6 +14,7 @@ class RepresentationSerializer extends AbstractSerializer implements Representat * {@inheritdoc} * * @psalm-suppress InvalidReturnType + * @psalm-suppress InvalidReturnStatement */ public function representationSerialize($value) { From 97bbca2c828651dfbd04769c94c1cc8b37e096a3 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 5 May 2020 15:57:15 +0200 Subject: [PATCH 0551/1161] Fix the behavior of the context_lines option as per documentation when null is used (#1003) --- CHANGELOG.md | 2 + src/Options.php | 19 ++++-- src/Serializer/RepresentationSerializer.php | 3 + src/Stacktrace.php | 10 +-- tests/Integration/ModulesIntegrationTest.php | 5 +- tests/OptionsTest.php | 38 +++++++++++ tests/StacktraceTest.php | 69 ++++++++++++++++---- 7 files changed, 119 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a0407444..b7291ef51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add missing validation for the `context_lines` option and fix its behavior when passing `null` to make it working as described in the documentation (#1003) + ## 2.3.2 (2020-03-06) - Hard-limit concurrent requests in `HttpTransport` and removed pre-init of promises (fixes "too many open files" errors) (#981) diff --git a/src/Options.php b/src/Options.php index c39dc37c9..8775366af 100644 --- a/src/Options.php +++ b/src/Options.php @@ -163,7 +163,7 @@ public function setAttachStacktrace(bool $enable): void /** * Gets the number of lines of code context to capture, or null if none. */ - public function getContextLines(): int + public function getContextLines(): ?int { return $this->options['context_lines']; } @@ -171,9 +171,9 @@ public function getContextLines(): int /** * Sets the number of lines of code context to capture, or null if none. * - * @param int $contextLines The number of lines of code + * @param int|null $contextLines The number of lines of code */ - public function setContextLines(int $contextLines): void + public function setContextLines(?int $contextLines): void { $options = array_merge($this->options, ['context_lines' => $contextLines]); @@ -804,7 +804,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('prefixes', 'array'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); - $resolver->setAllowedTypes('context_lines', 'int'); + $resolver->setAllowedTypes('context_lines', ['null', 'int']); $resolver->setAllowedTypes('enable_compression', 'bool'); $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('excluded_exceptions', 'array'); @@ -835,6 +835,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions'])); $resolver->setAllowedValues('class_serializers', \Closure::fromCallable([$this, 'validateClassSerializersOption'])); $resolver->setAllowedValues('tags', \Closure::fromCallable([$this, 'validateTagsOption'])); + $resolver->setAllowedValues('context_lines', \Closure::fromCallable([$this, 'validateContextLinesOption'])); $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); $resolver->setNormalizer('project_root', function (SymfonyOptions $options, ?string $value) { @@ -1037,6 +1038,16 @@ private function validateTagsOption(array $tags): bool return true; } + /** + * Validates that the value passed to the "context_lines" option is valid. + * + * @param int|null $contextLines The value to validate + */ + private function validateContextLinesOption(?int $contextLines): bool + { + return null === $contextLines || $contextLines >= 0; + } + /** * Gets the list of default integrations. * diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index f8b9e07e2..2d91446f6 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -12,6 +12,9 @@ class RepresentationSerializer extends AbstractSerializer implements Representat { /** * {@inheritdoc} + * + * @psalm-suppress InvalidReturnType + * @psalm-suppress InvalidReturnStatement */ public function representationSerialize($value) { diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 88677f345..af4510975 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -214,13 +214,13 @@ public function jsonSerialize() /** * Gets an excerpt of the source code around a given line. * - * @param string $path The file path - * @param int $lineNumber The line to centre about - * @param int $maxLinesToFetch The maximum number of lines to fetch + * @param string $path The file path + * @param int $lineNumber The line to centre about + * @param int|null $maxLinesToFetch The maximum number of lines to fetch */ - protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array + protected function getSourceCodeExcerpt(string $path, int $lineNumber, ?int $maxLinesToFetch): array { - if (@!is_readable($path) || !is_file($path)) { + if (null === $maxLinesToFetch || @!is_readable($path) || !is_file($path)) { return []; } diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index 7f189dea8..d664f0741 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -4,7 +4,6 @@ namespace Sentry\Tests\Integration; -use Jean85\PrettyVersions; use PHPUnit\Framework\TestCase; use Sentry\Event; use Sentry\Integration\ModulesIntegration; @@ -20,8 +19,6 @@ public function testInvoke(): void $modules = $event->getModules(); - $this->assertArrayHasKey('sentry/sentry', $modules, 'Root project missing'); - $this->assertArrayHasKey('ocramius/package-versions', $modules, 'Indirect dependency missing'); - $this->assertEquals(PrettyVersions::getVersion('sentry/sentry'), $modules['sentry/sentry']); + $this->assertNotEmpty($modules); } } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 7abc1f428..d7c313575 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -305,6 +305,44 @@ public function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array ]; } + /** + * @dataProvider contextLinesOptionValidatesInputValueDataProvider + */ + public function testContextLinesOptionValidatesInputValue(?int $value, ?string $expectedExceptionMessage): void + { + if (null !== $expectedExceptionMessage) { + $this->expectException(InvalidOptionsException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } else { + $this->expectNotToPerformAssertions(); + } + + new Options(['context_lines' => $value]); + } + + public function contextLinesOptionValidatesInputValueDataProvider(): \Generator + { + yield [ + -1, + 'The option "context_lines" with value -1 is invalid.', + ]; + + yield [ + 0, + null, + ]; + + yield [ + 1, + null, + ]; + + yield [ + null, + null, + ]; + } + public function testDsnOptionSupportsEnvironmentVariable(): void { $_SERVER['SENTRY_DSN'] = 'http://public@example.com/1'; diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index fb8dc5e11..e11408eae 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -300,9 +300,7 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator */ public function testAddFrameRespectsContextLinesOption(string $fixture, int $lineNumber, ?int $contextLines, int $preContextCount, int $postContextCount): void { - if (null !== $contextLines) { - $this->options->setContextLines($contextLines); - } + $this->options->setContextLines($contextLines); $fileContent = explode("\n", $this->getFixture($fixture)); $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); @@ -316,25 +314,68 @@ public function testAddFrameRespectsContextLinesOption(string $fixture, int $lin $this->assertCount($postContextCount, $frames[0]->getPostContext()); for ($i = 0; $i < $preContextCount; ++$i) { - $this->assertEquals(rtrim($fileContent[$i + ($lineNumber - $preContextCount - 1)]), $frames[0]->getPreContext()[$i]); + $this->assertSame(rtrim($fileContent[$i + ($lineNumber - $preContextCount - 1)]), $frames[0]->getPreContext()[$i]); } - $this->assertEquals(rtrim($fileContent[$lineNumber - 1]), $frames[0]->getContextLine()); + if (null === $contextLines) { + $this->assertNull($frames[0]->getContextLine()); + } else { + $this->assertSame(rtrim($fileContent[$lineNumber - 1]), $frames[0]->getContextLine()); + } for ($i = 0; $i < $postContextCount; ++$i) { - $this->assertEquals(rtrim($fileContent[$i + $lineNumber]), $frames[0]->getPostContext()[$i]); + $this->assertSame(rtrim($fileContent[$i + $lineNumber]), $frames[0]->getPostContext()[$i]); } } - public function addFrameRespectsContextLinesOptionDataProvider(): array + public function addFrameRespectsContextLinesOptionDataProvider(): \Generator { - return [ - 'read code from short file' => ['code/ShortFile.php', 3, 2, 2, 2], - 'read code from long file with default context' => ['code/LongFile.php', 8, null, 5, 5], - 'read code from long file with specified context' => ['code/LongFile.php', 8, 2, 2, 2], - 'read code from short file with no context' => ['code/ShortFile.php', 3, 0, 0, 0], - 'read code from long file near end of file' => ['code/LongFile.php', 11, 5, 5, 2], - 'read code from long file near beginning of file' => ['code/LongFile.php', 3, 5, 2, 5], + yield 'skip reading code when context_lines option == 0' => [ + 'code/ShortFile.php', + 3, + 0, + 0, + 0, + ]; + + yield 'skip reading pre_context and post_context when context_lines == null' => [ + 'code/ShortFile.php', + 3, + null, + 0, + 0, + ]; + + yield 'read code from short file' => [ + 'code/ShortFile.php', + 3, + 2, + 2, + 2, + ]; + + yield 'read code from long file' => [ + 'code/LongFile.php', + 8, + 2, + 2, + 2, + ]; + + yield 'read code from long file near end of file' => [ + 'code/LongFile.php', + 11, + 5, + 5, + 2, + ]; + + yield 'read code from long file near beginning of file' => [ + 'code/LongFile.php', + 3, + 5, + 2, + 5, ]; } From 83d205acad479ce74b668750084920e16aa2fce8 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 5 May 2020 16:01:50 +0200 Subject: [PATCH 0552/1161] Update PHPStan to version 0.12 (#1009) --- composer.json | 4 +- phpstan.neon | 37 ++++-------- src/Breadcrumb.php | 26 ++++++-- src/Client.php | 4 +- src/ClientBuilderInterface.php | 2 +- src/ClientInterface.php | 6 +- src/Context/Context.php | 19 ++++++ src/Context/RuntimeContext.php | 2 + src/Context/ServerOsContext.php | 2 + src/Context/TagsContext.php | 6 +- src/Context/UserContext.php | 2 + src/ErrorHandler.php | 20 ++++--- src/Event.php | 60 ++++++------------- src/EventFactoryInterface.php | 4 +- src/Frame.php | 21 ++++++- src/Integration/RequestIntegration.php | 6 +- src/Options.php | 12 ++-- src/Serializer/AbstractSerializer.php | 6 +- .../RepresentationSerializerInterface.php | 4 +- src/Serializer/SerializableInterface.php | 2 + src/Serializer/SerializerInterface.php | 2 +- src/Stacktrace.php | 38 ++++++++---- src/State/HubInterface.php | 2 +- src/State/Scope.php | 10 ++-- src/functions.php | 4 +- tests/ClientTest.php | 45 -------------- tests/Fixtures/classes/StubTransport.php | 40 ------------- tests/Fixtures/code/Latin1File.php | 5 -- tests/State/HubTest.php | 2 +- 29 files changed, 174 insertions(+), 219 deletions(-) delete mode 100644 tests/Fixtures/classes/StubTransport.php delete mode 100644 tests/Fixtures/code/Latin1File.php diff --git a/composer.json b/composer.json index e8ff9ef17..fce69aa6d 100644 --- a/composer.json +++ b/composer.json @@ -41,8 +41,8 @@ "monolog/monolog": "^1.3|^2.0", "php-http/mock-client": "^1.3", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", "phpunit/phpunit": "^7.5.18", "symfony/phpunit-bridge": "^4.3|^5.0", "vimeo/psalm": "^3.4" diff --git a/phpstan.neon b/phpstan.neon index 4cdea32b8..1054e02bd 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,43 +1,23 @@ parameters: tipsOfTheDay: false - level: 7 + level: 8 paths: - src - - tests ignoreErrors: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - - '/^Parameter #1 \$object of method ReflectionProperty::setValue\(\) expects object, null given\.$/' # https://github.com/phpstan/phpstan/pull/2340 - "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" - message: /^Cannot assign offset 'os' to array\|string\.$/ path: src/Event.php + - + message: '/^array\|string does not accept array\.$/' + path: src/Event.php - message: '/^Argument of an invalid type array\|object supplied for foreach, only iterables are supported\.$/' path: src/Util/JSON.php - - - message: '/^Class Http\\Client\\Curl\\Client not found\.$/' - path: src/HttpClient/HttpClientFactory.php - - - message: '/^Class Http\\Adapter\\Guzzle6\\Client not found\.$/' - path: src/HttpClient/HttpClientFactory.php - - - message: '/^Instantiated class Http\\Client\\Curl\\Client not found\.$/' - path: src/HttpClient/HttpClientFactory.php - - - message: '/^Call to static method createWithConfig\(\) on an unknown class Http\\Adapter\\Guzzle6\\Client\.$/' - path: src/HttpClient/HttpClientFactory.php - message: '/^Access to constant (?:PROXY|TIMEOUT|CONNECT_TIMEOUT) on an unknown class GuzzleHttp\\RequestOptions\.$/' path: src/HttpClient/HttpClientFactory.php - - - message: '/^Access to an undefined property Sentry\\Integration\\IntegrationInterface::\$options\.$/' - path: src/Integration/IgnoreErrorsIntegration.php - - - message: '/^Call to an undefined method Sentry\\Integration\\IntegrationInterface::shouldDropEvent\(\)\.$/' - path: src/Integration/IgnoreErrorsIntegration.php - - - message: '/^Call to an undefined method Sentry\\Integration\\IntegrationInterface::processEvent\(\)\.$/' - path: src/Integration/ModulesIntegration.php - message: '/^Parameter #2 \$str of function explode expects string, int\|string given\.$/' path: src/Dsn.php @@ -65,6 +45,15 @@ parameters: - message: '/^Parameter #1 \$c of function ctype_digit expects int\|string, string\|null given\.$/' path: src/Dsn.php + - + message: '/^Method Sentry\\Monolog\\Handler::write\(\) has parameter \$record with no value type specified in iterable type array\.$/' + path: src/Monolog/Handler.php + - + message: '/^Cannot cast array\|bool\|float\|int\|string\|null to string\.$/' + path: src/Serializer/RepresentationSerializer.php + - + message: '/^Method Sentry\\Client::getIntegration\(\) should return T of Sentry\\Integration\\IntegrationInterface\|null but returns Sentry\\Integration\\IntegrationInterface\|null\.$/' + path: src/Client.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index 191e0a268..efa247025 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -104,7 +104,7 @@ final class Breadcrumb implements \JsonSerializable private $level; /** - * @var array The meta data of the breadcrumb + * @var array The meta data of the breadcrumb */ private $metadata; @@ -116,11 +116,11 @@ final class Breadcrumb implements \JsonSerializable /** * Constructor. * - * @param string $level The error level of the breadcrumb - * @param string $type The type of the breadcrumb - * @param string $category The category of the breadcrumb - * @param string|null $message Optional text message - * @param array $metadata Additional information about the breadcrumb + * @param string $level The error level of the breadcrumb + * @param string $type The type of the breadcrumb + * @param string $category The category of the breadcrumb + * @param string|null $message Optional text message + * @param array $metadata Additional information about the breadcrumb */ public function __construct(string $level, string $type, string $category, ?string $message = null, array $metadata = []) { @@ -287,6 +287,8 @@ public function withMessage(string $message): self /** * Gets the breadcrumb meta data. + * + * @return array */ public function getMetadata(): array { @@ -345,6 +347,8 @@ public function getTimestamp(): float /** * Gets the breadcrumb as an array. + * + * @return array */ public function toArray(): array { @@ -362,6 +366,14 @@ public function toArray(): array * Helper method to create an instance of this class from an array of data. * * @param array $data Data used to populate the breadcrumb + * + * @psalm-param array{ + * level: string, + * type?: string, + * category: string, + * message?: string, + * data?: array + * } $data */ public static function fromArray(array $data): self { @@ -376,6 +388,8 @@ public static function fromArray(array $data): self /** * {@inheritdoc} + * + * @return array */ public function jsonSerialize(): array { diff --git a/src/Client.php b/src/Client.php index 09a9e77f6..9c5ff91cb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -165,8 +165,8 @@ public function flush(?int $timeout = null): PromiseInterface /** * Assembles an event and prepares it to be sent of to Sentry. * - * @param array $payload The payload that will be converted to an Event - * @param Scope|null $scope Optional scope which enriches the Event + * @param array $payload The payload that will be converted to an Event + * @param Scope|null $scope Optional scope which enriches the Event * * @return Event|null returns ready to send Event, however depending on options it can be discarded */ diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 010f20282..3b944af70 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -27,7 +27,7 @@ interface ClientBuilderInterface /** * Creates a new instance of this builder. * - * @param array $options The client options, in naked array form + * @param array $options The client options, in naked array form * * @return static */ diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 6007369ca..9f41c0181 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -46,15 +46,15 @@ public function captureLastError(?Scope $scope = null): ?string; /** * Captures a new event using the provided data. * - * @param array $payload The data of the event being captured - * @param Scope|null $scope An optional scope keeping the state + * @param array $payload The data of the event being captured + * @param Scope|null $scope An optional scope keeping the state */ public function captureEvent(array $payload, ?Scope $scope = null): ?string; /** * Returns the integration instance if it is installed on the client. * - * @param string $className the classname of the integration + * @param string $className The FQCN of the integration * * @psalm-template T of IntegrationInterface * diff --git a/src/Context/Context.php b/src/Context/Context.php index 92cdec7cb..7426280f3 100644 --- a/src/Context/Context.php +++ b/src/Context/Context.php @@ -9,6 +9,11 @@ * being sent. * * @author Stefano Arlandini + * + * @psalm-template T + * + * @template-implements \ArrayAccess + * @template-implements \IteratorAggregate */ class Context implements \ArrayAccess, \JsonSerializable, \IteratorAggregate { @@ -49,6 +54,8 @@ class Context implements \ArrayAccess, \JsonSerializable, \IteratorAggregate /** * @var array The data stored in this object + * + * @psalm-var array */ protected $data = []; @@ -56,6 +63,8 @@ class Context implements \ArrayAccess, \JsonSerializable, \IteratorAggregate * Constructor. * * @param array $data The initial data to store + * + * @psalm-param array $data */ public function __construct(array $data = []) { @@ -68,6 +77,8 @@ public function __construct(array $data = []) * * @param array $data The data to merge * @param bool $recursive Whether to merge the data recursively or not + * + * @psalm-param array $data */ public function merge(array $data, bool $recursive = false): void { @@ -79,6 +90,8 @@ public function merge(array $data, bool $recursive = false): void * the given input data. * * @param array $data The data to set + * + * @psalm-param array $data */ public function setData(array $data): void { @@ -91,6 +104,8 @@ public function setData(array $data): void * Replaces all the data contained in this object with the given one. * * @param array $data The data to set + * + * @psalm-param array $data */ public function replaceData(array $data): void { @@ -115,6 +130,8 @@ public function isEmpty(): bool /** * Returns an array representation of the data stored by the object. + * + * @psalm-return array */ public function toArray(): array { @@ -155,6 +172,8 @@ public function offsetUnset($offset): void /** * {@inheritdoc} + * + * @psalm-return array */ public function jsonSerialize(): array { diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 15ae3690f..b67b080cb 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -16,6 +16,8 @@ * @author Stefano Arlandini * * @final since version 2.3 + * + * @template-extends Context */ class RuntimeContext extends Context { diff --git a/src/Context/ServerOsContext.php b/src/Context/ServerOsContext.php index b751eaec6..3fc3151fe 100644 --- a/src/Context/ServerOsContext.php +++ b/src/Context/ServerOsContext.php @@ -15,6 +15,8 @@ * @author Stefano Arlandini * * @final since version 2.3 + * + * @template-extends Context */ class ServerOsContext extends Context { diff --git a/src/Context/TagsContext.php b/src/Context/TagsContext.php index 32349bd0e..2a892d334 100644 --- a/src/Context/TagsContext.php +++ b/src/Context/TagsContext.php @@ -11,6 +11,8 @@ * @author Stefano Arlandini * * @final since version 2.3 + * + * @template-extends Context */ class TagsContext extends Context { @@ -61,7 +63,9 @@ public function offsetSet($offset, $value): void /** * Sanitizes the given data by converting numeric values to strings. * - * @param array $data The data to sanitize + * @param array $data The data to sanitize + * + * @return array * * @throws \InvalidArgumentException If any of the values of the input data * is not a number or a string diff --git a/src/Context/UserContext.php b/src/Context/UserContext.php index bafe3586f..fe3667f62 100644 --- a/src/Context/UserContext.php +++ b/src/Context/UserContext.php @@ -7,6 +7,8 @@ /** * This class is a specialized version of the base `Context` adapted to work * for the user context. + * + * @template-extends Context */ final class UserContext extends Context { diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 1aed17f06..97e3841d9 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -349,12 +349,12 @@ public function addExceptionHandlerListener(callable $listener): void * Handles errors by capturing them through the client according to the * configured bit field. * - * @param int $level The level of the error raised, represented by - * one of the E_* constants - * @param string $message The error message - * @param string $file The filename the error was raised in - * @param int $line The line number the error was raised at - * @param array|null $errcontext The error context (deprecated since PHP 7.2) + * @param int $level The level of the error raised, represented by + * one of the E_* constants + * @param string $message The error message + * @param string $file The filename the error was raised in + * @param int $line The line number the error was raised at + * @param array|null $errcontext The error context (deprecated since PHP 7.2) * * @return bool If the function returns `false` then the PHP native error * handler will be called @@ -456,9 +456,11 @@ private function handleException(\Throwable $exception): void * Cleans and returns the backtrace without the first frames that belong to * this error handler. * - * @param array $backtrace The backtrace to clear - * @param string $file The filename the backtrace was raised in - * @param int $line The line number the backtrace was raised at + * @param array $backtrace The backtrace to clear + * @param string $file The filename the backtrace was raised in + * @param int $line The line number the backtrace was raised at + * + * @return array */ private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array { diff --git a/src/Event.php b/src/Event.php index 7c476d795..77eeb6f16 100644 --- a/src/Event.php +++ b/src/Event.php @@ -104,7 +104,7 @@ final class Event implements \JsonSerializable private $contexts = []; /** - * @var Context An arbitrary mapping of additional metadata + * @var Context An arbitrary mapping of additional metadata */ private $extraContext; @@ -126,7 +126,7 @@ final class Event implements \JsonSerializable /** * @var array> The exceptions * - * @psalm-var array */ public function getRequest(): array { @@ -419,6 +421,8 @@ public function setContext(string $name, array $data): self /** * Gets an arbitrary mapping of additional metadata. + * + * @return Context */ public function getExtraContext(): Context { @@ -519,6 +523,14 @@ public function setBreadcrumb(array $breadcrumbs): void /** * Gets the exception. + * + * @return array + * + * @psalm-return list */ public function getExceptions(): array { @@ -530,7 +542,7 @@ public function getExceptions(): array * * @param array> $exceptions The exception * - * @psalm-param array, - * extra?: mixed[], - * tags?: mixed[], - * user?: mixed[], - * contexts?: mixed[], - * breadcrumbs?: array{ - * values: Breadcrumb[] - * }, - * exception?: array{ - * values: array{ - * type: class-string, - * value: string, - * stacktrace?: array{ - * frames: Frame[] - * } - * }[] - * }, - * request?: array, - * message?: string|array{ - * message: string, - * params: mixed[], - * formatted: string - * } - * } + * @return array */ public function toArray(): array { @@ -713,6 +687,8 @@ public function toArray(): array /** * {@inheritdoc} + * + * @return array */ public function jsonSerialize(): array { diff --git a/src/EventFactoryInterface.php b/src/EventFactoryInterface.php index 479073ad8..405623588 100644 --- a/src/EventFactoryInterface.php +++ b/src/EventFactoryInterface.php @@ -12,14 +12,14 @@ interface EventFactoryInterface /** * Create an {@see Event} with a stacktrace attached to it. * - * @param array $payload The data to be attached to the Event + * @param array $payload The data to be attached to the Event */ public function createWithStacktrace(array $payload): Event; /** * Create an {@see Event} from a data payload. * - * @param array $payload The data to be attached to the Event + * @param array $payload The data to be attached to the Event */ public function create(array $payload): Event; } diff --git a/src/Frame.php b/src/Frame.php index 7f66a84d8..d5887fb41 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -51,8 +51,8 @@ final class Frame implements \JsonSerializable private $inApp = true; /** - * @var array A mapping of variables which were available within this - * frame (usually context-locals) + * @var array A mapping of variables which were available within this + * frame (usually context-locals) */ private $vars = []; @@ -177,6 +177,8 @@ public function setIsInApp(bool $inApp): void /** * Gets a mapping of variables which were available within this frame * (usually context-locals). + * + * @return array */ public function getVars(): array { @@ -187,7 +189,7 @@ public function getVars(): array * Sets a mapping of variables which were available within this frame * (usually context-locals). * - * @param array $vars The variables + * @param array $vars The variables */ public function setVars(array $vars): void { @@ -197,6 +199,17 @@ public function setVars(array $vars): void /** * Returns an array representation of the data of this frame modeled according * to the specifications of the Sentry SDK Stacktrace Interface. + * + * @psalm-return array{ + * function: string|null, + * filename: string, + * lineno: int, + * in_app: bool, + * pre_context?: string[], + * context_line?: string, + * post_context?: string[], + * vars?: array + * } */ public function toArray(): array { @@ -228,6 +241,8 @@ public function toArray(): array /** * {@inheritdoc} + * + * @return array */ public function jsonSerialize(): array { diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index e1a1a233d..9fee4d8b1 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -89,6 +89,8 @@ public function setupOnce(): void * @param self $self The current instance of the integration * @param Event $event The event that will be enriched with a request * @param ServerRequestInterface|null $request The Request that will be processed and added to the event + * + * @deprecated since version 2.1, to be removed in 3.0 */ public static function applyToEvent(self $self, Event $event, ?ServerRequestInterface $request = null): void { @@ -217,7 +219,9 @@ private function captureRequestBody(Options $options, ServerRequestInterface $se * Create an array with the same structure as $uploadedFiles, but replacing * each UploadedFileInterface with an array of info. * - * @param array $uploadedFiles The uploaded files info from a PSR-7 server request + * @param array $uploadedFiles The uploaded files info from a PSR-7 server request + * + * @return array */ private function parseUploadedFiles(array $uploadedFiles): array { diff --git a/src/Options.php b/src/Options.php index e5b0c056b..054ceabbc 100644 --- a/src/Options.php +++ b/src/Options.php @@ -43,7 +43,7 @@ final class Options /** * Class constructor. * - * @param array $options The configuration options + * @param array $options The configuration options */ public function __construct(array $options = []) { @@ -89,7 +89,7 @@ public function getPrefixes(): array * Sets the prefixes which should be stripped from filenames to create * relative paths. * - * @param array $prefixes The prefixes + * @param string[] $prefixes The prefixes */ public function setPrefixes(array $prefixes): void { @@ -271,7 +271,7 @@ public function getInAppExcludedPaths(): array /** * Sets the list of paths to exclude from in_app detection. * - * @param array $paths The list of paths + * @param string[] $paths The list of paths */ public function setInAppExcludedPaths(array $paths): void { @@ -976,7 +976,7 @@ private function validateDsnOption($dsn): bool * Validates that the elements of this option are all class instances that * implements the {@see IntegrationInterface} interface. * - * @param array|callable $integrations The value to validate + * @param IntegrationInterface[]|callable $integrations The value to validate */ private function validateIntegrationsOption($integrations): bool { @@ -1006,7 +1006,7 @@ private function validateMaxBreadcrumbsOptions(int $value): bool /** * Validates that the values passed to the `class_serializers` option are valid. * - * @param array $serializers The value to validate + * @param mixed[] $serializers The value to validate */ private function validateClassSerializersOption(array $serializers): bool { @@ -1022,7 +1022,7 @@ private function validateClassSerializersOption(array $serializers): bool /** * Validates that the values passed to the `tags` option are valid. * - * @param array $tags The value to validate + * @param mixed[] $tags The value to validate */ private function validateTagsOption(array $tags): bool { diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index edc394249..a1df54132 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -90,7 +90,7 @@ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDete * * @param mixed $value * - * @return string|bool|float|int|array|null + * @return string|bool|float|int|mixed[]|null */ protected function serializeRecursively($value, int $_depth = 0) { @@ -154,6 +154,8 @@ protected function serializeRecursively($value, int $_depth = 0) * objects implementing the `SerializableInterface`. * * @param object $object + * + * @return array */ protected function resolveClassSerializers($object): array { @@ -178,7 +180,7 @@ protected function resolveClassSerializers($object): array * @param object $object * @param string[] $hashes * - * @return array|string|bool|float|int|null + * @return mixed[]|string|bool|float|int|null */ protected function serializeObject($object, int $_depth = 0, array $hashes = []) { diff --git a/src/Serializer/RepresentationSerializerInterface.php b/src/Serializer/RepresentationSerializerInterface.php index 423464a19..6c6f52868 100644 --- a/src/Serializer/RepresentationSerializerInterface.php +++ b/src/Serializer/RepresentationSerializerInterface.php @@ -13,12 +13,12 @@ interface RepresentationSerializerInterface /** * Serialize an object (recursively) into something safe to be sent as a stacktrace frame argument. * - * The main difference with the {@link Sentry\SerializerInterface} is the fact that even basic types + * The main difference with the {@link SerializerInterface} is the fact that even basic types * (bool, int, float) are converted into strings, to avoid misrepresentations on the server side. * * @param mixed $value * - * @return array|string|null + * @return mixed[]|string|null */ public function representationSerialize($value); } diff --git a/src/Serializer/SerializableInterface.php b/src/Serializer/SerializableInterface.php index 81ba38674..04b7bd281 100644 --- a/src/Serializer/SerializableInterface.php +++ b/src/Serializer/SerializableInterface.php @@ -12,6 +12,8 @@ interface SerializableInterface { /** * Returns an array representation of the object for Sentry. + * + * @return mixed[]|null */ public function toSentry(): ?array; } diff --git a/src/Serializer/SerializerInterface.php b/src/Serializer/SerializerInterface.php index 33a399094..462de5596 100644 --- a/src/Serializer/SerializerInterface.php +++ b/src/Serializer/SerializerInterface.php @@ -14,7 +14,7 @@ interface SerializerInterface * * @param mixed $value * - * @return string|bool|float|int|object|array|null + * @return string|bool|float|int|object|mixed[]|null */ public function serialize($value); } diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 88677f345..f34e0f726 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -25,6 +25,8 @@ class Stacktrace implements \JsonSerializable /** * @var SerializerInterface The serializer + * + * @deprecated since version 2.4, to be removed in 3.0 */ protected $serializer; @@ -68,7 +70,7 @@ public function __construct(Options $options, SerializerInterface $serializer, R * @param Options $options The client options * @param SerializerInterface $serializer The serializer * @param RepresentationSerializerInterface $representationSerializer The representation serializer - * @param array $backtrace The backtrace + * @param array[] $backtrace The backtrace * @param string $file The file that originated the backtrace * @param int $line The line at which the backtrace originated * @@ -113,9 +115,9 @@ public function getFrame(int $index): Frame /** * Adds a new frame to the stacktrace. * - * @param string $file The file where the frame originated - * @param int $line The line at which the frame originated - * @param array $backtraceFrame The data of the frame to add + * @param string $file The file where the frame originated + * @param int $line The line at which the frame originated + * @param array $backtraceFrame The data of the frame to add */ public function addFrame(string $file, int $line, array $backtraceFrame): void { @@ -163,8 +165,8 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void foreach ($frameArguments as $argumentName => $argumentValue) { $argumentValue = $this->representationSerializer->representationSerialize($argumentValue); - if (is_numeric($argumentValue) || \is_string($argumentValue)) { - $frameArguments[(string) $argumentName] = mb_substr((string) $argumentValue, 0, $this->options->getMaxValueLength()); + if (\is_string($argumentValue)) { + $frameArguments[(string) $argumentName] = mb_substr($argumentValue, 0, $this->options->getMaxValueLength()); } else { $frameArguments[(string) $argumentName] = $argumentValue; } @@ -205,6 +207,8 @@ public function toArray(): array /** * {@inheritdoc} + * + * @return Frame[] */ public function jsonSerialize() { @@ -217,6 +221,14 @@ public function jsonSerialize() * @param string $path The file path * @param int $lineNumber The line to centre about * @param int $maxLinesToFetch The maximum number of lines to fetch + * + * @return array + * + * @psalm-return array{ + * pre_context?: string[], + * context_line?: string, + * post_context?: string[] + * } */ protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array { @@ -263,10 +275,6 @@ protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxL // it's not a drama } - $frame['pre_context'] = $this->serializer->serialize($frame['pre_context']); - $frame['context_line'] = $this->serializer->serialize($frame['context_line']); - $frame['post_context'] = $this->serializer->serialize($frame['post_context']); - return $frame; } @@ -289,7 +297,9 @@ protected function stripPrefixFromFilePath(string $filePath): string /** * Gets the values of the arguments of the given stackframe. * - * @param array $frame The frame from where arguments are retrieved + * @param array $frame The frame from where arguments are retrieved + * + * @return array */ protected function getFrameArgumentsValues(array $frame): array { @@ -314,7 +324,9 @@ protected function getFrameArgumentsValues(array $frame): array /** * Gets the arguments of the given stackframe. * - * @param array $frame The frame from where arguments are retrieved + * @param array $frame The frame from where arguments are retrieved + * + * @return array */ public function getFrameArguments(array $frame): array { @@ -376,7 +388,7 @@ public function getFrameArguments(array $frame): array if (isset($params[$index])) { // Assign the argument by the parameter name - $args[$params[$index]->name] = $arg; + $args[$params[$index]->getName()] = $arg; } else { $args['param' . $index] = $arg; } diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 2aaa57c20..2fc64b191 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -83,7 +83,7 @@ public function captureException(\Throwable $exception): ?string; /** * Captures a new event using the provided data. * - * @param array $payload The data of the event being captured + * @param array $payload The data of the event being captured */ public function captureEvent(array $payload): ?string; diff --git a/src/State/Scope.php b/src/State/Scope.php index 402387416..e4e1b5b5c 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -38,7 +38,7 @@ final class Scope private $tags; /** - * @var Context A set of extra data associated to this scope + * @var Context A set of extra data associated to this scope */ private $extra; @@ -165,8 +165,8 @@ public function setExtras(array $extras): self /** * Sets the given data in the user context. * - * @param array $data The data - * @param bool $merge If true, $data will be merged into user context instead of replacing it + * @param array $data The data + * @param bool $merge If true, $data will be merged into user context instead of replacing it * * @return $this */ @@ -290,8 +290,8 @@ public function clear(): self * Applies the current context and fingerprint to the event. If the event has * already some breadcrumbs on it, the ones from this scope won't get merged. * - * @param Event $event The event object that will be enriched with scope data - * @param array $payload The raw payload of the event that will be propagated to the event processors + * @param Event $event The event object that will be enriched with scope data + * @param array $payload The raw payload of the event that will be propagated to the event processors */ public function applyToEvent(Event $event, array $payload): ?Event { diff --git a/src/functions.php b/src/functions.php index efcf2614b..760004337 100644 --- a/src/functions.php +++ b/src/functions.php @@ -7,7 +7,7 @@ /** * Creates a new Client and Hub which will be set as current. * - * @param array $options The client options + * @param array $options The client options */ function init(array $options = []): void { @@ -40,7 +40,7 @@ function captureException(\Throwable $exception): ?string /** * Captures a new event using the provided data. * - * @param array $payload The data of the event being captured + * @param array $payload The data of the event being captured */ function captureEvent(array $payload): ?string { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 41b630d8b..8e5fad4b0 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -12,7 +12,6 @@ use Sentry\Event; use Sentry\Options; use Sentry\SentrySdk; -use Sentry\Serializer\Serializer; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; @@ -462,50 +461,6 @@ public function convertExceptionDataProvider(): array ]; } - public function testConvertExceptionThrownInLatin1File(): void - { - /** @var TransportInterface&MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $result = $event->getExceptions(); - $expectedValue = [ - [ - 'type' => \Exception::class, - 'value' => 'foo', - ], - ]; - - $this->assertArraySubset($expectedValue, $result); - - $latin1StringFound = false; - - /** @var \Sentry\Frame $frame */ - foreach ($result[0]['stacktrace']->getFrames() as $frame) { - if (null !== $frame->getPreContext() && \in_array('// äöü', $frame->getPreContext(), true)) { - $latin1StringFound = true; - - break; - } - } - - $this->assertTrue($latin1StringFound); - - return true; - })); - - $serializer = new Serializer(new Options()); - $serializer->setMbDetectOrder('ISO-8859-1, ASCII, UTF-8'); - - $client = ClientBuilder::create() - ->setTransportFactory($this->createTransportFactory($transport)) - ->setSerializer($serializer) - ->getClient(); - - $client->captureException(require_once __DIR__ . '/Fixtures/code/Latin1File.php'); - } - public function testAttachStacktrace(): void { /** @var TransportInterface&MockObject $transport */ diff --git a/tests/Fixtures/classes/StubTransport.php b/tests/Fixtures/classes/StubTransport.php deleted file mode 100644 index f5c6eb232..000000000 --- a/tests/Fixtures/classes/StubTransport.php +++ /dev/null @@ -1,40 +0,0 @@ -events[] = $event; - $this->lastSent = $event; - - return $event->getId(); - } - - /** - * @return Event[] - */ - public function getEvents(): array - { - return $this->events; - } - - public function getLastSent(): ?Event - { - return $this->lastSent; - } -} diff --git a/tests/Fixtures/code/Latin1File.php b/tests/Fixtures/code/Latin1File.php deleted file mode 100644 index b1c8d6edc..000000000 --- a/tests/Fixtures/code/Latin1File.php +++ /dev/null @@ -1,5 +0,0 @@ - $integration */ + /** @var IntegrationInterface&MockObject $integration */ $integration = $this->createMock(IntegrationInterface::class); /** @var ClientInterface&MockObject $client */ From 944e4c73bfddb029c6b6523c454c2d835ba89423 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 6 May 2020 16:11:09 +0200 Subject: [PATCH 0553/1161] Allow to pass either a PSR-17 or a Httplug stream factory to the constructor of the GzipEncoderPlugin class (#1012) --- CHANGELOG.md | 1 + composer.json | 1 + phpstan.neon | 1 + src/HttpClient/HttpClientFactory.php | 2 +- src/HttpClient/Plugin/GzipEncoderPlugin.php | 11 ++-- .../Plugin/GzipEncoderPluginTest.php | 56 ++++++++++++++++--- 6 files changed, 59 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84c89a2d3..188ed3a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Make `AbstractSerializer` to accept `Traversable` values using `is_iterable` instead of `is_array` (#991) - Refactor the `ModulesIntegration` integration to improve its code and its tests (#990) - Extract the parsing and validation logic of the DSN into its own value object (#995) +- Support passing either a Httplug or PSR-17 stream factory to the `GzipEncoderPlugin` class (#1012) - Add missing validation for the `context_lines` option and fix its behavior when passing `null` to make it working as described in the documentation (#1003) ## 2.3.2 (2020-03-06) diff --git a/composer.json b/composer.json index fce69aa6d..b5a65d842 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "php-http/discovery": "^1.6.1", "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", + "psr/http-factory": "^1.0", "psr/http-message-implementation": "^1.0", "psr/log": "^1.0", "symfony/options-resolver": "^2.7|^3.0|^4.0|^5.0", diff --git a/phpstan.neon b/phpstan.neon index 1054e02bd..77a59191d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,6 @@ parameters: tipsOfTheDay: false + treatPhpDocTypesAsCertain: false level: 8 paths: - src diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index e35f1a95c..353557f17 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -50,7 +50,7 @@ final class HttpClientFactory implements HttpClientFactoryInterface private $responseFactory; /** - * @var StreamFactoryInterface The PSR-7 stream factory + * @var StreamFactoryInterface The PSR-17 stream factory */ private $streamFactory; diff --git a/src/HttpClient/Plugin/GzipEncoderPlugin.php b/src/HttpClient/Plugin/GzipEncoderPlugin.php index 558ff9543..658311c06 100644 --- a/src/HttpClient/Plugin/GzipEncoderPlugin.php +++ b/src/HttpClient/Plugin/GzipEncoderPlugin.php @@ -6,9 +6,10 @@ use Http\Client\Common\Plugin as PluginInterface; use Http\Discovery\StreamFactoryDiscovery; -use Http\Message\StreamFactory as StreamFactoryInterface; +use Http\Message\StreamFactory as HttplugStreamFactoryInterface; use Http\Promise\Promise as PromiseInterface; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\StreamFactoryInterface; /** * This plugin encodes the request body by compressing it with Gzip. @@ -18,18 +19,18 @@ final class GzipEncoderPlugin implements PluginInterface { /** - * @var StreamFactoryInterface The PSR-17 stream factory + * @var HttplugStreamFactoryInterface|StreamFactoryInterface The PSR-17 stream factory */ private $streamFactory; /** * Constructor. * - * @param StreamFactoryInterface|null $streamFactory The stream factory + * @param HttplugStreamFactoryInterface|StreamFactoryInterface|null $streamFactory The stream factory * * @throws \RuntimeException If the zlib extension is not enabled */ - public function __construct(?StreamFactoryInterface $streamFactory = null) + public function __construct($streamFactory = null) { if (!\extension_loaded('zlib')) { throw new \RuntimeException('The "zlib" extension must be enabled to use this plugin.'); @@ -37,6 +38,8 @@ public function __construct(?StreamFactoryInterface $streamFactory = null) if (null === $streamFactory) { @trigger_error(sprintf('A PSR-17 stream factory is needed as argument of the constructor of the "%s" class since version 2.1.3 and will be required in 3.0.', self::class), E_USER_DEPRECATED); + } elseif (!$streamFactory instanceof HttplugStreamFactoryInterface && !$streamFactory instanceof StreamFactoryInterface) { + throw new \InvalidArgumentException(sprintf('The $streamFactory argument must be an instance of either the "%s" or the "%s" interface.', HttplugStreamFactoryInterface::class, StreamFactoryInterface::class)); } $this->streamFactory = $streamFactory ?? StreamFactoryDiscovery::find(); diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php index e0a540ca0..e87950251 100644 --- a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php +++ b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php @@ -6,20 +6,64 @@ use Http\Discovery\MessageFactoryDiscovery; use Http\Discovery\StreamFactoryDiscovery; +use Http\Message\StreamFactory as HttplugStreamFactoryInterface; use Http\Promise\Promise as PromiseInterface; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\StreamFactoryInterface; use Sentry\HttpClient\Plugin\GzipEncoderPlugin; +/** + * @requires extension zlib + */ final class GzipEncoderPluginTest extends TestCase { /** - * @requires extension zlib + * @dataProvider constructorThrowsIfArgumentsAreInvalidDataProvider */ + public function testConstructorThrowsIfArgumentsAreInvalid($streamFactory, bool $shouldThrowException): void + { + if ($shouldThrowException) { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $streamFactory argument must be an instance of either the "Http\Message\StreamFactory" or the "Psr\Http\Message\StreamFactoryInterface" interface.'); + } else { + $this->expectNotToPerformAssertions(); + } + + new GzipEncoderPlugin($streamFactory); + } + + public function constructorThrowsIfArgumentsAreInvalidDataProvider(): \Generator + { + yield [ + 'foo', + true, + ]; + + yield [ + $this->createMock(StreamFactoryInterface::class), + false, + ]; + + yield [ + $this->createMock(HttplugStreamFactoryInterface::class), + false, + ]; + } + + /** + * @group legacy + * + * @expectedDeprecation A PSR-17 stream factory is needed as argument of the constructor of the "Sentry\HttpClient\Plugin\GzipEncoderPlugin" class since version 2.1.3 and will be required in 3.0. + */ + public function testConstructorThrowsDeprecationIfNoStreamFactoryIsProvided(): void + { + new GzipEncoderPlugin(); + } + public function testHandleRequest(): void { $plugin = new GzipEncoderPlugin(StreamFactoryDiscovery::find()); - $nextCallableCalled = false; $expectedPromise = $this->createMock(PromiseInterface::class); $request = MessageFactoryDiscovery::find()->createRequest( 'POST', @@ -33,18 +77,14 @@ public function testHandleRequest(): void $expectedPromise, $plugin->handleRequest( $request, - function (RequestInterface $requestArg) use (&$nextCallableCalled, $expectedPromise): PromiseInterface { - $nextCallableCalled = true; - + function (RequestInterface $requestArg) use ($expectedPromise): PromiseInterface { $this->assertSame('gzip', $requestArg->getHeaderLine('Content-Encoding')); $this->assertSame(gzcompress('foo', -1, ZLIB_ENCODING_GZIP), (string) $requestArg->getBody()); return $expectedPromise; }, - static function () {} + static function (): void {} ) ); - - $this->assertTrue($nextCallableCalled); } } From a6bf8f41982269cd2d6c1aa5ac55672b515f6af6 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 7 May 2020 15:32:33 +0200 Subject: [PATCH 0554/1161] Encapsulate the event ID into its own value object (#1013) --- src/Event.php | 23 ++++++++----- src/EventId.php | 45 ++++++++++++++++++++++++++ src/Transport/HttpTransport.php | 2 +- src/Transport/NullTransport.php | 2 +- src/Transport/SpoolTransport.php | 2 +- tests/EventIdTest.php | 36 +++++++++++++++++++++ tests/EventTest.php | 20 +++++++++--- tests/Transport/NullTransportTest.php | 2 +- tests/Transport/SpoolTransportTest.php | 4 +-- 9 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 src/EventId.php create mode 100644 tests/EventIdTest.php diff --git a/src/Event.php b/src/Event.php index 77eeb6f16..bfd875e9d 100644 --- a/src/Event.php +++ b/src/Event.php @@ -19,7 +19,7 @@ final class Event implements \JsonSerializable { /** - * @var string The UUID + * @var EventId The ID */ private $id; @@ -150,14 +150,13 @@ final class Event implements \JsonSerializable private $sdkVersion; /** - * Event constructor. + * Class constructor. * - * @throws \InvalidArgumentException - * @throws \Exception + * @param EventId|null $eventId The ID of the event */ - public function __construct() + public function __construct(?EventId $eventId = null) { - $this->id = str_replace('-', '', uuid_create(UUID_TYPE_RANDOM)); + $this->id = $eventId ?? EventId::generate(); $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); $this->level = Severity::error(); $this->serverOsContext = new ServerOsContext(); @@ -170,9 +169,17 @@ public function __construct() /** * Gets the UUID of this event. + * + * @return string|EventId */ - public function getId(): string + public function getId(bool $returnAsString = true) { + if ($returnAsString) { + @trigger_error(sprintf('Calling the method %s() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0.', __METHOD__), E_USER_DEPRECATED); + + return (string) $this->id; + } + return $this->id; } @@ -579,7 +586,7 @@ public function setStacktrace(?Stacktrace $stacktrace): void public function toArray(): array { $data = [ - 'event_id' => $this->id, + 'event_id' => (string) $this->id, 'timestamp' => $this->timestamp, 'level' => (string) $this->level, 'platform' => 'php', diff --git a/src/EventId.php b/src/EventId.php new file mode 100644 index 000000000..d345e9a5e --- /dev/null +++ b/src/EventId.php @@ -0,0 +1,45 @@ + + */ +final class EventId +{ + /** + * @var string The ID + */ + private $value; + + /** + * Class constructor. + * + * @param string $value The ID + */ + public function __construct(string $value) + { + if (!preg_match('/^[a-f0-9]{32}$/i', $value)) { + throw new \InvalidArgumentException('The $value argument must be a 32 characters long hexadecimal string.'); + } + + $this->value = $value; + } + + /** + * Generates a new event ID. + */ + public static function generate(): self + { + return new self(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM))); + } + + public function __toString(): string + { + return $this->value; + } +} diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index aed2e0f5c..2e440bece 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -142,7 +142,7 @@ public function send(Event $event): ?string } } - return $event->getId(); + return (string) $event->getId(false); } /** diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index 6597448c8..02dc30a45 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -20,6 +20,6 @@ class NullTransport implements TransportInterface */ public function send(Event $event): ?string { - return $event->getId(); + return (string) $event->getId(false); } } diff --git a/src/Transport/SpoolTransport.php b/src/Transport/SpoolTransport.php index 10bebd8bb..c988c5e44 100644 --- a/src/Transport/SpoolTransport.php +++ b/src/Transport/SpoolTransport.php @@ -43,7 +43,7 @@ public function getSpool(): SpoolInterface public function send(Event $event): ?string { if ($this->spool->queueEvent($event)) { - return $event->getId(); + return (string) $event->getId(false); } return null; diff --git a/tests/EventIdTest.php b/tests/EventIdTest.php new file mode 100644 index 000000000..2e18b0fbf --- /dev/null +++ b/tests/EventIdTest.php @@ -0,0 +1,36 @@ +assertSame($value, (string) new EventId($value)); + } + + /** + * @dataProvider constructorThrowsOnInvalidValueDataProvider + */ + public function testConstructorThrowsOnInvalidValue(string $value): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $value argument must be a 32 characters long hexadecimal string.'); + + new EventId($value); + } + + public function constructorThrowsOnInvalidValueDataProvider(): \Generator + { + yield 'Value too long' => ['566e3688a61d4bc888951642d6f14a199']; + yield 'Value too short' => ['566e3688a61d4bc888951642d6f14a1']; + yield 'Value with invalid characters' => ['566e3688a61d4bc888951642d6f14a1g']; + } +} diff --git a/tests/EventTest.php b/tests/EventTest.php index 05b15381b..0da88d4a2 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -23,9 +23,19 @@ public function testEventIsGeneratedWithUniqueIdentifier(): void $event1 = new Event(); $event2 = new Event(); - $this->assertRegExp('/^[a-z0-9]{32}$/', $event1->getId()); - $this->assertRegExp('/^[a-z0-9]{32}$/', $event2->getId()); - $this->assertNotEquals($event1->getId(), $event2->getId()); + $this->assertNotEquals($event1->getId(false), $event2->getId(false)); + } + + /** + * @group legacy + * + * @expectedDeprecation Calling the method Sentry\Event::getId() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0. + */ + public function testGetEventIdThrowsDeprecationErrorIfExpectingStringReturnType(): void + { + $event = new Event(); + + $this->assertSame($event->getId(), (string) $event->getId(false)); } public function testToArray(): void @@ -33,7 +43,7 @@ public function testToArray(): void $event = new Event(); $expected = [ - 'event_id' => $event->getId(), + 'event_id' => (string) $event->getId(false), 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), 'level' => 'error', 'platform' => 'php', @@ -66,7 +76,7 @@ public function testToArrayMergesCustomContextsWithDefaultContexts(): void $event->setContext('runtime', ['baz' => 'baz']); $expected = [ - 'event_id' => $event->getId(), + 'event_id' => (string) $event->getId(false), 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), 'level' => 'error', 'platform' => 'php', diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php index c9133442b..ad69ce251 100644 --- a/tests/Transport/NullTransportTest.php +++ b/tests/Transport/NullTransportTest.php @@ -15,6 +15,6 @@ public function testSend(): void $transport = new NullTransport(); $event = new Event(); - $this->assertEquals($event->getId(), $transport->send($event)); + $this->assertSame((string) $event->getId(false), $transport->send($event)); } } diff --git a/tests/Transport/SpoolTransportTest.php b/tests/Transport/SpoolTransportTest.php index 501eefa61..b0e8ff14d 100644 --- a/tests/Transport/SpoolTransportTest.php +++ b/tests/Transport/SpoolTransportTest.php @@ -13,7 +13,7 @@ final class SpoolTransportTest extends TestCase { /** - * @var SpoolInterface|MockObject + * @var SpoolInterface&MockObject */ protected $spool; @@ -48,7 +48,7 @@ public function testSend(bool $isSendingSuccessful): void $eventId = $this->transport->send($event); if ($isSendingSuccessful) { - $this->assertSame($event->getId(), $eventId); + $this->assertSame((string) $event->getId(false), $eventId); } else { $this->assertNull($eventId); } From 9309740c5a10540158e128549b8fc7b14a977baf Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 18 May 2020 10:02:27 +0200 Subject: [PATCH 0555/1161] Fix BC break introduced with #1003 (#1018) --- src/Stacktrace.php | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Stacktrace.php b/src/Stacktrace.php index af4510975..f6e71caad 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -141,18 +141,21 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void } $frame = new Frame($functionName, $this->stripPrefixFromFilePath($file), $line); - $sourceCodeExcerpt = $this->getSourceCodeExcerpt($file, $line, $this->options->getContextLines()); - if (isset($sourceCodeExcerpt['pre_context'])) { - $frame->setPreContext($sourceCodeExcerpt['pre_context']); - } + if (null !== $this->options->getContextLines()) { + $sourceCodeExcerpt = $this->getSourceCodeExcerpt($file, $line, $this->options->getContextLines()); - if (isset($sourceCodeExcerpt['context_line'])) { - $frame->setContextLine($sourceCodeExcerpt['context_line']); - } + if (isset($sourceCodeExcerpt['pre_context'])) { + $frame->setPreContext($sourceCodeExcerpt['pre_context']); + } - if (isset($sourceCodeExcerpt['post_context'])) { - $frame->setPostContext($sourceCodeExcerpt['post_context']); + if (isset($sourceCodeExcerpt['context_line'])) { + $frame->setContextLine($sourceCodeExcerpt['context_line']); + } + + if (isset($sourceCodeExcerpt['post_context'])) { + $frame->setPostContext($sourceCodeExcerpt['post_context']); + } } $frame->setIsInApp($this->isFrameInApp($file, $functionName)); @@ -214,13 +217,13 @@ public function jsonSerialize() /** * Gets an excerpt of the source code around a given line. * - * @param string $path The file path - * @param int $lineNumber The line to centre about - * @param int|null $maxLinesToFetch The maximum number of lines to fetch + * @param string $path The file path + * @param int $lineNumber The line to centre about + * @param int $maxLinesToFetch The maximum number of lines to fetch */ - protected function getSourceCodeExcerpt(string $path, int $lineNumber, ?int $maxLinesToFetch): array + protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array { - if (null === $maxLinesToFetch || @!is_readable($path) || !is_file($path)) { + if (@!is_readable($path) || !is_file($path)) { return []; } From 2483b41560559f16c37cddac138792ea0232d8e4 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 19 May 2020 12:19:23 +0200 Subject: [PATCH 0556/1161] Add the FrameContextifierIntegration integration (#1011) --- CHANGELOG.md | 1 + phpstan.neon | 35 +--- src/Client.php | 11 +- src/Event.php | 6 +- src/EventFactory.php | 38 ++-- src/Frame.php | 48 ++++- .../FrameContextifierIntegration.php | 150 ++++++++++++++ src/Options.php | 13 +- src/Stacktrace.php | 91 ++++++--- tests/ClientBuilderTest.php | 3 + tests/ClientTest.php | 25 ++- tests/EventFactoryTest.php | 64 +++++- .../Plugin/GzipEncoderPluginTest.php | 2 +- .../FrameContextifierIntegrationTest.php | 185 ++++++++++++++++++ tests/Integration/RequestIntegrationTest.php | 2 +- tests/OptionsTest.php | 9 +- tests/Serializer/SerializerTest.php | 8 +- tests/StacktraceTest.php | 67 +++++-- tests/State/ScopeTest.php | 2 +- 19 files changed, 642 insertions(+), 118 deletions(-) create mode 100644 src/Integration/FrameContextifierIntegration.php create mode 100644 tests/Integration/FrameContextifierIntegrationTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 188ed3a57..b579adba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Refactor the `ModulesIntegration` integration to improve its code and its tests (#990) - Extract the parsing and validation logic of the DSN into its own value object (#995) - Support passing either a Httplug or PSR-17 stream factory to the `GzipEncoderPlugin` class (#1012) +- Add the `FrameContextifierIntegration` integration (#1011) - Add missing validation for the `context_lines` option and fix its behavior when passing `null` to make it working as described in the documentation (#1003) ## 2.3.2 (2020-03-06) diff --git a/phpstan.neon b/phpstan.neon index 77a59191d..282f5a727 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,12 +7,6 @@ parameters: ignoreErrors: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" - - - message: /^Cannot assign offset 'os' to array\|string\.$/ - path: src/Event.php - - - message: '/^array\|string does not accept array\.$/' - path: src/Event.php - message: '/^Argument of an invalid type array\|object supplied for foreach, only iterables are supported\.$/' path: src/Util/JSON.php @@ -20,31 +14,16 @@ parameters: message: '/^Access to constant (?:PROXY|TIMEOUT|CONNECT_TIMEOUT) on an unknown class GuzzleHttp\\RequestOptions\.$/' path: src/HttpClient/HttpClientFactory.php - - message: '/^Parameter #2 \$str of function explode expects string, int\|string given\.$/' - path: src/Dsn.php - - - message: '/^Parameter #1 \$haystack of function strrpos expects string, int\|string given\.$/' - path: src/Dsn.php - - - message: '/^Parameter #1 \$str of function substr expects string, int\|string given\.$/' - path: src/Dsn.php - - - message: '/^Parameter #2 \$host of class Sentry\\Dsn constructor expects string, int\|string given\.$/' - path: src/Dsn.php - - - message: '/^Parameter #3 \$port of class Sentry\\Dsn constructor expects int, int\|string given\.$/' - path: src/Dsn.php - - - message: '/^Parameter #5 \$path of class Sentry\\Dsn constructor expects string, int\|string given\.$/' + message: '/^Parameter #1 \$c of function ctype_digit expects int\|string, string\|null given\.$/' path: src/Dsn.php - - message: '/^Parameter #6 \$publicKey of class Sentry\\Dsn constructor expects string, int\|string given\.$/' + message: "/^Offset 'scheme' does not exist on array\\(\\?'scheme' => string, \\?'host' => string, \\?'port' => int, \\?'user' => string, \\?'pass' => string, \\?'path' => string, \\?'query' => string, \\?'fragment' => string\\)\\.$/" path: src/Dsn.php - - message: '/^Parameter #7 \$secretKey of class Sentry\\Dsn constructor expects string\|null, int\|string\|null given\.$/' + message: "/^Offset 'path' does not exist on array\\(\\?'host' => string, \\?'port' => int, \\?'user' => string, \\?'pass' => string, \\?'path' => string, \\?'query' => string, \\?'fragment' => string, 'scheme' => 'http'|'https'\\)\\.$/" path: src/Dsn.php - - message: '/^Parameter #1 \$c of function ctype_digit expects int\|string, string\|null given\.$/' + message: "/^Offset 'user' does not exist on array\\('scheme' => 'http'\\|'https', \\?'host' => string, \\?'port' => int, \\?'user' => string, \\?'pass' => string, \\?'path' => string, \\?'query' => string, \\?'fragment' => string\\)\\.$/" path: src/Dsn.php - message: '/^Method Sentry\\Monolog\\Handler::write\(\) has parameter \$record with no value type specified in iterable type array\.$/' @@ -55,6 +34,12 @@ parameters: - message: '/^Method Sentry\\Client::getIntegration\(\) should return T of Sentry\\Integration\\IntegrationInterface\|null but returns Sentry\\Integration\\IntegrationInterface\|null\.$/' path: src/Client.php + - + message: '/^Method Sentry\\EventFactoryInterface::createWithStacktrace\(\) invoked with 2 parameters, 1 required\.$/' + path: src/Client.php + - + message: '/^Method Sentry\\EventFactoryInterface::create\(\) invoked with 2 parameters, 1 required\.$/' + path: src/Client.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Client.php b/src/Client.php index 9c5ff91cb..cf001ce4a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -8,6 +8,7 @@ use GuzzleHttp\Promise\PromiseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\Handler; use Sentry\Integration\IgnoreErrorsIntegration; use Sentry\Integration\IntegrationInterface; @@ -168,14 +169,18 @@ public function flush(?int $timeout = null): PromiseInterface * @param array $payload The payload that will be converted to an Event * @param Scope|null $scope Optional scope which enriches the Event * - * @return Event|null returns ready to send Event, however depending on options it can be discarded + * @return Event|null The prepared event object or null if it must be discarded */ private function prepareEvent(array $payload, ?Scope $scope = null): ?Event { + $shouldReadSourceCodeExcerpts = !isset($this->integrations[FrameContextifierIntegration::class]) && null !== $this->options->getContextLines(); + if ($this->options->shouldAttachStacktrace() && !isset($payload['exception']) && !isset($payload['stacktrace'])) { - $event = $this->eventFactory->createWithStacktrace($payload); + /** @psalm-suppress TooManyArguments */ + $event = $this->eventFactory->createWithStacktrace($payload, $shouldReadSourceCodeExcerpts); } else { - $event = $this->eventFactory->create($payload); + /** @psalm-suppress TooManyArguments */ + $event = $this->eventFactory->create($payload, $shouldReadSourceCodeExcerpts); } $sampleRate = $this->options->getSampleRate(); diff --git a/src/Event.php b/src/Event.php index bfd875e9d..c38b94be2 100644 --- a/src/Event.php +++ b/src/Event.php @@ -531,7 +531,7 @@ public function setBreadcrumb(array $breadcrumbs): void /** * Gets the exception. * - * @return array + * @return array> * * @psalm-return list> $exceptions The exception + * @param array> $exceptions The exceptions * * @psalm-param listcreate($payload); - - if (!$event->getStacktrace()) { - $stacktrace = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), __FILE__, __LINE__); - - $event->setStacktrace($stacktrace); + if (!isset($payload['stacktrace']) || !$payload['stacktrace'] instanceof Stacktrace) { + $payload['stacktrace'] = Stacktrace::createFromBacktrace( + $this->options, + $this->serializer, + $this->representationSerializer, + debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), + __FILE__, + __LINE__ - 6, + \func_num_args() > 1 ? func_get_arg(1) : true + ); } - return $event; + return $this->create($payload); } /** * {@inheritdoc} */ - public function create(array $payload): Event + public function create(array $payload/*, bool $shouldReadSourceCodeExcerpts = true*/): Event { try { $event = new Event(); @@ -103,7 +107,7 @@ public function create(array $payload): Event } if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { - $this->addThrowableToEvent($event, $payload['exception']); + $this->addThrowableToEvent($event, $payload['exception'], \func_num_args() > 1 ? func_get_arg(1) : true); } if (isset($payload['level']) && $payload['level'] instanceof Severity) { @@ -120,10 +124,15 @@ public function create(array $payload): Event /** * Stores the given exception in the passed event. * - * @param Event $event The event that will be enriched with the exception - * @param \Throwable $exception The exception that will be processed and added to the event + * @param Event $event The event that will be enriched with the + * exception + * @param \Throwable $exception The exception that will be processed and + * added to the event + * @param bool $shouldReadSourceCodeExcerpts Whether to read the source code excerpts + * using the legacy method instead of using + * the integration */ - private function addThrowableToEvent(Event $event, \Throwable $exception): void + private function addThrowableToEvent(Event $event, \Throwable $exception, bool $shouldReadSourceCodeExcerpts): void { if ($exception instanceof \ErrorException) { $event->setLevel(Severity::fromError($exception->getSeverity())); @@ -142,7 +151,8 @@ private function addThrowableToEvent(Event $event, \Throwable $exception): void $this->representationSerializer, $currentException->getTrace(), $currentException->getFile(), - $currentException->getLine() + $currentException->getLine(), + $shouldReadSourceCodeExcerpts ), ]; } while ($currentException = $currentException->getPrevious()); diff --git a/src/Frame.php b/src/Frame.php index d5887fb41..d2f6a5e69 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -11,6 +11,8 @@ */ final class Frame implements \JsonSerializable { + private const INTERNAL_FRAME_FILENAME = '[internal]'; + /** * @var string|null The name of the function being called */ @@ -21,6 +23,11 @@ final class Frame implements \JsonSerializable */ private $file; + /** + * @var string The absolute path to the source file + */ + private $absoluteFilePath; + /** * @var int The line at which the frame originated */ @@ -48,26 +55,35 @@ final class Frame implements \JsonSerializable * @var bool Flag telling whether the frame is related to the execution of * the relevant code in this stacktrace */ - private $inApp = true; + private $inApp; /** - * @var array A mapping of variables which were available within this - * frame (usually context-locals) + * @var array A mapping of variables which were available within + * this frame (usually context-locals) */ private $vars = []; /** * Initializes a new instance of this class using the provided information. * - * @param string|null $functionName The name of the function being called - * @param string $file The file where the frame originated - * @param int $line The line at which the frame originated + * @param string|null $functionName The name of the function being called + * @param string $file The file where the frame originated + * @param string|null $absoluteFilePath The absolute path to the source file + * @param int $line The line at which the frame originated + * @param array $vars A mapping of variables which were available + * within the frame + * @param bool $inApp Whether the frame is related to the + * execution of code relevant to the + * application */ - public function __construct(?string $functionName, string $file, int $line) + public function __construct(?string $functionName, string $file, int $line, ?string $absoluteFilePath = null, array $vars = [], bool $inApp = true) { $this->functionName = $functionName; $this->file = $file; + $this->absoluteFilePath = $absoluteFilePath ?? $file; $this->line = $line; + $this->vars = $vars; + $this->inApp = $inApp; } /** @@ -86,6 +102,14 @@ public function getFile(): string return $this->file; } + /** + * Gets the absolute path to the source file. + */ + public function getAbsoluteFilePath(): string + { + return $this->absoluteFilePath; + } + /** * Gets the line at which the frame originated. */ @@ -196,6 +220,14 @@ public function setVars(array $vars): void $this->vars = $vars; } + /** + * Gets whether the frame is internal. + */ + public function isInternal(): bool + { + return self::INTERNAL_FRAME_FILENAME === $this->file; + } + /** * Returns an array representation of the data of this frame modeled according * to the specifications of the Sentry SDK Stacktrace Interface. @@ -205,6 +237,7 @@ public function setVars(array $vars): void * filename: string, * lineno: int, * in_app: bool, + * abs_path: string, * pre_context?: string[], * context_line?: string, * post_context?: string[], @@ -218,6 +251,7 @@ public function toArray(): array 'filename' => $this->file, 'lineno' => $this->line, 'in_app' => $this->inApp, + 'abs_path' => $this->absoluteFilePath, ]; if (0 !== \count($this->preContext)) { diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php new file mode 100644 index 000000000..d1ce70a4e --- /dev/null +++ b/src/Integration/FrameContextifierIntegration.php @@ -0,0 +1,150 @@ + + */ +final class FrameContextifierIntegration implements IntegrationInterface +{ + /** + * @var LoggerInterface A PSR-3 logger + */ + private $logger; + + /** + * Creates a new instance of this integration. + * + * @param LoggerInterface $logger A PSR-3 logger + */ + public function __construct(?LoggerInterface $logger = null) + { + $this->logger = $logger ?? new NullLogger(); + } + + /** + * {@inheritdoc} + */ + public function setupOnce(): void + { + Scope::addGlobalEventProcessor(static function (Event $event): Event { + $client = SentrySdk::getCurrentHub()->getClient(); + + if (null === $client) { + return $event; + } + + $maxContextLines = $client->getOptions()->getContextLines(); + $integration = $client->getIntegration(self::class); + + if (null === $integration || null === $maxContextLines) { + return $event; + } + + if (null !== $event->getStacktrace()) { + $integration->addContextToStacktraceFrames($maxContextLines, $event->getStacktrace()); + } + + foreach ($event->getExceptions() as $exception) { + if (!isset($exception['stacktrace'])) { + continue; + } + + $integration->addContextToStacktraceFrames($maxContextLines, $exception['stacktrace']); + } + + return $event; + }); + } + + /** + * Contextifies the frames of the given stacktrace. + * + * @param int $maxContextLines The maximum number of lines of code to read + * @param Stacktrace $stacktrace The stacktrace object + */ + private function addContextToStacktraceFrames(int $maxContextLines, Stacktrace $stacktrace): void + { + foreach ($stacktrace->getFrames() as $frame) { + if ($frame->isInternal()) { + continue; + } + + $sourceCodeExcerpt = $this->getSourceCodeExcerpt($maxContextLines, $frame->getAbsoluteFilePath(), $frame->getLine()); + + $frame->setPreContext($sourceCodeExcerpt['pre_context']); + $frame->setContextLine($sourceCodeExcerpt['context_line']); + $frame->setPostContext($sourceCodeExcerpt['post_context']); + } + } + + /** + * Gets an excerpt of the source code around a given line. + * + * @param int $maxContextLines The maximum number of lines of code to read + * @param string $filePath The file path + * @param int $lineNumber The line to centre about + * + * @return array + * + * @psalm-return array{ + * pre_context: string[], + * context_line: string|null, + * post_context: string[] + * } + */ + private function getSourceCodeExcerpt(int $maxContextLines, string $filePath, int $lineNumber): array + { + $frame = [ + 'pre_context' => [], + 'context_line' => null, + 'post_context' => [], + ]; + + $target = max(0, ($lineNumber - ($maxContextLines + 1))); + $currentLineNumber = $target + 1; + + try { + $file = new \SplFileObject($filePath); + $file->seek($target); + + while (!$file->eof()) { + /** @var string $line */ + $line = $file->current(); + $line = rtrim($line, "\r\n"); + + if ($currentLineNumber === $lineNumber) { + $frame['context_line'] = $line; + } elseif ($currentLineNumber < $lineNumber) { + $frame['pre_context'][] = $line; + } elseif ($currentLineNumber > $lineNumber) { + $frame['post_context'][] = $line; + } + + ++$currentLineNumber; + + if ($currentLineNumber > $lineNumber + $maxContextLines) { + break; + } + + $file->next(); + } + } catch (\Throwable $exception) { + $this->logger->warning(sprintf('Failed to get the source code excerpt for the file "%s".', $filePath)); + } + + return $frame; + } +} diff --git a/src/Options.php b/src/Options.php index 84769f520..01b83108c 100644 --- a/src/Options.php +++ b/src/Options.php @@ -7,6 +7,7 @@ use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; +use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; use Sentry\Integration\TransactionIntegration; @@ -21,7 +22,8 @@ final class Options { /** - * The default maximum number of breadcrumbs that will be sent with an event. + * The default maximum number of breadcrumbs that will be sent with an + * event. */ public const DEFAULT_MAX_BREADCRUMBS = 100; @@ -210,7 +212,7 @@ public function setEnvironment(string $environment): void */ public function getExcludedExceptions(): array { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "Sentry\Integration\IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); return $this->options['excluded_exceptions']; } @@ -225,7 +227,7 @@ public function getExcludedExceptions(): array */ public function setExcludedExceptions(array $exceptions): void { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "Sentry\Integration\IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); $options = array_merge($this->options, ['excluded_exceptions' => $exceptions]); @@ -246,7 +248,7 @@ public function setExcludedExceptions(array $exceptions): void public function isExcludedException(\Throwable $exception, bool $throwDeprecation = true): bool { if ($throwDeprecation) { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "Sentry\Integration\IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); } foreach ($this->options['excluded_exceptions'] as $exceptionClass) { @@ -872,7 +874,7 @@ private function configureOptions(OptionsResolver $resolver): void return null; } - @trigger_error('The option "project_root" is deprecated. Please use the "in_app_include" option instead.', E_USER_DEPRECATED); + @trigger_error('The option "project_root" is deprecated. Use the "in_app_include" option instead.', E_USER_DEPRECATED); return $this->normalizeAbsolutePath($value); }); @@ -1064,6 +1066,7 @@ private function getDefaultIntegrations(): array new FatalErrorListenerIntegration(), new RequestIntegration(), new TransactionIntegration(), + new FrameContextifierIntegration(), ]; } diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 7e9c816c0..55c4d035f 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -18,6 +18,16 @@ class Stacktrace implements \JsonSerializable { private const INTERNAL_FRAME_FILENAME = '[internal]'; + /** + * @var bool Flag indicating whether it's responsibility of this class to + * read the source code excerpts for each frame + * + * @internal + * + * @deprecated since version 2.4, to be removed in 3.0 + */ + protected $shouldReadSourceCodeExcerpts = false; + /** * @var Options The client options */ @@ -51,14 +61,20 @@ class Stacktrace implements \JsonSerializable ]; /** - * Stacktrace constructor. + * Constructor. * * @param Options $options The client options * @param SerializerInterface $serializer The serializer * @param RepresentationSerializerInterface $representationSerializer The representation serializer */ - public function __construct(Options $options, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer) + public function __construct(Options $options, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer/*, bool $shouldReadSourceCodeExcerpts = true*/) { + if (\func_num_args() <= 3 || false !== func_get_arg(3)) { + @trigger_error(sprintf('Relying on the "%s" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead.', self::class), E_USER_DEPRECATED); + + $this->shouldReadSourceCodeExcerpts = true; + } + $this->options = $options; $this->serializer = $serializer; $this->representationSerializer = $representationSerializer; @@ -76,9 +92,9 @@ public function __construct(Options $options, SerializerInterface $serializer, R * * @return static */ - public static function createFromBacktrace(Options $options, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer, array $backtrace, string $file, int $line) + public static function createFromBacktrace(Options $options, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer, array $backtrace, string $file, int $line/*, bool $shouldReadSourceCodeExcerpts = true*/) { - $stacktrace = new static($options, $serializer, $representationSerializer); + $stacktrace = new static($options, $serializer, $representationSerializer, \func_num_args() > 6 ? func_get_arg(6) : true); foreach ($backtrace as $frame) { $stacktrace->addFrame($file, $line, $frame); @@ -103,6 +119,13 @@ public function getFrames(): array return $this->frames; } + /** + * Gets the frame at the given index. + * + * @param int $index The index from which the frame should be get + * + * @throws \OutOfBoundsException + */ public function getFrame(int $index): Frame { if ($index < 0 || $index >= \count($this->frames)) { @@ -142,37 +165,41 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void $functionName = null; } - $frame = new Frame($functionName, $this->stripPrefixFromFilePath($file), $line); - $sourceCodeExcerpt = $this->getSourceCodeExcerpt($file, $line, $this->options->getContextLines()); - - if (isset($sourceCodeExcerpt['pre_context'])) { - $frame->setPreContext($sourceCodeExcerpt['pre_context']); - } + $frameArguments = $this->getFrameArguments($backtraceFrame); - if (isset($sourceCodeExcerpt['context_line'])) { - $frame->setContextLine($sourceCodeExcerpt['context_line']); - } + foreach ($frameArguments as $argumentName => $argumentValue) { + $argumentValue = $this->representationSerializer->representationSerialize($argumentValue); - if (isset($sourceCodeExcerpt['post_context'])) { - $frame->setPostContext($sourceCodeExcerpt['post_context']); + if (\is_string($argumentValue)) { + $frameArguments[(string) $argumentName] = mb_substr($argumentValue, 0, $this->options->getMaxValueLength()); + } else { + $frameArguments[(string) $argumentName] = $argumentValue; + } } - $frame->setIsInApp($this->isFrameInApp($file, $functionName)); + $frame = new Frame( + $functionName, + $this->stripPrefixFromFilePath($file), + $line, + $file, + $frameArguments, + $this->isFrameInApp($file, $functionName) + ); - $frameArguments = $this->getFrameArguments($backtraceFrame); + if ($this->shouldReadSourceCodeExcerpts && null !== $this->options->getContextLines()) { + $sourceCodeExcerpt = $this->getSourceCodeExcerpt($file, $line, $this->options->getContextLines()); - if (!empty($frameArguments)) { - foreach ($frameArguments as $argumentName => $argumentValue) { - $argumentValue = $this->representationSerializer->representationSerialize($argumentValue); + if (isset($sourceCodeExcerpt['pre_context'])) { + $frame->setPreContext($sourceCodeExcerpt['pre_context']); + } - if (\is_string($argumentValue)) { - $frameArguments[(string) $argumentName] = mb_substr($argumentValue, 0, $this->options->getMaxValueLength()); - } else { - $frameArguments[(string) $argumentName] = $argumentValue; - } + if (isset($sourceCodeExcerpt['context_line'])) { + $frame->setContextLine($sourceCodeExcerpt['context_line']); } - $frame->setVars($frameArguments); + if (isset($sourceCodeExcerpt['post_context'])) { + $frame->setPostContext($sourceCodeExcerpt['post_context']); + } } array_unshift($this->frames, $frame); @@ -218,21 +245,23 @@ public function jsonSerialize() /** * Gets an excerpt of the source code around a given line. * - * @param string $path The file path - * @param int $lineNumber The line to centre about - * @param int|null $maxLinesToFetch The maximum number of lines to fetch + * @param string $path The file path + * @param int $lineNumber The line to centre about + * @param int $maxLinesToFetch The maximum number of lines to fetch * * @return array * + * @deprecated since version 2.4, to be removed in 3.0 + * * @psalm-return array{ * pre_context?: string[], * context_line?: string, * post_context?: string[] * } */ - protected function getSourceCodeExcerpt(string $path, int $lineNumber, ?int $maxLinesToFetch): array + protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array { - if (null === $maxLinesToFetch || @!is_readable($path) || !is_file($path)) { + if (@!is_readable($path) || !is_file($path)) { return []; } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 0e61ed983..b9183e970 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -21,6 +21,7 @@ use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; +use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; use Sentry\Integration\TransactionIntegration; @@ -210,6 +211,7 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array ExceptionListenerIntegration::class, RequestIntegration::class, TransactionIntegration::class, + FrameContextifierIntegration::class, ], ], [ @@ -221,6 +223,7 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array ExceptionListenerIntegration::class, RequestIntegration::class, TransactionIntegration::class, + FrameContextifierIntegration::class, StubIntegration::class, ], ], diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 8e5fad4b0..430e151c7 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -69,7 +69,7 @@ public function testCaptureException(): void /** * @dataProvider captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesDataProvider */ - public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches(bool $shouldCapture, string $excluded, \Throwable $thrown): void + public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches(bool $shouldCapture, string $excluded, \Throwable $thrownException): void { /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -88,7 +88,7 @@ public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); - SentrySdk::getCurrentHub()->captureException($thrown); + SentrySdk::getCurrentHub()->captureException($thrownException); } public function captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesDataProvider(): array @@ -259,6 +259,27 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void $client->captureLastError(); } + /** + * @group legacy + * + * @dataProvider captureEventThrowsDeprecationErrorIfContextLinesOptionIsNotNullAndFrameContextifierIntegrationIsNotUsedDataProvider + * + * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. + */ + public function testCaptureEventThrowsDeprecationErrorIfContextLinesOptionIsNotNullAndFrameContextifierIntegrationIsNotUsed(array $payload): void + { + ClientBuilder::create(['attach_stacktrace' => true, 'default_integrations' => false]) + ->getClient() + ->captureEvent($payload); + } + + public function captureEventThrowsDeprecationErrorIfContextLinesOptionIsNotNullAndFrameContextifierIntegrationIsNotUsedDataProvider(): \Generator + { + yield [[]]; + + yield [['exception' => new \Exception()]]; + } + /** * @group legacy * diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index 717e20679..bd767299e 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -133,7 +133,7 @@ public function testCreateWithException(): void '1.2.3' ); - $event = $eventFactory->create(['exception' => $exception]); + $event = $eventFactory->create(['exception' => $exception], false); $expectedData = [ [ 'type' => \Exception::class, @@ -164,7 +164,7 @@ public function testCreateWithErrorException(): void '1.2.3' ); - $event = $eventFactory->create(['exception' => $exception]); + $event = $eventFactory->create(['exception' => $exception], false); $this->assertTrue(Severity::error()->isEqualTo($event->getLevel())); } @@ -182,7 +182,7 @@ public function testCreateWithStacktrace(): void '1.2.3' ); - $event = $eventFactory->createWithStacktrace([]); + $event = $eventFactory->createWithStacktrace([], false); $stacktrace = $event->getStacktrace(); $this->assertInstanceOf(Stacktrace::class, $stacktrace); @@ -195,4 +195,62 @@ public function testCreateWithStacktrace(): void ltrim($lastFrame->getFile(), \DIRECTORY_SEPARATOR) ); } + + /** + * @group legacy + * + * @dataProvider createThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider + * + * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. + */ + public function testCreateThrowsDeprecationErrorIfLastArgumentIsNotSetToFalse(array ...$constructorArguments): void + { + $options = new Options(); + $eventFactory = new EventFactory( + new Serializer($options), + $this->createMock(RepresentationSerializerInterface::class), + $options, + 'sentry.sdk.identifier', + '1.2.3', + ...$constructorArguments + ); + + $eventFactory->create(['exception' => new \Exception()]); + } + + /** + * @group legacy + * + * @dataProvider createThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider + * + * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. + */ + public function testCreateWithStacktraceThrowsDeprecationErrorIfLastArgumentIsNotSetToFalse(array ...$constructorArguments): void + { + $options = new Options(); + $eventFactory = new EventFactory( + new Serializer($options), + $this->createMock(RepresentationSerializerInterface::class), + $options, + 'sentry.sdk.identifier', + '1.2.3', + ...$constructorArguments + ); + + $eventFactory->createWithStacktrace([]); + } + + public function createThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider(): \Generator + { + yield [[true]]; + + yield [[1]]; + + yield [['foo']]; + + yield [[new class() { + }]]; + + yield [[]]; + } } diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php index e87950251..4a88e8a57 100644 --- a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php +++ b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php @@ -56,7 +56,7 @@ public function constructorThrowsIfArgumentsAreInvalidDataProvider(): \Generator * * @expectedDeprecation A PSR-17 stream factory is needed as argument of the constructor of the "Sentry\HttpClient\Plugin\GzipEncoderPlugin" class since version 2.1.3 and will be required in 3.0. */ - public function testConstructorThrowsDeprecationIfNoStreamFactoryIsProvided(): void + public function testConstructorThrowsDeprecationErrorIfNoStreamFactoryIsProvided(): void { new GzipEncoderPlugin(); } diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php new file mode 100644 index 000000000..badbc7e5c --- /dev/null +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -0,0 +1,185 @@ +serializer = $this->createMock(SerializerInterface::class); + $this->representationSerializer = $this->createMock(RepresentationSerializerInterface::class); + } + + /** + * @group legacy + * + * @dataProvider invokeDataProvider + */ + public function testInvoke(string $fixtureFilePath, int $lineNumber, int $contextLines, int $preContextCount, int $postContextCount): void + { + $options = new Options(['context_lines' => $contextLines]); + $integration = new FrameContextifierIntegration(); + $integration->setupOnce(); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getIntegration') + ->with(FrameContextifierIntegration::class) + ->willReturn($integration); + + $client->expects($this->once()) + ->method('getOptions') + ->willReturn($options); + + SentrySdk::getCurrentHub()->bindClient($client); + + $stacktrace = new Stacktrace($options, $this->serializer, $this->representationSerializer, false); + $stacktrace->addFrame($fixtureFilePath, $lineNumber, ['function' => '[unknown]']); + + $event = new Event(); + $event->setStacktrace($stacktrace); + + withScope(static function (Scope $scope) use (&$event): void { + $event = $scope->applyToEvent($event, []); + }); + + $this->assertNotNull($event); + $this->assertNotNull($event->getStacktrace()); + + $frames = $event->getStacktrace()->getFrames(); + + $this->assertCount(1, $frames); + $this->assertCount($preContextCount, $frames[0]->getPreContext()); + $this->assertCount($postContextCount, $frames[0]->getPostContext()); + + $fileContent = explode("\n", $this->getFixtureFileContent($fixtureFilePath)); + + for ($i = 0; $i < $preContextCount; ++$i) { + $this->assertSame(rtrim($fileContent[$i + ($lineNumber - $preContextCount - 1)]), $frames[0]->getPreContext()[$i]); + } + + $this->assertSame(rtrim($fileContent[$lineNumber - 1]), $frames[0]->getContextLine()); + + for ($i = 0; $i < $postContextCount; ++$i) { + $this->assertSame(rtrim($fileContent[$i + $lineNumber]), $frames[0]->getPostContext()[$i]); + } + } + + public function invokeDataProvider(): \Generator + { + yield 'short file' => [ + realpath(__DIR__ . '/../Fixtures/code/ShortFile.php'), + 3, + 2, + 2, + 2, + ]; + + yield 'long file with specified context' => [ + realpath(__DIR__ . '/../Fixtures/code/LongFile.php'), + 8, + 2, + 2, + 2, + ]; + + yield 'long file near end of file' => [ + realpath(__DIR__ . '/../Fixtures/code/LongFile.php'), + 11, + 5, + 5, + 2, + ]; + + yield 'long file near beginning of file' => [ + realpath(__DIR__ . '/../Fixtures/code/LongFile.php'), + 3, + 5, + 2, + 5, + ]; + } + + public function testInvokeLogsWarningMessageIfSourceCodeExcerptCannotBeRetrievedForFrame(): void + { + /** @var MockObject&LoggerInterface $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('warning') + ->with('Failed to get the source code excerpt for the file "file.ext".'); + + $options = new Options(); + $integration = new FrameContextifierIntegration($logger); + $integration->setupOnce(); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getIntegration') + ->with(FrameContextifierIntegration::class) + ->willReturn($integration); + + $client->expects($this->once()) + ->method('getOptions') + ->willReturn($options); + + SentrySdk::getCurrentHub()->bindClient($client); + + $stacktrace = new Stacktrace(new Options(), $this->serializer, $this->representationSerializer, false); + $stacktrace->addFrame('[internal]', 0, []); + $stacktrace->addFrame('file.ext', 10, []); + + $event = new Event(); + $event->setStacktrace($stacktrace); + + withScope(static function (Scope $scope) use (&$event): void { + $event = $scope->applyToEvent($event, []); + }); + + $this->assertNotNull($event); + + foreach ($stacktrace->getFrames() as $frame) { + $this->assertNull($frame->getContextLine()); + $this->assertEmpty($frame->getPreContext()); + $this->assertEmpty($frame->getPostContext()); + } + } + + private function getFixtureFileContent(string $file): string + { + $fileContent = file_get_contents($file); + + if (false === $fileContent) { + throw new \RuntimeException(sprintf('The fixture file at path "%s" could not be read.', $file)); + } + + return $fileContent; + } +} diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index cf2bfc5aa..c6335311f 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -27,7 +27,7 @@ final class RequestIntegrationTest extends TestCase * * @expectedDeprecation Passing the options as argument of the constructor of the "Sentry\Integration\RequestIntegration" class is deprecated since version 2.1 and will not work in 3.0. */ - public function testConstructorThrowsDeprecationIfPassingOptions(): void + public function testConstructorThrowsDeprecationErrorIfPassingOptions(): void { new RequestIntegration(new Options([])); } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 523d65a81..1e3679b69 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -9,6 +9,7 @@ use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; +use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestIntegration; use Sentry\Integration\TransactionIntegration; @@ -65,8 +66,8 @@ public function optionsDataProvider(): array ['tags', ['foo', 'bar'], 'getTags', 'setTags'], ['error_types', 0, 'getErrorTypes', 'setErrorTypes'], ['max_breadcrumbs', 50, 'getMaxBreadcrumbs', 'setMaxBreadcrumbs'], - ['before_send', function () {}, 'getBeforeSendCallback', 'setBeforeSendCallback'], - ['before_breadcrumb', function () {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback'], + ['before_send', static function (): void {}, 'getBeforeSendCallback', 'setBeforeSendCallback'], + ['before_breadcrumb', static function (): void {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback'], ['send_default_pii', true, 'shouldSendDefaultPii', 'setSendDefaultPii'], ['default_integrations', false, 'hasDefaultIntegrations', 'setDefaultIntegrations'], ['max_value_length', 50, 'getMaxValueLength', 'setMaxValueLength'], @@ -319,6 +320,8 @@ public function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array } /** + * @group legacy + * * @dataProvider contextLinesOptionValidatesInputValueDataProvider */ public function testContextLinesOptionValidatesInputValue(?int $value, ?string $expectedExceptionMessage): void @@ -436,6 +439,7 @@ public function setupOnce(): void new FatalErrorListenerIntegration(), new RequestIntegration(), new TransactionIntegration(), + new FrameContextifierIntegration(), $integration, ], ]; @@ -490,6 +494,7 @@ static function (array $defaultIntegrations): array { new FatalErrorListenerIntegration(), new RequestIntegration(), new TransactionIntegration(), + new FrameContextifierIntegration(), ], ]; } diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index bbf3bf702..e0a90eeff 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -209,16 +209,10 @@ public function testClippingUTF8Characters(): void } /** - * @param Options $options|null - * * @return Serializer */ protected function createSerializer(?Options $options = null): AbstractSerializer { - if (null === $options) { - $options = new Options(); - } - - return new Serializer($options); + return new Serializer($options ?? new Options()); } } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index e11408eae..6c10efff6 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -35,9 +35,19 @@ protected function setUp(): void $this->representationSerializer = new RepresentationSerializer($this->options); } + /** + * @group legacy + * + * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. + */ + public function testConstructorThrowsDeprecationErrorIfLastArgumentIsNotSetToFalse(): void + { + new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + } + public function testGetFramesAndToArray(): void { - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'class' => 'TestClass']); $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function']); @@ -54,7 +64,7 @@ public function testGetFramesAndToArray(): void public function testStacktraceJsonSerialization(): void { - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); @@ -70,7 +80,7 @@ public function testStacktraceJsonSerialization(): void public function testAddFrame(): void { - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); $frames = [ $this->getJsonFixture('frames/eval.json'), $this->getJsonFixture('frames/runtime_created.json'), @@ -93,7 +103,7 @@ public function testAddFrame(): void public function testAddFrameSerializesMethodArguments(): void { - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); $stacktrace->addFrame('path/to/file', 12, [ 'file' => 'path/to/file', 'line' => 12, @@ -112,7 +122,7 @@ public function testAddFrameStripsPath(): void { $this->options->setPrefixes(['path/to/', 'path/to/app']); - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); $stacktrace->addFrame('path/to/app/file', 12, ['function' => 'test_function_parent_parent_parent']); $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function_parent_parent']); @@ -138,7 +148,8 @@ public function testAddFrameSetsInAppFlagCorrectly(array $options, string $file, $stacktrace = new Stacktrace( $options, new Serializer($options), - new RepresentationSerializer($options) + new RepresentationSerializer($options), + false ); $stacktrace->addFrame($file, 0, ['function' => $functionName]); @@ -296,7 +307,11 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator } /** + * @group legacy + * * @dataProvider addFrameRespectsContextLinesOptionDataProvider + * + * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. */ public function testAddFrameRespectsContextLinesOption(string $fixture, int $lineNumber, ?int $contextLines, int $preContextCount, int $postContextCount): void { @@ -389,8 +404,7 @@ public function testRemoveFrame(int $index, bool $throwException): void $this->expectExceptionMessage('Invalid frame index to remove.'); } - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); - + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); $stacktrace->addFrame('path/to/file', 12, [ 'function' => 'test_function_parent', ]); @@ -421,7 +435,7 @@ public function removeFrameDataProvider(): array public function testFromBacktrace(): void { $fixture = $this->getJsonFixture('backtraces/exception.json'); - $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'], false)->getFrames(); $this->assertFrameEquals($frames[0], null, 'path/to/file', 16); $this->assertFrameEquals($frames[1], 'TestClass::crashyFunction', 'path/to/file', 7); @@ -431,7 +445,7 @@ public function testFromBacktrace(): void public function testFromBacktraceWithAnonymousFrame(): void { $fixture = $this->getJsonFixture('backtraces/anonymous_frame.json'); - $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'], false)->getFrames(); $this->assertFrameEquals($frames[0], null, 'path/to/file', 7); $this->assertFrameEquals($frames[1], 'call_user_func', '[internal]', 0); @@ -441,7 +455,7 @@ public function testFromBacktraceWithAnonymousFrame(): void public function testFromBacktraceWithAnonymousClass(): void { $fixture = $this->getJsonFixture('backtraces/anonymous_frame_with_memory_address.json'); - $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); + $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'], false)->getFrames(); $this->assertFrameEquals( $frames[0], @@ -449,6 +463,7 @@ public function testFromBacktraceWithAnonymousClass(): void '[internal]', 0 ); + $this->assertFrameEquals( $frames[1], 'class@anonymous/path/to/app/consumer.php::messageCallback', @@ -457,6 +472,32 @@ public function testFromBacktraceWithAnonymousClass(): void ); } + /** + * @group legacy + * + * @dataProvider createFromBacktraceThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider + * + * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. + */ + public function testCreateFromBacktraceThrowsDeprecationErrorIfLastArgumentIsNotSetToFalse(array ...$constructorArguments): void + { + Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, [], __FILE__, __LINE__, ...$constructorArguments); + } + + public function createFromBacktraceThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider(): \Generator + { + yield [[true]]; + + yield [[1]]; + + yield [['foo']]; + + yield [[new class() { + }]]; + + yield [[]]; + } + public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void { // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. @@ -477,7 +518,7 @@ public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void 'function' => 'a_test', ]; - $stacktrace = new Stacktrace(new Options(['max_value_length' => 5]), $this->serializer, $this->representationSerializer); + $stacktrace = new Stacktrace(new Options(['max_value_length' => 5]), $this->serializer, $this->representationSerializer, false); $result = $stacktrace->getFrameArguments($frame); // Check we haven't modified our vars. @@ -501,7 +542,7 @@ public function testPreserveXdebugFrameArgumentNames(): void 'function' => 'a_test', ]; - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); $result = $stacktrace->getFrameArguments($frame); $this->assertEquals('bar', $result['foo']); diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index a7188388e..d1f58097c 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -105,7 +105,7 @@ public function testSetExtras(): void * * @expectedDeprecation Replacing the data is deprecated since version 2.3 and will stop working from version 3.0. Set the second argument to `true` to merge the data instead. */ - public function testSetUserThrowsDeprecation(): void + public function testSetUserThrowsDeprecationError(): void { $scope = new Scope(); From 571a07fac024fb65ea58895c397f1e6affdce051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Tue, 19 May 2020 13:30:35 +0200 Subject: [PATCH 0557/1161] Trim the file path from the anonymous class name in the stacktrace (#1016) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + src/Stacktrace.php | 6 ++++++ .../anonymous_frame_with_memory_address.json | 7 ++++++- tests/StacktraceTest.php | 17 +++++++++++++---- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7291ef51..e47f8d29c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Add missing validation for the `context_lines` option and fix its behavior when passing `null` to make it working as described in the documentation (#1003) +- Trim the file path from the anonymous class name in the stacktrace according to the `prefixes` option (#1016) ## 2.3.2 (2020-03-06) diff --git a/src/Stacktrace.php b/src/Stacktrace.php index f6e71caad..0362e0810 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -18,6 +18,8 @@ class Stacktrace implements \JsonSerializable { private const INTERNAL_FRAME_FILENAME = '[internal]'; + private const ANONYMOUS_CLASS_PREFIX = "class@anonymous\x00"; + /** * @var Options The client options */ @@ -129,6 +131,10 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void } if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { + if (0 === strpos($backtraceFrame['class'], self::ANONYMOUS_CLASS_PREFIX)) { + $backtraceFrame['class'] = self::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath(substr($backtraceFrame['class'], \strlen(self::ANONYMOUS_CLASS_PREFIX))); + } + $functionName = sprintf( '%s::%s', preg_replace('/0x[a-fA-F0-9]+$/', '', $backtraceFrame['class']), diff --git a/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json b/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json index 7181588b6..6bddf4619 100644 --- a/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json +++ b/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json @@ -3,7 +3,12 @@ "line": 12, "backtrace": [ { - "class": "class@anonymous/path/to/app/consumer.php0x7fc3bc369418", + "class": "class@anonymous\u0000/path/to/app/consumer.php0x7fc3bc369418", + "function": "messageCallback", + "type": "->" + }, + { + "class": "class@anonymous\u0000/path-prefix/path/to/app/consumer.php0x7fc3bc369418", "function": "messageCallback", "type": "->" } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index e11408eae..897deef4c 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -418,7 +418,7 @@ public function removeFrameDataProvider(): array ]; } - public function testFromBacktrace(): void + public function testCreateFromBacktrace(): void { $fixture = $this->getJsonFixture('backtraces/exception.json'); $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); @@ -428,7 +428,7 @@ public function testFromBacktrace(): void $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); } - public function testFromBacktraceWithAnonymousFrame(): void + public function testCreateFromBacktraceWithAnonymousFrame(): void { $fixture = $this->getJsonFixture('backtraces/anonymous_frame.json'); $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); @@ -438,8 +438,10 @@ public function testFromBacktraceWithAnonymousFrame(): void $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); } - public function testFromBacktraceWithAnonymousClass(): void + public function testCreateFromBacktraceWithAnonymousClass(): void { + $this->options->setPrefixes(['/path-prefix']); + $fixture = $this->getJsonFixture('backtraces/anonymous_frame_with_memory_address.json'); $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'])->getFrames(); @@ -451,7 +453,14 @@ public function testFromBacktraceWithAnonymousClass(): void ); $this->assertFrameEquals( $frames[1], - 'class@anonymous/path/to/app/consumer.php::messageCallback', + "class@anonymous\x00/path/to/app/consumer.php::messageCallback", + '[internal]', + 0 + ); + + $this->assertFrameEquals( + $frames[2], + "class@anonymous\x00/path/to/app/consumer.php::messageCallback", 'path/to/file', 12 ); From e44561875e0d724bac3d9cdb705bf58847acd425 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 20 May 2020 22:49:38 +0200 Subject: [PATCH 0558/1161] Update CHANGELOG for release 2.4.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c30c568f..a2bd3c160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +### 2.4.0 (2020-05-21) + - Enforce a timeout for connecting to the server and for the requests instead of waiting indefinitely (#979) - Add `RequestFetcherInterface` to allow customizing the request data attached to the logged event (#984) - Log internal debug and error messages to a PSR-3 compatible logger (#989) From c4359c244b903212d8e2e23755c1087c5f362ff2 Mon Sep 17 00:00:00 2001 From: Rhodri Pugh Date: Thu, 21 May 2020 18:10:43 +0100 Subject: [PATCH 0559/1161] Fix wrong value mentioned in the PHPDoc of the Options::getMaxRequestBodySize() method (#1019) --- src/Options.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options.php b/src/Options.php index 01b83108c..eef47dd2b 100644 --- a/src/Options.php +++ b/src/Options.php @@ -744,7 +744,7 @@ public function getMaxRequestBodySize(): string * captured. It can be set to one of the * following values: * - * - never: request bodies are never sent + * - none: request bodies are never sent * - small: only small request bodies will * be captured where the cutoff for small * depends on the SDK (typically 4KB) From b586900f6912a69c7743dbed268981b10500e586 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 9 Jun 2020 12:36:46 +0200 Subject: [PATCH 0560/1161] Fix broken tests when using a newer version of the OptionsResolver component of Symfony (#1024) --- .../FrameContextifierIntegration.php | 6 ++++-- tests/Context/AbstractContextTest.php | 10 +++++----- tests/Context/RuntimeContextTest.php | 12 +++++------ tests/Context/ServerOsContextTest.php | 20 +++++++++---------- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index d1ce70a4e..68f2f7f9d 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -53,8 +53,10 @@ public function setupOnce(): void return $event; } - if (null !== $event->getStacktrace()) { - $integration->addContextToStacktraceFrames($maxContextLines, $event->getStacktrace()); + $stacktrace = $event->getStacktrace(); + + if (null !== $stacktrace) { + $integration->addContextToStacktraceFrames($maxContextLines, $stacktrace); } foreach ($event->getExceptions() as $exception) { diff --git a/tests/Context/AbstractContextTest.php b/tests/Context/AbstractContextTest.php index 8e1b82fe7..67df457a1 100644 --- a/tests/Context/AbstractContextTest.php +++ b/tests/Context/AbstractContextTest.php @@ -19,7 +19,7 @@ public function testConstructor(array $initialData, array $expectedData, ?string } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext($initialData); @@ -37,7 +37,7 @@ public function testMerge(array $initialData, array $expectedData, ?string $expe } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext(); @@ -56,7 +56,7 @@ public function testSetData(array $initialData, array $expectedData, ?string $ex } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext(); @@ -75,7 +75,7 @@ public function testReplaceData(array $initialData, array $expectedData, ?string } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext(); @@ -94,7 +94,7 @@ public function testOffsetSet(string $key, $value, ?string $expectedExceptionCla } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext(); diff --git a/tests/Context/RuntimeContextTest.php b/tests/Context/RuntimeContextTest.php index 78f881ba4..5a1c86331 100644 --- a/tests/Context/RuntimeContextTest.php +++ b/tests/Context/RuntimeContextTest.php @@ -53,7 +53,7 @@ public function valuesDataProvider(): array ], [], UndefinedOptionsException::class, - 'The option "foo" does not exist. Defined options are: "name", "version".', + '/^The option "foo" does not exist\. Defined options are: "name", "version"\.$/', ], [ [ @@ -61,7 +61,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ [ @@ -69,7 +69,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], ]; } @@ -87,19 +87,19 @@ public function offsetSetDataProvider(): array 'name', 1, InvalidOptionsException::class, - 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'version', 1, InvalidOptionsException::class, - 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'foo', 'bar', UndefinedOptionsException::class, - 'The option "foo" does not exist. Defined options are: "name", "version".', + '/^The option "foo" does not exist\. Defined options are: "name", "version"\.$/', ], ]; } diff --git a/tests/Context/ServerOsContextTest.php b/tests/Context/ServerOsContextTest.php index ac5885573..644aa621d 100644 --- a/tests/Context/ServerOsContextTest.php +++ b/tests/Context/ServerOsContextTest.php @@ -83,7 +83,7 @@ public function valuesDataProvider(): array ], [], UndefinedOptionsException::class, - 'The option "foo" does not exist. Defined options are: "build", "kernel_version", "name", "version".', + '/^The option "foo" does not exist\. Defined options are: "build", "kernel_version", "name", "version"\.$/', ], [ [ @@ -91,7 +91,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ [ @@ -99,7 +99,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ [ @@ -107,7 +107,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "build" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "build" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ [ @@ -115,7 +115,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "kernel_version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "kernel_version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], ]; } @@ -133,7 +133,7 @@ public function offsetSetDataProvider(): array 'name', 1, InvalidOptionsException::class, - 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'version', @@ -145,7 +145,7 @@ public function offsetSetDataProvider(): array 'version', 1, InvalidOptionsException::class, - 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'build', @@ -157,7 +157,7 @@ public function offsetSetDataProvider(): array 'build', 1, InvalidOptionsException::class, - 'The option "build" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "build" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'kernel_version', @@ -169,13 +169,13 @@ public function offsetSetDataProvider(): array 'kernel_version', 1, InvalidOptionsException::class, - 'The option "kernel_version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "kernel_version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'foo', 'bar', UndefinedOptionsException::class, - 'The option "foo" does not exist. Defined options are: "build", "kernel_version", "name", "version".', + '/^The option "foo" does not exist\. Defined options are: "build", "kernel_version", "name", "version"\.$/', ], ]; } From dc93ab86cabf8bf0fb2a53d35d86aa426c0821ae Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 24 Jun 2020 12:05:20 +0200 Subject: [PATCH 0561/1161] Fix broken tests due to deps update (#1032) --- phpstan.neon | 1 - .../FrameContextifierIntegration.php | 6 ++++-- tests/Context/AbstractContextTest.php | 10 +++++----- tests/Context/RuntimeContextTest.php | 12 +++++------ tests/Context/ServerOsContextTest.php | 20 +++++++++---------- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 282f5a727..bbdace8dc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,7 +6,6 @@ parameters: - src ignoreErrors: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - - "/^Parameter #1 \\$function of function register_shutdown_function expects callable\\(\\): void, 'register_shutdown…' given\\.$/" - message: '/^Argument of an invalid type array\|object supplied for foreach, only iterables are supported\.$/' path: src/Util/JSON.php diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index d1ce70a4e..68f2f7f9d 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -53,8 +53,10 @@ public function setupOnce(): void return $event; } - if (null !== $event->getStacktrace()) { - $integration->addContextToStacktraceFrames($maxContextLines, $event->getStacktrace()); + $stacktrace = $event->getStacktrace(); + + if (null !== $stacktrace) { + $integration->addContextToStacktraceFrames($maxContextLines, $stacktrace); } foreach ($event->getExceptions() as $exception) { diff --git a/tests/Context/AbstractContextTest.php b/tests/Context/AbstractContextTest.php index 8e1b82fe7..67df457a1 100644 --- a/tests/Context/AbstractContextTest.php +++ b/tests/Context/AbstractContextTest.php @@ -19,7 +19,7 @@ public function testConstructor(array $initialData, array $expectedData, ?string } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext($initialData); @@ -37,7 +37,7 @@ public function testMerge(array $initialData, array $expectedData, ?string $expe } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext(); @@ -56,7 +56,7 @@ public function testSetData(array $initialData, array $expectedData, ?string $ex } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext(); @@ -75,7 +75,7 @@ public function testReplaceData(array $initialData, array $expectedData, ?string } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext(); @@ -94,7 +94,7 @@ public function testOffsetSet(string $key, $value, ?string $expectedExceptionCla } if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessage($expectedExceptionMessage); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); } $context = $this->createContext(); diff --git a/tests/Context/RuntimeContextTest.php b/tests/Context/RuntimeContextTest.php index 78f881ba4..5a1c86331 100644 --- a/tests/Context/RuntimeContextTest.php +++ b/tests/Context/RuntimeContextTest.php @@ -53,7 +53,7 @@ public function valuesDataProvider(): array ], [], UndefinedOptionsException::class, - 'The option "foo" does not exist. Defined options are: "name", "version".', + '/^The option "foo" does not exist\. Defined options are: "name", "version"\.$/', ], [ [ @@ -61,7 +61,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ [ @@ -69,7 +69,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], ]; } @@ -87,19 +87,19 @@ public function offsetSetDataProvider(): array 'name', 1, InvalidOptionsException::class, - 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'version', 1, InvalidOptionsException::class, - 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'foo', 'bar', UndefinedOptionsException::class, - 'The option "foo" does not exist. Defined options are: "name", "version".', + '/^The option "foo" does not exist\. Defined options are: "name", "version"\.$/', ], ]; } diff --git a/tests/Context/ServerOsContextTest.php b/tests/Context/ServerOsContextTest.php index ac5885573..644aa621d 100644 --- a/tests/Context/ServerOsContextTest.php +++ b/tests/Context/ServerOsContextTest.php @@ -83,7 +83,7 @@ public function valuesDataProvider(): array ], [], UndefinedOptionsException::class, - 'The option "foo" does not exist. Defined options are: "build", "kernel_version", "name", "version".', + '/^The option "foo" does not exist\. Defined options are: "build", "kernel_version", "name", "version"\.$/', ], [ [ @@ -91,7 +91,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ [ @@ -99,7 +99,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ [ @@ -107,7 +107,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "build" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "build" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ [ @@ -115,7 +115,7 @@ public function valuesDataProvider(): array ], [], InvalidOptionsException::class, - 'The option "kernel_version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "kernel_version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], ]; } @@ -133,7 +133,7 @@ public function offsetSetDataProvider(): array 'name', 1, InvalidOptionsException::class, - 'The option "name" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'version', @@ -145,7 +145,7 @@ public function offsetSetDataProvider(): array 'version', 1, InvalidOptionsException::class, - 'The option "version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'build', @@ -157,7 +157,7 @@ public function offsetSetDataProvider(): array 'build', 1, InvalidOptionsException::class, - 'The option "build" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "build" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'kernel_version', @@ -169,13 +169,13 @@ public function offsetSetDataProvider(): array 'kernel_version', 1, InvalidOptionsException::class, - 'The option "kernel_version" with value 1 is expected to be of type "string", but is of type "integer".', + '/^The option "kernel_version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', ], [ 'foo', 'bar', UndefinedOptionsException::class, - 'The option "foo" does not exist. Defined options are: "build", "kernel_version", "name", "version".', + '/^The option "foo" does not exist\. Defined options are: "build", "kernel_version", "name", "version"\.$/', ], ]; } From b2cfb8b9e524727db63592fd5183e7a48058bb73 Mon Sep 17 00:00:00 2001 From: Guilliam Xavier Date: Fri, 26 Jun 2020 10:19:40 +0200 Subject: [PATCH 0562/1161] Revert "Add support for iterables in the serializer (#991)" (#1030) --- src/Serializer/AbstractSerializer.php | 2 +- tests/Serializer/AbstractSerializerTest.php | 42 +++++++++++++++++++++ tests/Serializer/SerializerTest.php | 18 --------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index a1df54132..8562eec83 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -103,7 +103,7 @@ protected function serializeRecursively($value, int $_depth = 0) return $this->serializeCallableWithoutTypeHint($value); } - if (is_iterable($value)) { + if (\is_array($value)) { $serializedArray = []; foreach ($value as $k => $v) { diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index b4cc24695..0ec7c17c8 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -65,6 +65,48 @@ public function testObjectsAreNotStrings(): void $this->assertSame(['key' => 'value'], $result); } + /** + * @dataProvider iterableDataProvider + */ + public function testIterablesAreNotConsumed(iterable $iterable, array $input): void + { + $serializer = $this->createSerializer(); + $output = []; + + foreach ($iterable as $k => $v) { + $output[$k] = $v; + + $this->invokeSerialization($serializer, $iterable); + } + + $this->assertSame($input, $output); + } + + public function iterableDataProvider(): \Generator + { + yield [ + 'iterable' => ['value1', 'value2'], + 'input' => ['value1', 'value2'], + ]; + + yield [ + 'iterable' => new \ArrayIterator(['value1', 'value2']), + 'input' => ['value1', 'value2'], + ]; + + // Also test with a non-rewindable non-cloneable iterator: + yield [ + 'iterable' => (static function (): \Generator { + yield 'value1'; + yield 'value2'; + })(), + 'input' => [ + 'value1', + 'value2', + ], + ]; + } + /** * @dataProvider serializeAllObjectsDataProvider */ diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index e0a90eeff..f04e55e00 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -27,24 +27,6 @@ public function testArraysAreArrays(bool $serializeAllObjects): void $this->assertSame([1, 2, 3], $result); } - /** - * @dataProvider serializeAllObjectsDataProvider - */ - public function testTraversablesAreArrays(bool $serializeAllObjects): void - { - $serializer = $this->createSerializer(); - - if ($serializeAllObjects) { - $serializer->setSerializeAllObjects(true); - } - - $content = [1, 2, 3]; - $traversable = new \ArrayIterator($content); - $result = $this->invokeSerialization($serializer, $traversable); - - $this->assertSame([1, 2, 3], $result); - } - /** * @dataProvider serializeAllObjectsDataProvider */ From c6acde745e851246443922afcb340e31bf8e632f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20M=C3=BCns?= Date: Fri, 3 Jul 2020 11:22:25 +0200 Subject: [PATCH 0563/1161] Fix the HTTP client timeouts not being set if the http_proxy option is not being used (#1034) --- CHANGELOG.md | 2 ++ src/HttpClient/HttpClientFactory.php | 32 +++++++++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2bd3c160..3df675eb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix timeouts for connecting to the server and for the requests only being applied if a http proxy was specified (#1033) + ### 2.4.0 (2020-05-21) - Enforce a timeout for connecting to the server and for the requests instead of waiting indefinitely (#979) diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 353557f17..d5abc7602 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -110,22 +110,34 @@ public function create(Options $options): HttpAsyncClientInterface throw new \RuntimeException('The "http_proxy" option does not work together with a custom HTTP client.'); } - if (null === $httpClient && null !== $options->getHttpProxy()) { + if (null === $httpClient) { if (class_exists(GuzzleHttpClient::class)) { - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $httpClient = GuzzleHttpClient::createWithConfig([ - GuzzleHttpClientOptions::PROXY => $options->getHttpProxy(), + /** @psalm-suppress UndefinedClass */ + $guzzleConfig = [ GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, - ]); - } elseif (class_exists(CurlHttpClient::class)) { + ]; + + if (null !== $options->getHttpProxy()) { + /** @psalm-suppress UndefinedClass */ + $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); + } + /** @psalm-suppress InvalidPropertyAssignmentValue */ - $httpClient = new CurlHttpClient($this->responseFactory, $this->streamFactory, [ - CURLOPT_PROXY => $options->getHttpProxy(), + $httpClient = GuzzleHttpClient::createWithConfig($guzzleConfig); + } elseif (class_exists(CurlHttpClient::class)) { + $curlConfig = [ CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, - ]); - } else { + ]; + + if (null !== $options->getHttpProxy()) { + $curlConfig[CURLOPT_PROXY] = $options->getHttpProxy(); + } + + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $httpClient = new CurlHttpClient($this->responseFactory, $this->streamFactory, $curlConfig); + } elseif (null !== $options->getHttpProxy()) { throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); } } From 407573e22e6cc46b72cff07c117eeb16bf3a17de Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 3 Jul 2020 11:33:09 +0200 Subject: [PATCH 0564/1161] Update the CHANGELOG for version 2.4.1 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df675eb5..926715b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ## Unreleased -- Fix timeouts for connecting to the server and for the requests only being applied if a http proxy was specified (#1033) +### 2.4.1 (2020-07-03) + +- Fix HTTP client connection timeouts not being applied if an HTTP proxy is specified (#1033) +- [BC CHANGE] Revert "Add support for iterables in the serializer (#991)" (#1030) ### 2.4.0 (2020-05-21) From 9dece0dbafa376842aaf6d3501f2cab82b61d8b7 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 3 Jul 2020 15:31:36 +0200 Subject: [PATCH 0565/1161] fix: dev-develop in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3cbd5cfb1..42861066d 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-develop": "2.5.x-dev" } } } From 1eb9eb15f32f6f94c0982a3b465fe161f65e8cf3 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 3 Jul 2020 15:35:08 +0200 Subject: [PATCH 0566/1161] fix: Composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3cbd5cfb1..549f8c561 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "3.x-dev" } } } From 8544f70eccb13c82c265a57bff8f2c6fe929c98b Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 3 Jul 2020 15:35:32 +0200 Subject: [PATCH 0567/1161] fix: Composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 549f8c561..248fafa3f 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "3.0.x-dev" } } } From b5a9ed2e6eb61011bc7a327fc24d2beeae2e49eb Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 3 Jul 2020 15:36:06 +0200 Subject: [PATCH 0568/1161] fix: composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 42861066d..3deb9567d 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-develop": "2.5.x-dev" + "dev-master": "2.5.x-dev" } } } From dd6ef340e7ac730a96fb0cf9b8463dd157b7b3e1 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 6 Jul 2020 12:38:50 +0200 Subject: [PATCH 0569/1161] feat: Add Performance API to the SDK (#1021) * feat: Span and SpanContext Class (#1020) * feat: Add Transaction and TransactionContext (#1022) * feat[breaking]: startTransaction (#1025) * feat: Add Envelope support (#1027) * feat[am]: Adding missing pieces to actually send a Transaction (#1031) --- .appveyor.yml | 5 - .travis.yml | 6 - Makefile | 5 +- src/Client.php | 12 +- src/ClientInterface.php | 6 +- src/Dsn.php | 20 +- src/Event.php | 105 +++++++- src/EventFactory.php | 61 +++-- src/EventFactoryInterface.php | 8 +- src/Integration/TransactionIntegration.php | 6 +- src/Options.php | 24 ++ src/State/Hub.php | 37 ++- src/State/HubAdapter.php | 12 +- src/State/HubInterface.php | 26 +- src/State/Scope.php | 52 +++- src/Tracing/Span.php | 280 +++++++++++++++++++++ src/Tracing/SpanContext.php | 96 +++++++ src/Tracing/SpanId.php | 43 ++++ src/Tracing/SpanRecorder.php | 49 ++++ src/Tracing/TraceId.php | 43 ++++ src/Tracing/Transaction.php | 139 ++++++++++ src/Tracing/TransactionContext.php | 13 + src/Transport/HttpTransport.php | 21 +- src/functions.php | 25 ++ tests/EventTest.php | 18 +- tests/Tracing/SpanTest.php | 92 +++++++ tests/Tracing/TransactionTest.php | 120 +++++++++ tests/Transport/HttpTransportTest.php | 42 ++++ 28 files changed, 1288 insertions(+), 78 deletions(-) create mode 100644 src/Tracing/Span.php create mode 100644 src/Tracing/SpanContext.php create mode 100644 src/Tracing/SpanId.php create mode 100644 src/Tracing/SpanRecorder.php create mode 100644 src/Tracing/TraceId.php create mode 100644 src/Tracing/Transaction.php create mode 100644 src/Tracing/TransactionContext.php create mode 100644 tests/Tracing/SpanTest.php create mode 100644 tests/Tracing/TransactionTest.php diff --git a/.appveyor.yml b/.appveyor.yml index 33b3eecb3..3c9772209 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,11 +4,6 @@ clone_depth: 2 clone_folder: c:\projects\sentry-php skip_branch_with_pr: true image: Visual Studio 2019 -branches: - only: - - master - - develop - - /^release\/.+$/ environment: matrix: diff --git a/.travis.yml b/.travis.yml index d98bd6a2c..2216d439c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,5 @@ language: php -branches: - only: - - master - - develop - - /^release\/.+$/ - php: - 7.1 - 7.2 diff --git a/Makefile b/Makefile index 03d373440..4a5163ad8 100644 --- a/Makefile +++ b/Makefile @@ -17,10 +17,13 @@ cs-dry-run: cs-fix: vendor/bin/php-cs-fixer fix +psalm: + vendor/bin/psalm + phpstan: vendor/bin/phpstan analyse -test: cs-fix phpstan +test: cs-fix phpstan psalm vendor/bin/phpunit --verbose setup-git: diff --git a/src/Client.php b/src/Client.php index cf001ce4a..125e34956 100644 --- a/src/Client.php +++ b/src/Client.php @@ -113,7 +113,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? /** * {@inheritdoc} */ - public function captureEvent(array $payload, ?Scope $scope = null): ?string + public function captureEvent($payload, ?Scope $scope = null): ?string { $event = $this->prepareEvent($payload, $scope); @@ -166,16 +166,16 @@ public function flush(?int $timeout = null): PromiseInterface /** * Assembles an event and prepares it to be sent of to Sentry. * - * @param array $payload The payload that will be converted to an Event - * @param Scope|null $scope Optional scope which enriches the Event + * @param array|Event $payload The payload that will be converted to an Event + * @param Scope|null $scope Optional scope which enriches the Event * * @return Event|null The prepared event object or null if it must be discarded */ - private function prepareEvent(array $payload, ?Scope $scope = null): ?Event + private function prepareEvent($payload, ?Scope $scope = null): ?Event { $shouldReadSourceCodeExcerpts = !isset($this->integrations[FrameContextifierIntegration::class]) && null !== $this->options->getContextLines(); - if ($this->options->shouldAttachStacktrace() && !isset($payload['exception']) && !isset($payload['stacktrace'])) { + if ($this->options->shouldAttachStacktrace() && !($payload instanceof Event) && !isset($payload['exception']) && !isset($payload['stacktrace'])) { /** @psalm-suppress TooManyArguments */ $event = $this->eventFactory->createWithStacktrace($payload, $shouldReadSourceCodeExcerpts); } else { @@ -185,7 +185,7 @@ private function prepareEvent(array $payload, ?Scope $scope = null): ?Event $sampleRate = $this->options->getSampleRate(); - if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { + if ('transaction' !== $event->getType() && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]); return null; diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 9f41c0181..36e6803ee 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -46,10 +46,10 @@ public function captureLastError(?Scope $scope = null): ?string; /** * Captures a new event using the provided data. * - * @param array $payload The data of the event being captured - * @param Scope|null $scope An optional scope keeping the state + * @param array|Event $payload The data of the event being captured + * @param Scope|null $scope An optional scope keeping the state */ - public function captureEvent(array $payload, ?Scope $scope = null): ?string; + public function captureEvent($payload, ?Scope $scope = null): ?string; /** * Returns the integration instance if it is installed on the client. diff --git a/src/Dsn.php b/src/Dsn.php index 02eaed8b0..fe2dc84cf 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -181,9 +181,25 @@ public function getSecretKey(): ?string } /** - * Gets the URL of the API endpoint to use to post an event to Sentry. + * Returns the URL of the API for the store endpoint. */ public function getStoreApiEndpointUrl(): string + { + return $this->getBaseEndpointUrl() . '/store/'; + } + + /** + * Returns the URL of the API for the envelope endpoint. + */ + public function getEnvelopeApiEndpointUrl(): string + { + return $this->getBaseEndpointUrl() . '/envelope/'; + } + + /** + * Returns the base url to Sentry from the DSN. + */ + protected function getBaseEndpointUrl(): string { $url = $this->scheme . '://' . $this->host; @@ -195,7 +211,7 @@ public function getStoreApiEndpointUrl(): string $url .= $this->path; } - $url .= '/api/' . $this->projectId . '/store/'; + $url .= '/api/' . $this->projectId; return $url; } diff --git a/src/Event.php b/src/Event.php index c38b94be2..a8e2b2778 100644 --- a/src/Event.php +++ b/src/Event.php @@ -10,6 +10,8 @@ use Sentry\Context\ServerOsContext; use Sentry\Context\TagsContext; use Sentry\Context\UserContext; +use Sentry\Tracing\Span; +use Sentry\Util\JSON; /** * This is the base class for classes containing event data. @@ -24,12 +26,19 @@ final class Event implements \JsonSerializable private $id; /** - * @var string The date and time of when this event was generated + * @var string|float The date and time of when this event was generated */ private $timestamp; /** - * @var Severity The severity of this event + * This property is used if it's a Transaction event together with $timestamp it's the duration of the transaction. + * + * @var string|float|null The date and time of when this event was generated + */ + private $startTimestamp; + + /** + * @var Severity|null The severity of this event */ private $level; @@ -123,6 +132,11 @@ final class Event implements \JsonSerializable */ private $breadcrumbs = []; + /** + * @var Span[] The array of spans if it's a transaction + */ + private $spans = []; + /** * @var array> The exceptions * @@ -149,6 +163,11 @@ final class Event implements \JsonSerializable */ private $sdkVersion; + /** + * @var string|null The type of the Event "default" | "transaction" + */ + private $type; + /** * Class constructor. * @@ -225,16 +244,28 @@ public function setSdkVersion(string $sdkVersion): void /** * Gets the timestamp of when this event was generated. + * + * @return string|float */ - public function getTimestamp(): string + public function getTimestamp() { return $this->timestamp; } + /** + * Sets the timestamp of when the Event was created. + * + * @param float|string $timestamp + */ + public function setTimestamp($timestamp): void + { + $this->timestamp = $timestamp; + } + /** * Gets the severity of this event. */ - public function getLevel(): Severity + public function getLevel(): ?Severity { return $this->level; } @@ -242,9 +273,9 @@ public function getLevel(): Severity /** * Sets the severity of this event. * - * @param Severity $level The severity + * @param Severity|null $level The severity */ - public function setLevel(Severity $level): void + public function setLevel(?Severity $level): void { $this->level = $level; } @@ -578,6 +609,35 @@ public function setStacktrace(?Stacktrace $stacktrace): void $this->stacktrace = $stacktrace; } + public function getType(): ?string + { + return $this->type; + } + + public function setType(?string $type): void + { + if ('default' !== $type && 'transaction' !== $type) { + $type = null; + } + $this->type = $type; + } + + /** + * @param string|float|null $startTimestamp The start time of the event + */ + public function setStartTimestamp($startTimestamp): void + { + $this->startTimestamp = $startTimestamp; + } + + /** + * @param Span[] $spans Array of spans + */ + public function setSpans(array $spans): void + { + $this->spans = $spans; + } + /** * Gets the event as an array. * @@ -588,7 +648,6 @@ public function toArray(): array $data = [ 'event_id' => (string) $this->id, 'timestamp' => $this->timestamp, - 'level' => (string) $this->level, 'platform' => 'php', 'sdk' => [ 'name' => $this->sdkIdentifier, @@ -596,6 +655,18 @@ public function toArray(): array ], ]; + if (null !== $this->level) { + $data['level'] = (string) $this->level; + } + + if (null !== $this->startTimestamp) { + $data['start_timestamp'] = $this->startTimestamp; + } + + if (null !== $this->type) { + $data['type'] = $this->type; + } + if (null !== $this->logger) { $data['logger'] = $this->logger; } @@ -652,6 +723,12 @@ public function toArray(): array $data['breadcrumbs']['values'] = $this->breadcrumbs; } + if ('transaction' === $this->getType()) { + $data['spans'] = array_values(array_map(function (Span $span): array { + return $span->toArray(); + }, $this->spans)); + } + foreach (array_reverse($this->exceptions) as $exception) { $exceptionData = [ 'type' => $exception['type'], @@ -701,4 +778,18 @@ public function jsonSerialize(): array { return $this->toArray(); } + + /** + * Converts an Event to an Envelope. + * + * @throws Exception\JsonException + */ + public function toEnvelope(): string + { + $rawEvent = $this->jsonSerialize(); + $envelopeHeader = JSON::encode(['event_id' => $rawEvent['event_id'], 'sent_at' => gmdate('Y-m-d\TH:i:s\Z')]); + $itemHeader = JSON::encode(['type' => $rawEvent['type'] ?? 'event', 'content_type' => 'application/json']); + + return vsprintf("%s\n%s\n%s", [$envelopeHeader, $itemHeader, JSON::encode($rawEvent)]); + } } diff --git a/src/EventFactory.php b/src/EventFactory.php index 8401daa76..94ba84f2b 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -59,8 +59,12 @@ public function __construct(SerializerInterface $serializer, RepresentationSeria /** * {@inheritdoc} */ - public function createWithStacktrace(array $payload/*, bool $shouldReadSourceCodeExcerpts = true*/): Event + public function createWithStacktrace($payload/*, bool $shouldReadSourceCodeExcerpts = true*/): Event { + if ($payload instanceof Event) { + return $this->create($payload); + } + if (!isset($payload['stacktrace']) || !$payload['stacktrace'] instanceof Stacktrace) { $payload['stacktrace'] = Stacktrace::createFromBacktrace( $this->options, @@ -79,10 +83,37 @@ public function createWithStacktrace(array $payload/*, bool $shouldReadSourceCod /** * {@inheritdoc} */ - public function create(array $payload/*, bool $shouldReadSourceCodeExcerpts = true*/): Event + public function create($payload/*, bool $shouldReadSourceCodeExcerpts = true*/): Event { try { - $event = new Event(); + if ($payload instanceof Event) { + $event = $payload; + } else { + $event = new Event(); + if (isset($payload['logger'])) { + $event->setLogger($payload['logger']); + } + + $message = isset($payload['message']) ? mb_substr($payload['message'], 0, $this->options->getMaxValueLength()) : null; + $messageParams = $payload['message_params'] ?? []; + $messageFormatted = isset($payload['message_formatted']) ? mb_substr($payload['message_formatted'], 0, $this->options->getMaxValueLength()) : null; + + if (null !== $message) { + $event->setMessage($message, $messageParams, $messageFormatted); + } + + if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { + $this->addThrowableToEvent($event, $payload['exception'], \func_num_args() > 1 ? func_get_arg(1) : true); + } + + if (isset($payload['level']) && $payload['level'] instanceof Severity) { + $event->setLevel($payload['level']); + } + + if (isset($payload['stacktrace']) && $payload['stacktrace'] instanceof Stacktrace) { + $event->setStacktrace($payload['stacktrace']); + } + } } catch (\Throwable $exception) { throw new EventCreationException($exception); } @@ -94,30 +125,6 @@ public function create(array $payload/*, bool $shouldReadSourceCodeExcerpts = tr $event->getTagsContext()->merge($this->options->getTags()); $event->setEnvironment($this->options->getEnvironment()); - if (isset($payload['logger'])) { - $event->setLogger($payload['logger']); - } - - $message = isset($payload['message']) ? mb_substr($payload['message'], 0, $this->options->getMaxValueLength()) : null; - $messageParams = $payload['message_params'] ?? []; - $messageFormatted = isset($payload['message_formatted']) ? mb_substr($payload['message_formatted'], 0, $this->options->getMaxValueLength()) : null; - - if (null !== $message) { - $event->setMessage($message, $messageParams, $messageFormatted); - } - - if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { - $this->addThrowableToEvent($event, $payload['exception'], \func_num_args() > 1 ? func_get_arg(1) : true); - } - - if (isset($payload['level']) && $payload['level'] instanceof Severity) { - $event->setLevel($payload['level']); - } - - if (isset($payload['stacktrace']) && $payload['stacktrace'] instanceof Stacktrace) { - $event->setStacktrace($payload['stacktrace']); - } - return $event; } diff --git a/src/EventFactoryInterface.php b/src/EventFactoryInterface.php index 405623588..cd949767d 100644 --- a/src/EventFactoryInterface.php +++ b/src/EventFactoryInterface.php @@ -12,14 +12,14 @@ interface EventFactoryInterface /** * Create an {@see Event} with a stacktrace attached to it. * - * @param array $payload The data to be attached to the Event + * @param array|Event $payload The data to be attached to the Event */ - public function createWithStacktrace(array $payload): Event; + public function createWithStacktrace($payload): Event; /** * Create an {@see Event} from a data payload. * - * @param array $payload The data to be attached to the Event + * @param array|Event $payload The data to be attached to the Event */ - public function create(array $payload): Event; + public function create($payload): Event; } diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index 7c669eb83..4471ba419 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -22,7 +22,7 @@ final class TransactionIntegration implements IntegrationInterface */ public function setupOnce(): void { - Scope::addGlobalEventProcessor(static function (Event $event, array $payload): Event { + Scope::addGlobalEventProcessor(static function (Event $event, $payload): Event { $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); // The client bound to the current hub, if any, could not have this @@ -35,7 +35,9 @@ public function setupOnce(): void return $event; } - if (isset($payload['transaction'])) { + if ($payload instanceof Event && $payload->getTransaction()) { + $event->setTransaction($payload->getTransaction()); + } elseif (isset($payload['transaction'])) { $event->setTransaction($payload['transaction']); } elseif (isset($_SERVER['PATH_INFO'])) { $event->setTransaction($_SERVER['PATH_INFO']); diff --git a/src/Options.php b/src/Options.php index eef47dd2b..67a1aa9ad 100644 --- a/src/Options.php +++ b/src/Options.php @@ -122,6 +122,28 @@ public function setSampleRate(float $sampleRate): void $this->options = $this->resolver->resolve($options); } + /** + * Gets the sampling factor to apply to transaction. A value of 0 will deny + * sending any transaction, and a value of 1 will send 100% of transaction. + */ + public function getTracesSampleRate(): float + { + return $this->options['traces_sample_rate']; + } + + /** + * Sets the sampling factor to apply to transactions. A value of 0 will deny + * sending any transactions, and a value of 1 will send 100% of transactions. + * + * @param float $sampleRate The sampling factor + */ + public function setTracesSampleRate(float $sampleRate): void + { + $options = array_merge($this->options, ['traces_sample_rate' => $sampleRate]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets whether the stacktrace will be attached on captureMessage. */ @@ -802,6 +824,7 @@ private function configureOptions(OptionsResolver $resolver): void 'send_attempts' => 3, 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'sample_rate' => 1, + 'traces_sample_rate' => 0, 'attach_stacktrace' => false, 'context_lines' => 5, 'enable_compression' => true, @@ -834,6 +857,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'array'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); + $resolver->setAllowedTypes('traces_sample_rate', ['int', 'float']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); $resolver->setAllowedTypes('enable_compression', 'bool'); diff --git a/src/State/Hub.php b/src/State/Hub.php index 2814633e4..7470ffd63 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -9,6 +9,8 @@ use Sentry\Integration\IntegrationInterface; use Sentry\SentrySdk; use Sentry\Severity; +use Sentry\Tracing\Transaction; +use Sentry\Tracing\TransactionContext; /** * This class is a basic implementation of the {@see HubInterface} interface. @@ -138,7 +140,7 @@ public function captureException(\Throwable $exception): ?string /** * {@inheritdoc} */ - public function captureEvent(array $payload): ?string + public function captureEvent($payload): ?string { $client = $this->getClient(); @@ -227,6 +229,39 @@ public function getIntegration(string $className): ?IntegrationInterface return null; } + /** + * {@inheritdoc} + */ + public function startTransaction(TransactionContext $context): Transaction + { + $client = $this->getClient(); + + // Roll the dice for sampling transaction, all child spans inherit the sampling decision. + if (null === $context->sampled) { + if (null !== $client) { + $sampleRate = $client->getOptions()->getTracesSampleRate(); + + if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { + // if true = we want to have the transaction + // if false = we don't want to have it + $context->sampled = false; + } else { + $context->sampled = true; + } + } + } + + $transaction = new Transaction($context, $this); + + // We only want to create a span list if we sampled the transaction + // If sampled == false, we will discard the span anyway, so we can save memory by not storing child spans + if ($context->sampled) { + $transaction->initSpanRecorder(); + } + + return $transaction; + } + /** * Gets the scope bound to the top of the stack. */ diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 1085f9658..82d30bcdb 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -9,6 +9,8 @@ use Sentry\Integration\IntegrationInterface; use Sentry\SentrySdk; use Sentry\Severity; +use Sentry\Tracing\Transaction; +use Sentry\Tracing\TransactionContext; /** * An implementation of {@see HubInterface} that uses {@see SentrySdk} internally @@ -116,7 +118,7 @@ public function captureException(\Throwable $exception): ?string /** * {@inheritdoc} */ - public function captureEvent(array $payload): ?string + public function captureEvent($payload): ?string { return SentrySdk::getCurrentHub()->captureEvent($payload); } @@ -165,6 +167,14 @@ public function getIntegration(string $className): ?IntegrationInterface return SentrySdk::getCurrentHub()->getIntegration($className); } + /** + * {@inheritdoc} + */ + public function startTransaction(TransactionContext $context): Transaction + { + return SentrySdk::getCurrentHub()->startTransaction($context); + } + /** * @see https://www.php.net/manual/en/language.oop5.cloning.php#object.clone */ diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 2fc64b191..1f2dff515 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -6,8 +6,11 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; +use Sentry\Event; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; +use Sentry\Tracing\Transaction; +use Sentry\Tracing\TransactionContext; /** * This interface represent the class which is responsible for maintaining a @@ -83,9 +86,9 @@ public function captureException(\Throwable $exception): ?string; /** * Captures a new event using the provided data. * - * @param array $payload The data of the event being captured + * @param Event|array $payload The data of the event being captured */ - public function captureEvent(array $payload): ?string; + public function captureEvent($payload): ?string; /** * Captures an event that logs the last occurred error. @@ -135,4 +138,23 @@ public static function setCurrent(self $hub): self; * @psalm-return T|null */ public function getIntegration(string $className): ?IntegrationInterface; + + /** + * Starts a new `Transaction` and returns it. This is the entry point to manual + * tracing instrumentation. + * + * A tree structure can be built by adding child spans to the transaction, and + * child spans to other spans. To start a new child span within the transaction + * or any span, call the respective `startChild()` method. + * + * Every child span must be finished before the transaction is finished, + * otherwise the unfinished spans are discarded. + * + * The transaction must be finished with a call to its `finish()` method, at + * which point the transaction with all its finished child spans will be sent to + * Sentry. + * + * @param TransactionContext $context properties of the new `Transaction` + */ + public function startTransaction(TransactionContext $context): Transaction; } diff --git a/src/State/Scope.php b/src/State/Scope.php index e4e1b5b5c..2093e1f8e 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -10,6 +10,8 @@ use Sentry\Context\UserContext; use Sentry\Event; use Sentry\Severity; +use Sentry\Tracing\Span; +use Sentry\Tracing\Transaction; /** * The scope holds data that should implicitly be sent with Sentry events. It @@ -54,6 +56,11 @@ final class Scope */ private $level; + /** + * @var Span|null Set a Span on the Scope + */ + private $span; + /** * @var callable[] List of event processors */ @@ -213,6 +220,45 @@ public function setLevel(?Severity $level): self return $this; } + /** + * Returns the Span that is on the Scope. + */ + public function getSpan(): ?Span + { + return $this->span; + } + + /** + * Sets the Span on the Scope. + * + * @param Span|null $span The Span + * + * @return $this + */ + public function setSpan(?Span $span): self + { + $this->span = $span; + + return $this; + } + + /** + * Returns the Transaction that is on the Scope. + * + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ + public function getTransaction(): ?Transaction + { + $span = $this->span; + if (null !== $span && null !== $span->spanRecorder) { + /** @phpstan-ignore-next-line */ + return $span->spanRecorder->getSpans()[0]; + } + + return null; + } + /** * Add the given breadcrumb to the scope. * @@ -290,10 +336,10 @@ public function clear(): self * Applies the current context and fingerprint to the event. If the event has * already some breadcrumbs on it, the ones from this scope won't get merged. * - * @param Event $event The event object that will be enriched with scope data - * @param array $payload The raw payload of the event that will be propagated to the event processors + * @param Event $event The event object that will be enriched with scope data + * @param array|Event $payload The raw payload of the event that will be propagated to the event processors */ - public function applyToEvent(Event $event, array $payload): ?Event + public function applyToEvent(Event $event, $payload): ?Event { if (empty($event->getFingerprint())) { $event->setFingerprint($this->fingerprint); diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php new file mode 100644 index 000000000..38684666b --- /dev/null +++ b/src/Tracing/Span.php @@ -0,0 +1,280 @@ + An arbitrary mapping of additional metadata + */ + protected $data; + + /** + * @var float Timestamp in seconds (epoch time) indicating when the span started + */ + protected $startTimestamp; + + /** + * @var float|null Timestamp in seconds (epoch time) indicating when the span ended + */ + protected $endTimestamp; + + /** + * @var SpanRecorder|null Reference instance to the SpanRecorder + * + * @internal + */ + public $spanRecorder; + + /** + * Span constructor. + * + * @param SpanContext|null $context The context to create the span with + * + * @internal + */ + public function __construct(?SpanContext $context = null) + { + $this->traceId = $context->traceId ?? TraceId::generate(); + $this->spanId = $context->spanId ?? SpanId::generate(); + $this->parentSpanId = $context->parentSpanId ?? null; + $this->description = $context->description ?? null; + $this->op = $context->op ?? null; + $this->status = $context->status ?? null; + $this->sampled = $context->sampled ?? null; + + if ($context && $context->tags) { + $this->tags = new TagsContext($context->tags); + } else { + $this->tags = new TagsContext(); + } + + if ($context && $context->data) { + $this->data = new Context($context->data); + } else { + $this->data = new Context(); + } + + $this->startTimestamp = $context->startTimestamp ?? microtime(true); + $this->endTimestamp = $context->endTimestamp ?? null; + } + + /** + * Sets the finish timestamp on the current span. + * + * @param float|null $endTimestamp Takes an endTimestamp if the end should not be the time when you call this function + * + * @return string|null Finish for a span always returns null + */ + public function finish($endTimestamp = null): ?string + { + $this->endTimestamp = $endTimestamp ?? microtime(true); + + return null; + } + + /** + * Creates a new `Span` while setting the current `Span.id` as `parentSpanId`. + * Also the `sampled` decision will be inherited. + * + * @param SpanContext $context The Context of the child span + * + * @return Span Instance of the newly created Span + */ + public function startChild(SpanContext $context): self + { + $context->sampled = $this->sampled; + $context->parentSpanId = $this->spanId; + $context->traceId = $this->traceId; + + $span = new self($context); + + $span->spanRecorder = $this->spanRecorder; + if (null != $span->spanRecorder) { + $span->spanRecorder->add($span); + } + + return $span; + } + + public function getStartTimestamp(): float + { + return $this->startTimestamp; + } + + /** + * Returns `sentry-trace` header content. + */ + public function toTraceparent(): string + { + $sampled = ''; + if (null !== $this->sampled) { + $sampled = $this->sampled ? '-1' : '-0'; + } + + return $this->traceId . '-' . $this->spanId . $sampled; + } + + /** + * Gets the event as an array. + * + * @return array + */ + public function toArray(): array + { + $data = [ + 'span_id' => (string) $this->spanId, + 'trace_id' => (string) $this->traceId, + 'start_timestamp' => $this->startTimestamp, + ]; + + if (null !== $this->parentSpanId) { + $data['parent_span_id'] = (string) $this->parentSpanId; + } + + if (null !== $this->endTimestamp) { + $data['timestamp'] = $this->endTimestamp; + } + + if (null !== $this->status) { + $data['status'] = $this->status; + } + + if (null !== $this->description) { + $data['description'] = $this->description; + } + + if (null !== $this->op) { + $data['op'] = $this->op; + } + + if (!$this->data->isEmpty()) { + $data['data'] = $this->data->toArray(); + } + + if (!$this->tags->isEmpty()) { + $data['tags'] = $this->tags->toArray(); + } + + return $data; + } + + /** + * {@inheritdoc} + * + * @return array + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): void + { + $this->description = $description; + } + + public function getOp(): ?string + { + return $this->op; + } + + public function setOp(?string $op): void + { + $this->op = $op; + } + + public function getStatus(): ?string + { + return $this->status; + } + + public function setStatus(?string $status): void + { + $this->status = $status; + } + + public function getTags(): TagsContext + { + return $this->tags; + } + + /** + * @param array $tags + */ + public function setTags(array $tags): void + { + $this->tags->merge($tags); + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data->merge($data); + } + + public function setStartTimestamp(float $startTimestamp): void + { + $this->startTimestamp = $startTimestamp; + } + + public function getEndTimestamp(): ?float + { + return $this->endTimestamp; + } +} diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php new file mode 100644 index 000000000..885d0e606 --- /dev/null +++ b/src/Tracing/SpanContext.php @@ -0,0 +1,96 @@ +[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + + /** + * @var string|null Description of the Span + */ + public $description; + + /** + * @var string|null Operation of the Span + */ + public $op; + + /** + * @var string|null Completion status of the Span + */ + public $status; + + /** + * @var SpanId|null ID of the parent Span + */ + public $parentSpanId; + + /** + * @var bool|null Has the sample decision been made? + */ + public $sampled; + + /** + * @var SpanId|null Span ID + */ + public $spanId; + + /** + * @var TraceId|null Trace ID + */ + public $traceId; + + /** + * @var array|null A List of tags associated to this Span + */ + public $tags; + + /** + * @var array|null An arbitrary mapping of additional metadata + */ + public $data; + + /** + * @var float|null Timestamp in seconds (epoch time) indicating when the span started + */ + public $startTimestamp; + + /** + * @var float|null Timestamp in seconds (epoch time) indicating when the span ended + */ + public $endTimestamp; + + /** + * Returns a context depending on the header given. Containing trace_id, parent_span_id and sampled. + * + * @param string $header The sentry-trace header from the request + * + * @return static + */ + public static function fromTraceparent(string $header) + { + /** @phpstan-ignore-next-line */ + $context = new static(); + + if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { + return $context; + } + + if (mb_strlen($matches['trace_id']) > 0) { + $context->traceId = new TraceId($matches['trace_id']); + } + + if (mb_strlen($matches['span_id']) > 0) { + $context->parentSpanId = new SpanId($matches['span_id']); + } + + if (\array_key_exists('sampled', $matches)) { + $context->sampled = '1' === $matches['sampled']; + } + + return $context; + } +} diff --git a/src/Tracing/SpanId.php b/src/Tracing/SpanId.php new file mode 100644 index 000000000..b852b4682 --- /dev/null +++ b/src/Tracing/SpanId.php @@ -0,0 +1,43 @@ +value = $value; + } + + /** + * Generates a new span ID. + */ + public static function generate(): self + { + return new self(substr(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM)), 0, 16)); + } + + public function __toString(): string + { + return $this->value; + } +} diff --git a/src/Tracing/SpanRecorder.php b/src/Tracing/SpanRecorder.php new file mode 100644 index 000000000..c133da352 --- /dev/null +++ b/src/Tracing/SpanRecorder.php @@ -0,0 +1,49 @@ +maxSpans = $maxSpans; + } + + /** + * Adds a span to the array. + */ + public function add(Span $span): void + { + if (\count($this->spans) > $this->maxSpans) { + $span->spanRecorder = null; + } else { + $this->spans[] = $span; + } + } + + /** + * @return Span[] + */ + public function getSpans(): array + { + return $this->spans; + } +} diff --git a/src/Tracing/TraceId.php b/src/Tracing/TraceId.php new file mode 100644 index 000000000..86cdf31e6 --- /dev/null +++ b/src/Tracing/TraceId.php @@ -0,0 +1,43 @@ +value = $value; + } + + /** + * Generates a new trace ID. + */ + public static function generate(): self + { + return new self(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM))); + } + + public function __toString(): string + { + return $this->value; + } +} diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php new file mode 100644 index 000000000..7504899a9 --- /dev/null +++ b/src/Tracing/Transaction.php @@ -0,0 +1,139 @@ +hub = $hub; + $this->name = $context->name ?? ''; + } + + /** + * @param string $name Name of the transaction + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return array Returns the trace context + */ + public function getTraceContext(): array + { + $data = $this->toArray(); + + unset($data['start_timestamp']); + unset($data['timestamp']); + + return $data; + } + + /** + * Attaches SpanRecorder to the transaction itself. + */ + public function initSpanRecorder(): void + { + if (null === $this->spanRecorder) { + $this->spanRecorder = new SpanRecorder(); + } + $this->spanRecorder->add($this); + } + + /** + * {@inheritdoc} + * + * @return string|null Finish for a transaction returns the eventId or null in case we didn't send it + */ + public function finish($endTimestamp = null): ?string + { + if (null !== $this->endTimestamp) { + // Transaction was already finished once and we don't want to re-flush it + return null; + } + + parent::finish($endTimestamp); + + if (true !== $this->sampled) { + // At this point if `sampled !== true` we want to discard the transaction. + return null; + } + + if (null !== $this->hub) { + return $this->hub->captureEvent($this->toEvent()); + } + + return null; + } + + /** + * Returns an Event. + */ + public function toEvent(): Event + { + $event = new Event(); + $event->setType('transaction'); + $event->getTagsContext()->merge($this->tags->toArray()); + $event->setTransaction($this->name); + $event->setStartTimestamp($this->startTimestamp); + $event->setContext('trace', $this->getTraceContext()); + + if (null != $this->spanRecorder) { + $event->setSpans(array_filter($this->spanRecorder->getSpans(), function (Span $span): bool { + return $this->spanId != $span->spanId && null != $span->endTimestamp; + })); + } + + $event->setLevel(null); + if (null != $this->status && 'ok' != $this->status) { + $event->setLevel(Severity::error()); + } + + if ($this->endTimestamp) { + $event->setTimestamp($this->endTimestamp); + } + + return $event; + } + + /** + * {@inheritdoc} + * + * @return array + */ + public function jsonSerialize(): array + { + return $this->toEvent()->toArray(); + } +} diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php new file mode 100644 index 000000000..a5058c484 --- /dev/null +++ b/src/Tracing/TransactionContext.php @@ -0,0 +1,13 @@ +requestFactory->createRequest( - 'POST', - $dsn->getStoreApiEndpointUrl(), - ['Content-Type' => 'application/json'], - JSON::encode($event->toArray()) - ); + if ('transaction' === $event->getType()) { + $request = $this->requestFactory->createRequest( + 'POST', + $dsn->getEnvelopeApiEndpointUrl(), + ['Content-Type' => 'application/x-sentry-envelope'], + $event->toEnvelope() + ); + } else { + $request = $this->requestFactory->createRequest( + 'POST', + $dsn->getStoreApiEndpointUrl(), + ['Content-Type' => 'application/json'], + JSON::encode($event->toArray()) + ); + } if ($this->delaySendingUntilShutdown) { $this->pendingRequests[] = [$request, $event]; diff --git a/src/functions.php b/src/functions.php index 760004337..97b7f4a1d 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,6 +4,9 @@ namespace Sentry; +use Sentry\Tracing\Transaction; +use Sentry\Tracing\TransactionContext; + /** * Creates a new Client and Hub which will be set as current. * @@ -88,3 +91,25 @@ function withScope(callable $callback): void { SentrySdk::getCurrentHub()->withScope($callback); } + +/** + * Starts a new `Transaction` and returns it. This is the entry point to manual + * tracing instrumentation. + * + * A tree structure can be built by adding child spans to the transaction, and + * child spans to other spans. To start a new child span within the transaction + * or any span, call the respective `startChild()` method. + * + * Every child span must be finished before the transaction is finished, + * otherwise the unfinished spans are discarded. + * + * The transaction must be finished with a call to its `finish()` method, at + * which point the transaction with all its finished child spans will be sent to + * Sentry. + * + * @param TransactionContext $context properties of the new `Transaction` + */ +function startTransaction(TransactionContext $context): Transaction +{ + return SentrySdk::getCurrentHub()->startTransaction($context); +} diff --git a/tests/EventTest.php b/tests/EventTest.php index 0da88d4a2..a8dfb73d0 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -45,12 +45,12 @@ public function testToArray(): void $expected = [ 'event_id' => (string) $event->getId(false), 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), - 'level' => 'error', 'platform' => 'php', 'sdk' => [ 'name' => Client::SDK_IDENTIFIER, 'version' => PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(), ], + 'level' => 'error', 'contexts' => [ 'os' => [ 'name' => php_uname('s'), @@ -78,12 +78,12 @@ public function testToArrayMergesCustomContextsWithDefaultContexts(): void $expected = [ 'event_id' => (string) $event->getId(false), 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), - 'level' => 'error', 'platform' => 'php', 'sdk' => [ 'name' => Client::SDK_IDENTIFIER, 'version' => PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(), ], + 'level' => 'error', 'contexts' => [ 'os' => [ 'name' => php_uname('s'), @@ -289,4 +289,18 @@ public function testEventJsonSerialization(): void $this->assertNotFalse($serializedEvent); $this->assertJsonStringEqualsJsonString($encodingOfToArray, $serializedEvent); } + + public function testToEnvelope(): void + { + $event = new Event(); + + $envelope = $event->toEnvelope(); + $envelopeLines = explode("\n", $envelope); + + $this->assertContains('event_id', $envelopeLines[0]); + $this->assertContains('sent_at', $envelopeLines[0]); + $this->assertContains('type', $envelopeLines[1]); + $this->assertContains('event', $envelopeLines[1]); + $this->assertContains($event->getId(false)->__toString(), $envelopeLines[2]); + } } diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php new file mode 100644 index 000000000..bc0cb8772 --- /dev/null +++ b/tests/Tracing/SpanTest.php @@ -0,0 +1,92 @@ +traceId = TraceId::generate(); + $context->spanId = SpanId::generate(); + $context->parentSpanId = SpanId::generate(); + $context->description = 'description'; + $context->op = 'op'; + $context->status = 'ok'; + $context->sampled = true; + $tags = []; + $tags['a'] = 'b'; + $context->tags = $tags; + $data = []; + $data['c'] = 'd'; + $context->data = $data; + $context->startTimestamp = microtime(true); + $context->endTimestamp = microtime(true); + $span = new Span($context); + $data = $span->jsonSerialize(); + + $this->assertEquals($context->op, $data['op']); + $this->assertEquals($context->traceId->__toString(), $data['trace_id']); + $this->assertEquals($context->spanId->__toString(), $data['span_id']); + $this->assertEquals($context->parentSpanId->__toString(), $data['parent_span_id']); + $this->assertEquals($context->description, $data['description']); + $this->assertEquals($context->status, $data['status']); + $this->assertEquals($context->tags, $data['tags']); + $this->assertEquals($context->data, $data['data']); + $this->assertEquals($context->startTimestamp, $data['start_timestamp']); + $this->assertEquals($context->endTimestamp, $data['timestamp']); + } + + public function testFinish(): void + { + $span = new Span(); + $span->finish(); + $this->assertIsFloat($span->jsonSerialize()['timestamp']); + + $time = microtime(true); + $span = new Span(); + $span->finish($time); + $this->assertEquals($span->jsonSerialize()['timestamp'], $time); + } + + public function testTraceparentHeader(): void + { + $context = SpanContext::fromTraceparent('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb'); + $this->assertEquals('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', $context->traceId); + $this->assertNotEquals('bbbbbbbbbbbbbbbb', $context->spanId); + $this->assertEquals('bbbbbbbbbbbbbbbb', $context->parentSpanId); + $this->assertNull($context->sampled); + } + + public function testTraceparentHeaderSampledTrue(): void + { + $context = SpanContext::fromTraceparent('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-1'); + $this->assertTrue($context->sampled); + } + + public function testTraceparentHeaderSampledFalse(): void + { + $context = SpanContext::fromTraceparent('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-0'); + $this->assertFalse($context->sampled); + } + + public function testTraceparentHeaderJustSampleRate(): void + { + $context = SpanContext::fromTraceparent('1'); + $this->assertTrue($context->sampled); + + $context = SpanContext::fromTraceparent('0'); + $this->assertFalse($context->sampled); + } +} diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php new file mode 100644 index 000000000..5575a6458 --- /dev/null +++ b/tests/Tracing/TransactionTest.php @@ -0,0 +1,120 @@ +traceId = TraceId::generate(); + $context->spanId = SpanId::generate(); + $context->parentSpanId = SpanId::generate(); + $context->description = 'description'; + $context->name = 'name'; + $context->op = 'op'; + $context->status = 'ok'; + $context->sampled = true; + $tags = []; + $tags['a'] = 'b'; + $context->tags = $tags; + $data = []; + $data['c'] = 'd'; + $context->data = $data; + $context->startTimestamp = microtime(true); + $context->endTimestamp = microtime(true); + $transaction = new Transaction($context); + $data = $transaction->jsonSerialize(); + + $this->assertEquals($context->op, $data['contexts']['trace']['op']); + $this->assertEquals($context->name, $data['transaction']); + $this->assertEquals($context->traceId->__toString(), $data['contexts']['trace']['trace_id']); + $this->assertEquals($context->spanId->__toString(), $data['contexts']['trace']['span_id']); + $this->assertEquals($context->parentSpanId->__toString(), $data['contexts']['trace']['parent_span_id']); + $this->assertEquals($context->description, $data['contexts']['trace']['description']); + $this->assertEquals($context->status, $data['contexts']['trace']['status']); + $this->assertEquals($context->tags, $data['tags']); + $this->assertEquals($context->data, $data['contexts']['trace']['data']); + $this->assertEquals($context->startTimestamp, $data['start_timestamp']); + $this->assertEquals($context->endTimestamp, $data['timestamp']); + } + + public function testShouldContainFinishSpans(): void + { + $transaction = new Transaction(new TransactionContext()); + $transaction->spanRecorder = new SpanRecorder(); + $span1 = $transaction->startChild(new SpanContext()); + $span2 = $transaction->startChild(new SpanContext()); + $span3 = $transaction->startChild(new SpanContext()); + $span1->finish(); + $span2->finish(); + // $span3 is not finished and therefore not included + $transaction->finish(); + $data = $transaction->jsonSerialize(); + $this->assertCount(2, $data['spans']); + } + + public function testStartAndSendTransaction(): void + { + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 1])); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext()); + $span1 = $transaction->startChild(new SpanContext()); + $span2 = $transaction->startChild(new SpanContext()); + $span1->finish(); + $span2->finish(); + $endTimestamp = microtime(true); + $data = $transaction->jsonSerialize(); + + // We fake the endtime here + $data['timestamp'] = $endTimestamp; + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(function ($event) use ($data): bool { + $this->assertEqualWithIgnore($data, $event->toArray(), ['event_id']); + + return true; + })); + + $transaction->finish($endTimestamp); + + $this->assertCount(2, $data['spans']); + } + + private function assertEqualWithIgnore($expected, $actual, $ignoreKeys = [], $currentKey = null): void + { + if (\is_object($expected)) { + foreach ($expected as $key => $value) { + $this->assertEqualWithIgnore($expected->$key, $actual->$key, $ignoreKeys, $key); + } + } elseif (\is_array($expected)) { + foreach ($expected as $key => $value) { + $this->assertEqualWithIgnore($expected[$key], $actual[$key], $ignoreKeys, $key); + } + } elseif (null !== $currentKey && !\in_array($currentKey, $ignoreKeys)) { + $this->assertEquals($expected, $actual); + } + } +} diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index bdbb16de2..bcac41746 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -6,12 +6,17 @@ use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Discovery\MessageFactoryDiscovery; +use Http\Discovery\StreamFactoryDiscovery; +use Http\Discovery\UriFactoryDiscovery; +use Http\Mock\Client as HttpMockClient; use Http\Promise\FulfilledPromise; use Http\Promise\RejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\RequestInterface; use Psr\Log\LoggerInterface; use Sentry\Event; +use Sentry\HttpClient\HttpClientFactory; use Sentry\Options; use Sentry\Transport\HttpTransport; @@ -32,6 +37,43 @@ public function testSendThrowsIfDsnOptionIsNotSet(): void $transport->send(new Event()); } + public function testSendTransactionAsEnvelope(): void + { + $mockHttpClient = new HttpMockClient(); + + $httpClientFactory = new HttpClientFactory( + UriFactoryDiscovery::find(), + MessageFactoryDiscovery::find(), + StreamFactoryDiscovery::find(), + $mockHttpClient, + 'sentry.php.test', + '1.2.3' + ); + + $httpClient = $httpClientFactory->create(new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + 'default_integrations' => false, + ])); + + $transport = new HttpTransport( + new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + ]), + $httpClient, + MessageFactoryDiscovery::find(), + false + ); + + $event = new Event(); + $event->setType('transaction'); + $transport->send($event); + + /** @var RequestInterface|bool $httpRequest */ + $httpRequest = $mockHttpClient->getLastRequest(); + + $this->assertSame('application/x-sentry-envelope', $httpRequest->getHeaders()['Content-Type'][0]); + } + /** * @group legacy * From fa33c033d66d206a4022a99d0274a177d044f310 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 6 Jul 2020 15:09:01 +0200 Subject: [PATCH 0570/1161] Cleanup the deprecations triggered by Hub::getCurrent() and Hub::setCurrent() (#1038) --- CHANGELOG.md | 2 ++ UPGRADE-3.0.md | 3 +++ src/State/Hub.php | 23 ----------------------- src/State/HubAdapter.php | 20 -------------------- src/State/HubInterface.php | 20 -------------------- 5 files changed, 5 insertions(+), 63 deletions(-) create mode 100644 UPGRADE-3.0.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 926715b87..6b79d06f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton + ### 2.4.1 (2020-07-03) - Fix HTTP client connection timeouts not being applied if an HTTP proxy is specified (#1033) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md new file mode 100644 index 000000000..1c15c911f --- /dev/null +++ b/UPGRADE-3.0.md @@ -0,0 +1,3 @@ +# Upgrade 2.x to 3.0 + +- Removed the `HubInterface::getCurrentHub()` and `HubInterface::setCurrentHub()` methods. Use `SentrySdk::getCurrentHub()` and `SentrySdk::setCurrentHub()` instead. diff --git a/src/State/Hub.php b/src/State/Hub.php index 7470ffd63..f76b5ab0b 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -7,7 +7,6 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Integration\IntegrationInterface; -use Sentry\SentrySdk; use Sentry\Severity; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -193,28 +192,6 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool return null !== $breadcrumb; } - /** - * {@inheritdoc} - */ - public static function getCurrent(): HubInterface - { - @trigger_error(sprintf('The %s() method is deprecated since version 2.2 and will be removed in 3.0. Use SentrySdk::getCurrentHub() instead.', __METHOD__), E_USER_DEPRECATED); - - return SentrySdk::getCurrentHub(); - } - - /** - * {@inheritdoc} - */ - public static function setCurrent(HubInterface $hub): HubInterface - { - @trigger_error(sprintf('The %s() method is deprecated since version 2.2 and will be removed in 3.0. Use SentrySdk::setCurrentHub() instead.', __METHOD__), E_USER_DEPRECATED); - - SentrySdk::setCurrentHub($hub); - - return $hub; - } - /** * {@inheritdoc} */ diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 82d30bcdb..46154aa8e 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -139,26 +139,6 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool return SentrySdk::getCurrentHub()->addBreadcrumb($breadcrumb); } - /** - * {@inheritdoc} - */ - public static function getCurrent(): HubInterface - { - @trigger_error(sprintf('The %s() method is deprecated since version 2.2 and will be removed in 3.0. Use SentrySdk::getCurrentHub() instead.', __METHOD__), E_USER_DEPRECATED); - - return SentrySdk::getCurrentHub(); - } - - /** - * {@inheritdoc} - */ - public static function setCurrent(HubInterface $hub): HubInterface - { - @trigger_error(sprintf('The %s() method is deprecated since version 2.2 and will be removed in 3.0. Use SentrySdk::getCurrentHub() instead.', __METHOD__), E_USER_DEPRECATED); - - return SentrySdk::setCurrentHub($hub); - } - /** * {@inheritdoc} */ diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 1f2dff515..01faec17a 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -106,26 +106,6 @@ public function captureLastError(): ?string; */ public function addBreadcrumb(Breadcrumb $breadcrumb): bool; - /** - * Returns the current global Hub. - * - * @return HubInterface - * - * @deprecated since version 2.2, to be removed in 3.0 - */ - public static function getCurrent(): self; - - /** - * Sets the Hub as the current. - * - * @param HubInterface $hub The Hub that will become the current one - * - * @return HubInterface - * - * @deprecated since version 2.2, to be removed in 3.0 - */ - public static function setCurrent(self $hub): self; - /** * Gets the integration whose FQCN matches the given one if it's available on the current client. * From ce982cc7f1068e58efccca4b55b3b3b948fc08a2 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 6 Jul 2020 20:12:10 +0200 Subject: [PATCH 0571/1161] Cleanup the deprecations related to the error handler (#1037) --- CHANGELOG.md | 4 +- UPGRADE-3.0.md | 8 ++ src/ErrorHandler.php | 99 ------------------- src/Integration/ErrorListenerIntegration.php | 47 +-------- .../ExceptionListenerIntegration.php | 3 +- .../FatalErrorListenerIntegration.php | 26 +---- src/Options.php | 4 +- .../error_handler_can_be_registered_once.phpt | 5 +- .../error_handler_captures_fatal_error.phpt | 3 +- ...er_captures_out_of_memory_fatal_error.phpt | 5 +- ...er_gets_registered_with_legacy_method.phpt | 27 ----- ...er_gets_registered_with_legacy_method.phpt | 29 ------ ...er_gets_registered_with_legacy_method.phpt | 29 ------ ...s_previous_error_handler_return_value.phpt | 2 +- ...ror_integration_captures_fatal_error.phpt} | 7 +- ...tegration_respects_error_types_option.phpt | 12 +-- 16 files changed, 35 insertions(+), 275 deletions(-) delete mode 100644 tests/phpt/error_handler_error_listener_gets_registered_with_legacy_method.phpt delete mode 100644 tests/phpt/error_handler_exception_listener_gets_registered_with_legacy_method.phpt delete mode 100644 tests/phpt/error_handler_fatal_error_listener_gets_registered_with_legacy_method.phpt rename tests/phpt/{error_listener_integration_skips_fatal_errors_if_configured.phpt => fatal_error_integration_captures_fatal_error.phpt} (84%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b79d06f0..3a9557602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased -- [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton +- [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) +- [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) +- [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) ### 2.4.1 (2020-07-03) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 1c15c911f..5e45313ef 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -1,3 +1,11 @@ # Upgrade 2.x to 3.0 - Removed the `HubInterface::getCurrentHub()` and `HubInterface::setCurrentHub()` methods. Use `SentrySdk::getCurrentHub()` and `SentrySdk::setCurrentHub()` instead. +- Removed the `ErrorHandler::registerOnce()` method, use `ErrorHandler::register*Handler()` instead. +- Removed the `ErrorHandler::addErrorListener` method, use `ErrorHandler::addErrorHandlerListener()` instead. +- Removed the `ErrorHandler::addFatalErrorListener` method, use `ErrorHandler::addFatalErrorHandlerListener()` instead. +- Removed the `ErrorHandler::addExceptionListener` method, use `ErrorHandler::addExceptionHandlerListener()` instead. +- The signature of the `ErrorListenerIntegration::__construct()` method changed to not accept any parameter +- The signature of the `FatalErrorListenerIntegration::__construct()` method changed to not accept any parameter +- The `ErrorListenerIntegration` integration does not get called anymore when a fatal error occurs +- The default value of the `error_types` option changed to the value get from `error_reporting()` diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 97e3841d9..95493f447 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -120,38 +120,6 @@ private function __construct() $this->exceptionReflection->setAccessible(true); } - /** - * Gets the current registered error handler; if none is present, it will - * register it. Subsequent calls will not change the reserved memory size. - * - * @param int $reservedMemorySize The amount of memory to reserve for the - * fatal error handler - * @param bool $triggerDeprecation Whether to trigger the deprecation about - * the usage of this method. This is used - * to avoid errors when this method is called - * from other methods of this class until - * their implementation and behavior of - * registering all handlers can be changed - * - * @deprecated since version 2.1, to be removed in 3.0. - */ - public static function registerOnce(int $reservedMemorySize = self::DEFAULT_RESERVED_MEMORY_SIZE, bool $triggerDeprecation = true): self - { - if ($triggerDeprecation) { - @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Please use the registerOnceErrorHandler(), registerOnceFatalErrorHandler() or registerOnceExceptionHandler() methods instead.', __METHOD__), E_USER_DEPRECATED); - } - - if (null === self::$handlerInstance) { - self::$handlerInstance = new self(); - } - - self::registerOnceErrorHandler(); - self::registerOnceFatalErrorHandler($reservedMemorySize); - self::registerOnceExceptionHandler(); - - return self::$handlerInstance; - } - /** * Registers the error handler once and returns its instance. */ @@ -234,72 +202,6 @@ public static function registerOnceExceptionHandler(): self return self::$handlerInstance; } - /** - * Adds a listener to the current error handler to be called upon each - * invoked captured error; if no handler is registered, this method will - * instantiate and register it. - * - * @param callable $listener A callable that will act as a listener; - * this callable will receive a single - * \ErrorException argument - * - * @psalm-param callable(\ErrorException): void $listener - * - * @deprecated since version 2.1, to be removed in 3.0 - */ - public static function addErrorListener(callable $listener): void - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addErrorHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); - - /** @psalm-suppress DeprecatedMethod */ - $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); - $handler->errorListeners[] = $listener; - } - - /** - * Adds a listener to the current error handler to be called upon each - * invoked captured fatal error; if no handler is registered, this method - * will instantiate and register it. - * - * @param callable $listener A callable that will act as a listener; - * this callable will receive a single - * \ErrorException argument - * - * @psalm-param callable(FatalErrorException): void $listener - * - * @deprecated since version 2.1, to be removed in 3.0 - */ - public static function addFatalErrorListener(callable $listener): void - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addFatalErrorHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); - - /** @psalm-suppress DeprecatedMethod */ - $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); - $handler->fatalErrorListeners[] = $listener; - } - - /** - * Adds a listener to the current error handler to be called upon each - * invoked captured exception; if no handler is registered, this method - * will instantiate and register it. - * - * @param callable $listener A callable that will act as a listener; - * this callable will receive a single - * \Throwable argument - * - * @psalm-param callable(\Throwable): void $listener - * - * @deprecated since version 2.1, to be removed in 3.0 - */ - public static function addExceptionListener(callable $listener): void - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.1 and will be removed in 3.0. Use the addExceptionHandlerListener() method instead.', __METHOD__), E_USER_DEPRECATED); - - /** @psalm-suppress DeprecatedMethod */ - $handler = self::registerOnce(self::DEFAULT_RESERVED_MEMORY_SIZE, false); - $handler->exceptionListeners[] = $listener; - } - /** * Adds a listener to the current error handler that will be called every * time an error is captured. @@ -403,7 +305,6 @@ private function handleFatalError(): void $this->exceptionReflection->setValue($errorAsException, []); - $this->invokeListeners($this->errorListeners, $errorAsException); $this->invokeListeners($this->fatalErrorListeners, $errorAsException); } } diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index 039d2e158..68a02455d 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -5,9 +5,7 @@ namespace Sentry\Integration; use Sentry\ErrorHandler; -use Sentry\Exception\FatalErrorException; use Sentry\Exception\SilencedErrorException; -use Sentry\Options; use Sentry\SentrySdk; /** @@ -16,48 +14,13 @@ */ final class ErrorListenerIntegration implements IntegrationInterface { - /** - * @var Options|null The options, to know which error level to use - */ - private $options; - - /** - * @var bool Whether to handle fatal errors or not - */ - private $handleFatalErrors; - - /** - * Constructor. - * - * @param Options|null $options The options to be used with this integration - * @param bool $handleFatalErrors Whether to handle fatal errors or not - */ - public function __construct(?Options $options = null, bool $handleFatalErrors = true) - { - if (null !== $options) { - @trigger_error(sprintf('Passing the options as argument of the constructor of the "%s" class is deprecated since version 2.1 and will not work in 3.0.', self::class), E_USER_DEPRECATED); - } - - if ($handleFatalErrors) { - @trigger_error(sprintf('Handling fatal errors with the "%s" class is deprecated since version 2.1. Use the "%s" integration instead.', self::class, FatalErrorListenerIntegration::class), E_USER_DEPRECATED); - } - - $this->options = $options; - $this->handleFatalErrors = $handleFatalErrors; - } - /** * {@inheritdoc} */ public function setupOnce(): void { - /** @psalm-suppress DeprecatedMethod */ - $errorHandler = ErrorHandler::registerOnce(ErrorHandler::DEFAULT_RESERVED_MEMORY_SIZE, false); - $errorHandler->addErrorHandlerListener(function (\ErrorException $exception): void { - if (!$this->handleFatalErrors && $exception instanceof FatalErrorException) { - return; - } - + $errorHandler = ErrorHandler::registerOnceErrorHandler(); + $errorHandler->addErrorHandlerListener(static function (\ErrorException $exception): void { $currentHub = SentrySdk::getCurrentHub(); $integration = $currentHub->getIntegration(self::class); $client = $currentHub->getClient(); @@ -68,13 +31,11 @@ public function setupOnce(): void return; } - $options = $this->options ?? $client->getOptions(); - - if ($exception instanceof SilencedErrorException && !$options->shouldCaptureSilencedErrors()) { + if ($exception instanceof SilencedErrorException && !$client->getOptions()->shouldCaptureSilencedErrors()) { return; } - if (!($options->getErrorTypes() & $exception->getSeverity())) { + if (!($client->getOptions()->getErrorTypes() & $exception->getSeverity())) { return; } diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index 4fb95dcaf..606f161b1 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -18,8 +18,7 @@ final class ExceptionListenerIntegration implements IntegrationInterface */ public function setupOnce(): void { - /** @psalm-suppress DeprecatedMethod */ - $errorHandler = ErrorHandler::registerOnce(ErrorHandler::DEFAULT_RESERVED_MEMORY_SIZE, false); + $errorHandler = ErrorHandler::registerOnceExceptionHandler(); $errorHandler->addExceptionHandlerListener(static function (\Throwable $exception): void { $currentHub = SentrySdk::getCurrentHub(); $integration = $currentHub->getIntegration(self::class); diff --git a/src/Integration/FatalErrorListenerIntegration.php b/src/Integration/FatalErrorListenerIntegration.php index a1d2bf88d..9434c8562 100644 --- a/src/Integration/FatalErrorListenerIntegration.php +++ b/src/Integration/FatalErrorListenerIntegration.php @@ -6,7 +6,6 @@ use Sentry\ErrorHandler; use Sentry\Exception\FatalErrorException; -use Sentry\Options; use Sentry\SentrySdk; /** @@ -16,32 +15,13 @@ */ final class FatalErrorListenerIntegration implements IntegrationInterface { - /** - * @var Options|null The options, to know which error level to use - */ - private $options; - - /** - * Constructor. - * - * @param Options|null $options The options to be used with this integration - */ - public function __construct(?Options $options = null) - { - if (null !== $options) { - @trigger_error(sprintf('Passing the options as argument of the constructor of the "%s" class is deprecated since version 2.1 and will not work in 3.0.', self::class), E_USER_DEPRECATED); - } - - $this->options = $options; - } - /** * {@inheritdoc} */ public function setupOnce(): void { $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); - $errorHandler->addFatalErrorHandlerListener(function (FatalErrorException $exception): void { + $errorHandler->addFatalErrorHandlerListener(static function (FatalErrorException $exception): void { $currentHub = SentrySdk::getCurrentHub(); $integration = $currentHub->getIntegration(self::class); $client = $currentHub->getClient(); @@ -52,9 +32,7 @@ public function setupOnce(): void return; } - $options = $this->options ?? $client->getOptions(); - - if (!($options->getErrorTypes() & $exception->getSeverity())) { + if (!($client->getOptions()->getErrorTypes() & $exception->getSeverity())) { return; } diff --git a/src/Options.php b/src/Options.php index 67a1aa9ad..19c9ffd93 100644 --- a/src/Options.php +++ b/src/Options.php @@ -838,7 +838,7 @@ private function configureOptions(OptionsResolver $resolver): void return $event; }, 'tags' => [], - 'error_types' => E_ALL, + 'error_types' => error_reporting(), 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, 'before_breadcrumb' => static function (Breadcrumb $breadcrumb): Breadcrumb { return $breadcrumb; @@ -1086,7 +1086,7 @@ private function getDefaultIntegrations(): array if (null === $this->defaultIntegrations) { $this->defaultIntegrations = [ new ExceptionListenerIntegration(), - new ErrorListenerIntegration(null, false), + new ErrorListenerIntegration(), new FatalErrorListenerIntegration(), new RequestIntegration(), new TransactionIntegration(), diff --git a/tests/phpt/error_handler_can_be_registered_once.phpt b/tests/phpt/error_handler_can_be_registered_once.phpt index e949b5e8e..4a1c9ad27 100644 --- a/tests/phpt/error_handler_can_be_registered_once.phpt +++ b/tests/phpt/error_handler_can_be_registered_once.phpt @@ -52,11 +52,14 @@ function getHandlerRegistrationCount(callable $setHandlerCallback, callable $res return count($savedErrorHandlers); } -var_dump(ErrorHandler::registerOnce(10240, false) === ErrorHandler::registerOnce(10240, false)); +var_dump(ErrorHandler::registerOnceErrorHandler() === ErrorHandler::registerOnceErrorHandler()); var_dump(1 === getHandlerRegistrationCount('set_error_handler', 'restore_error_handler')); + +var_dump(ErrorHandler::registerOnceExceptionHandler() === ErrorHandler::registerOnceExceptionHandler()); var_dump(1 === getHandlerRegistrationCount('set_exception_handler', 'restore_exception_handler')); ?> --EXPECT-- bool(true) bool(true) bool(true) +bool(true) diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index efeae34db..d0aaf767d 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -45,7 +45,7 @@ SentrySdk::getCurrentHub()->bindClient($client); $errorHandler = ErrorHandler::registerOnceErrorHandler(); $errorHandler->addErrorHandlerListener(static function (): void { - echo 'Error listener called' . PHP_EOL; + echo 'Error listener called (it should not have been)' . PHP_EOL; }); $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); @@ -64,6 +64,5 @@ class TestClass implements \Serializable ?> --EXPECTF-- Fatal error: Class Sentry\Tests\TestClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d -Error listener called Transport called Fatal error listener called diff --git a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt index 82f2fc2b5..68e207879 100644 --- a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt @@ -21,7 +21,7 @@ require $vendor . '/vendor/autoload.php'; $errorHandler = ErrorHandler::registerOnceErrorHandler(); $errorHandler->addErrorHandlerListener(static function (): void { - echo 'Error listener called' . PHP_EOL; + echo 'Error listener called (it should not have been)' . PHP_EOL; }); $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); @@ -31,12 +31,11 @@ $errorHandler->addFatalErrorHandlerListener(static function (): void { $errorHandler = ErrorHandler::registerOnceExceptionHandler(); $errorHandler->addExceptionHandlerListener(static function (): void { - echo 'Exception listener called' . PHP_EOL; + echo 'Exception listener called (it should not have been)' . PHP_EOL; }); $foo = str_repeat('x', 1024 * 1024 * 30); ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d -Error listener called Fatal error listener called diff --git a/tests/phpt/error_handler_error_listener_gets_registered_with_legacy_method.phpt b/tests/phpt/error_handler_error_listener_gets_registered_with_legacy_method.phpt deleted file mode 100644 index 539b50e12..000000000 --- a/tests/phpt/error_handler_error_listener_gets_registered_with_legacy_method.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -Test that the error listener gets registered using the deprecated method ---FILE-- - false, 'integrations' => [ - new ErrorListenerIntegration(null, false), + new FatalErrorListenerIntegration(), ], ]); @@ -56,3 +56,4 @@ class FooClass implements \Serializable ?> --EXPECTF-- Fatal error: Class Sentry\Tests\FooClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d +Transport called diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index d7468bf7c..289d4204f 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -1,5 +1,5 @@ --TEST-- -Test that the FatalErrorListenerIntegration integration captures only the errors allowed by the `error_types` option +Test that the FatalErrorListenerIntegration integration captures only the errors allowed by the error_types option --FILE-- E_ALL & ~E_ERROR, 'default_integrations' => false, 'integrations' => [ new FatalErrorListenerIntegration(), @@ -53,13 +54,6 @@ SentrySdk::getCurrentHub()->bindClient($client); class FooClass implements \Serializable { } - -$options->setErrorTypes(E_ALL & ~E_ERROR); - -class BarClass implements \Serializable -{ -} ?> --EXPECTF-- Fatal error: Class Sentry\Tests\FooClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d -Transport called From 2e329d571299deb9284b66b5466141b899b54b14 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 6 Jul 2020 20:31:28 +0200 Subject: [PATCH 0572/1161] Cleanup the deprecated returning of an event ID as a string instead of as a value object (#1036) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 4 ++ src/Client.php | 8 +-- src/ClientInterface.php | 8 +-- src/Event.php | 12 +--- src/State/Hub.php | 13 ++-- src/State/HubAdapter.php | 11 ++-- src/State/HubInterface.php | 11 ++-- src/Tracing/Span.php | 5 +- src/Tracing/Transaction.php | 5 +- src/Transport/HttpTransport.php | 5 +- src/Transport/NullTransport.php | 5 +- src/Transport/SpoolTransport.php | 5 +- src/Transport/TransportInterface.php | 5 +- src/functions.php | 8 +-- tests/ClientBuilderTest.php | 7 ++- tests/ClientTest.php | 42 +++++++++---- tests/EventTest.php | 18 +----- tests/FunctionsTest.php | 25 +++++--- tests/State/HubTest.php | 63 +++++++++++-------- tests/Transport/NullTransportTest.php | 2 +- tests/Transport/SpoolTransportTest.php | 2 +- .../error_handler_captures_fatal_error.phpt | 3 +- ...rror_handler_respects_error_reporting.phpt | 3 +- ...tegration_respects_error_types_option.phpt | 3 +- ...rror_integration_captures_fatal_error.phpt | 3 +- ...tegration_respects_error_types_option.phpt | 3 +- 27 files changed, 159 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9557602..c56192327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) - [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) - [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) +- [BC BREAK] Remove deprecated code to return the event ID as a `string` rather than an object instance from the transport, the client and the hub (#1036) ### 2.4.1 (2020-07-03) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 5e45313ef..e2d1c02d0 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -9,3 +9,7 @@ - The signature of the `FatalErrorListenerIntegration::__construct()` method changed to not accept any parameter - The `ErrorListenerIntegration` integration does not get called anymore when a fatal error occurs - The default value of the `error_types` option changed to the value get from `error_reporting()` +- The signature of the `capture*()` global functions changed to return an instance of the `Sentry\EventId` class instead of a `string` +- The signature of the `ClientInterface::capture*()` methods changed to return an instance of the `Sentry\EventId` class instead of a `string` +- The signature of the `HubInterface::capture*e()` methods changed to return an instance of the `Sentry\EventId` class instead of a `string` +- The signature of the `Event::getId()` method changed to return an instance of the `Sentry\EventId` class instead of a `string` diff --git a/src/Client.php b/src/Client.php index 125e34956..6c07ac613 100644 --- a/src/Client.php +++ b/src/Client.php @@ -88,7 +88,7 @@ public function getOptions(): Options /** * {@inheritdoc} */ - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?EventId { $payload = [ 'message' => $message, @@ -101,7 +101,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope /** * {@inheritdoc} */ - public function captureException(\Throwable $exception, ?Scope $scope = null): ?string + public function captureException(\Throwable $exception, ?Scope $scope = null): ?EventId { if (!isset($this->integrations[IgnoreErrorsIntegration::class]) && $this->options->isExcludedException($exception, false)) { return null; @@ -113,7 +113,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null): ? /** * {@inheritdoc} */ - public function captureEvent($payload, ?Scope $scope = null): ?string + public function captureEvent($payload, ?Scope $scope = null): ?EventId { $event = $this->prepareEvent($payload, $scope); @@ -127,7 +127,7 @@ public function captureEvent($payload, ?Scope $scope = null): ?string /** * {@inheritdoc} */ - public function captureLastError(?Scope $scope = null): ?string + public function captureLastError(?Scope $scope = null): ?EventId { $error = error_get_last(); diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 36e6803ee..39dcbaf02 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -26,7 +26,7 @@ public function getOptions(): Options; * @param Severity $level The level of the message to be sent * @param Scope|null $scope An optional scope keeping the state */ - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?string; + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?EventId; /** * Logs an exception. @@ -34,14 +34,14 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope * @param \Throwable $exception The exception object * @param Scope|null $scope An optional scope keeping the state */ - public function captureException(\Throwable $exception, ?Scope $scope = null): ?string; + public function captureException(\Throwable $exception, ?Scope $scope = null): ?EventId; /** * Logs the most recent error (obtained with {@link error_get_last}). * * @param Scope|null $scope An optional scope keeping the state */ - public function captureLastError(?Scope $scope = null): ?string; + public function captureLastError(?Scope $scope = null): ?EventId; /** * Captures a new event using the provided data. @@ -49,7 +49,7 @@ public function captureLastError(?Scope $scope = null): ?string; * @param array|Event $payload The data of the event being captured * @param Scope|null $scope An optional scope keeping the state */ - public function captureEvent($payload, ?Scope $scope = null): ?string; + public function captureEvent($payload, ?Scope $scope = null): ?EventId; /** * Returns the integration instance if it is installed on the client. diff --git a/src/Event.php b/src/Event.php index a8e2b2778..2437e9604 100644 --- a/src/Event.php +++ b/src/Event.php @@ -187,18 +187,10 @@ public function __construct(?EventId $eventId = null) } /** - * Gets the UUID of this event. - * - * @return string|EventId + * Gets the ID of this event. */ - public function getId(bool $returnAsString = true) + public function getId(): EventId { - if ($returnAsString) { - @trigger_error(sprintf('Calling the method %s() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0.', __METHOD__), E_USER_DEPRECATED); - - return (string) $this->id; - } - return $this->id; } diff --git a/src/State/Hub.php b/src/State/Hub.php index f76b5ab0b..0a6804f51 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -6,6 +6,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; +use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; use Sentry\Tracing\Transaction; @@ -22,7 +23,7 @@ final class Hub implements HubInterface private $stack = []; /** - * @var string|null The ID of the last captured event + * @var EventId|null The ID of the last captured event */ private $lastEventId; @@ -48,7 +49,7 @@ public function getClient(): ?ClientInterface /** * {@inheritdoc} */ - public function getLastEventId(): ?string + public function getLastEventId(): ?EventId { return $this->lastEventId; } @@ -111,7 +112,7 @@ public function bindClient(ClientInterface $client): void /** * {@inheritdoc} */ - public function captureMessage(string $message, ?Severity $level = null): ?string + public function captureMessage(string $message, ?Severity $level = null): ?EventId { $client = $this->getClient(); @@ -125,7 +126,7 @@ public function captureMessage(string $message, ?Severity $level = null): ?strin /** * {@inheritdoc} */ - public function captureException(\Throwable $exception): ?string + public function captureException(\Throwable $exception): ?EventId { $client = $this->getClient(); @@ -139,7 +140,7 @@ public function captureException(\Throwable $exception): ?string /** * {@inheritdoc} */ - public function captureEvent($payload): ?string + public function captureEvent($payload): ?EventId { $client = $this->getClient(); @@ -153,7 +154,7 @@ public function captureEvent($payload): ?string /** * {@inheritdoc} */ - public function captureLastError(): ?string + public function captureLastError(): ?EventId { $client = $this->getClient(); diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 46154aa8e..17d6e4bf0 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -6,6 +6,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; +use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\SentrySdk; use Sentry\Severity; @@ -54,7 +55,7 @@ public function getClient(): ?ClientInterface /** * {@inheritdoc} */ - public function getLastEventId(): ?string + public function getLastEventId(): ?EventId { return SentrySdk::getCurrentHub()->getLastEventId(); } @@ -102,7 +103,7 @@ public function bindClient(ClientInterface $client): void /** * {@inheritdoc} */ - public function captureMessage(string $message, ?Severity $level = null): ?string + public function captureMessage(string $message, ?Severity $level = null): ?EventId { return SentrySdk::getCurrentHub()->captureMessage($message, $level); } @@ -110,7 +111,7 @@ public function captureMessage(string $message, ?Severity $level = null): ?strin /** * {@inheritdoc} */ - public function captureException(\Throwable $exception): ?string + public function captureException(\Throwable $exception): ?EventId { return SentrySdk::getCurrentHub()->captureException($exception); } @@ -118,7 +119,7 @@ public function captureException(\Throwable $exception): ?string /** * {@inheritdoc} */ - public function captureEvent($payload): ?string + public function captureEvent($payload): ?EventId { return SentrySdk::getCurrentHub()->captureEvent($payload); } @@ -126,7 +127,7 @@ public function captureEvent($payload): ?string /** * {@inheritdoc} */ - public function captureLastError(): ?string + public function captureLastError(): ?EventId { return SentrySdk::getCurrentHub()->captureLastError(); } diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 01faec17a..cb58b349b 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -7,6 +7,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; use Sentry\Tracing\Transaction; @@ -27,7 +28,7 @@ public function getClient(): ?ClientInterface; /** * Gets the ID of the last captured event. */ - public function getLastEventId(): ?string; + public function getLastEventId(): ?EventId; /** * Creates a new scope to store context information that will be layered on @@ -74,26 +75,26 @@ public function bindClient(ClientInterface $client): void; * @param string $message The message * @param Severity $level The severity level of the message */ - public function captureMessage(string $message, ?Severity $level = null): ?string; + public function captureMessage(string $message, ?Severity $level = null): ?EventId; /** * Captures an exception event and sends it to Sentry. * * @param \Throwable $exception The exception */ - public function captureException(\Throwable $exception): ?string; + public function captureException(\Throwable $exception): ?EventId; /** * Captures a new event using the provided data. * * @param Event|array $payload The data of the event being captured */ - public function captureEvent($payload): ?string; + public function captureEvent($payload): ?EventId; /** * Captures an event that logs the last occurred error. */ - public function captureLastError(): ?string; + public function captureLastError(): ?EventId; /** * Records a new breadcrumb which will be attached to future events. They diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 38684666b..5d0904ae6 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -6,6 +6,7 @@ use Sentry\Context\Context; use Sentry\Context\TagsContext; +use Sentry\EventId; /** * This class stores all the information about a Span. @@ -112,9 +113,9 @@ public function __construct(?SpanContext $context = null) * * @param float|null $endTimestamp Takes an endTimestamp if the end should not be the time when you call this function * - * @return string|null Finish for a span always returns null + * @return EventId|null Finish for a span always returns null */ - public function finish($endTimestamp = null): ?string + public function finish($endTimestamp = null): ?EventId { $this->endTimestamp = $endTimestamp ?? microtime(true); diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index 7504899a9..b56e0f478 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -5,6 +5,7 @@ namespace Sentry\Tracing; use Sentry\Event; +use Sentry\EventId; use Sentry\Severity; use Sentry\State\HubInterface; @@ -74,9 +75,9 @@ public function initSpanRecorder(): void /** * {@inheritdoc} * - * @return string|null Finish for a transaction returns the eventId or null in case we didn't send it + * @return EventId|null Finish for a transaction returns the eventId or null in case we didn't send it */ - public function finish($endTimestamp = null): ?string + public function finish($endTimestamp = null): ?EventId { if (null !== $this->endTimestamp) { // Transaction was already finished once and we don't want to re-flush it diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 079a15193..bd6fd8937 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -13,6 +13,7 @@ use Psr\Log\NullLogger; use Sentry\Dsn; use Sentry\Event; +use Sentry\EventId; use Sentry\Options; use Sentry\Util\JSON; @@ -109,7 +110,7 @@ public function __destruct() /** * {@inheritdoc} */ - public function send(Event $event): ?string + public function send(Event $event): ?EventId { $dsn = $this->options->getDsn(false); @@ -151,7 +152,7 @@ public function send(Event $event): ?string } } - return (string) $event->getId(false); + return $event->getId(); } /** diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index 02dc30a45..3219044c7 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -5,6 +5,7 @@ namespace Sentry\Transport; use Sentry\Event; +use Sentry\EventId; /** * This transport fakes the sending of events by just ignoring them. @@ -18,8 +19,8 @@ class NullTransport implements TransportInterface /** * {@inheritdoc} */ - public function send(Event $event): ?string + public function send(Event $event): ?EventId { - return (string) $event->getId(false); + return $event->getId(); } } diff --git a/src/Transport/SpoolTransport.php b/src/Transport/SpoolTransport.php index c988c5e44..65715b22a 100644 --- a/src/Transport/SpoolTransport.php +++ b/src/Transport/SpoolTransport.php @@ -5,6 +5,7 @@ namespace Sentry\Transport; use Sentry\Event; +use Sentry\EventId; use Sentry\Spool\SpoolInterface; /** @@ -40,10 +41,10 @@ public function getSpool(): SpoolInterface /** * {@inheritdoc} */ - public function send(Event $event): ?string + public function send(Event $event): ?EventId { if ($this->spool->queueEvent($event)) { - return (string) $event->getId(false); + return $event->getId(); } return null; diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index b6d2a8567..166714eb8 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -5,6 +5,7 @@ namespace Sentry\Transport; use Sentry\Event; +use Sentry\EventId; /** * This interface must be implemented by all classes willing to provide a way @@ -19,7 +20,7 @@ interface TransportInterface * * @param Event $event The event * - * @return string|null Returns the ID of the event or `null` if it failed to be sent + * @return EventId|null Returns the ID of the event or `null` if it failed to be sent */ - public function send(Event $event): ?string; + public function send(Event $event): ?EventId; } diff --git a/src/functions.php b/src/functions.php index 97b7f4a1d..dfd13842a 100644 --- a/src/functions.php +++ b/src/functions.php @@ -25,7 +25,7 @@ function init(array $options = []): void * @param string $message The message * @param Severity $level The severity level of the message */ -function captureMessage(string $message, ?Severity $level = null): ?string +function captureMessage(string $message, ?Severity $level = null): ?EventId { return SentrySdk::getCurrentHub()->captureMessage($message, $level); } @@ -35,7 +35,7 @@ function captureMessage(string $message, ?Severity $level = null): ?string * * @param \Throwable $exception The exception */ -function captureException(\Throwable $exception): ?string +function captureException(\Throwable $exception): ?EventId { return SentrySdk::getCurrentHub()->captureException($exception); } @@ -45,7 +45,7 @@ function captureException(\Throwable $exception): ?string * * @param array $payload The data of the event being captured */ -function captureEvent(array $payload): ?string +function captureEvent(array $payload): ?EventId { return SentrySdk::getCurrentHub()->captureEvent($payload); } @@ -53,7 +53,7 @@ function captureEvent(array $payload): ?string /** * Logs the most recent error (obtained with {@link error_get_last}). */ -function captureLastError(): ?string +function captureLastError(): ?EventId { return SentrySdk::getCurrentHub()->captureLastError(); } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index b9183e970..32eab048d 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -17,6 +17,7 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventId; use Sentry\FlushableClientInterface; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; @@ -82,17 +83,19 @@ public function testSetMessageFactory(): void */ public function testSetTransport(): void { + $eventId = EventId::generate(); + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') - ->willReturn('ddb4a0b9ab1941bf92bd2520063663e3'); + ->willReturn($eventId); $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) ->setTransport($transport) ->getClient(); - $this->assertSame('ddb4a0b9ab1941bf92bd2520063663e3', $client->captureMessage('foo')); + $this->assertSame($eventId, $client->captureMessage('foo')); } /** diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 430e151c7..45a8841e8 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,6 +10,7 @@ use Psr\Log\LoggerInterface; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventId; use Sentry\Options; use Sentry\SentrySdk; use Sentry\Severity; @@ -114,11 +115,13 @@ public function captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesData public function testCaptureEvent(): void { + $eventId = EventId::generate(); + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') - ->willReturn('500a339f3ab2450b96dee542adf36ba7'); + ->willReturn($eventId); $client = ClientBuilder::create() ->setTransportFactory($this->createTransportFactory($transport)) @@ -133,7 +136,7 @@ public function testCaptureEvent(): void 'user_context' => ['bar' => 'foo'], ]; - $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent($inputData)); + $this->assertSame($eventId, $client->captureEvent($inputData)); } /** @@ -141,6 +144,8 @@ public function testCaptureEvent(): void */ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOption(bool $attachStacktraceOption, array $payload, bool $shouldAttachStacktrace): void { + $eventId = EventId::generate(); + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -156,13 +161,13 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt return true; })) - ->willReturn('500a339f3ab2450b96dee542adf36ba7'); + ->willReturn($eventId); $client = ClientBuilder::create(['attach_stacktrace' => $attachStacktraceOption]) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent($payload)); + $this->assertEquals($eventId, $client->captureEvent($payload)); } public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider(): \Generator @@ -198,6 +203,7 @@ public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionD public function testCaptureEventPrefersExplicitStacktrace(): void { + $eventId = EventId::generate(); $explicitStacktrace = $this->createMock(Stacktrace::class); $payload = ['stacktrace' => $explicitStacktrace]; @@ -208,17 +214,19 @@ public function testCaptureEventPrefersExplicitStacktrace(): void ->with($this->callback(static function (Event $event) use ($explicitStacktrace): bool { return $explicitStacktrace === $event->getStacktrace(); })) - ->willReturn('500a339f3ab2450b96dee542adf36ba7'); + ->willReturn($eventId); $client = ClientBuilder::create(['attach_stacktrace' => true]) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertEquals('500a339f3ab2450b96dee542adf36ba7', $client->captureEvent($payload)); + $this->assertEquals($eventId, $client->captureEvent($payload)); } public function testCaptureLastError(): void { + $eventId = EventId::generate(); + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -230,7 +238,8 @@ public function testCaptureLastError(): void $this->assertEquals('foo', $exception['value']); return true; - })); + })) + ->willReturn($eventId); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) ->setTransportFactory($this->createTransportFactory($transport)) @@ -238,7 +247,7 @@ public function testCaptureLastError(): void @trigger_error('foo', E_USER_NOTICE); - $client->captureLastError(); + $this->assertSame($eventId, $client->captureLastError()); $this->clearLastError(); } @@ -248,7 +257,9 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) - ->method('send'); + ->method('send') + ->with($this->anything()) + ->willReturn(null); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) ->setTransportFactory($this->createTransportFactory($transport)) @@ -256,7 +267,7 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void $this->clearLastError(); - $client->captureLastError(); + $this->assertNull($client->captureLastError()); } /** @@ -339,7 +350,9 @@ public function testProcessEventDiscardsEventWhenItIsSampledDueToSampleRateOptio /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($transportCallInvocationMatcher) - ->method('send'); + ->method('send') + ->with($this->anything()) + ->willReturn(null); /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); @@ -484,6 +497,8 @@ public function convertExceptionDataProvider(): array public function testAttachStacktrace(): void { + $eventId = EventId::generate(); + /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -492,13 +507,14 @@ public function testAttachStacktrace(): void $result = $event->getStacktrace(); return null !== $result; - })); + })) + ->willReturn($eventId); $client = ClientBuilder::create(['attach_stacktrace' => true]) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $client->captureMessage('test'); + $this->assertSame($eventId, $client->captureMessage('test')); } /** diff --git a/tests/EventTest.php b/tests/EventTest.php index a8dfb73d0..bc91f73e3 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -23,19 +23,7 @@ public function testEventIsGeneratedWithUniqueIdentifier(): void $event1 = new Event(); $event2 = new Event(); - $this->assertNotEquals($event1->getId(false), $event2->getId(false)); - } - - /** - * @group legacy - * - * @expectedDeprecation Calling the method Sentry\Event::getId() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0. - */ - public function testGetEventIdThrowsDeprecationErrorIfExpectingStringReturnType(): void - { - $event = new Event(); - - $this->assertSame($event->getId(), (string) $event->getId(false)); + $this->assertNotEquals($event1->getId(), $event2->getId()); } public function testToArray(): void @@ -43,7 +31,7 @@ public function testToArray(): void $event = new Event(); $expected = [ - 'event_id' => (string) $event->getId(false), + 'event_id' => (string) $event->getId(), 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), 'platform' => 'php', 'sdk' => [ @@ -76,7 +64,7 @@ public function testToArrayMergesCustomContextsWithDefaultContexts(): void $event->setContext('runtime', ['baz' => 'baz']); $expected = [ - 'event_id' => (string) $event->getId(false), + 'event_id' => (string) $event->getId(), 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), 'platform' => 'php', 'sdk' => [ diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 00db75d10..15aee4ce2 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -9,6 +9,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventId; use Sentry\Options; use Sentry\SentrySdk; use Sentry\Severity; @@ -36,20 +37,23 @@ public function testInit(): void public function testCaptureMessage(): void { + $eventId = EventId::generate(); + /** @var ClientInterface|MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') ->with('foo', Severity::debug()) - ->willReturn('92db40a886c0458288c7c83935a350ef'); + ->willReturn($eventId); SentrySdk::getCurrentHub()->bindClient($client); - $this->assertEquals('92db40a886c0458288c7c83935a350ef', captureMessage('foo', Severity::debug())); + $this->assertSame($eventId, captureMessage('foo', Severity::debug())); } public function testCaptureException(): void { + $eventId = EventId::generate(); $exception = new \RuntimeException('foo'); /** @var ClientInterface|MockObject $client */ @@ -57,39 +61,44 @@ public function testCaptureException(): void $client->expects($this->once()) ->method('captureException') ->with($exception) - ->willReturn('2b867534eead412cbdb882fd5d441690'); + ->willReturn($eventId); SentrySdk::getCurrentHub()->bindClient($client); - $this->assertEquals('2b867534eead412cbdb882fd5d441690', captureException($exception)); + $this->assertSame($eventId, captureException($exception)); } public function testCaptureEvent(): void { + $eventId = EventId::generate(); + /** @var ClientInterface|MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') ->with(['message' => 'foo']) - ->willReturn('2b867534eead412cbdb882fd5d441690'); + ->willReturn($eventId); SentrySdk::getCurrentHub()->bindClient($client); - $this->assertEquals('2b867534eead412cbdb882fd5d441690', captureEvent(['message' => 'foo'])); + $this->assertSame($eventId, captureEvent(['message' => 'foo'])); } public function testCaptureLastError() { + $eventId = EventId::generate(); + /** @var ClientInterface|MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) - ->method('captureLastError'); + ->method('captureLastError') + ->willReturn($eventId); SentrySdk::getCurrentHub()->bindClient($client); @trigger_error('foo', E_USER_NOTICE); - captureLastError(); + $this->assertSame($eventId, captureLastError()); } public function testAddBreadcrumb(): void diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 9b024795b..6650a0bf7 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -9,6 +9,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Options; use Sentry\Severity; @@ -43,16 +44,19 @@ public function testGetScope(): void public function testGetLastEventId(): void { + $eventId = EventId::generate(); + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') - ->willReturn('92db40a886c0458288c7c83935a350ef'); + ->willReturn($eventId); $hub = new Hub($client); $this->assertNull($hub->getLastEventId()); - $this->assertEquals($hub->captureMessage('foo'), $hub->getLastEventId()); + $this->assertSame($hub->captureMessage('foo'), $hub->getLastEventId()); + $this->assertSame($eventId, $hub->getLastEventId()); } public function testPushScope(): void @@ -193,12 +197,14 @@ public function testBindClient(): void public function testCaptureMessage(): void { + $eventId = EventId::generate(); + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') ->with('foo', Severity::debug()) - ->willReturn('2b867534eead412cbdb882fd5d441690'); + ->willReturn($eventId); $hub = new Hub(); @@ -206,11 +212,12 @@ public function testCaptureMessage(): void $hub->bindClient($client); - $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureMessage('foo', Severity::debug())); + $this->assertSame($eventId, $hub->captureMessage('foo', Severity::debug())); } public function testCaptureException(): void { + $eventId = EventId::generate(); $exception = new \RuntimeException('foo'); /** @var ClientInterface&MockObject $client */ @@ -218,7 +225,7 @@ public function testCaptureException(): void $client->expects($this->once()) ->method('captureException') ->with($exception) - ->willReturn('2b867534eead412cbdb882fd5d441690'); + ->willReturn($eventId); $hub = new Hub(); @@ -226,16 +233,18 @@ public function testCaptureException(): void $hub->bindClient($client); - $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureException($exception)); + $this->assertSame($eventId, $hub->captureException($exception)); } public function testCaptureLastError(): void { + $eventId = EventId::generate(); + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureLastError') - ->willReturn('7565e130d1d14e639442110a6dd1cbab'); + ->willReturn($eventId); $hub = new Hub(); @@ -243,7 +252,27 @@ public function testCaptureLastError(): void $hub->bindClient($client); - $this->assertEquals('7565e130d1d14e639442110a6dd1cbab', $hub->captureLastError()); + $this->assertSame($eventId, $hub->captureLastError()); + } + + public function testCaptureEvent(): void + { + $eventId = EventId::generate(); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('captureEvent') + ->with(['message' => 'test']) + ->willReturn($eventId); + + $hub = new Hub(); + + $this->assertNull($hub->captureEvent([])); + + $hub->bindClient($client); + + $this->assertSame($eventId, $hub->captureEvent(['message' => 'test'])); } public function testAddBreadcrumb(): void @@ -386,24 +415,6 @@ public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallb $this->assertTrue($callbackInvoked); } - public function testCaptureEvent(): void - { - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('captureEvent') - ->with(['message' => 'test']) - ->willReturn('2b867534eead412cbdb882fd5d441690'); - - $hub = new Hub(); - - $this->assertNull($hub->captureEvent([])); - - $hub->bindClient($client); - - $this->assertEquals('2b867534eead412cbdb882fd5d441690', $hub->captureEvent(['message' => 'test'])); - } - public function testGetIntegration(): void { /** @var IntegrationInterface&MockObject $integration */ diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php index ad69ce251..3d95c81ff 100644 --- a/tests/Transport/NullTransportTest.php +++ b/tests/Transport/NullTransportTest.php @@ -15,6 +15,6 @@ public function testSend(): void $transport = new NullTransport(); $event = new Event(); - $this->assertSame((string) $event->getId(false), $transport->send($event)); + $this->assertSame($event->getId(), $transport->send($event)); } } diff --git a/tests/Transport/SpoolTransportTest.php b/tests/Transport/SpoolTransportTest.php index b0e8ff14d..a65699a2c 100644 --- a/tests/Transport/SpoolTransportTest.php +++ b/tests/Transport/SpoolTransportTest.php @@ -48,7 +48,7 @@ public function testSend(bool $isSendingSuccessful): void $eventId = $this->transport->send($event); if ($isSendingSuccessful) { - $this->assertSame((string) $event->getId(false), $eventId); + $this->assertSame($event->getId(), $eventId); } else { $this->assertNull($eventId); } diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index d0aaf767d..6e89f2092 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -10,6 +10,7 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\ErrorHandler; use Sentry\Event; +use Sentry\EventId; use Sentry\Options; use Sentry\SentrySdk; use Sentry\Transport\TransportFactoryInterface; @@ -27,7 +28,7 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?string + public function send(Event $event): ?EventId { echo 'Transport called' . PHP_EOL; diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt index 0b6dc3420..244106008 100644 --- a/tests/phpt/error_handler_respects_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -9,6 +9,7 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventId; use Sentry\Options; use Sentry\SentrySdk; use Sentry\Transport\TransportFactoryInterface; @@ -26,7 +27,7 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?string + public function send(Event $event): ?EventId { echo 'Transport called' . PHP_EOL; diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt index 3d3873396..d98bc0409 100644 --- a/tests/phpt/error_listener_integration_respects_error_types_option.phpt +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -9,6 +9,7 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventId; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Options; use Sentry\SentrySdk; @@ -27,7 +28,7 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?string + public function send(Event $event): ?EventId { echo 'Transport called'; diff --git a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt index fbbe4fc4c..d159b594e 100644 --- a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt @@ -9,6 +9,7 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventId; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; use Sentry\SentrySdk; @@ -27,7 +28,7 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?string + public function send(Event $event): ?EventId { echo 'Transport called' . PHP_EOL; diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index 289d4204f..c1c90aa00 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -9,6 +9,7 @@ namespace Sentry\Tests; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventId; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; use Sentry\SentrySdk; @@ -27,7 +28,7 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?string + public function send(Event $event): ?EventId { echo 'Transport called (it should not have been)' . PHP_EOL; From 5356a2bab1b465a9e4978fdec3276597dccddaef Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 7 Jul 2020 13:08:38 +0200 Subject: [PATCH 0573/1161] Remove deprecated methods from the Options class related to getting the DSN option and its parts (#1044) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 10 ++- src/Dsn.php | 28 +++---- .../MissingProjectIdCredentialException.php | 23 ------ .../MissingPublicKeyCredentialException.php | 23 ------ .../Authentication/SentryAuthentication.php | 5 +- src/HttpClient/HttpClientFactory.php | 2 +- src/Options.php | 77 +------------------ src/Transport/DefaultTransportFactory.php | 2 +- src/Transport/HttpTransport.php | 5 +- ...issingProjectIdCredentialExceptionTest.php | 22 ------ ...issingPublicKeyCredentialExceptionTest.php | 22 ------ tests/OptionsTest.php | 73 ++---------------- 13 files changed, 34 insertions(+), 259 deletions(-) delete mode 100644 src/Exception/MissingProjectIdCredentialException.php delete mode 100644 src/Exception/MissingPublicKeyCredentialException.php delete mode 100644 tests/Exception/MissingProjectIdCredentialExceptionTest.php delete mode 100644 tests/Exception/MissingPublicKeyCredentialExceptionTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c56192327..a8035985c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) - [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) - [BC BREAK] Remove deprecated code to return the event ID as a `string` rather than an object instance from the transport, the client and the hub (#1036) +- [BC BREAK] Remove some deprecated methods from the `Options` class. ### 2.4.1 (2020-07-03) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index e2d1c02d0..6be8baf57 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -9,7 +9,9 @@ - The signature of the `FatalErrorListenerIntegration::__construct()` method changed to not accept any parameter - The `ErrorListenerIntegration` integration does not get called anymore when a fatal error occurs - The default value of the `error_types` option changed to the value get from `error_reporting()` -- The signature of the `capture*()` global functions changed to return an instance of the `Sentry\EventId` class instead of a `string` -- The signature of the `ClientInterface::capture*()` methods changed to return an instance of the `Sentry\EventId` class instead of a `string` -- The signature of the `HubInterface::capture*e()` methods changed to return an instance of the `Sentry\EventId` class instead of a `string` -- The signature of the `Event::getId()` method changed to return an instance of the `Sentry\EventId` class instead of a `string` +- The signature of the `capture*()` global functions changed to return an instance of the `EventId` class instead of a `string` +- The signature of the `ClientInterface::capture*()` methods changed to return an instance of the `EventId` class instead of a `string` +- The signature of the `HubInterface::capture*e()` methods changed to return an instance of the `EventId` class instead of a `string` +- The signature of the `Event::getId()` method changed to return an instance of the `EventId` class instead of a `string` +- The signature of the `Options::getDsn()` method changed to always return an instance of the `Dsn` class instead of a `string` +- Removed the `Options::getProjectId`, `Options::getPublicKey` and `Options::getSecretKey` methods, use `Options::getDsn()` instead. diff --git a/src/Dsn.php b/src/Dsn.php index fe2dc84cf..f99754dc3 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -197,11 +197,17 @@ public function getEnvelopeApiEndpointUrl(): string } /** - * Returns the base url to Sentry from the DSN. + * @see https://www.php.net/manual/en/language.oop5.magic.php#object.tostring */ - protected function getBaseEndpointUrl(): string + public function __toString(): string { - $url = $this->scheme . '://' . $this->host; + $url = $this->scheme . '://' . $this->publicKey; + + if (null !== $this->secretKey) { + $url .= ':' . $this->secretKey; + } + + $url .= '@' . $this->host; if (('http' === $this->scheme && 80 !== $this->port) || ('https' === $this->scheme && 443 !== $this->port)) { $url .= ':' . $this->port; @@ -211,23 +217,17 @@ protected function getBaseEndpointUrl(): string $url .= $this->path; } - $url .= '/api/' . $this->projectId; + $url .= '/' . $this->projectId; return $url; } /** - * @see https://www.php.net/manual/en/language.oop5.magic.php#object.tostring + * Returns the base url to Sentry from the DSN. */ - public function __toString(): string + private function getBaseEndpointUrl(): string { - $url = $this->scheme . '://' . $this->publicKey; - - if (null !== $this->secretKey) { - $url .= ':' . $this->secretKey; - } - - $url .= '@' . $this->host; + $url = $this->scheme . '://' . $this->host; if (('http' === $this->scheme && 80 !== $this->port) || ('https' === $this->scheme && 443 !== $this->port)) { $url .= ':' . $this->port; @@ -237,7 +237,7 @@ public function __toString(): string $url .= $this->path; } - $url .= '/' . $this->projectId; + $url .= '/api/' . $this->projectId; return $url; } diff --git a/src/Exception/MissingProjectIdCredentialException.php b/src/Exception/MissingProjectIdCredentialException.php deleted file mode 100644 index f3adf3181..000000000 --- a/src/Exception/MissingProjectIdCredentialException.php +++ /dev/null @@ -1,23 +0,0 @@ -options->getDsn(false); + $dsn = $this->options->getDsn(); - if (!$dsn instanceof Dsn) { + if (null === $dsn) { return $request; } diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index d5abc7602..de2f28655 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -100,7 +100,7 @@ public function __construct( */ public function create(Options $options): HttpAsyncClientInterface { - if (null === $options->getDsn(false)) { + if (null === $options->getDsn()) { throw new \RuntimeException('Cannot create an HTTP client without the Sentry DSN set in the options.'); } diff --git a/src/Options.php b/src/Options.php index 19c9ffd93..5a0d781f6 100644 --- a/src/Options.php +++ b/src/Options.php @@ -326,22 +326,6 @@ public function setInAppIncludedPaths(array $paths): void $this->options = $this->resolver->resolve($options); } - /** - * Gets the project ID number to send to the Sentry server. - * - * @deprecated since version 2.4, to be removed in 3.0 - */ - public function getProjectId(): ?string - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.4 and will be removed in 3.0. Use the getDsn() method instead.', __METHOD__), E_USER_DEPRECATED); - - if (null === $this->options['dsn']) { - return null; - } - - return (string) $this->options['dsn']->getProjectId(); - } - /** * Gets the project which the authenticated user is bound to. */ @@ -362,38 +346,6 @@ public function setProjectRoot(?string $path): void $this->options = $this->resolver->resolve($options); } - /** - * Gets the public key to authenticate the SDK. - * - * @deprecated since version 2.4, to be removed in 3.0 - */ - public function getPublicKey(): ?string - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.4 and will be removed in 3.0. Use the getDsn() method instead.', __METHOD__), E_USER_DEPRECATED); - - if (null === $this->options['dsn']) { - return null; - } - - return $this->options['dsn']->getPublicKey(); - } - - /** - * Gets the secret key to authenticate the SDK. - * - * @deprecated since version 2.4, to be removed in 3.0 - */ - public function getSecretKey(): ?string - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.4 and will be removed in 3.0. Use the getDsn() method instead.', __METHOD__), E_USER_DEPRECATED); - - if (null === $this->options['dsn']) { - return null; - } - - return $this->options['dsn']->getSecretKey(); - } - /** * Gets the logger used by Sentry. */ @@ -438,35 +390,10 @@ public function setRelease(?string $release): void /** * Gets the DSN of the Sentry server the authenticated user is bound to. - * - * @param bool $returnAsString Whether to return the DSN as a string or as an object - * - * @return string|Dsn|null */ - public function getDsn(bool $returnAsString = true) + public function getDsn(): ?Dsn { - /** @var Dsn|null $dsn */ - $dsn = $this->options['dsn']; - - if (null === $dsn) { - return null; - } - - if ($returnAsString) { - @trigger_error(sprintf('Calling the method %s() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0.', __METHOD__), E_USER_DEPRECATED); - - $url = $dsn->getScheme() . '://' . $dsn->getHost(); - - if (('http' === $dsn->getScheme() && 80 !== $dsn->getPort()) || ('https' === $dsn->getScheme() && 443 !== $dsn->getPort())) { - $url .= ':' . $dsn->getPort(); - } - - $url .= $dsn->getPath(); - - return $url; - } - - return $dsn; + return $this->options['dsn']; } /** diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php index 16a5919d4..73c45c3fb 100644 --- a/src/Transport/DefaultTransportFactory.php +++ b/src/Transport/DefaultTransportFactory.php @@ -49,7 +49,7 @@ public function __construct(MessageFactoryInterface $messageFactory, HttpClientF */ public function create(Options $options): TransportInterface { - if (null === $options->getDsn(false)) { + if (null === $options->getDsn()) { return new NullTransport(); } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index bd6fd8937..7697e6c59 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -11,7 +11,6 @@ use Http\Message\RequestFactory as RequestFactoryInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Sentry\Dsn; use Sentry\Event; use Sentry\EventId; use Sentry\Options; @@ -112,9 +111,9 @@ public function __destruct() */ public function send(Event $event): ?EventId { - $dsn = $this->options->getDsn(false); + $dsn = $this->options->getDsn(); - if (!$dsn instanceof Dsn) { + if (null === $dsn) { throw new \RuntimeException(sprintf('The DSN option must be set to use the "%s" transport.', self::class)); } diff --git a/tests/Exception/MissingProjectIdCredentialExceptionTest.php b/tests/Exception/MissingProjectIdCredentialExceptionTest.php deleted file mode 100644 index 5a79607a1..000000000 --- a/tests/Exception/MissingProjectIdCredentialExceptionTest.php +++ /dev/null @@ -1,22 +0,0 @@ -assertSame('The project ID of the DSN is required to authenticate with the Sentry server.', $exception->getMessage()); - } -} diff --git a/tests/Exception/MissingPublicKeyCredentialExceptionTest.php b/tests/Exception/MissingPublicKeyCredentialExceptionTest.php deleted file mode 100644 index 01b9a0e16..000000000 --- a/tests/Exception/MissingPublicKeyCredentialExceptionTest.php +++ /dev/null @@ -1,22 +0,0 @@ -assertSame('The public key of the DSN is required to authenticate with the Sentry server.', $exception->getMessage()); - } -} diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 1e3679b69..78aa94814 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -78,122 +78,70 @@ public function optionsDataProvider(): array } /** - * @group legacy - * * @dataProvider dsnOptionDataProvider - * - * @expectedDeprecationMessage Calling the method getDsn() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0. */ - public function testDsnOption($value, ?string $expectedProjectId, ?string $expectedPublicKey, ?string $expectedSecretKey, ?string $expectedDsnAsString, ?Dsn $expectedDsnAsObject): void + public function testDsnOption($value, ?Dsn $expectedDsnAsObject): void { $options = new Options(['dsn' => $value]); - $this->assertSame($expectedProjectId, $options->getProjectId()); - $this->assertSame($expectedPublicKey, $options->getPublicKey()); - $this->assertSame($expectedSecretKey, $options->getSecretKey()); - $this->assertEquals($expectedDsnAsString, $options->getDsn()); - $this->assertEquals($expectedDsnAsObject, $options->getDsn(false)); + $this->assertEquals($expectedDsnAsObject, $options->getDsn()); } public function dsnOptionDataProvider(): \Generator { yield [ 'http://public:secret@example.com/sentry/1', - '1', - 'public', - 'secret', - 'http://example.com/sentry', Dsn::createFromString('http://public:secret@example.com/sentry/1'), ]; yield [ Dsn::createFromString('http://public:secret@example.com/sentry/1'), - '1', - 'public', - 'secret', - 'http://example.com/sentry', Dsn::createFromString('http://public:secret@example.com/sentry/1'), ]; yield [ null, null, - null, - null, - null, - null, ]; yield [ 'null', null, - null, - null, - null, - null, ]; yield [ '(null)', null, - null, - null, - null, - null, ]; yield [ false, null, - null, - null, - null, - null, ]; yield [ 'false', null, - null, - null, - null, - null, ]; yield [ '(false)', null, - null, - null, - null, - null, ]; yield [ '', null, - null, - null, - null, - null, ]; yield [ 'empty', null, - null, - null, - null, - null, ]; yield [ '(empty)', null, - null, - null, - null, - null, ]; } @@ -360,20 +308,13 @@ public function contextLinesOptionValidatesInputValueDataProvider(): \Generator } /** - * @group legacy * @backupGlobals enabled - * - * @expectedDeprecationMessage Calling the method getDsn() and expecting it to return a string is deprecated since version 2.4 and will stop working in 3.0. */ public function testDsnOptionDefaultValueIsGotFromEnvironmentVariable(): void { $_SERVER['SENTRY_DSN'] = 'http://public@example.com/1'; - $options = new Options(); - - $this->assertSame('http://example.com', $options->getDsn()); - $this->assertSame('public', $options->getPublicKey()); - $this->assertSame('1', $options->getProjectId()); + $this->assertEquals(Dsn::createFromString($_SERVER['SENTRY_DSN']), (new Options())->getDsn()); } /** @@ -383,9 +324,7 @@ public function testEnvironmentOptionDefaultValueIsGotFromEnvironmentVariable(): { $_SERVER['SENTRY_ENVIRONMENT'] = 'test_environment'; - $options = new Options(); - - $this->assertSame('test_environment', $options->getEnvironment()); + $this->assertSame('test_environment', (new Options())->getEnvironment()); } /** @@ -395,9 +334,7 @@ public function testReleaseOptionDefaultValueIsGotFromEnvironmentVariable(): voi { $_SERVER['SENTRY_RELEASE'] = '0.0.1'; - $options = new Options(); - - $this->assertSame('0.0.1', $options->getRelease()); + $this->assertSame('0.0.1', (new Options())->getRelease()); } /** From f2b11fe0526b422cefb454a0a271c05740d1b709 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 17 Jul 2020 10:22:36 +0200 Subject: [PATCH 0574/1161] Fix typehint errors caused by usage of non-PSR-17 factories while instantiating the Httplug cURL client (#1052) --- CHANGELOG.md | 2 ++ src/HttpClient/HttpClientFactory.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 926715b87..f906eb008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix typehint errors while instantiating the Httplug cURL client by forcing the usage of PSR-17 complaint factories (#1052) + ### 2.4.1 (2020-07-03) - Fix HTTP client connection timeouts not being applied if an HTTP proxy is specified (#1033) diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index d5abc7602..9ce606feb 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -136,7 +136,7 @@ public function create(Options $options): HttpAsyncClientInterface } /** @psalm-suppress InvalidPropertyAssignmentValue */ - $httpClient = new CurlHttpClient($this->responseFactory, $this->streamFactory, $curlConfig); + $httpClient = new CurlHttpClient(null, null, $curlConfig); } elseif (null !== $options->getHttpProxy()) { throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); } From 33e8293f613f7628bfd52400340a3a0d729f05ad Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 24 Jul 2020 08:54:39 +0200 Subject: [PATCH 0575/1161] Update the CHANGELOG for version 2.4.2 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f906eb008..15f452497 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +### 2.4.2 (2020-07-24) + - Fix typehint errors while instantiating the Httplug cURL client by forcing the usage of PSR-17 complaint factories (#1052) ### 2.4.1 (2020-07-03) From b3b4f4a08b184c3f22b208f357e8720ef42938b0 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 24 Jul 2020 09:02:19 +0200 Subject: [PATCH 0576/1161] Update the CHANGELOG for version 2.4.2 From c34f36890bb84d65fdf9258ea3bc1d12b006141e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me?= Date: Thu, 30 Jul 2020 21:00:34 +0200 Subject: [PATCH 0577/1161] Allow setting the environment to null (#1057) Null is a valid value for the environment option (it means no environment is specified). This allow setting the environment to null in the Option class. --- CHANGELOG.md | 2 ++ src/Options.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15f452497..4c3f0f31e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix `Options::setEnvironment` method not accepting `null` values (#1057) + ### 2.4.2 (2020-07-24) - Fix typehint errors while instantiating the Httplug cURL client by forcing the usage of PSR-17 complaint factories (#1052) diff --git a/src/Options.php b/src/Options.php index eef47dd2b..055f3a617 100644 --- a/src/Options.php +++ b/src/Options.php @@ -193,9 +193,9 @@ public function getEnvironment(): ?string /** * Sets the environment. * - * @param string $environment The environment + * @param string|null $environment The environment */ - public function setEnvironment(string $environment): void + public function setEnvironment(?string $environment): void { $options = array_merge($this->options, ['environment' => $environment]); From 0acbc520d1bdd0f93eabc7fef9426e5f364f5a5c Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 31 Jul 2020 13:47:04 +0200 Subject: [PATCH 0578/1161] Cleanup various deprecated code and options (#1047) --- CHANGELOG.md | 8 +- UPGRADE-3.0.md | 30 +++- src/Breadcrumb.php | 45 ------ src/Client.php | 5 - src/ClientBuilder.php | 144 +----------------- src/ClientBuilderInterface.php | 88 ++--------- src/Context/Context.php | 35 ----- src/HttpClient/PluggableHttpClientFactory.php | 54 ------- src/Integration/ModulesIntegration.php | 32 +--- src/Integration/RequestIntegration.php | 55 ++----- src/Options.php | 91 ----------- src/Stacktrace.php | 5 - src/State/Scope.php | 15 +- tests/ClientBuilderTest.php | 131 ---------------- tests/ClientTest.php | 73 --------- .../PluggableHttpClientFactoryTest.php | 57 ------- tests/Integration/ModulesIntegrationTest.php | 14 -- tests/Integration/RequestIntegrationTest.php | 109 ++++++------- tests/OptionsTest.php | 33 ---- tests/StacktraceTest.php | 45 ------ tests/State/ScopeTest.php | 35 +---- 21 files changed, 134 insertions(+), 970 deletions(-) delete mode 100644 src/HttpClient/PluggableHttpClientFactory.php delete mode 100644 tests/HttpClient/PluggableHttpClientFactoryTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a8035985c..d36b373ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,13 @@ - [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) - [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) - [BC BREAK] Remove deprecated code to return the event ID as a `string` rather than an object instance from the transport, the client and the hub (#1036) -- [BC BREAK] Remove some deprecated methods from the `Options` class. +- [BC BREAK] Remove some deprecated methods from the `Options` class. (#1047) +- [BC BREAK] Remove the deprecated code from the `ModulesIntegration` integration (#1047) +- [BC BREAK] Remove the deprecated code from the `RequestIntegration` integration (#1047) +- [BC BREAK] Remove the deprecated code from the `Breadcrumb` class (#1047) +- [BC BREAK] Remove the deprecated methods from the `ClientBuilderInterface` interface and its implementations (#1047) +- [BC BREAK] The `Scope::setUser()` method now always merges the given data with the existing one instead of replacing it as a whole (#1047) +- [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) ### 2.4.1 (2020-07-03) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 6be8baf57..689822e70 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -1,10 +1,10 @@ # Upgrade 2.x to 3.0 -- Removed the `HubInterface::getCurrentHub()` and `HubInterface::setCurrentHub()` methods. Use `SentrySdk::getCurrentHub()` and `SentrySdk::setCurrentHub()` instead. -- Removed the `ErrorHandler::registerOnce()` method, use `ErrorHandler::register*Handler()` instead. -- Removed the `ErrorHandler::addErrorListener` method, use `ErrorHandler::addErrorHandlerListener()` instead. -- Removed the `ErrorHandler::addFatalErrorListener` method, use `ErrorHandler::addFatalErrorHandlerListener()` instead. -- Removed the `ErrorHandler::addExceptionListener` method, use `ErrorHandler::addExceptionHandlerListener()` instead. +- Removed the `HubInterface::getCurrentHub()` and `HubInterface::setCurrentHub()` methods. Use `SentrySdk::getCurrentHub()` and `SentrySdk::setCurrentHub()` instead +- Removed the `ErrorHandler::registerOnce()` method, use `ErrorHandler::register*Handler()` instead +- Removed the `ErrorHandler::addErrorListener` method, use `ErrorHandler::addErrorHandlerListener()` instead +- Removed the `ErrorHandler::addFatalErrorListener` method, use `ErrorHandler::addFatalErrorHandlerListener()` instead +- Removed the `ErrorHandler::addExceptionListener` method, use `ErrorHandler::addExceptionHandlerListener()` instead - The signature of the `ErrorListenerIntegration::__construct()` method changed to not accept any parameter - The signature of the `FatalErrorListenerIntegration::__construct()` method changed to not accept any parameter - The `ErrorListenerIntegration` integration does not get called anymore when a fatal error occurs @@ -14,4 +14,22 @@ - The signature of the `HubInterface::capture*e()` methods changed to return an instance of the `EventId` class instead of a `string` - The signature of the `Event::getId()` method changed to return an instance of the `EventId` class instead of a `string` - The signature of the `Options::getDsn()` method changed to always return an instance of the `Dsn` class instead of a `string` -- Removed the `Options::getProjectId`, `Options::getPublicKey` and `Options::getSecretKey` methods, use `Options::getDsn()` instead. +- Removed the `Options::getProjectId`, `Options::getPublicKey` and `Options::getSecretKey` methods, use `Options::getDsn()` instead +- Removed the `Breadcrumb::LEVEL_CRITICAL` constant, use `Breadcrumb::LEVEL_FATAL` instead +- Removed the `Breadcrumb::levelFromErrorException()` method +- Removed the `PluggableHttpClientFactory` class +- Removed the following methods from the `ClientBuilderInterface` interface, use `ClientBuilderInterface::setTransportFactory()` instead: + - `ClientBuilderInterface::setUriFactory()` + - `ClientBuilderInterface::setMessageFactory()` + - `ClientBuilderInterface::setTransport()` + - `ClientBuilderInterface::setHttpClient()` + - `ClientBuilderInterface::addHttpClientPlugin()` + - `ClientBuilderInterface::removeHttpClientPlugin()`. +- Removed the following methods from the `Options` class, use the `IgnoreErrorsIntegration` integration instead: + - `Options::getExcludedExceptions()` + - `Options::setExcludedExceptions()` + - `Options::isExcludedException()` + - `Options::getProjectRoot()` + - `Options::setProjectRoot()` +- Removed the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants +- The signature of the `Scope::setUser()` method changed to not accept the `$merge` parameter anymore diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index efa247025..b8fe14d95 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -58,13 +58,6 @@ final class Breadcrumb implements \JsonSerializable */ public const LEVEL_ERROR = 'error'; - /** - * This constant defines the critical level for a breadcrumb. - * - * @deprecated since version 2.2.2, to be removed in 3.0; use fatal instead. - */ - public const LEVEL_CRITICAL = 'critical'; - /** * This constant defines the fatal level for a breadcrumb. */ @@ -79,7 +72,6 @@ final class Breadcrumb implements \JsonSerializable self::LEVEL_INFO, self::LEVEL_WARNING, self::LEVEL_ERROR, - self::LEVEL_CRITICAL, self::LEVEL_FATAL, ]; @@ -136,43 +128,6 @@ public function __construct(string $level, string $type, string $category, ?stri $this->timestamp = microtime(true); } - /** - * Maps the severity of the error to one of the levels supported by the - * breadcrumbs. - * - * @param \ErrorException $exception The exception - * - * @deprecated since version 2.3, to be removed in 3.0 - */ - public static function levelFromErrorException(\ErrorException $exception): string - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - switch ($exception->getSeverity()) { - case E_DEPRECATED: - case E_USER_DEPRECATED: - case E_WARNING: - case E_USER_WARNING: - case E_RECOVERABLE_ERROR: - return self::LEVEL_WARNING; - case E_ERROR: - case E_PARSE: - case E_CORE_ERROR: - case E_CORE_WARNING: - case E_COMPILE_ERROR: - case E_COMPILE_WARNING: - return self::LEVEL_FATAL; - case E_USER_ERROR: - return self::LEVEL_ERROR; - case E_NOTICE: - case E_USER_NOTICE: - case E_STRICT: - return self::LEVEL_INFO; - default: - return self::LEVEL_ERROR; - } - } - /** * Gets the breadcrumb type. */ diff --git a/src/Client.php b/src/Client.php index 6c07ac613..bd33e784f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -10,7 +10,6 @@ use Psr\Log\NullLogger; use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\Handler; -use Sentry\Integration\IgnoreErrorsIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; use Sentry\Transport\ClosableTransportInterface; @@ -103,10 +102,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope */ public function captureException(\Throwable $exception, ?Scope $scope = null): ?EventId { - if (!isset($this->integrations[IgnoreErrorsIntegration::class]) && $this->options->isExcludedException($exception, false)) { - return null; - } - return $this->captureEvent(['exception' => $exception], $scope); } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index d4982b179..f2f044e8c 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,18 +4,13 @@ namespace Sentry; -use Http\Client\Common\Plugin as PluginInterface; use Http\Client\HttpAsyncClient; use Http\Discovery\MessageFactoryDiscovery; use Http\Discovery\StreamFactoryDiscovery; use Http\Discovery\UriFactoryDiscovery; -use Http\Message\MessageFactory as MessageFactoryInterface; -use Http\Message\StreamFactory as StreamFactoryInterface; -use Http\Message\UriFactory as UriFactoryInterface; use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; -use Sentry\HttpClient\PluggableHttpClientFactory; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\Serializer; @@ -36,21 +31,6 @@ final class ClientBuilder implements ClientBuilderInterface */ private $options; - /** - * @var UriFactoryInterface|null The PSR-7 URI factory - */ - private $uriFactory; - - /** - * @var StreamFactoryInterface|null The PSR-17 stream factory - */ - private $streamFactory; - - /** - * @var MessageFactoryInterface|null The PSR-7 message factory - */ - private $messageFactory; - /** * @var TransportFactoryInterface|null The transport factory */ @@ -66,11 +46,6 @@ final class ClientBuilder implements ClientBuilderInterface */ private $httpClient; - /** - * @var PluginInterface[] The list of Httplug plugins - */ - private $httpClientPlugins = []; - /** * @var SerializerInterface|null The serializer to be injected in the client */ @@ -123,84 +98,6 @@ public function getOptions(): Options return $this->options; } - /** - * {@inheritdoc} - */ - public function setUriFactory(UriFactoryInterface $uriFactory): ClientBuilderInterface - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - $this->uriFactory = $uriFactory; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function setMessageFactory(MessageFactoryInterface $messageFactory): ClientBuilderInterface - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - $this->messageFactory = $messageFactory; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function setTransport(TransportInterface $transport): ClientBuilderInterface - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the setTransportFactory() method instead.', __METHOD__), E_USER_DEPRECATED); - - $this->transport = $transport; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function setHttpClient(HttpAsyncClient $httpClient): ClientBuilderInterface - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - $this->httpClient = $httpClient; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function addHttpClientPlugin(PluginInterface $plugin): ClientBuilderInterface - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - $this->httpClientPlugins[] = $plugin; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function removeHttpClientPlugin(string $className): ClientBuilderInterface - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - foreach ($this->httpClientPlugins as $index => $httpClientPlugin) { - if (!$httpClientPlugin instanceof $className) { - continue; - } - - unset($this->httpClientPlugins[$index]); - } - - return $this; - } - /** * {@inheritdoc} */ @@ -251,24 +148,6 @@ public function setSdkVersion(string $sdkVersion): ClientBuilderInterface return $this; } - /** - * Sets the version of the SDK package that generated this Event using the Packagist name. - * - * @param string $packageName The package name that will be used to get the version from (i.e. "sentry/sentry") - * - * @return $this - * - * @deprecated since version 2.2, to be removed in 3.0 - */ - public function setSdkVersionByPackageName(string $packageName): ClientBuilderInterface - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.2 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - $this->sdkVersion = PrettyVersions::getVersion($packageName)->getPrettyVersion(); - - return $this; - } - /** * {@inheritdoc} */ @@ -280,11 +159,7 @@ public function getClient(): ClientInterface } /** - * Sets the transport factory. - * - * @param TransportFactoryInterface $transportFactory The transport factory - * - * @return $this + * {@inheritdoc} */ public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface { @@ -323,23 +198,16 @@ private function createEventFactory(): EventFactoryInterface */ private function createDefaultTransportFactory(): DefaultTransportFactory { - $this->messageFactory = $this->messageFactory ?? MessageFactoryDiscovery::find(); - $this->uriFactory = $this->uriFactory ?? UriFactoryDiscovery::find(); - $this->streamFactory = $this->streamFactory ?? StreamFactoryDiscovery::find(); - + $messageFactory = MessageFactoryDiscovery::find(); $httpClientFactory = new HttpClientFactory( - $this->uriFactory, - $this->messageFactory, - $this->streamFactory, + UriFactoryDiscovery::find(), + $messageFactory, + StreamFactoryDiscovery::find(), $this->httpClient, $this->sdkIdentifier, $this->sdkVersion ); - if (!empty($this->httpClientPlugins)) { - $httpClientFactory = new PluggableHttpClientFactory($httpClientFactory, $this->httpClientPlugins); - } - - return new DefaultTransportFactory($this->messageFactory, $httpClientFactory, $this->logger); + return new DefaultTransportFactory($messageFactory, $httpClientFactory, $this->logger); } } diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php index 3b944af70..35d67b12a 100644 --- a/src/ClientBuilderInterface.php +++ b/src/ClientBuilderInterface.php @@ -4,23 +4,15 @@ namespace Sentry; -use Http\Client\Common\Plugin as PluginInterface; -use Http\Client\HttpAsyncClient; -use Http\Message\MessageFactory as MessageFactoryInterface; -use Http\Message\UriFactory as UriFactoryInterface; use Psr\Log\LoggerInterface; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\SerializerInterface; use Sentry\Transport\TransportFactoryInterface; -use Sentry\Transport\TransportInterface; /** * A configurable builder for Client objects. * * @author Stefano Arlandini - * - * @method self setTransportFactory(TransportFactoryInterface $transportFactory) - * @method self setLogger(LoggerInterface $logger) */ interface ClientBuilderInterface { @@ -39,95 +31,47 @@ public static function create(array $options = []): self; public function getOptions(): Options; /** - * Sets the factory to use to create URIs. - * - * @param UriFactoryInterface $uriFactory The factory - * - * @return $this - * - * @deprecated Since version 2.3, to be removed in 3.0 - */ - public function setUriFactory(UriFactoryInterface $uriFactory): self; - - /** - * Sets the factory to use to create PSR-7 messages. - * - * @param MessageFactoryInterface $messageFactory The factory - * - * @return $this - * - * @deprecated Since version 2.3, to be removed in 3.0 - */ - public function setMessageFactory(MessageFactoryInterface $messageFactory): self; - - /** - * Sets the transport that will be used to send events. - * - * @param TransportInterface $transport The transport - * - * @return $this - * - * @deprecated Since version 2.3, to be removed in 3.0 - */ - public function setTransport(TransportInterface $transport): self; - - /** - * Sets the HTTP client. - * - * @param HttpAsyncClient $httpClient The HTTP client - * - * @return $this - * - * @deprecated Since version 2.3, to be removed in 3.0 + * Gets the instance of the client built using the configured options. */ - public function setHttpClient(HttpAsyncClient $httpClient): self; + public function getClient(): ClientInterface; /** - * Adds a new HTTP client plugin to the end of the plugins chain. + * Sets a serializer instance to be injected as a dependency of the client. * - * @param PluginInterface $plugin The plugin instance + * @param SerializerInterface $serializer The serializer to be used by the client to fill the events * * @return $this - * - * @deprecated Since version 2.3, to be removed in 3.0 */ - public function addHttpClientPlugin(PluginInterface $plugin): self; + public function setSerializer(SerializerInterface $serializer): self; /** - * Removes a HTTP client plugin by its fully qualified class name (FQCN). + * Sets a representation serializer instance to be injected as a dependency of the client. * - * @param string $className The class name + * @param RepresentationSerializerInterface $representationSerializer The representation serializer, used to serialize function + * arguments in stack traces, to have string representation + * of non-string values * * @return $this - * - * @deprecated Since version 2.3, to be removed in 3.0 */ - public function removeHttpClientPlugin(string $className): self; - - /** - * Gets the instance of the client built using the configured options. - */ - public function getClient(): ClientInterface; + public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self; /** - * Sets a serializer instance to be injected as a dependency of the client. + * Sets a PSR-3 logger to log internal debug messages. * - * @param SerializerInterface $serializer The serializer to be used by the client to fill the events + * @param LoggerInterface $logger The logger instance * * @return $this */ - public function setSerializer(SerializerInterface $serializer): self; + public function setLogger(LoggerInterface $logger): ClientBuilderInterface; /** - * Sets a representation serializer instance to be injected as a dependency of the client. + * Sets the transport factory. * - * @param RepresentationSerializerInterface $representationSerializer The representation serializer, used to serialize function - * arguments in stack traces, to have string representation - * of non-string values + * @param TransportFactoryInterface $transportFactory The transport factory * * @return $this */ - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self; + public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface; /** * Sets the SDK identifier to be passed onto {@see Event} and HTTP User-Agent header. diff --git a/src/Context/Context.php b/src/Context/Context.php index 7426280f3..652be6497 100644 --- a/src/Context/Context.php +++ b/src/Context/Context.php @@ -17,41 +17,6 @@ */ class Context implements \ArrayAccess, \JsonSerializable, \IteratorAggregate { - /** - * This constant defines the alias used for the user context. - * - * @deprecated To be removed in 3.0 because unused - */ - public const CONTEXT_USER = 'user'; - - /** - * This constant defines the alias used for the runtime context. - * - * @deprecated To be removed in 3.0 because unused - */ - public const CONTEXT_RUNTIME = 'runtime'; - - /** - * This constant defines the alias used for the tags context. - * - * @deprecated To be removed in 3.0 because unused - */ - public const CONTEXT_TAGS = 'tags'; - - /** - * This constant defines the alias used for the extra context. - * - * @deprecated To be removed in 3.0 because unused - */ - public const CONTEXT_EXTRA = 'extra'; - - /** - * This constant defines the alias used for the server OS context. - * - * @deprecated To be removed in 3.0 because unused - */ - public const CONTEXT_SERVER_OS = 'server_os'; - /** * @var array The data stored in this object * diff --git a/src/HttpClient/PluggableHttpClientFactory.php b/src/HttpClient/PluggableHttpClientFactory.php deleted file mode 100644 index 981eab5b6..000000000 --- a/src/HttpClient/PluggableHttpClientFactory.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * @deprecated since version 2.3, to be removed in 3.0 - */ -final class PluggableHttpClientFactory implements HttpClientFactoryInterface -{ - /** - * @var HttpClientFactoryInterface The HTTP factory being decorated - */ - private $decoratedHttpClientFactory; - - /** - * @var HttpClientPluginInterface[] The list of plugins to add to the HTTP client - */ - private $httpClientPlugins; - - /** - * Constructor. - * - * @param HttpClientFactoryInterface $decoratedHttpClientFactory The HTTP factory being decorated - * @param HttpClientPluginInterface[] $httpClientPlugins The list of plugins to add to the HTTP client - */ - public function __construct(HttpClientFactoryInterface $decoratedHttpClientFactory, array $httpClientPlugins) - { - @trigger_error(sprintf('The "%s" class is deprecated since version 2.3 and will be removed in 3.0.', self::class), E_USER_DEPRECATED); - - $this->decoratedHttpClientFactory = $decoratedHttpClientFactory; - $this->httpClientPlugins = $httpClientPlugins; - } - - /** - * {@inheritdoc} - */ - public function create(Options $options): HttpAsyncClientInterface - { - $httpClient = $this->decoratedHttpClientFactory->create($options); - - return new PluginClient($httpClient, $this->httpClientPlugins); - } -} diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index ecf20601d..5ae22bfa9 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -19,7 +19,7 @@ final class ModulesIntegration implements IntegrationInterface /** * @var array The list of installed vendors */ - private static $loadedModules = []; + private static $packages = []; /** * {@inheritdoc} @@ -32,7 +32,7 @@ public function setupOnce(): void // The integration could be bound to a client that is not the one // attached to the current hub. If this is the case, bail out if (null !== $integration) { - $integration->processEvent($event); + $event->setModules(self::getComposerPackages()); } return $event; @@ -40,34 +40,16 @@ public function setupOnce(): void } /** - * Applies the information gathered by this integration to the event. - * - * @param self $self The instance of this integration - * @param Event $event The event that will be enriched with the modules - * - * @deprecated since version 2.4, to be removed in 3.0 + * @return array */ - public static function applyToEvent(self $self, Event $event): void + private static function getComposerPackages(): array { - @trigger_error(sprintf('The "%s" method is deprecated since version 2.4 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - $self->processEvent($event); - } - - /** - * Gathers information about the versions of the installed dependencies of - * the application and sets them on the event. - * - * @param Event $event The event - */ - private function processEvent(Event $event): void - { - if (empty(self::$loadedModules)) { + if (empty(self::$packages)) { foreach (Versions::VERSIONS as $package => $rawVersion) { - self::$loadedModules[$package] = PrettyVersions::getVersion($package)->getPrettyVersion(); + self::$packages[$package] = PrettyVersions::getVersion($package)->getPrettyVersion(); } } - $event->setModules(self::$loadedModules); + return self::$packages; } } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 9fee4d8b1..0d8942e05 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -35,11 +35,6 @@ final class RequestIntegration implements IntegrationInterface */ private const REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH = 10 ** 4; - /** - * @var Options|null The client options - */ - private $options; - /** * @var RequestFetcherInterface PSR-7 request fetcher */ @@ -48,16 +43,10 @@ final class RequestIntegration implements IntegrationInterface /** * Constructor. * - * @param Options|null $options The client options * @param RequestFetcherInterface|null $requestFetcher PSR-7 request fetcher */ - public function __construct(?Options $options = null, ?RequestFetcherInterface $requestFetcher = null) + public function __construct(?RequestFetcherInterface $requestFetcher = null) { - if (null !== $options) { - @trigger_error(sprintf('Passing the options as argument of the constructor of the "%s" class is deprecated since version 2.1 and will not work in 3.0.', self::class), E_USER_DEPRECATED); - } - - $this->options = $options; $this->requestFetcher = $requestFetcher ?? new RequestFetcher(); } @@ -77,37 +66,15 @@ public function setupOnce(): void return $event; } - $this->processEvent($event, $this->options ?? $client->getOptions()); + $this->processEvent($event, $client->getOptions()); return $event; }); } - /** - * Applies the information gathered by the this integration to the event. - * - * @param self $self The current instance of the integration - * @param Event $event The event that will be enriched with a request - * @param ServerRequestInterface|null $request The Request that will be processed and added to the event - * - * @deprecated since version 2.1, to be removed in 3.0 - */ - public static function applyToEvent(self $self, Event $event, ?ServerRequestInterface $request = null): void + private function processEvent(Event $event, Options $options): void { - @trigger_error(sprintf('The "%s" method is deprecated since version 2.1 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); - - if (null === $self->options) { - throw new \BadMethodCallException('The options of the integration must be set.'); - } - - $self->processEvent($event, $self->options, $request); - } - - private function processEvent(Event $event, Options $options, ?ServerRequestInterface $request = null): void - { - if (null === $request) { - $request = $this->requestFetcher->fetchRequest(); - } + $request = $this->requestFetcher->fetchRequest(); if (null === $request) { return; @@ -176,15 +143,15 @@ static function (string $key) use ($keysToRemove): bool { * the parsing fails then the raw data is returned. If there are submitted * fields or files, all of their information are parsed and returned. * - * @param Options $options The options of the client - * @param ServerRequestInterface $serverRequest The server request + * @param Options $options The options of the client + * @param ServerRequestInterface $request The server request * * @return mixed */ - private function captureRequestBody(Options $options, ServerRequestInterface $serverRequest) + private function captureRequestBody(Options $options, ServerRequestInterface $request) { $maxRequestBodySize = $options->getMaxRequestBodySize(); - $requestBody = $serverRequest->getBody(); + $requestBody = $request->getBody(); if ( 'none' === $maxRequestBodySize || @@ -194,9 +161,9 @@ private function captureRequestBody(Options $options, ServerRequestInterface $se return null; } - $requestData = $serverRequest->getParsedBody(); + $requestData = $request->getParsedBody(); $requestData = array_merge( - $this->parseUploadedFiles($serverRequest->getUploadedFiles()), + $this->parseUploadedFiles($request->getUploadedFiles()), \is_array($requestData) ? $requestData : [] ); @@ -204,7 +171,7 @@ private function captureRequestBody(Options $options, ServerRequestInterface $se return $requestData; } - if ('application/json' === $serverRequest->getHeaderLine('Content-Type')) { + if ('application/json' === $request->getHeaderLine('Content-Type')) { try { return JSON::decode($requestBody->getContents()); } catch (JsonException $exception) { diff --git a/src/Options.php b/src/Options.php index 5a0d781f6..08d0fa334 100644 --- a/src/Options.php +++ b/src/Options.php @@ -224,64 +224,6 @@ public function setEnvironment(string $environment): void $this->options = $this->resolver->resolve($options); } - /** - * Gets the list of exception classes that should be ignored when sending - * events to Sentry. - * - * @return string[] - * - * @deprecated since version 2.3, to be removed in 3.0 - */ - public function getExcludedExceptions(): array - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "Sentry\Integration\IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->options['excluded_exceptions']; - } - - /** - * Sets the list of exception classes that should be ignored when sending - * events to Sentry. - * - * @param string[] $exceptions The list of exception classes - * - * @deprecated since version 2.3, to be removed in 3.0 - */ - public function setExcludedExceptions(array $exceptions): void - { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "Sentry\Integration\IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); - - $options = array_merge($this->options, ['excluded_exceptions' => $exceptions]); - - $this->options = $this->resolver->resolve($options); - } - - /** - * Checks whether the given exception should be ignored when sending events - * to Sentry. - * - * @param \Throwable $exception The exception - * @param bool $throwDeprecation Flag indicating whether to throw a - * deprecation for the usage of this - * method - * - * @deprecated since version 2.3, to be removed in 3.0 - */ - public function isExcludedException(\Throwable $exception, bool $throwDeprecation = true): bool - { - if ($throwDeprecation) { - @trigger_error(sprintf('Method %s() is deprecated since version 2.3 and will be removed in 3.0. Use the "Sentry\Integration\IgnoreErrorsIntegration" integration instead.', __METHOD__), E_USER_DEPRECATED); - } - - foreach ($this->options['excluded_exceptions'] as $exceptionClass) { - if ($exception instanceof $exceptionClass) { - return true; - } - } - - return false; - } - /** * Gets the list of paths to exclude from in_app detection. * @@ -326,26 +268,6 @@ public function setInAppIncludedPaths(array $paths): void $this->options = $this->resolver->resolve($options); } - /** - * Gets the project which the authenticated user is bound to. - */ - public function getProjectRoot(): ?string - { - return $this->options['project_root']; - } - - /** - * Sets the project which the authenticated user is bound to. - * - * @param string|null $path The path to the project root - */ - public function setProjectRoot(?string $path): void - { - $options = array_merge($this->options, ['project_root' => $path]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the logger used by Sentry. */ @@ -756,7 +678,6 @@ private function configureOptions(OptionsResolver $resolver): void 'context_lines' => 5, 'enable_compression' => true, 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, - 'project_root' => null, 'logger' => 'php', 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, @@ -770,7 +691,6 @@ private function configureOptions(OptionsResolver $resolver): void 'before_breadcrumb' => static function (Breadcrumb $breadcrumb): Breadcrumb { return $breadcrumb; }, - 'excluded_exceptions' => [], 'in_app_exclude' => [], 'in_app_include' => [], 'send_default_pii' => false, @@ -789,10 +709,8 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('context_lines', ['null', 'int']); $resolver->setAllowedTypes('enable_compression', 'bool'); $resolver->setAllowedTypes('environment', ['null', 'string']); - $resolver->setAllowedTypes('excluded_exceptions', 'array'); $resolver->setAllowedTypes('in_app_exclude', 'array'); $resolver->setAllowedTypes('in_app_include', 'array'); - $resolver->setAllowedTypes('project_root', ['null', 'string']); $resolver->setAllowedTypes('logger', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); @@ -820,15 +738,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedValues('context_lines', \Closure::fromCallable([$this, 'validateContextLinesOption'])); $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); - $resolver->setNormalizer('project_root', function (SymfonyOptions $options, ?string $value) { - if (null === $value) { - return null; - } - - @trigger_error('The option "project_root" is deprecated. Use the "in_app_include" option instead.', E_USER_DEPRECATED); - - return $this->normalizeAbsolutePath($value); - }); $resolver->setNormalizer('prefixes', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); diff --git a/src/Stacktrace.php b/src/Stacktrace.php index fc49496fc..939361be1 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -478,16 +478,11 @@ private function isFrameInApp(string $file, ?string $functionName): bool return false; } - $projectRoot = $this->options->getProjectRoot(); $excludedAppPaths = $this->options->getInAppExcludedPaths(); $includedAppPaths = $this->options->getInAppIncludedPaths(); $absoluteFilePath = @realpath($file) ?: $file; $isInApp = true; - if (null !== $projectRoot) { - $isInApp = 0 === mb_strpos($absoluteFilePath, $projectRoot); - } - foreach ($excludedAppPaths as $excludedAppPath) { if (0 === mb_strpos($absoluteFilePath, $excludedAppPath)) { $isInApp = false; diff --git a/src/State/Scope.php b/src/State/Scope.php index 2093e1f8e..fba249579 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -172,22 +172,13 @@ public function setExtras(array $extras): self /** * Sets the given data in the user context. * - * @param array $data The data - * @param bool $merge If true, $data will be merged into user context instead of replacing it + * @param array $data The data * * @return $this */ - public function setUser(array $data, bool $merge = false): self + public function setUser(array $data): self { - if ($merge) { - $this->user->merge($data); - - return $this; - } - - @trigger_error('Replacing the data is deprecated since version 2.3 and will stop working from version 3.0. Set the second argument to `true` to merge the data instead.', E_USER_DEPRECATED); - - $this->user->replaceData($data); + $this->user->merge($data); return $this; } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 32eab048d..9de42004c 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -4,21 +4,11 @@ namespace Sentry\Tests; -use Http\Client\Common\Plugin as PluginInterface; -use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Message\MessageFactory as MessageFactoryInterface; -use Http\Promise\FulfilledPromise; -use Http\Promise\Promise as PromiseInterface; use Jean85\PrettyVersions; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestInterface; use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\EventId; -use Sentry\FlushableClientInterface; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -29,7 +19,6 @@ use Sentry\Options; use Sentry\Transport\HttpTransport; use Sentry\Transport\NullTransport; -use Sentry\Transport\TransportInterface; final class ClientBuilderTest extends TestCase { @@ -56,126 +45,6 @@ public function testNullTransportIsUsedWhenNoServerIsConfigured(): void $this->assertInstanceOf(NullTransport::class, $transport); } - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ - public function testSetMessageFactory(): void - { - /** @var MessageFactoryInterface&MockObject $messageFactory */ - $messageFactory = $this->createMock(MessageFactoryInterface::class); - $messageFactory->expects($this->once()) - ->method('createRequest') - ->willReturn(MessageFactoryDiscovery::find()->createRequest('POST', 'http://www.example.com')); - - $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) - ->setMessageFactory($messageFactory) - ->getClient(); - - $client->captureMessage('foo'); - } - - /** - * @group legacy - * - * @expectedDeprecation Method Sentry\ClientBuilder::setTransport() is deprecated since version 2.3 and will be removed in 3.0. Use the setTransportFactory() method instead. - */ - public function testSetTransport(): void - { - $eventId = EventId::generate(); - - /** @var TransportInterface&MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->willReturn($eventId); - - $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) - ->setTransport($transport) - ->getClient(); - - $this->assertSame($eventId, $client->captureMessage('foo')); - } - - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ - public function testSetHttpClient(): void - { - /** @var HttpAsyncClientInterface&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClientInterface::class); - $httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn(new FulfilledPromise(true)); - - /** @var FlushableClientInterface $client */ - $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) - ->setHttpClient($httpClient) - ->getClient(); - - $client->captureMessage('foo'); - $client->flush(); - } - - /** - * @group legacy - * - * @expectedDeprecationMessage Method Sentry\ClientBuilder::addHttpClientPlugin() is deprecated since version 2.3 and will be removed in 3.0. - */ - public function testAddHttpClientPlugin(): void - { - /** @var PluginInterface&MockObject $plugin */ - $plugin = $this->createMock(PluginInterface::class); - $plugin->expects($this->once()) - ->method('handleRequest') - ->willReturn(new FulfilledPromise(true)); - - /** @var FlushableClientInterface $client */ - $client = ClientBuilder::create(['dsn' => 'http://public@example.com/sentry/1']) - ->addHttpClientPlugin($plugin) - ->getClient(); - - $client->captureMessage('foo'); - $client->flush(); - } - - /** - * @group legacy - * - * @expectedDeprecation Method Sentry\ClientBuilder::addHttpClientPlugin() is deprecated since version 2.3 and will be removed in 3.0. - * @expectedDeprecation Method Sentry\ClientBuilder::removeHttpClientPlugin() is deprecated since version 2.3 and will be removed in 3.0. - */ - public function testRemoveHttpClientPlugin(): void - { - $plugin = new class() implements PluginInterface { - public function handleRequest(RequestInterface $request, callable $next, callable $first): PromiseInterface - { - return new FulfilledPromise(true); - } - }; - - $plugin2 = new class() implements PluginInterface { - public function handleRequest(RequestInterface $request, callable $next, callable $first): PromiseInterface - { - return new FulfilledPromise(true); - } - }; - - /** @var FlushableClientInterface $client */ - $client = ClientBuilder::create() - ->addHttpClientPlugin($plugin) - ->addHttpClientPlugin($plugin) - ->addHttpClientPlugin($plugin2) - ->removeHttpClientPlugin(\get_class($plugin2)) - ->getClient(); - - $client->captureMessage('foo'); - $client->flush(); - } - /** * @dataProvider integrationsAreAddedToClientCorrectlyDataProvider */ diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 45a8841e8..963266f4e 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -12,7 +12,6 @@ use Sentry\Event; use Sentry\EventId; use Sentry\Options; -use Sentry\SentrySdk; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; @@ -67,52 +66,6 @@ public function testCaptureException(): void $client->captureException($exception); } - /** - * @dataProvider captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesDataProvider - */ - public function testCaptureExceptionDoesNothingIfExcludedExceptionsOptionMatches(bool $shouldCapture, string $excluded, \Throwable $thrownException): void - { - /** @var TransportInterface&MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transportFactory = $this->createTransportFactory($transport); - - $transport->expects($shouldCapture ? $this->once() : $this->never()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $this->assertNotEmpty($event->getExceptions()); - - return true; - })); - - $client = ClientBuilder::create(['excluded_exceptions' => [$excluded]]) - ->setTransportFactory($transportFactory) - ->getClient(); - - SentrySdk::getCurrentHub()->bindClient($client); - SentrySdk::getCurrentHub()->captureException($thrownException); - } - - public function captureExceptionDoesNothingIfExcludedExceptionsOptionMatchesDataProvider(): array - { - return [ - [ - true, - \Exception::class, - new \Error(), - ], - [ - false, - \Exception::class, - new \LogicException(), - ], - [ - false, - \Throwable::class, - new \Error(), - ], - ]; - } - public function testCaptureEvent(): void { $eventId = EventId::generate(); @@ -291,32 +244,6 @@ public function captureEventThrowsDeprecationErrorIfContextLinesOptionIsNotNullA yield [['exception' => new \Exception()]]; } - /** - * @group legacy - * - * @requires OSFAMILY Linux - */ - public function testAppPathLinux(): void - { - $client = ClientBuilder::create(['project_root' => '/foo/bar'])->getClient(); - - $this->assertEquals('/foo/bar', $client->getOptions()->getProjectRoot()); - - $client->getOptions()->setProjectRoot('/foo/baz/'); - - $this->assertEquals('/foo/baz/', $client->getOptions()->getProjectRoot()); - } - - /** - * @group legacy - */ - public function testAppPathWindows(): void - { - $client = ClientBuilder::create(['project_root' => 'C:\\foo\\bar\\'])->getClient(); - - $this->assertEquals('C:\\foo\\bar\\', $client->getOptions()->getProjectRoot()); - } - public function testSendChecksBeforeSendOption(): void { $beforeSendCalled = false; diff --git a/tests/HttpClient/PluggableHttpClientFactoryTest.php b/tests/HttpClient/PluggableHttpClientFactoryTest.php deleted file mode 100644 index 0c34f318f..000000000 --- a/tests/HttpClient/PluggableHttpClientFactoryTest.php +++ /dev/null @@ -1,57 +0,0 @@ -createMock(HttpAsyncClientInterface::class); - $wrappedHttpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn($this->createMock(PromiseInterface::class)); - - /** @var HttpClientPluginInterface&MockObject $httpClientPlugin */ - $httpClientPlugin = $this->createMock(HttpClientPluginInterface::class); - $httpClientPlugin->expects($this->once()) - ->method('handleRequest') - ->willReturnCallback(static function (RequestInterface $request, callable $next): PromiseInterface { - return $next($request); - }); - - $httpClientFactory = new class($wrappedHttpClient) implements HttpClientFactoryInterface { - private $httpClient; - - public function __construct(HttpAsyncClientInterface $httpClient) - { - $this->httpClient = $httpClient; - } - - public function create(Options $options): HttpAsyncClientInterface - { - return $this->httpClient; - } - }; - - $httpClientFactory = new PluggableHttpClientFactory($httpClientFactory, [$httpClientPlugin]); - $httpClient = $httpClientFactory->create(new Options(['default_integrations' => false])); - - $httpClient->sendAsyncRequest($this->createMock(RequestInterface::class)); - } -} diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index 6f359b9f3..81c4d92db 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -56,18 +56,4 @@ public function invokeDataProvider(): \Generator false, ]; } - - /** - * @group legacy - * - * @expectedDeprecationMessage The "Sentry\Integration\ModulesIntegration::applyToEvent" method is deprecated since version 2.4 and will be removed in 3.0. - */ - public function testApplyToEvent(): void - { - $event = new Event(); - $integration = new ModulesIntegration(); - $integration->applyToEvent($integration, $event); - - $this->assertNotEmpty($event->getModules()); - } } diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index c6335311f..cfe173b9f 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -23,25 +23,14 @@ final class RequestIntegrationTest extends TestCase { /** - * @group legacy - * - * @expectedDeprecation Passing the options as argument of the constructor of the "Sentry\Integration\RequestIntegration" class is deprecated since version 2.1 and will not work in 3.0. + * @dataProvider invokeDataProvider */ - public function testConstructorThrowsDeprecationErrorIfPassingOptions(): void - { - new RequestIntegration(new Options([])); - } - - /** - * @dataProvider applyToEventWithRequestHavingIpAddressDataProvider - */ - public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $expectedValue): void + public function testInvoke(array $options, ServerRequestInterface $request, array $expectedRequestContextData, array $expectedUserContextData): void { $event = new Event(); $event->getUserContext()->setData(['foo' => 'bar']); - $request = new ServerRequest('GET', new Uri('http://www.example.com/fo'), [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1']); - $integration = new RequestIntegration(null, $this->createRequestFetcher($request)); + $integration = new RequestIntegration($this->createRequestFetcher($request)); $integration->setupOnce(); /** @var ClientInterface&MockObject $client */ @@ -53,53 +42,20 @@ public function testInvokeWithRequestHavingIpAddress(bool $shouldSendPii, array $client->expects($this->once()) ->method('getOptions') - ->willReturn(new Options(['send_default_pii' => $shouldSendPii])); + ->willReturn(new Options($options)); SentrySdk::getCurrentHub()->bindClient($client); - withScope(function (Scope $scope) use ($event, $expectedValue): void { + withScope(function (Scope $scope) use ($event, $expectedRequestContextData, $expectedUserContextData): void { $event = $scope->applyToEvent($event, []); $this->assertNotNull($event); - $this->assertEquals($expectedValue, $event->getUserContext()->toArray()); + $this->assertEquals($expectedRequestContextData, $event->getRequest()); + $this->assertEquals($expectedUserContextData, $event->getUserContext()->toArray()); }); } - public function applyToEventWithRequestHavingIpAddressDataProvider(): array - { - return [ - [ - true, - [ - 'ip_address' => '127.0.0.1', - 'foo' => 'bar', - ], - ], - [ - false, - ['foo' => 'bar'], - ], - ]; - } - - /** - * @group legacy - * - * @dataProvider applyToEventDataProvider - * - * @expectedDeprecation The "Sentry\Integration\RequestIntegration::applyToEvent" method is deprecated since version 2.1 and will be removed in 3.0. - */ - public function testApplyToEvent(array $options, ServerRequestInterface $request, array $expectedResult): void - { - $event = new Event(); - $integration = new RequestIntegration(new Options($options)); - - RequestIntegration::applyToEvent($integration, $event, $request); - - $this->assertEquals($expectedResult, $event->getRequest()); - } - - public function applyToEventDataProvider(): \Generator + public function invokeDataProvider(): \Generator { yield [ [ @@ -117,6 +73,9 @@ public function applyToEventDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -132,6 +91,9 @@ public function applyToEventDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -147,6 +109,9 @@ public function applyToEventDataProvider(): \Generator 'Host' => ['www.example.com:1234'], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -161,6 +126,9 @@ public function applyToEventDataProvider(): \Generator 'Host' => ['www.example.com:1234'], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -187,6 +155,10 @@ public function applyToEventDataProvider(): \Generator 'REMOTE_ADDR' => '127.0.0.1', ], ], + [ + 'ip_address' => '127.0.0.1', + 'foo' => 'bar', + ], ]; yield [ @@ -206,6 +178,9 @@ public function applyToEventDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -225,6 +200,9 @@ public function applyToEventDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -248,6 +226,9 @@ public function applyToEventDataProvider(): \Generator 'bar' => 'bar value', ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -267,6 +248,9 @@ public function applyToEventDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -290,6 +274,9 @@ public function applyToEventDataProvider(): \Generator 'bar' => 'bar value', ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -309,6 +296,9 @@ public function applyToEventDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -333,6 +323,9 @@ public function applyToEventDataProvider(): \Generator ], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -367,6 +360,9 @@ public function applyToEventDataProvider(): \Generator ], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -405,6 +401,9 @@ public function applyToEventDataProvider(): \Generator ], ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -425,6 +424,9 @@ public function applyToEventDataProvider(): \Generator 'foo' => 'bar', ], ], + [ + 'foo' => 'bar', + ], ]; yield [ @@ -443,6 +445,9 @@ public function applyToEventDataProvider(): \Generator ], 'data' => '{', ], + [ + 'foo' => 'bar', + ], ]; } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 78aa94814..20cbc0766 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -56,10 +56,8 @@ public function optionsDataProvider(): array ['context_lines', 3, 'getContextLines', 'setContextLines'], ['enable_compression', false, 'isCompressionEnabled', 'setEnableCompression'], ['environment', 'foo', 'getEnvironment', 'setEnvironment'], - ['excluded_exceptions', ['foo', 'bar', 'baz'], 'getExcludedExceptions', 'setExcludedExceptions'], ['in_app_exclude', ['foo', 'bar'], 'getInAppExcludedPaths', 'setInAppExcludedPaths'], ['in_app_include', ['foo', 'bar'], 'getInAppIncludedPaths', 'setInAppIncludedPaths'], - ['project_root', 'baz', 'getProjectRoot', 'setProjectRoot'], ['logger', 'foo', 'getLogger', 'setLogger'], ['release', 'dev', 'getRelease', 'setRelease'], ['server_name', 'foo', 'getServerName', 'setServerName'], @@ -169,37 +167,6 @@ public function dsnOptionThrowsOnInvalidValueDataProvider(): \Generator ]; } - /** - * @dataProvider excludedExceptionsDataProvider - */ - public function testIsExcludedException(array $excludedExceptions, \Throwable $exception, bool $result): void - { - $configuration = new Options(['excluded_exceptions' => $excludedExceptions]); - - $this->assertSame($result, $configuration->isExcludedException($exception, false)); - } - - public function excludedExceptionsDataProvider(): \Generator - { - yield [ - [\BadFunctionCallException::class, \BadMethodCallException::class], - new \BadMethodCallException(), - true, - ]; - - yield [ - [\BadFunctionCallException::class], - new \Exception(), - false, - ]; - - yield [ - [\Exception::class], - new \BadFunctionCallException(), - true, - ]; - } - /** * @dataProvider excludedPathProviders */ diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 9d96b097f..846ac00b3 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -161,7 +161,6 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator { yield 'No config specified' => [ [ - 'project_root' => null, 'in_app_exclude' => [], 'in_app_include' => [], ], @@ -170,46 +169,8 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator false, ]; - yield 'project_root specified && file path matching project_root' => [ - [ - 'project_root' => 'path/to', - 'in_app_exclude' => [], - 'in_app_include' => [], - ], - 'path/to/file', - 'test_function', - true, - ]; - - yield 'project_root specified && file path matching project_root && file path matching in_app_exclude' => [ - [ - 'project_root' => 'path/to/file', - 'in_app_exclude' => [ - 'path/to', - ], - 'in_app_include' => [], - ], - 'path/to/file', - 'test_function', - false, - ]; - - yield 'project_root specified && file path not maching project_root && file path matching in_app_include' => [ - [ - 'project_root' => 'nested/path/to', - 'in_app_exclude' => [], - 'in_app_include' => [ - 'path/to', - ], - ], - 'path/to/file', - 'test_function', - true, - ]; - yield 'in_app_include specified && file path not matching' => [ [ - 'project_root' => null, 'in_app_exclude' => [], 'in_app_include' => [ 'path/to/nested/file', @@ -222,7 +183,6 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator yield 'in_app_include not specified && file path not matching' => [ [ - 'project_root' => null, 'in_app_exclude' => [], 'in_app_include' => [], ], @@ -233,7 +193,6 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator yield 'in_app_include specified && file path matching' => [ [ - 'project_root' => null, 'in_app_exclude' => [], 'in_app_include' => [ 'path/to/nested/file', @@ -247,7 +206,6 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_include' => [ [ - 'project_root' => null, 'in_app_exclude' => [ 'path/to/nested/file', ], @@ -262,7 +220,6 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_exclude' => [ [ - 'project_root' => null, 'in_app_exclude' => [ 'path/to/nested/file', ], @@ -277,7 +234,6 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_include && in_app_include prioritized over in_app_exclude' => [ [ - 'project_root' => null, 'in_app_exclude' => [ 'path/to/file', ], @@ -292,7 +248,6 @@ public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_exclude && in_app_exclude prioritized over in_app_include' => [ [ - 'project_root' => null, 'in_app_exclude' => [ 'path/to/file', ], diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index d1f58097c..4efc4eb79 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -100,12 +100,7 @@ public function testSetExtras(): void $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtraContext()->toArray()); } - /** - * @group legacy - * - * @expectedDeprecation Replacing the data is deprecated since version 2.3 and will stop working from version 3.0. Set the second argument to `true` to merge the data instead. - */ - public function testSetUserThrowsDeprecationError(): void + public function testSetUser(): void { $scope = new Scope(); @@ -125,30 +120,6 @@ public function testSetUserThrowsDeprecationError(): void $event = $scope->applyToEvent(new Event(), []); - $this->assertNotNull($event); - $this->assertSame(['bar' => 'baz'], $event->getUserContext()->toArray()); - } - - public function testSetUser(): void - { - $scope = new Scope(); - - $event = $scope->applyToEvent(new Event(), []); - - $this->assertNotNull($event); - $this->assertSame([], $event->getUserContext()->toArray()); - - $scope->setUser(['foo' => 'bar'], true); - - $event = $scope->applyToEvent(new Event(), []); - - $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar'], $event->getUserContext()->toArray()); - - $scope->setUser(['bar' => 'baz'], true); - - $event = $scope->applyToEvent(new Event(), []); - $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getUserContext()->toArray()); } @@ -284,7 +255,7 @@ public function testClear(): void $scope->setFingerprint(['foo']); $scope->setExtras(['foo' => 'bar']); $scope->setTags(['bar' => 'foo']); - $scope->setUser(['foobar' => 'barfoo'], true); + $scope->setUser(['foobar' => 'barfoo']); $event = $scope->applyToEvent(new Event(), []); @@ -322,7 +293,7 @@ public function testApplyToEvent(): void $scope->addBreadcrumb($breadcrumb); $scope->setTag('foo', 'bar'); $scope->setExtra('bar', 'foo'); - $scope->setUser(['foo' => 'baz'], true); + $scope->setUser(['foo' => 'baz']); $scope->setContext('foocontext', ['foo' => 'bar']); $scope->setContext('barcontext', ['bar' => 'foo']); From ae0369ae0b1d99bdf3a22cc82077e427aa869bbb Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 3 Aug 2020 10:27:37 +0200 Subject: [PATCH 0579/1161] Fix unreliable test comparing timestamps (#1055) --- tests/EventTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/EventTest.php b/tests/EventTest.php index 0da88d4a2..caf6f82fe 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -12,6 +12,7 @@ use Sentry\Severity; use Sentry\Stacktrace; use Sentry\Util\PHPVersion; +use Symfony\Bridge\PhpUnit\ClockMock; /** * @group time-sensitive @@ -40,6 +41,8 @@ public function testGetEventIdThrowsDeprecationErrorIfExpectingStringReturnType( public function testToArray(): void { + ClockMock::register(Event::class); + $event = new Event(); $expected = [ @@ -70,6 +73,8 @@ public function testToArray(): void public function testToArrayMergesCustomContextsWithDefaultContexts(): void { + ClockMock::register(Event::class); + $event = new Event(); $event->setContext('foo', ['foo' => 'bar']); $event->setContext('bar', ['bar' => 'foo']); From 06e2ccbfcb77ed850ac571b464a98e024c333065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Wed, 5 Aug 2020 11:54:56 +0300 Subject: [PATCH 0580/1161] README: use composer installed globally in the installation instructions (#1060) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e5f01a1e1..5f4f8c442 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This is our "core" SDK, meaning that all the important code regarding error hand If you are happy with using the HTTP client we recommend install the SDK like: [`sentry/sdk`](https://github.com/getsentry/sentry-php-sdk) ```bash -php composer.phar require sentry/sdk +composer require sentry/sdk ``` This package (`sentry/sentry`) is not tied to any specific library that sends HTTP messages. Instead, @@ -40,7 +40,7 @@ PSR-7 implementation and HTTP client they want to use. If you just want to get started quickly you should run the following command: ```bash -php composer.phar require sentry/sentry php-http/curl-client +composer require sentry/sentry php-http/curl-client ``` This is basically what our metapackage (`sentry/sdk`) provides. From 9cbc9c368bf1c71542da074ddaaf6385796f540e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Thu, 6 Aug 2020 16:04:04 +0200 Subject: [PATCH 0581/1161] Fix wrong Psalm annotation in the IgnoreErrorsIntegration class (#1061) --- src/Integration/IgnoreErrorsIntegration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index 497411817..35f2e02d9 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -29,7 +29,7 @@ final class IgnoreErrorsIntegration implements IntegrationInterface * @param array $options The options * * @psalm-param array{ - * ignore_exceptions?: bool + * ignore_exceptions?: list> * } $options */ public function __construct(array $options = []) From b3081fe7915f0e5765a18914d066128c83d94175 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 11 Aug 2020 12:36:46 +0200 Subject: [PATCH 0582/1161] Avoid capturing the request body in the RequestIntegration integration when the stream size is unknown (#1064) --- CHANGELOG.md | 1 + src/Integration/RequestIntegration.php | 6 ++++-- tests/Integration/RequestIntegrationTest.php | 21 ++++++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c3f0f31e..fe777d335 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix `Options::setEnvironment` method not accepting `null` values (#1057) +- Fix the capture of the request body in the `RequestIntegration` integration when the stream size is unknown (#1064) ### 2.4.2 (2020-07-24) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 9fee4d8b1..9dcf3fc08 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -185,11 +185,13 @@ private function captureRequestBody(Options $options, ServerRequestInterface $se { $maxRequestBodySize = $options->getMaxRequestBodySize(); $requestBody = $serverRequest->getBody(); + $requestBodySize = $requestBody->getSize(); if ( + null === $requestBodySize || 'none' === $maxRequestBodySize || - ('small' === $maxRequestBodySize && $requestBody->getSize() > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) || - ('medium' === $maxRequestBodySize && $requestBody->getSize() > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) + ('small' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) || + ('medium' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) ) { return null; } diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index c6335311f..e4d205846 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -444,9 +444,26 @@ public function applyToEventDataProvider(): \Generator 'data' => '{', ], ]; + + yield [ + [ + 'max_request_body_size' => 'always', + ], + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->getStreamMock(null)), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + 'Content-Type' => ['application/json'], + ], + ], + ]; } - private function getStreamMock(int $size, string $content = ''): StreamInterface + private function getStreamMock(?int $size, string $content = ''): StreamInterface { /** @var MockObject|StreamInterface $stream */ $stream = $this->createMock(StreamInterface::class); @@ -454,7 +471,7 @@ private function getStreamMock(int $size, string $content = ''): StreamInterface ->method('getSize') ->willReturn($size); - $stream->expects($this->any()) + $stream->expects(null === $size ? $this->never() : $this->any()) ->method('getContents') ->willReturn($content); From 89fd1f91657b33ec9139f33f8a201eb086276103 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 13 Aug 2020 12:54:32 +0200 Subject: [PATCH 0583/1161] prepare: 2.4.3 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe777d335..83ac5bb8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +### 2.4.3 (2020-08-13) + - Fix `Options::setEnvironment` method not accepting `null` values (#1057) - Fix the capture of the request body in the `RequestIntegration` integration when the stream size is unknown (#1064) From 84348611c974f3f7ecbb5c5dbf2beca19eb0c210 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 19 Aug 2020 12:38:52 +0200 Subject: [PATCH 0584/1161] Refactor the Stacktrace and Frame classes and add support for exception mechanism (#1063) --- composer.json | 1 + phpstan.neon | 7 +- src/Client.php | 9 +- src/Event.php | 53 +- src/EventFactory.php | 60 +- src/ExceptionDataBag.php | 96 +++ src/ExceptionMechanism.php | 60 ++ src/Frame.php | 28 +- src/FrameBuilder.php | 226 +++++++ .../AbstractErrorListenerIntegration.php | 44 ++ src/Integration/ErrorListenerIntegration.php | 4 +- .../ExceptionListenerIntegration.php | 4 +- .../FatalErrorListenerIntegration.php | 4 +- .../FrameContextifierIntegration.php | 6 +- src/Integration/IgnoreErrorsIntegration.php | 4 +- src/Stacktrace.php | 435 +----------- src/StacktraceBuilder.php | 77 +++ src/Tracing/SpanContext.php | 2 +- tests/ClientTest.php | 104 +-- tests/EventFactoryTest.php | 75 +-- tests/EventTest.php | 15 - .../Fixtures/backtraces/anonymous_frame.json | 15 - .../anonymous_frame_with_memory_address.json | 16 - tests/Fixtures/backtraces/exception.json | 18 - tests/Fixtures/frames/eval.json | 5 - tests/Fixtures/frames/function.json | 6 - .../Fixtures/frames/missing_function_key.json | 5 - tests/Fixtures/frames/runtime_created.json | 5 - tests/FrameBuilderTest.php | 260 ++++++++ .../FrameContextifierIntegrationTest.php | 13 +- .../IgnoreErrorsIntegrationTest.php | 17 +- tests/StacktraceBuilderTest.php | 51 ++ tests/StacktraceTest.php | 617 +++++------------- ...rror_handler_respects_error_reporting.phpt | 2 + ...tegration_respects_error_types_option.phpt | 2 + 35 files changed, 1134 insertions(+), 1212 deletions(-) create mode 100644 src/ExceptionDataBag.php create mode 100644 src/ExceptionMechanism.php create mode 100644 src/FrameBuilder.php create mode 100644 src/Integration/AbstractErrorListenerIntegration.php create mode 100644 src/StacktraceBuilder.php delete mode 100644 tests/Fixtures/backtraces/anonymous_frame.json delete mode 100644 tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json delete mode 100644 tests/Fixtures/backtraces/exception.json delete mode 100644 tests/Fixtures/frames/eval.json delete mode 100644 tests/Fixtures/frames/function.json delete mode 100644 tests/Fixtures/frames/missing_function_key.json delete mode 100644 tests/Fixtures/frames/runtime_created.json create mode 100644 tests/FrameBuilderTest.php create mode 100644 tests/StacktraceBuilderTest.php diff --git a/composer.json b/composer.json index 248fafa3f..f2f6a10d0 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "psr/http-message-implementation": "^1.0", "psr/log": "^1.0", "symfony/options-resolver": "^2.7|^3.0|^4.0|^5.0", + "symfony/polyfill-php80": "^1.17", "symfony/polyfill-uuid": "^1.13.1" }, "require-dev": { diff --git a/phpstan.neon b/phpstan.neon index bbdace8dc..a71bdc3b2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -34,11 +34,8 @@ parameters: message: '/^Method Sentry\\Client::getIntegration\(\) should return T of Sentry\\Integration\\IntegrationInterface\|null but returns Sentry\\Integration\\IntegrationInterface\|null\.$/' path: src/Client.php - - message: '/^Method Sentry\\EventFactoryInterface::createWithStacktrace\(\) invoked with 2 parameters, 1 required\.$/' - path: src/Client.php - - - message: '/^Method Sentry\\EventFactoryInterface::create\(\) invoked with 2 parameters, 1 required\.$/' - path: src/Client.php + message: "/Call to function in_array\\(\\) with arguments callable\\(\\): mixed&string, array\\('{closure}', '__lambda_func'\\) and true will always evaluate to false\\.$/" + path: src/FrameBuilder.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Client.php b/src/Client.php index bd33e784f..1c91b6833 100644 --- a/src/Client.php +++ b/src/Client.php @@ -8,7 +8,6 @@ use GuzzleHttp\Promise\PromiseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\Handler; use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; @@ -168,14 +167,10 @@ public function flush(?int $timeout = null): PromiseInterface */ private function prepareEvent($payload, ?Scope $scope = null): ?Event { - $shouldReadSourceCodeExcerpts = !isset($this->integrations[FrameContextifierIntegration::class]) && null !== $this->options->getContextLines(); - if ($this->options->shouldAttachStacktrace() && !($payload instanceof Event) && !isset($payload['exception']) && !isset($payload['stacktrace'])) { - /** @psalm-suppress TooManyArguments */ - $event = $this->eventFactory->createWithStacktrace($payload, $shouldReadSourceCodeExcerpts); + $event = $this->eventFactory->createWithStacktrace($payload); } else { - /** @psalm-suppress TooManyArguments */ - $event = $this->eventFactory->create($payload, $shouldReadSourceCodeExcerpts); + $event = $this->eventFactory->create($payload); } $sampleRate = $this->options->getSampleRate(); diff --git a/src/Event.php b/src/Event.php index 2437e9604..b9a416692 100644 --- a/src/Event.php +++ b/src/Event.php @@ -138,13 +138,7 @@ final class Event implements \JsonSerializable private $spans = []; /** - * @var array> The exceptions - * - * @psalm-var list + * @var ExceptionDataBag[] The exceptions */ private $exceptions = []; @@ -554,13 +548,7 @@ public function setBreadcrumb(array $breadcrumbs): void /** * Gets the exception. * - * @return array> - * - * @psalm-return list + * @return ExceptionDataBag[] */ public function getExceptions(): array { @@ -570,16 +558,16 @@ public function getExceptions(): array /** * Sets the exceptions. * - * @param array> $exceptions The exceptions - * - * @psalm-param list $exceptions + * @param ExceptionDataBag[] $exceptions The exceptions */ public function setExceptions(array $exceptions): void { + foreach ($exceptions as $exception) { + if (!$exception instanceof ExceptionDataBag) { + throw new \UnexpectedValueException(sprintf('Expected an instance of the "%s" class. Got: "%s".', ExceptionDataBag::class, get_debug_type($exception))); + } + } + $this->exceptions = $exceptions; } @@ -722,18 +710,27 @@ public function toArray(): array } foreach (array_reverse($this->exceptions) as $exception) { - $exceptionData = [ - 'type' => $exception['type'], - 'value' => $exception['value'], + $exceptionMechanism = $exception->getMechanism(); + $exceptionStacktrace = $exception->getStacktrace(); + $exceptionValue = [ + 'type' => $exception->getType(), + 'value' => $exception->getValue(), ]; - if (isset($exception['stacktrace'])) { - $exceptionData['stacktrace'] = [ - 'frames' => $exception['stacktrace']->toArray(), + if (null !== $exceptionStacktrace) { + $exceptionValue['stacktrace'] = [ + 'frames' => $exceptionStacktrace->toArray(), + ]; + } + + if (null !== $exceptionMechanism) { + $exceptionValue['mechanism'] = [ + 'type' => $exceptionMechanism->getType(), + 'handled' => $exceptionMechanism->isHandled(), ]; } - $data['exception']['values'][] = $exceptionData; + $data['exception']['values'][] = $exceptionValue; } if (null !== $this->stacktrace) { diff --git a/src/EventFactory.php b/src/EventFactory.php index 94ba84f2b..f19a16174 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -18,11 +18,6 @@ final class EventFactory implements EventFactoryInterface */ private $serializer; - /** - * @var RepresentationSerializerInterface The representation serializer - */ - private $representationSerializer; - /** * @var Options The Sentry options */ @@ -38,6 +33,11 @@ final class EventFactory implements EventFactoryInterface */ private $sdkVersion; + /** + * @var StacktraceBuilder + */ + private $stacktraceBuilder; + /** * EventFactory constructor. * @@ -50,30 +50,26 @@ final class EventFactory implements EventFactoryInterface public function __construct(SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer, Options $options, string $sdkIdentifier, string $sdkVersion) { $this->serializer = $serializer; - $this->representationSerializer = $representationSerializer; $this->options = $options; $this->sdkIdentifier = $sdkIdentifier; $this->sdkVersion = $sdkVersion; + $this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer); } /** * {@inheritdoc} */ - public function createWithStacktrace($payload/*, bool $shouldReadSourceCodeExcerpts = true*/): Event + public function createWithStacktrace($payload): Event { if ($payload instanceof Event) { return $this->create($payload); } if (!isset($payload['stacktrace']) || !$payload['stacktrace'] instanceof Stacktrace) { - $payload['stacktrace'] = Stacktrace::createFromBacktrace( - $this->options, - $this->serializer, - $this->representationSerializer, + $payload['stacktrace'] = $this->stacktraceBuilder->buildFromBacktrace( debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), __FILE__, - __LINE__ - 6, - \func_num_args() > 1 ? func_get_arg(1) : true + __LINE__ - 3 ); } @@ -83,7 +79,7 @@ public function createWithStacktrace($payload/*, bool $shouldReadSourceCodeExcer /** * {@inheritdoc} */ - public function create($payload/*, bool $shouldReadSourceCodeExcerpts = true*/): Event + public function create($payload): Event { try { if ($payload instanceof Event) { @@ -103,7 +99,7 @@ public function create($payload/*, bool $shouldReadSourceCodeExcerpts = true*/): } if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { - $this->addThrowableToEvent($event, $payload['exception'], \func_num_args() > 1 ? func_get_arg(1) : true); + $this->addThrowableToEvent($event, $payload['exception']); } if (isset($payload['level']) && $payload['level'] instanceof Severity) { @@ -131,38 +127,26 @@ public function create($payload/*, bool $shouldReadSourceCodeExcerpts = true*/): /** * Stores the given exception in the passed event. * - * @param Event $event The event that will be enriched with the - * exception - * @param \Throwable $exception The exception that will be processed and - * added to the event - * @param bool $shouldReadSourceCodeExcerpts Whether to read the source code excerpts - * using the legacy method instead of using - * the integration + * @param Event $event The event that will be enriched with the + * exception + * @param \Throwable $exception The exception that will be processed and + * added to the event */ - private function addThrowableToEvent(Event $event, \Throwable $exception, bool $shouldReadSourceCodeExcerpts): void + private function addThrowableToEvent(Event $event, \Throwable $exception): void { if ($exception instanceof \ErrorException) { $event->setLevel(Severity::fromError($exception->getSeverity())); } $exceptions = []; - $currentException = $exception; do { - $exceptions[] = [ - 'type' => \get_class($currentException), - 'value' => $currentException->getMessage(), - 'stacktrace' => Stacktrace::createFromBacktrace( - $this->options, - $this->serializer, - $this->representationSerializer, - $currentException->getTrace(), - $currentException->getFile(), - $currentException->getLine(), - $shouldReadSourceCodeExcerpts - ), - ]; - } while ($currentException = $currentException->getPrevious()); + $exceptions[] = new ExceptionDataBag( + $exception, + $this->stacktraceBuilder->buildFromException($exception), + new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true) + ); + } while ($exception = $exception->getPrevious()); $event->setExceptions($exceptions); } diff --git a/src/ExceptionDataBag.php b/src/ExceptionDataBag.php new file mode 100644 index 000000000..1d096c867 --- /dev/null +++ b/src/ExceptionDataBag.php @@ -0,0 +1,96 @@ + + */ +final class ExceptionDataBag +{ + /** + * @var class-string<\Throwable> The type of exception, e.g. RuntimeException + */ + private $type; + + /** + * @var string The value of the exception + */ + private $value; + + /** + * @var Stacktrace|null An optional stack trace object corresponding to the Stack Trace Interface + */ + private $stacktrace; + + /** + * @var ExceptionMechanism|null An optional object describing the mechanism that created this exception + */ + private $mechanism; + + public function __construct(\Throwable $exception, ?Stacktrace $stacktrace = null, ?ExceptionMechanism $mechanism = null) + { + $this->type = \get_class($exception); + $this->value = $exception->getMessage(); + $this->stacktrace = $stacktrace; + $this->mechanism = $mechanism; + } + + /** + * Gets the type of exception, e.g. RuntimeException. + * + * @return class-string<\Throwable> + */ + public function getType(): string + { + return $this->type; + } + + /** + * Gets the value of the exception. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Gets the stack trace object corresponding to the Stack Trace Interface. + */ + public function getStacktrace(): ?Stacktrace + { + return $this->stacktrace; + } + + /** + * Sets the stack trace object corresponding to the Stack Trace Interface. + * + * @param Stacktrace $stacktrace The stacktrace + */ + public function setStacktrace(Stacktrace $stacktrace): void + { + $this->stacktrace = $stacktrace; + } + + /** + * Gets the object describing the mechanism that created this exception. + */ + public function getMechanism(): ?ExceptionMechanism + { + return $this->mechanism; + } + + /** + * Sets the object describing the mechanism that created this exception. + * + * @param ExceptionMechanism|null $mechanism The mechanism that created this exception + */ + public function setMechanism(?ExceptionMechanism $mechanism): void + { + $this->mechanism = $mechanism; + } +} diff --git a/src/ExceptionMechanism.php b/src/ExceptionMechanism.php new file mode 100644 index 000000000..8dc83a9ea --- /dev/null +++ b/src/ExceptionMechanism.php @@ -0,0 +1,60 @@ +type = $type; + $this->handled = $handled; + } + + /** + * Returns the unique identifier of this mechanism determining rendering and + * processing of the mechanism data. + */ + public function getType(): string + { + return $this->type; + } + + /** + * Returns the flag indicating whether the exception has been handled by the + * user (e.g. via try..catch). + */ + public function isHandled(): bool + { + return $this->handled; + } +} diff --git a/src/Frame.php b/src/Frame.php index d2f6a5e69..91a01b801 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -11,13 +11,21 @@ */ final class Frame implements \JsonSerializable { - private const INTERNAL_FRAME_FILENAME = '[internal]'; + public const INTERNAL_FRAME_FILENAME = '[internal]'; + + public const ANONYMOUS_CLASS_PREFIX = "class@anonymous\x00"; /** * @var string|null The name of the function being called */ private $functionName; + /** + * @var string|null The original function name, if the function name is + * shortened or demangled + */ + private $rawFunctionName; + /** * @var string The file where the frame originated */ @@ -68,6 +76,8 @@ final class Frame implements \JsonSerializable * * @param string|null $functionName The name of the function being called * @param string $file The file where the frame originated + * @param string|null $rawFunctionName The original function name, if the function + * name is shortened or demangled * @param string|null $absoluteFilePath The absolute path to the source file * @param int $line The line at which the frame originated * @param array $vars A mapping of variables which were available @@ -76,12 +86,13 @@ final class Frame implements \JsonSerializable * execution of code relevant to the * application */ - public function __construct(?string $functionName, string $file, int $line, ?string $absoluteFilePath = null, array $vars = [], bool $inApp = true) + public function __construct(?string $functionName, string $file, int $line, ?string $rawFunctionName = null, ?string $absoluteFilePath = null, array $vars = [], bool $inApp = true) { $this->functionName = $functionName; $this->file = $file; - $this->absoluteFilePath = $absoluteFilePath ?? $file; $this->line = $line; + $this->rawFunctionName = $rawFunctionName; + $this->absoluteFilePath = $absoluteFilePath ?? $file; $this->vars = $vars; $this->inApp = $inApp; } @@ -94,6 +105,15 @@ public function getFunctionName(): ?string return $this->functionName; } + /** + * Gets the original function name, if the function name is shortened or + * demangled. + */ + public function getRawFunctionName(): ?string + { + return $this->rawFunctionName; + } + /** * Gets the file where the frame originated. */ @@ -234,6 +254,7 @@ public function isInternal(): bool * * @psalm-return array{ * function: string|null, + * raw_function: string|null, * filename: string, * lineno: int, * in_app: bool, @@ -248,6 +269,7 @@ public function toArray(): array { $result = [ 'function' => $this->functionName, + 'raw_function' => $this->rawFunctionName, 'filename' => $this->file, 'lineno' => $this->line, 'in_app' => $this->inApp, diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php new file mode 100644 index 000000000..91ad97cd5 --- /dev/null +++ b/src/FrameBuilder.php @@ -0,0 +1,226 @@ +options = $options; + $this->representationSerializer = $representationSerializer; + } + + /** + * Builds a {@see Frame} object from the given backtrace's raw frame. + * + * @param string $file The file where the frame originated + * @param int $line The line at which the frame originated + * @param array $backtraceFrame The raw frame + * + * @psalm-param array{ + * function?: callable-string, + * line?: integer, + * file?: string, + * class?: class-string, + * type?: string, + * args?: array + * } $backtraceFrame + */ + public function buildFromBacktraceFrame(string $file, int $line, array $backtraceFrame): Frame + { + // The filename can be in any of these formats: + // - + // - () : eval()'d code + // - () : runtime-created function + if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d code|runtime-created function)$/', $file, $matches)) { + $file = $matches[1]; + $line = (int) $matches[2]; + } + + $functionName = null; + $rawFunctionName = null; + + if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { + $functionName = $backtraceFrame['class']; + + if (str_starts_with($functionName, Frame::ANONYMOUS_CLASS_PREFIX)) { + $functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath(substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX))); + } + + $rawFunctionName = sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); + $functionName = sprintf('%s::%s', preg_replace('/0x[a-fA-F0-9]+$/', '', $functionName), $backtraceFrame['function']); + } elseif (isset($backtraceFrame['function'])) { + $functionName = $backtraceFrame['function']; + } + + return new Frame( + $functionName, + $this->stripPrefixFromFilePath($file), + $line, + $rawFunctionName, + Frame::INTERNAL_FRAME_FILENAME !== $file ? $file : null, + $this->getFunctionArguments($backtraceFrame), + $this->isFrameInApp($file, $functionName) + ); + } + + /** + * Removes from the given file path the specified prefixes. + * + * @param string $filePath The path to the file + */ + private function stripPrefixFromFilePath(string $filePath): string + { + foreach ($this->options->getPrefixes() as $prefix) { + if (str_starts_with($filePath, $prefix)) { + return mb_substr($filePath, mb_strlen($prefix)); + } + } + + return $filePath; + } + + /** + * Checks whether a certain frame should be marked as "in app" or not. + * + * @param string $file The file to check + * @param string|null $functionName The name of the function + */ + private function isFrameInApp(string $file, ?string $functionName): bool + { + if (Frame::INTERNAL_FRAME_FILENAME === $file) { + return false; + } + + if (null !== $functionName && str_starts_with($functionName, 'Sentry\\')) { + return false; + } + + $excludedAppPaths = $this->options->getInAppExcludedPaths(); + $includedAppPaths = $this->options->getInAppIncludedPaths(); + $absoluteFilePath = @realpath($file) ?: $file; + $isInApp = true; + + foreach ($excludedAppPaths as $excludedAppPath) { + if (str_starts_with($absoluteFilePath, $excludedAppPath)) { + $isInApp = false; + + break; + } + } + + foreach ($includedAppPaths as $includedAppPath) { + if (str_starts_with($absoluteFilePath, $includedAppPath)) { + $isInApp = true; + + break; + } + } + + return $isInApp; + } + + /** + * Gets the arguments of the function called in the given frame. + * + * @param array $backtraceFrame The frame data + * + * @return array + * + * @throws \ReflectionException + * + * @psalm-param array{ + * function?: callable-string, + * class?: class-string, + * type?: string, + * args?: array + * } $backtraceFrame + */ + private function getFunctionArguments(array $backtraceFrame): array + { + if (!isset($backtraceFrame['function'], $backtraceFrame['args'])) { + return []; + } + + $reflectionFunction = null; + + if (isset($backtraceFrame['class'], $backtraceFrame['function'])) { + if (method_exists($backtraceFrame['class'], $backtraceFrame['function'])) { + $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], $backtraceFrame['function']); + } elseif (isset($backtraceFrame['type']) && '::' === $backtraceFrame['type']) { + $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__callStatic'); + } else { + $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); + } + } elseif (isset($backtraceFrame['function']) && !\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { + $reflectionFunction = new \ReflectionFunction($backtraceFrame['function']); + } + + $argumentValues = []; + + if (null !== $reflectionFunction) { + $argumentValues = $this->getFunctionArgumentValues($reflectionFunction, $backtraceFrame['args']); + } else { + foreach ($backtraceFrame['args'] as $parameterPosition => $parameterValue) { + $argumentValues['param' . $parameterPosition] = $parameterValue; + } + } + + foreach ($argumentValues as $argumentName => $argumentValue) { + $argumentValues[$argumentName] = $this->representationSerializer->representationSerialize($argumentValue); + } + + return $argumentValues; + } + + /** + * Gets an hashmap indexed by argument name containing all the arguments + * passed to the function called in the given frame of the stacktrace. + * + * @param \ReflectionFunctionAbstract $reflectionFunction A reflection object + * @param mixed[] $backtraceFrameArgs The arguments of the frame + * + * @return array + */ + private function getFunctionArgumentValues(\ReflectionFunctionAbstract $reflectionFunction, array $backtraceFrameArgs): array + { + $argumentValues = []; + + foreach ($reflectionFunction->getParameters() as $reflectionParameter) { + $parameterPosition = $reflectionParameter->getPosition(); + + if (!isset($backtraceFrameArgs[$parameterPosition])) { + continue; + } + + $argumentValues[$reflectionParameter->getName()] = $backtraceFrameArgs[$parameterPosition]; + } + + return $argumentValues; + } +} diff --git a/src/Integration/AbstractErrorListenerIntegration.php b/src/Integration/AbstractErrorListenerIntegration.php new file mode 100644 index 000000000..487933a09 --- /dev/null +++ b/src/Integration/AbstractErrorListenerIntegration.php @@ -0,0 +1,44 @@ +withScope(function (Scope $scope) use ($hub, $exception): void { + $scope->addEventProcessor(\Closure::fromCallable([$this, 'addExceptionMechanismToEvent'])); + + $hub->captureException($exception); + }); + } + + /** + * Adds the exception mechanism to the event. + * + * @param Event $event The event object + */ + protected function addExceptionMechanismToEvent(Event $event): Event + { + $exceptions = $event->getExceptions(); + + foreach ($exceptions as $exception) { + $exception->setMechanism(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false)); + } + + return $event; + } +} diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index 68a02455d..3dc2bfb0b 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -12,7 +12,7 @@ * This integration hooks into the global error handlers and emits events to * Sentry. */ -final class ErrorListenerIntegration implements IntegrationInterface +final class ErrorListenerIntegration extends AbstractErrorListenerIntegration { /** * {@inheritdoc} @@ -39,7 +39,7 @@ public function setupOnce(): void return; } - $currentHub->captureException($exception); + $integration->captureException($currentHub, $exception); }); } } diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index 606f161b1..4c3d2c386 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -11,7 +11,7 @@ * This integration hooks into the global error handlers and emits events to * Sentry. */ -final class ExceptionListenerIntegration implements IntegrationInterface +final class ExceptionListenerIntegration extends AbstractErrorListenerIntegration { /** * {@inheritdoc} @@ -29,7 +29,7 @@ public function setupOnce(): void return; } - $currentHub->captureException($exception); + $integration->captureException($currentHub, $exception); }); } } diff --git a/src/Integration/FatalErrorListenerIntegration.php b/src/Integration/FatalErrorListenerIntegration.php index 9434c8562..5c72341e3 100644 --- a/src/Integration/FatalErrorListenerIntegration.php +++ b/src/Integration/FatalErrorListenerIntegration.php @@ -13,7 +13,7 @@ * * @author Stefano Arlandini */ -final class FatalErrorListenerIntegration implements IntegrationInterface +final class FatalErrorListenerIntegration extends AbstractErrorListenerIntegration { /** * {@inheritdoc} @@ -36,7 +36,7 @@ public function setupOnce(): void return; } - $currentHub->captureException($exception); + $integration->captureException($currentHub, $exception); }); } } diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 68f2f7f9d..36fee0e78 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -60,11 +60,9 @@ public function setupOnce(): void } foreach ($event->getExceptions() as $exception) { - if (!isset($exception['stacktrace'])) { - continue; + if (null !== $exception->getStacktrace()) { + $integration->addContextToStacktraceFrames($maxContextLines, $exception->getStacktrace()); } - - $integration->addContextToStacktraceFrames($maxContextLines, $exception['stacktrace']); } return $event; diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index 497411817..f73c95c27 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -87,12 +87,12 @@ private function isIgnoredException(Event $event, array $options): bool { $exceptions = $event->getExceptions(); - if (empty($exceptions) || !isset($exceptions[0]['type'])) { + if (empty($exceptions)) { return false; } foreach ($options['ignore_exceptions'] as $ignoredException) { - if (is_a($exceptions[0]['type'], $ignoredException, true)) { + if (is_a($exceptions[0]->getType(), $ignoredException, true)) { return true; } } diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 939361be1..84198c66a 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -4,111 +4,38 @@ namespace Sentry; -use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\SerializerInterface; - /** * This class contains all the information about an error stacktrace. * * @author Stefano Arlandini - * - * @final since version 2.3 */ -class Stacktrace implements \JsonSerializable +final class Stacktrace implements \JsonSerializable { - private const INTERNAL_FRAME_FILENAME = '[internal]'; - - private const ANONYMOUS_CLASS_PREFIX = "class@anonymous\x00"; - - /** - * @var bool Flag indicating whether it's responsibility of this class to - * read the source code excerpts for each frame - * - * @internal - * - * @deprecated since version 2.4, to be removed in 3.0 - */ - protected $shouldReadSourceCodeExcerpts = false; - - /** - * @var Options The client options - */ - protected $options; - - /** - * @var SerializerInterface The serializer - * - * @deprecated since version 2.4, to be removed in 3.0 - */ - protected $serializer; - - /** - * @var RepresentationSerializerInterface The representation serializer - */ - protected $representationSerializer; - /** * @var Frame[] The frames that compose the stacktrace */ - protected $frames = []; - - /** - * @var string[] The list of functions to import a file - */ - protected static $importStatements = [ - 'include', - 'include_once', - 'require', - 'require_once', - ]; + private $frames = []; /** * Constructor. * - * @param Options $options The client options - * @param SerializerInterface $serializer The serializer - * @param RepresentationSerializerInterface $representationSerializer The representation serializer + * @param Frame[] $frames A non-empty list of stack frames. The list must be + * ordered from caller to callee. The last frame is the + * one creating the exception */ - public function __construct(Options $options, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer/*, bool $shouldReadSourceCodeExcerpts = true*/) + public function __construct(array $frames) { - if (\func_num_args() <= 3 || false !== func_get_arg(3)) { - @trigger_error(sprintf('Relying on the "%s" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead.', self::class), E_USER_DEPRECATED); - - $this->shouldReadSourceCodeExcerpts = true; + if (empty($frames)) { + throw new \InvalidArgumentException('Expected a non empty list of frames.'); } - $this->options = $options; - $this->serializer = $serializer; - $this->representationSerializer = $representationSerializer; - } - - /** - * Creates a new instance of this class from the given backtrace. - * - * @param Options $options The client options - * @param SerializerInterface $serializer The serializer - * @param RepresentationSerializerInterface $representationSerializer The representation serializer - * @param array[] $backtrace The backtrace - * @param string $file The file that originated the backtrace - * @param int $line The line at which the backtrace originated - * - * @return static - */ - public static function createFromBacktrace(Options $options, SerializerInterface $serializer, RepresentationSerializerInterface $representationSerializer, array $backtrace, string $file, int $line/*, bool $shouldReadSourceCodeExcerpts = true*/) - { - $stacktrace = new static($options, $serializer, $representationSerializer, \func_num_args() > 6 ? func_get_arg(6) : true); - - foreach ($backtrace as $frame) { - $stacktrace->addFrame($file, $line, $frame); - - $file = $frame['file'] ?? self::INTERNAL_FRAME_FILENAME; - $line = $frame['line'] ?? 0; + foreach ($frames as $frame) { + if (!$frame instanceof Frame) { + throw new \UnexpectedValueException(sprintf('Expected an instance of the "%s" class. Got: "%s".', Frame::class, get_debug_type($frame))); + } } - // Add a final stackframe for the first method ever of this stacktrace - $stacktrace->addFrame($file, $line, []); - - return $stacktrace; + $this->frames = $frames; } /** @@ -140,74 +67,10 @@ public function getFrame(int $index): Frame /** * Adds a new frame to the stacktrace. * - * @param string $file The file where the frame originated - * @param int $line The line at which the frame originated - * @param array $backtraceFrame The data of the frame to add + * @param Frame $frame The frame */ - public function addFrame(string $file, int $line, array $backtraceFrame): void + public function addFrame(Frame $frame): void { - // The $file argument can be any of these formats: - // - // () : eval()'d code - // () : runtime-created function - if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d code|runtime-created function)$/', $file, $matches)) { - $file = $matches[1]; - $line = (int) $matches[2]; - } - - if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { - if (0 === strpos($backtraceFrame['class'], self::ANONYMOUS_CLASS_PREFIX)) { - $backtraceFrame['class'] = self::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath(substr($backtraceFrame['class'], \strlen(self::ANONYMOUS_CLASS_PREFIX))); - } - - $functionName = sprintf( - '%s::%s', - preg_replace('/0x[a-fA-F0-9]+$/', '', $backtraceFrame['class']), - $backtraceFrame['function'] - ); - } elseif (isset($backtraceFrame['function'])) { - $functionName = $backtraceFrame['function']; - } else { - $functionName = null; - } - - $frameArguments = $this->getFrameArguments($backtraceFrame); - - foreach ($frameArguments as $argumentName => $argumentValue) { - $argumentValue = $this->representationSerializer->representationSerialize($argumentValue); - - if (\is_string($argumentValue)) { - $frameArguments[(string) $argumentName] = mb_substr($argumentValue, 0, $this->options->getMaxValueLength()); - } else { - $frameArguments[(string) $argumentName] = $argumentValue; - } - } - - $frame = new Frame( - $functionName, - $this->stripPrefixFromFilePath($file), - $line, - $file, - $frameArguments, - $this->isFrameInApp($file, $functionName) - ); - - if ($this->shouldReadSourceCodeExcerpts && null !== $this->options->getContextLines()) { - $sourceCodeExcerpt = $this->getSourceCodeExcerpt($file, $line, $this->options->getContextLines()); - - if (isset($sourceCodeExcerpt['pre_context'])) { - $frame->setPreContext($sourceCodeExcerpt['pre_context']); - } - - if (isset($sourceCodeExcerpt['context_line'])) { - $frame->setContextLine($sourceCodeExcerpt['context_line']); - } - - if (isset($sourceCodeExcerpt['post_context'])) { - $frame->setPostContext($sourceCodeExcerpt['post_context']); - } - } - array_unshift($this->frames, $frame); } @@ -221,7 +84,11 @@ public function addFrame(string $file, int $line, array $backtraceFrame): void public function removeFrame(int $index): void { if (!isset($this->frames[$index])) { - throw new \OutOfBoundsException('Invalid frame index to remove.'); + throw new \OutOfBoundsException(sprintf('Cannot remove the frame at index %d.', $index)); + } + + if (1 === \count($this->frames)) { + throw new \RuntimeException('Cannot remove all frames from the stacktrace.'); } array_splice($this->frames, $index, 1); @@ -231,274 +98,22 @@ public function removeFrame(int $index): void * Gets the stacktrace frames (this is the same as calling the getFrames * method). * - * @return Frame[] + * @return array */ public function toArray(): array { - return $this->frames; + return array_map(static function (Frame $frame): array { + return $frame->toArray(); + }, $this->frames); } /** * {@inheritdoc} * - * @return Frame[] + * @return array */ public function jsonSerialize() { return $this->toArray(); } - - /** - * Gets an excerpt of the source code around a given line. - * - * @param string $path The file path - * @param int $lineNumber The line to centre about - * @param int $maxLinesToFetch The maximum number of lines to fetch - * - * @return array - * - * @deprecated since version 2.4, to be removed in 3.0 - * - * @psalm-return array{ - * pre_context?: string[], - * context_line?: string, - * post_context?: string[] - * } - */ - protected function getSourceCodeExcerpt(string $path, int $lineNumber, int $maxLinesToFetch): array - { - if (@!is_readable($path) || !is_file($path)) { - return []; - } - - $frame = [ - 'pre_context' => [], - 'context_line' => '', - 'post_context' => [], - ]; - - $target = max(0, ($lineNumber - ($maxLinesToFetch + 1))); - $currentLineNumber = $target + 1; - - try { - $file = new \SplFileObject($path); - $file->seek($target); - - while (!$file->eof()) { - /** @var string $line */ - $line = $file->current(); - $line = rtrim($line, "\r\n"); - - if ($currentLineNumber == $lineNumber) { - $frame['context_line'] = $line; - } elseif ($currentLineNumber < $lineNumber) { - $frame['pre_context'][] = $line; - } elseif ($currentLineNumber > $lineNumber) { - $frame['post_context'][] = $line; - } - - ++$currentLineNumber; - - if ($currentLineNumber > $lineNumber + $maxLinesToFetch) { - break; - } - - $file->next(); - } - } catch (\Exception $exception) { - // Do nothing, if any error occurs while trying to get the excerpts - // it's not a drama - } - - return $frame; - } - - /** - * Removes from the given file path the specified prefixes. - * - * @param string $filePath The path to the file - */ - protected function stripPrefixFromFilePath(string $filePath): string - { - foreach ($this->options->getPrefixes() as $prefix) { - if (0 === mb_strpos($filePath, $prefix)) { - return mb_substr($filePath, mb_strlen($prefix)); - } - } - - return $filePath; - } - - /** - * Gets the values of the arguments of the given stackframe. - * - * @param array $frame The frame from where arguments are retrieved - * - * @return array - */ - protected function getFrameArgumentsValues(array $frame): array - { - if (empty($frame['args'])) { - return []; - } - - $result = []; - - if (\is_string(array_keys($frame['args'])[0])) { - $result = array_map([$this, 'serializeArgument'], $frame['args']); - } else { - $index = 0; - foreach (array_values($frame['args']) as $argument) { - $result['param' . (++$index)] = $this->serializeArgument($argument); - } - } - - return $result; - } - - /** - * Gets the arguments of the given stackframe. - * - * @param array $frame The frame from where arguments are retrieved - * - * @return array - */ - public function getFrameArguments(array $frame): array - { - if (!isset($frame['args'])) { - return []; - } - - // The Reflection API seems more appropriate if we associate it with the frame - // where the function is actually called (since we're treating them as function context) - if (!isset($frame['function'])) { - return $this->getFrameArgumentsValues($frame); - } - - if (false !== strpos($frame['function'], '__lambda_func')) { - return $this->getFrameArgumentsValues($frame); - } - - if (false !== strpos($frame['function'], '{closure}')) { - return $this->getFrameArgumentsValues($frame); - } - - if (isset($frame['class']) && 'Closure' === $frame['class']) { - return $this->getFrameArgumentsValues($frame); - } - - if (\in_array($frame['function'], static::$importStatements, true)) { - if (empty($frame['args'])) { - return []; - } - - return [ - 'param1' => $this->serializeArgument($frame['args'][0]), - ]; - } - - try { - if (isset($frame['class'])) { - if (method_exists($frame['class'], $frame['function'])) { - $reflection = new \ReflectionMethod($frame['class'], $frame['function']); - } elseif ('::' === $frame['type']) { - $reflection = new \ReflectionMethod($frame['class'], '__callStatic'); - } else { - $reflection = new \ReflectionMethod($frame['class'], '__call'); - } - } elseif (\function_exists($frame['function'])) { - $reflection = new \ReflectionFunction($frame['function']); - } else { - return $this->getFrameArgumentsValues($frame); - } - } catch (\ReflectionException $ex) { - return $this->getFrameArgumentsValues($frame); - } - - $params = $reflection->getParameters(); - $args = []; - - foreach ($frame['args'] as $index => $arg) { - $arg = $this->serializeArgument($arg); - - if (isset($params[$index])) { - // Assign the argument by the parameter name - $args[$params[$index]->getName()] = $arg; - } else { - $args['param' . $index] = $arg; - } - } - - return $args; - } - - /** - * Serializes the given argument. - * - * @param mixed $arg The argument to serialize - * - * @return mixed - */ - protected function serializeArgument($arg) - { - $maxValueLength = $this->options->getMaxValueLength(); - - if (\is_array($arg)) { - $result = []; - - foreach ($arg as $key => $value) { - if (\is_string($value) || is_numeric($value)) { - $result[$key] = mb_substr((string) $value, 0, $maxValueLength); - } else { - $result[$key] = $value; - } - } - - return $result; - } elseif (\is_string($arg) || is_numeric($arg)) { - return mb_substr((string) $arg, 0, $maxValueLength); - } else { - return $arg; - } - } - - /** - * Checks whether a certain frame should be marked as "in app" or not. - * - * @param string $file The file to check - * @param string|null $functionName The name of the function - */ - private function isFrameInApp(string $file, ?string $functionName): bool - { - if (self::INTERNAL_FRAME_FILENAME === $file) { - return false; - } - - if (null !== $functionName && 0 === strpos($functionName, 'Sentry\\')) { - return false; - } - - $excludedAppPaths = $this->options->getInAppExcludedPaths(); - $includedAppPaths = $this->options->getInAppIncludedPaths(); - $absoluteFilePath = @realpath($file) ?: $file; - $isInApp = true; - - foreach ($excludedAppPaths as $excludedAppPath) { - if (0 === mb_strpos($absoluteFilePath, $excludedAppPath)) { - $isInApp = false; - - break; - } - } - - foreach ($includedAppPaths as $includedAppPath) { - if (0 === mb_strpos($absoluteFilePath, $includedAppPath)) { - $isInApp = true; - - break; - } - } - - return $isInApp; - } } diff --git a/src/StacktraceBuilder.php b/src/StacktraceBuilder.php new file mode 100644 index 000000000..fc4b66c8d --- /dev/null +++ b/src/StacktraceBuilder.php @@ -0,0 +1,77 @@ +options = $options; + $this->frameBuilder = new FrameBuilder($options, $representationSerializer); + } + + /** + * Builds a {@see Stacktrace} object from the given exception. + * + * @param \Throwable $exception The exception object + */ + public function buildFromException(\Throwable $exception): Stacktrace + { + return $this->buildFromBacktrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); + } + + /** + * Builds a {@see Stacktrace} object from the given backtrace. + * + * @param array> $backtrace The backtrace + * @param string $file The file where the backtrace originated from + * @param int $line The line from which the backtrace originated from + * + * @phpstan-param list $backtrace + */ + public function buildFromBacktrace(array $backtrace, string $file, int $line): Stacktrace + { + $frames = []; + + foreach ($backtrace as $backtraceFrame) { + array_unshift($frames, $this->frameBuilder->buildFromBacktraceFrame($file, $line, $backtraceFrame)); + + $file = $backtraceFrame['file'] ?? Frame::INTERNAL_FRAME_FILENAME; + $line = $backtraceFrame['line'] ?? 0; + } + + // Add a final stackframe for the first method ever of this stacktrace + array_unshift($frames, new Frame(null, $file, $line)); + + return new Stacktrace($frames); + } +} diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 885d0e606..e412358e9 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -72,7 +72,7 @@ class SpanContext */ public static function fromTraceparent(string $header) { - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore-next-line */ /** @psalm-suppress UnsafeInstantiation */ $context = new static(); if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 963266f4e..d0d230889 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -11,6 +11,7 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\EventId; +use Sentry\Frame; use Sentry\Options; use Sentry\Severity; use Sentry\Stacktrace; @@ -53,8 +54,8 @@ public function testCaptureException(): void $exceptionData = $event->getExceptions()[0]; - $this->assertSame(\get_class($exception), $exceptionData['type']); - $this->assertSame($exception->getMessage(), $exceptionData['value']); + $this->assertSame(\get_class($exception), $exceptionData->getType()); + $this->assertSame($exception->getMessage(), $exceptionData->getValue()); return true; })); @@ -157,15 +158,16 @@ public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionD public function testCaptureEventPrefersExplicitStacktrace(): void { $eventId = EventId::generate(); - $explicitStacktrace = $this->createMock(Stacktrace::class); - $payload = ['stacktrace' => $explicitStacktrace]; + $stacktrace = new Stacktrace([ + new Frame(__METHOD__, __FILE__, __LINE__), + ]); /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') - ->with($this->callback(static function (Event $event) use ($explicitStacktrace): bool { - return $explicitStacktrace === $event->getStacktrace(); + ->with($this->callback(static function (Event $event) use ($stacktrace): bool { + return $stacktrace === $event->getStacktrace(); })) ->willReturn($eventId); @@ -173,7 +175,7 @@ public function testCaptureEventPrefersExplicitStacktrace(): void ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertEquals($eventId, $client->captureEvent($payload)); + $this->assertEquals($eventId, $client->captureEvent(['stacktrace' => $stacktrace])); } public function testCaptureLastError(): void @@ -187,8 +189,8 @@ public function testCaptureLastError(): void ->with($this->callback(function (Event $event): bool { $exception = $event->getExceptions()[0]; - $this->assertEquals('ErrorException', $exception['type']); - $this->assertEquals('foo', $exception['value']); + $this->assertEquals('ErrorException', $exception->getType()); + $this->assertEquals('foo', $exception->getValue()); return true; })) @@ -223,27 +225,6 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void $this->assertNull($client->captureLastError()); } - /** - * @group legacy - * - * @dataProvider captureEventThrowsDeprecationErrorIfContextLinesOptionIsNotNullAndFrameContextifierIntegrationIsNotUsedDataProvider - * - * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. - */ - public function testCaptureEventThrowsDeprecationErrorIfContextLinesOptionIsNotNullAndFrameContextifierIntegrationIsNotUsed(array $payload): void - { - ClientBuilder::create(['attach_stacktrace' => true, 'default_integrations' => false]) - ->getClient() - ->captureEvent($payload); - } - - public function captureEventThrowsDeprecationErrorIfContextLinesOptionIsNotNullAndFrameContextifierIntegrationIsNotUsedDataProvider(): \Generator - { - yield [[]]; - - yield [['exception' => new \Exception()]]; - } - public function testSendChecksBeforeSendOption(): void { $beforeSendCalled = false; @@ -359,69 +340,6 @@ public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): vo $client->captureMessage('foo', Severity::debug(), $scope); } - /** - * @dataProvider convertExceptionDataProvider - */ - public function testConvertException(\Exception $exception, array $expectedResult): void - { - /** @var TransportInterface&MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event) use ($expectedResult): bool { - $this->assertArraySubset($expectedResult, $event->toArray()); - $this->assertArrayNotHasKey('values', $event->getExceptions()); - $this->assertArrayHasKey('values', $event->toArray()['exception']); - - foreach ($event->getExceptions() as $exceptionData) { - $this->assertArrayHasKey('stacktrace', $exceptionData); - $this->assertInstanceOf(Stacktrace::class, $exceptionData['stacktrace']); - } - - return true; - })); - - $client = ClientBuilder::create() - ->setTransportFactory($this->createTransportFactory($transport)) - ->getClient(); - - $client->captureException($exception); - } - - public function convertExceptionDataProvider(): array - { - return [ - [ - new \RuntimeException('foo'), - [ - 'level' => Severity::ERROR, - 'exception' => [ - 'values' => [ - [ - 'type' => \RuntimeException::class, - 'value' => 'foo', - ], - ], - ], - ], - ], - [ - new \ErrorException('foo', 0, E_USER_WARNING), - [ - 'level' => Severity::WARNING, - 'exception' => [ - 'values' => [ - [ - 'type' => \ErrorException::class, - 'value' => 'foo', - ], - ], - ], - ], - ], - ]; - } - public function testAttachStacktrace(): void { $eventId = EventId::generate(); diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index bd767299e..44e6a5af5 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Sentry\EventFactory; +use Sentry\ExceptionMechanism; use Sentry\Frame; use Sentry\Options; use Sentry\Serializer\RepresentationSerializerInterface; @@ -133,23 +134,19 @@ public function testCreateWithException(): void '1.2.3' ); - $event = $eventFactory->create(['exception' => $exception], false); - $expectedData = [ - [ - 'type' => \Exception::class, - 'value' => 'testMessage', - ], - [ - 'type' => \RuntimeException::class, - 'value' => 'testMessage2', - ], - ]; + $event = $eventFactory->create(['exception' => $exception]); + $capturedExceptions = $event->getExceptions(); - $this->assertArraySubset($expectedData, $event->getExceptions()); + $this->assertCount(2, $capturedExceptions); + $this->assertNotNull($capturedExceptions[0]->getStacktrace()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), $capturedExceptions[0]->getMechanism()); + $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); + $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); - foreach ($event->getExceptions() as $exceptionData) { - $this->assertInstanceOf(Stacktrace::class, $exceptionData['stacktrace']); - } + $this->assertNotNull($capturedExceptions[1]->getStacktrace()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), $capturedExceptions[1]->getMechanism()); + $this->assertSame(\RuntimeException::class, $capturedExceptions[1]->getType()); + $this->assertSame('testMessage2', $capturedExceptions[1]->getValue()); } public function testCreateWithErrorException(): void @@ -164,7 +161,7 @@ public function testCreateWithErrorException(): void '1.2.3' ); - $event = $eventFactory->create(['exception' => $exception], false); + $event = $eventFactory->create(['exception' => $exception]); $this->assertTrue(Severity::error()->isEqualTo($event->getLevel())); } @@ -182,7 +179,7 @@ public function testCreateWithStacktrace(): void '1.2.3' ); - $event = $eventFactory->createWithStacktrace([], false); + $event = $eventFactory->createWithStacktrace([]); $stacktrace = $event->getStacktrace(); $this->assertInstanceOf(Stacktrace::class, $stacktrace); @@ -196,50 +193,6 @@ public function testCreateWithStacktrace(): void ); } - /** - * @group legacy - * - * @dataProvider createThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider - * - * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. - */ - public function testCreateThrowsDeprecationErrorIfLastArgumentIsNotSetToFalse(array ...$constructorArguments): void - { - $options = new Options(); - $eventFactory = new EventFactory( - new Serializer($options), - $this->createMock(RepresentationSerializerInterface::class), - $options, - 'sentry.sdk.identifier', - '1.2.3', - ...$constructorArguments - ); - - $eventFactory->create(['exception' => new \Exception()]); - } - - /** - * @group legacy - * - * @dataProvider createThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider - * - * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. - */ - public function testCreateWithStacktraceThrowsDeprecationErrorIfLastArgumentIsNotSetToFalse(array ...$constructorArguments): void - { - $options = new Options(); - $eventFactory = new EventFactory( - new Serializer($options), - $this->createMock(RepresentationSerializerInterface::class), - $options, - 'sentry.sdk.identifier', - '1.2.3', - ...$constructorArguments - ); - - $eventFactory->createWithStacktrace([]); - } - public function createThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider(): \Generator { yield [[true]]; diff --git a/tests/EventTest.php b/tests/EventTest.php index bc91f73e3..a10264694 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -10,7 +10,6 @@ use Sentry\Client; use Sentry\Event; use Sentry\Severity; -use Sentry\Stacktrace; use Sentry\Util\PHPVersion; /** @@ -252,20 +251,6 @@ public function gettersAndSettersDataProvider(): array ]; } - public function testSetStacktrace(): void - { - $stacktrace = $this->createMock(Stacktrace::class); - - $event = new Event(); - $event->setStacktrace($stacktrace); - - $this->assertSame($stacktrace, $event->getStacktrace()); - - $event->setStacktrace(null); - - $this->assertNull($event->getStacktrace()); - } - public function testEventJsonSerialization(): void { $event = new Event(); diff --git a/tests/Fixtures/backtraces/anonymous_frame.json b/tests/Fixtures/backtraces/anonymous_frame.json deleted file mode 100644 index 2f450a5db..000000000 --- a/tests/Fixtures/backtraces/anonymous_frame.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "file": "path/to/file", - "line": 12, - "backtrace": [ - { - "function": "triggerError", - "class": "TestClass" - }, - { - "file": "path/to/file", - "line": 7, - "function": "call_user_func" - } - ] -} diff --git a/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json b/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json deleted file mode 100644 index 6bddf4619..000000000 --- a/tests/Fixtures/backtraces/anonymous_frame_with_memory_address.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "file": "path/to/file", - "line": 12, - "backtrace": [ - { - "class": "class@anonymous\u0000/path/to/app/consumer.php0x7fc3bc369418", - "function": "messageCallback", - "type": "->" - }, - { - "class": "class@anonymous\u0000/path-prefix/path/to/app/consumer.php0x7fc3bc369418", - "function": "messageCallback", - "type": "->" - } - ] -} diff --git a/tests/Fixtures/backtraces/exception.json b/tests/Fixtures/backtraces/exception.json deleted file mode 100644 index f371c73e5..000000000 --- a/tests/Fixtures/backtraces/exception.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "file": "path/to/file", - "line": 12, - "backtrace": [ - { - "file": "path/to/file", - "function": "triggerError", - "line": 7, - "class": "TestClass" - }, - { - "file": "path/to/file", - "line": 16, - "class": "TestClass", - "function": "crashyFunction" - } - ] -} diff --git a/tests/Fixtures/frames/eval.json b/tests/Fixtures/frames/eval.json deleted file mode 100644 index cc3932f29..000000000 --- a/tests/Fixtures/frames/eval.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "file": "path/to/file(12) : eval()'d code", - "line": 12, - "function": "test_function" -} diff --git a/tests/Fixtures/frames/function.json b/tests/Fixtures/frames/function.json deleted file mode 100644 index a90611af2..000000000 --- a/tests/Fixtures/frames/function.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "file": "path/to/file", - "line": 12, - "function": "test_function", - "class": "TestClass" -} diff --git a/tests/Fixtures/frames/missing_function_key.json b/tests/Fixtures/frames/missing_function_key.json deleted file mode 100644 index 898b752e7..000000000 --- a/tests/Fixtures/frames/missing_function_key.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "file": "path/to/file", - "line": 12, - "class": "TestClass" -} diff --git a/tests/Fixtures/frames/runtime_created.json b/tests/Fixtures/frames/runtime_created.json deleted file mode 100644 index 24d2c0500..000000000 --- a/tests/Fixtures/frames/runtime_created.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "file": "path/to/file(12) : runtime-created function", - "line": 12, - "function": "test_function" -} diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php new file mode 100644 index 000000000..e219798d5 --- /dev/null +++ b/tests/FrameBuilderTest.php @@ -0,0 +1,260 @@ +buildFromBacktraceFrame($backtraceFrame['file'], $backtraceFrame['line'], $backtraceFrame); + + $this->assertSame($expectedFrame->getFunctionName(), $frame->getFunctionName()); + $this->assertSame($expectedFrame->getRawFunctionName(), $frame->getRawFunctionName()); + $this->assertSame($expectedFrame->getFile(), $frame->getFile()); + $this->assertSame($expectedFrame->getLine(), $frame->getLine()); + $this->assertSame($expectedFrame->getAbsoluteFilePath(), $frame->getAbsoluteFilePath()); + } + + public function buildFromBacktraceFrameDataProvider(): \Generator + { + yield [ + new Options([]), + [ + 'file' => '/path/to/file(10) : eval()\'d code', + 'line' => 20, + 'function' => 'test_function', + ], + new Frame('test_function', '/path/to/file', 10), + ]; + + yield [ + new Options([]), + [ + 'file' => '/path/to/file(10) : runtime-created function', + 'line' => 20, + 'function' => 'test_function', + ], + new Frame('test_function', '/path/to/file', 10), + ]; + + yield [ + new Options([]), + [ + 'file' => '/path/to/file', + 'line' => 10, + 'function' => 'test_function', + 'class' => 'TestClass', + ], + new Frame('TestClass::test_function', '/path/to/file', 10, 'TestClass::test_function'), + ]; + + yield [ + new Options([]), + [ + 'file' => '/path/to/file', + 'line' => 10, + 'function' => 'test_function', + ], + new Frame('test_function', '/path/to/file', 10), + ]; + + yield [ + new Options([]), + [ + 'file' => '/path/to/file', + 'line' => 10, + 'function' => 'test_function', + 'class' => "class@anonymous\0/path/to/file", + ], + new Frame("class@anonymous\0/path/to/file::test_function", '/path/to/file', 10, "class@anonymous\0/path/to/file::test_function"), + ]; + + yield [ + new Options([ + 'prefixes' => ['/path/to'], + ]), + [ + 'file' => '/path/to/file', + 'line' => 10, + 'function' => 'test_function', + 'class' => "class@anonymous\0/path/to/file", + ], + new Frame("class@anonymous\0/file::test_function", '/file', 10, "class@anonymous\0/path/to/file::test_function", '/path/to/file'), + ]; + + yield [ + new Options([ + 'prefixes' => [ + '/path/to', + '/path/to/app', + ], + ]), + [ + 'file' => '/path/to/app/file', + 'line' => 10, + ], + new Frame(null, '/app/file', 10, null, '/path/to/app/file'), + ]; + + yield [ + new Options([ + 'prefixes' => [ + '/path/to', + '/path/to/app', + ], + ]), + [ + 'file' => '/path/to/file', + 'line' => 10, + ], + new Frame(null, '/file', 10, null, '/path/to/file'), + ]; + + yield [ + new Options([ + 'prefixes' => [ + '/path/to', + '/path/to/app', + ], + ]), + [ + 'file' => 'path/not/of/app/path/to/file', + 'line' => 10, + ], + new Frame(null, 'path/not/of/app/path/to/file', 10, null, 'path/not/of/app/path/to/file'), + ]; + + yield [ + new Options([ + 'prefixes' => [ + '/path/to', + '/path/to/app', + ], + ]), + [ + 'file' => 'path/not/of/app/to/file', + 'line' => 10, + ], + new Frame(null, 'path/not/of/app/to/file', 10, null, 'path/not/of/app/to/file'), + ]; + } + + /** + * @dataProvider addFrameSetsInAppFlagCorrectlyDataProvider + */ + public function testAddFrameSetsInAppFlagCorrectly(Options $options, string $file, bool $expectedResult): void + { + $stacktraceBuilder = new FrameBuilder($options, new RepresentationSerializer($options)); + $frame = $stacktraceBuilder->buildFromBacktraceFrame($file, __LINE__, []); + + $this->assertSame($expectedResult, $frame->isInApp()); + } + + public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator + { + yield 'No config specified' => [ + new Options([ + 'in_app_exclude' => [], + 'in_app_include' => [], + ]), + '[internal]', + false, + ]; + + yield 'in_app_include specified && file path not matching' => [ + new Options([ + 'in_app_exclude' => [], + 'in_app_include' => [ + 'path/to/nested/file', + ], + ]), + 'path/to/file', + true, + ]; + + yield 'in_app_include not specified && file path not matching' => [ + new Options([ + 'in_app_exclude' => [], + 'in_app_include' => [], + ]), + 'path/to/file', + true, + ]; + + yield 'in_app_include specified && file path matching' => [ + new Options([ + 'in_app_exclude' => [], + 'in_app_include' => [ + 'path/to/nested/file', + 'path/to/file', + ], + ]), + 'path/to/file', + true, + ]; + + yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_include' => [ + new Options([ + 'in_app_exclude' => [ + 'path/to/nested/file', + ], + 'in_app_include' => [ + 'path/to/file', + ], + ]), + 'path/to/file', + true, + ]; + + yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_exclude' => [ + new Options([ + 'in_app_exclude' => [ + 'path/to/nested/file', + ], + 'in_app_include' => [ + 'path/to/file', + ], + ]), + 'path/to/nested/file', + false, + ]; + + yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_include && in_app_include prioritized over in_app_exclude' => [ + new Options([ + 'in_app_exclude' => [ + 'path/to/file', + ], + 'in_app_include' => [ + 'path/to/file/nested', + ], + ]), + 'path/to/file/nested', + true, + ]; + + yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_exclude && in_app_exclude prioritized over in_app_include' => [ + new Options([ + 'in_app_exclude' => [ + 'path/to/file', + ], + 'in_app_include' => [ + 'path/to/file/nested', + ], + ]), + 'path/to/file', + false, + ]; + } +} diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index badbc7e5c..e8ddded9d 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -9,6 +9,7 @@ use Psr\Log\LoggerInterface; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\Frame; use Sentry\Integration\FrameContextifierIntegration; use Sentry\Options; use Sentry\SentrySdk; @@ -60,8 +61,9 @@ public function testInvoke(string $fixtureFilePath, int $lineNumber, int $contex SentrySdk::getCurrentHub()->bindClient($client); - $stacktrace = new Stacktrace($options, $this->serializer, $this->representationSerializer, false); - $stacktrace->addFrame($fixtureFilePath, $lineNumber, ['function' => '[unknown]']); + $stacktrace = new Stacktrace([ + new Frame('[unknown]', $fixtureFilePath, $lineNumber), + ]); $event = new Event(); $event->setStacktrace($stacktrace); @@ -152,9 +154,10 @@ public function testInvokeLogsWarningMessageIfSourceCodeExcerptCannotBeRetrieved SentrySdk::getCurrentHub()->bindClient($client); - $stacktrace = new Stacktrace(new Options(), $this->serializer, $this->representationSerializer, false); - $stacktrace->addFrame('[internal]', 0, []); - $stacktrace->addFrame('file.ext', 10, []); + $stacktrace = new Stacktrace([ + new Frame(null, '[internal]', 0), + new Frame(null, 'file.ext', 10), + ]); $event = new Event(); $event->setStacktrace($stacktrace); diff --git a/tests/Integration/IgnoreErrorsIntegrationTest.php b/tests/Integration/IgnoreErrorsIntegrationTest.php index d8b2e648d..88b58bfe1 100644 --- a/tests/Integration/IgnoreErrorsIntegrationTest.php +++ b/tests/Integration/IgnoreErrorsIntegrationTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\ExceptionDataBag; use Sentry\Integration\IgnoreErrorsIntegration; use Sentry\SentrySdk; use Sentry\State\Scope; @@ -45,9 +46,7 @@ public function testInvoke(Event $event, bool $isIntegrationEnabled, array $inte public function invokeDataProvider(): \Generator { $event = new Event(); - $event->setExceptions([ - ['type' => \RuntimeException::class], - ]); + $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); yield 'Integration disabled' => [ new Event(), @@ -59,9 +58,7 @@ public function invokeDataProvider(): \Generator ]; $event = new Event(); - $event->setExceptions([ - ['type' => \RuntimeException::class], - ]); + $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); yield 'No exceptions to check' => [ new Event(), @@ -73,9 +70,7 @@ public function invokeDataProvider(): \Generator ]; $event = new Event(); - $event->setExceptions([ - ['type' => \RuntimeException::class], - ]); + $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); yield 'The exception is matching exactly the "ignore_exceptions" option' => [ $event, @@ -89,9 +84,7 @@ public function invokeDataProvider(): \Generator ]; $event = new Event(); - $event->setExceptions([ - ['type' => \RuntimeException::class], - ]); + $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); yield 'The exception is matching the "ignore_exceptions" option' => [ $event, diff --git a/tests/StacktraceBuilderTest.php b/tests/StacktraceBuilderTest.php new file mode 100644 index 000000000..1e8f0dccf --- /dev/null +++ b/tests/StacktraceBuilderTest.php @@ -0,0 +1,51 @@ + false]); + $representationSerializer = new RepresentationSerializer($options); + $stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer); + $backtrace = [ + [ + 'file' => '/in/jXVmi', + 'line' => 9, + 'function' => 'main', + 'args' => [], + ], + [ + 'file' => '/in/jXVmi', + 'line' => 5, + 'function' => '{closure}', + 'args' => [], + ], + ]; + + $stacktrace = $stacktraceBuilder->buildFromBacktrace($backtrace, __FILE__, $expectedLine = __LINE__); + $frames = $stacktrace->getFrames(); + + $this->assertCount(3, $frames); + + $this->assertNull($frames[0]->getFunctionName()); + $this->assertSame('/in/jXVmi', $frames[0]->getFile()); + $this->assertSame(5, $frames[0]->getLine()); + + $this->assertSame('{closure}', $frames[1]->getFunctionName()); + $this->assertSame('/in/jXVmi', $frames[1]->getFile()); + $this->assertSame(9, $frames[1]->getLine()); + + $this->assertSame('main', $frames[2]->getFunctionName()); + $this->assertSame(__FILE__, $frames[2]->getAbsoluteFilePath()); + $this->assertSame($expectedLine, $frames[2]->getLine()); + } +} diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 846ac00b3..0b39d335f 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -8,67 +8,59 @@ use Sentry\Frame; use Sentry\Options; use Sentry\Serializer\RepresentationSerializer; -use Sentry\Serializer\Serializer; use Sentry\Stacktrace; +use Sentry\StacktraceBuilder; final class StacktraceTest extends TestCase { - /** - * @var Options - */ - private $options; - - /** - * @var Serializer - */ - private $serializer; - - /** - * @var RepresentationSerializer - */ - private $representationSerializer; - - protected function setUp(): void + public function testConstructorThrowsIfFramesListIsEmpty(): void { - $this->options = new Options(); - $this->serializer = new Serializer($this->options); - $this->representationSerializer = new RepresentationSerializer($this->options); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Expected a non empty list of frames.'); + + new Stacktrace([]); } /** - * @group legacy - * - * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. + * @dataProvider constructorThrowsIfFramesListContainsUnexpectedValueDataProvider */ - public function testConstructorThrowsDeprecationErrorIfLastArgumentIsNotSetToFalse(): void + public function testConstructorThrowsIfFramesListContainsUnexpectedValue(array $values, string $expectedExceptionMessage): void { - new Stacktrace($this->options, $this->serializer, $this->representationSerializer); + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessageRegExp($expectedExceptionMessage); + + new Stacktrace($values); } - public function testGetFramesAndToArray(): void + public function constructorThrowsIfFramesListContainsUnexpectedValueDataProvider(): \Generator { - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); - - $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'class' => 'TestClass']); - $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function']); - $stacktrace->addFrame('path/to/file', 3, ['file' => 'path/to/file', 'line' => 3, 'function' => 'test_function', 'class' => 'TestClass']); + yield [ + [ + new Frame(__FUNCTION__, __FILE__, __LINE__), + 10, + ], + '/^Expected an instance of the "Sentry\\\\Frame" class\. Got: "int"\.$/', + ]; - $frames = $stacktrace->getFrames(); + yield [ + [(object) []], + '/^Expected an instance of the "Sentry\\\\Frame" class\. Got: "stdClass"\.$/', + ]; - $this->assertCount(3, $frames); - $this->assertEquals($frames, $stacktrace->toArray()); - $this->assertFrameEquals($frames[0], 'TestClass::test_function', 'path/to/file', 3); - $this->assertFrameEquals($frames[1], 'test_function', 'path/to/file', 2); - $this->assertFrameEquals($frames[2], null, 'path/to/file', 1); + yield [ + [new class() { + }], + '/^Expected an instance of the "Sentry\\\\Frame" class\. Got: "class@anonymous.*"\.$/', + ]; } public function testStacktraceJsonSerialization(): void { - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); - - $stacktrace->addFrame('path/to/file', 1, ['file' => 'path/to/file', 'line' => 1, 'function' => 'test_function']); - $stacktrace->addFrame('path/to/file', 2, ['file' => 'path/to/file', 'line' => 2, 'function' => 'test_function', 'class' => 'TestClass']); - $stacktrace->addFrame('path/to/file', 3, ['file' => 'path/to/file', 'line' => 3, 'class' => 'TestClass']); + $stacktrace = new Stacktrace([ + new Frame('test_function', 'path/to/file', 1), + new Frame('TestClass::test_function', 'path/to/file', 2), + new Frame(null, 'path/to/file', 3), + ]); $frames = json_encode($stacktrace->getFrames()); $serializedStacktrace = json_encode($stacktrace); @@ -80,470 +72,191 @@ public function testStacktraceJsonSerialization(): void public function testAddFrame(): void { - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); - $frames = [ - $this->getJsonFixture('frames/eval.json'), - $this->getJsonFixture('frames/runtime_created.json'), - $this->getJsonFixture('frames/function.json'), - $this->getJsonFixture('frames/missing_function_key.json'), - ]; + $stacktrace = new Stacktrace([ + new Frame('function_1', 'path/to/file_1', 10), + ]); - foreach ($frames as $frame) { - $stacktrace->addFrame($frame['file'], $frame['line'], $frame); - } + $stacktrace->addFrame(new Frame('function_2', 'path/to/file_2', 20)); $frames = $stacktrace->getFrames(); - $this->assertCount(4, $frames); - $this->assertFrameEquals($frames[0], null, 'path/to/file', 12); - $this->assertFrameEquals($frames[1], 'TestClass::test_function', 'path/to/file', 12); - $this->assertFrameEquals($frames[2], 'test_function', 'path/to/file', 12); - $this->assertFrameEquals($frames[3], 'test_function', 'path/to/file', 12); + $this->assertCount(2, $frames); + $this->assertFrameEquals($frames[0], 'function_2', 'path/to/file_2', 20); + $this->assertFrameEquals($frames[1], 'function_1', 'path/to/file_1', 10); } - public function testAddFrameSerializesMethodArguments(): void + /** + * @dataProvider removeFrameDataProvider + */ + public function testRemoveFrame(int $index, ?string $expectedExceptionMessage): void { - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); - $stacktrace->addFrame('path/to/file', 12, [ - 'file' => 'path/to/file', - 'line' => 12, - 'function' => 'test_function', - 'args' => [1, 'foo'], + if (null !== $expectedExceptionMessage) { + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $stacktrace = new Stacktrace([ + new Frame('test_function', 'path/to/file', 12), + new Frame('test_function_parent', 'path/to/file', 12), ]); + $this->assertCount(2, $stacktrace->getFrames()); + + $stacktrace->removeFrame($index); + $frames = $stacktrace->getFrames(); $this->assertCount(1, $frames); - $this->assertFrameEquals($frames[0], 'test_function', 'path/to/file', 12); - $this->assertEquals(['param1' => 1, 'param2' => 'foo'], $frames[0]->getVars()); + $this->assertFrameEquals($frames[0], 'test_function_parent', 'path/to/file', 12); } - public function testAddFrameStripsPath(): void + public function removeFrameDataProvider(): \Generator { - $this->options->setPrefixes(['path/to/', 'path/to/app']); - - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); - - $stacktrace->addFrame('path/to/app/file', 12, ['function' => 'test_function_parent_parent_parent']); - $stacktrace->addFrame('path/to/file', 12, ['function' => 'test_function_parent_parent']); - $stacktrace->addFrame('path/not/of/app/path/to/file', 12, ['function' => 'test_function_parent']); - $stacktrace->addFrame('path/not/of/app/to/file', 12, ['function' => 'test_function']); + yield [ + -1, + 'Cannot remove the frame at index -1.', + ]; - $frames = $stacktrace->getFrames(); + yield [ + 2, + 'Cannot remove the frame at index 2.', + ]; - $this->assertFrameEquals($frames[0], 'test_function', 'path/not/of/app/to/file', 12); - $this->assertFrameEquals($frames[1], 'test_function_parent', 'path/not/of/app/path/to/file', 12); - $this->assertFrameEquals($frames[2], 'test_function_parent_parent', 'file', 12); - $this->assertFrameEquals($frames[3], 'test_function_parent_parent_parent', 'app/file', 12); + yield [ + 0, + null, + ]; } /** - * @group legacy - * - * @dataProvider addFrameSetsInAppFlagCorrectlyDataProvider + * @dataProvider buildFromBacktraceDataProvider */ - public function testAddFrameSetsInAppFlagCorrectly(array $options, string $file, string $functionName, bool $expectedResult): void + public function testCreateFromBacktrace(Options $options, array $backtrace, array $expectedFramesData): void { - $options = new Options($options); - $stacktrace = new Stacktrace( - $options, - new Serializer($options), - new RepresentationSerializer($options), - false - ); - - $stacktrace->addFrame($file, 0, ['function' => $functionName]); + $stacktraceBuilder = new StacktraceBuilder($options, new RepresentationSerializer($options)); + $stacktrace = $stacktraceBuilder->buildFromBacktrace($backtrace['backtrace'], $backtrace['file'], $backtrace['line']); + $frames = $stacktrace->getFrames(); - $this->assertSame($expectedResult, $stacktrace->getFrame(0)->isInApp()); + for ($i = 0, $count = \count($frames); $i < $count; ++$i) { + $this->assertFrameEquals($frames[$i], $expectedFramesData[$i][0], $expectedFramesData[$i][1], $expectedFramesData[$i][2]); + } } - public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator + public function buildFromBacktraceDataProvider(): \Generator { - yield 'No config specified' => [ + yield 'Plain backtrace' => [ + new Options(), [ - 'in_app_exclude' => [], - 'in_app_include' => [], + 'file' => 'path/to/file', + 'line' => 12, + 'backtrace' => [ + [ + 'file' => 'path/to/file', + 'function' => 'triggerError', + 'line' => 7, + 'class' => 'TestClass', + ], + [ + 'file' => 'path/to/file', + 'line' => 16, + 'class' => 'TestClass', + 'function' => 'crashyFunction', + ], + ], ], - '[internal]', - 'test_function', - false, - ]; - - yield 'in_app_include specified && file path not matching' => [ [ - 'in_app_exclude' => [], - 'in_app_include' => [ - 'path/to/nested/file', + [ + null, + 'path/to/file', + 16, + ], + [ + 'TestClass::crashyFunction', + 'path/to/file', + 7, + ], + [ + 'TestClass::triggerError', + 'path/to/file', + 12, ], ], - 'path/to/file', - 'test_function', - true, ]; - yield 'in_app_include not specified && file path not matching' => [ + yield 'Backtrace containing anonymous frame' => [ + new Options(), [ - 'in_app_exclude' => [], - 'in_app_include' => [], + 'file' => 'path/to/file', + 'line' => 12, + 'backtrace' => [ + [ + 'function' => 'triggerError', + 'class' => 'TestClass', + ], + [ + 'file' => 'path/to/file', + 'line' => 7, + 'function' => 'call_user_func', + ], + ], ], - 'path/to/file', - 'test_function', - true, - ]; - - yield 'in_app_include specified && file path matching' => [ [ - 'in_app_exclude' => [], - 'in_app_include' => [ - 'path/to/nested/file', + [ + null, 'path/to/file', + 7, ], - ], - 'path/to/file', - 'test_function', - true, - ]; - - yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_include' => [ - [ - 'in_app_exclude' => [ - 'path/to/nested/file', + [ + 'call_user_func', + Frame::INTERNAL_FRAME_FILENAME, + 0, ], - 'in_app_include' => [ + [ + 'TestClass::triggerError', 'path/to/file', + 12, ], ], - 'path/to/file', - 'test_function', - true, ]; - yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_exclude' => [ + yield 'Backtrace with frame containing memory address' => [ + new Options([ + 'prefixes' => ['/path-prefix'], + ]), [ - 'in_app_exclude' => [ - 'path/to/nested/file', - ], - 'in_app_include' => [ - 'path/to/file', + 'file' => 'path/to/file', + 'line' => 12, + 'backtrace' => [ + [ + 'class' => "class@anonymous\x00/path/to/app/consumer.php0x7fc3bc369418", + 'function' => 'messageCallback', + 'type' => '->', + ], + [ + 'class' => "class@anonymous\x00/path-prefix/path/to/app/consumer.php0x7fc3bc369418", + 'function' => 'messageCallback', + 'type' => '->', + ], ], ], - 'path/to/nested/file', - 'test_function', - false, - ]; - - yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_include && in_app_include prioritized over in_app_exclude' => [ [ - 'in_app_exclude' => [ - 'path/to/file', + [ + null, + Frame::INTERNAL_FRAME_FILENAME, + 0, ], - 'in_app_include' => [ - 'path/to/file/nested', + [ + "class@anonymous\x00/path/to/app/consumer.php::messageCallback", + Frame::INTERNAL_FRAME_FILENAME, + 0, ], - ], - 'path/to/file/nested', - 'test_function', - true, - ]; - - yield 'in_app_include specified && in_app_exclude specified && file path matching in_app_exclude && in_app_exclude prioritized over in_app_include' => [ - [ - 'in_app_exclude' => [ + [ + "class@anonymous\x00/path/to/app/consumer.php::messageCallback", 'path/to/file', + 12, ], - 'in_app_include' => [ - 'path/to/file/nested', - ], - ], - 'path/to/file', - 'test_function', - false, - ]; - } - - /** - * @group legacy - * - * @dataProvider addFrameRespectsContextLinesOptionDataProvider - * - * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. - */ - public function testAddFrameRespectsContextLinesOption(string $fixture, int $lineNumber, ?int $contextLines, int $preContextCount, int $postContextCount): void - { - $this->options->setContextLines($contextLines); - - $fileContent = explode("\n", $this->getFixture($fixture)); - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer); - - $stacktrace->addFrame($this->getFixturePath($fixture), $lineNumber, ['function' => '[unknown]']); - - $frames = $stacktrace->getFrames(); - - $this->assertCount(1, $frames); - $this->assertCount($preContextCount, $frames[0]->getPreContext()); - $this->assertCount($postContextCount, $frames[0]->getPostContext()); - - for ($i = 0; $i < $preContextCount; ++$i) { - $this->assertSame(rtrim($fileContent[$i + ($lineNumber - $preContextCount - 1)]), $frames[0]->getPreContext()[$i]); - } - - if (null === $contextLines) { - $this->assertNull($frames[0]->getContextLine()); - } else { - $this->assertSame(rtrim($fileContent[$lineNumber - 1]), $frames[0]->getContextLine()); - } - - for ($i = 0; $i < $postContextCount; ++$i) { - $this->assertSame(rtrim($fileContent[$i + $lineNumber]), $frames[0]->getPostContext()[$i]); - } - } - - public function addFrameRespectsContextLinesOptionDataProvider(): \Generator - { - yield 'skip reading code when context_lines option == 0' => [ - 'code/ShortFile.php', - 3, - 0, - 0, - 0, - ]; - - yield 'skip reading pre_context and post_context when context_lines == null' => [ - 'code/ShortFile.php', - 3, - null, - 0, - 0, - ]; - - yield 'read code from short file' => [ - 'code/ShortFile.php', - 3, - 2, - 2, - 2, - ]; - - yield 'read code from long file' => [ - 'code/LongFile.php', - 8, - 2, - 2, - 2, - ]; - - yield 'read code from long file near end of file' => [ - 'code/LongFile.php', - 11, - 5, - 5, - 2, - ]; - - yield 'read code from long file near beginning of file' => [ - 'code/LongFile.php', - 3, - 5, - 2, - 5, - ]; - } - - /** - * @dataProvider removeFrameDataProvider - */ - public function testRemoveFrame(int $index, bool $throwException): void - { - if ($throwException) { - $this->expectException(\OutOfBoundsException::class); - $this->expectExceptionMessage('Invalid frame index to remove.'); - } - - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); - $stacktrace->addFrame('path/to/file', 12, [ - 'function' => 'test_function_parent', - ]); - - $stacktrace->addFrame('path/to/file', 12, [ - 'function' => 'test_function', - ]); - - $this->assertCount(2, $stacktrace->getFrames()); - - $stacktrace->removeFrame($index); - - $frames = $stacktrace->getFrames(); - - $this->assertCount(1, $frames); - $this->assertFrameEquals($frames[0], 'test_function_parent', 'path/to/file', 12); - } - - public function removeFrameDataProvider(): array - { - return [ - [-1, true], - [2, true], - [0, false], - ]; - } - - public function testCreateFromBacktrace(): void - { - $fixture = $this->getJsonFixture('backtraces/exception.json'); - $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'], false)->getFrames(); - - $this->assertFrameEquals($frames[0], null, 'path/to/file', 16); - $this->assertFrameEquals($frames[1], 'TestClass::crashyFunction', 'path/to/file', 7); - $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); - } - - public function testCreateFromBacktraceWithAnonymousFrame(): void - { - $fixture = $this->getJsonFixture('backtraces/anonymous_frame.json'); - $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'], false)->getFrames(); - - $this->assertFrameEquals($frames[0], null, 'path/to/file', 7); - $this->assertFrameEquals($frames[1], 'call_user_func', '[internal]', 0); - $this->assertFrameEquals($frames[2], 'TestClass::triggerError', 'path/to/file', 12); - } - - public function testCreateFromBacktraceWithAnonymousClass(): void - { - $this->options->setPrefixes(['/path-prefix']); - - $fixture = $this->getJsonFixture('backtraces/anonymous_frame_with_memory_address.json'); - $frames = Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, $fixture['backtrace'], $fixture['file'], $fixture['line'], false)->getFrames(); - - $this->assertFrameEquals( - $frames[0], - null, - '[internal]', - 0 - ); - - $this->assertFrameEquals( - $frames[1], - "class@anonymous\x00/path/to/app/consumer.php::messageCallback", - '[internal]', - 0 - ); - - $this->assertFrameEquals( - $frames[2], - "class@anonymous\x00/path/to/app/consumer.php::messageCallback", - 'path/to/file', - 12 - ); - } - - /** - * @group legacy - * - * @dataProvider createFromBacktraceThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider - * - * @expectedDeprecation Relying on the "Sentry\Stacktrace" class to contexify the frames of the stacktrace is deprecated since version 2.4 and will stop working in 3.0. Set the $shouldReadSourceCodeExcerpts parameter to "false" and use the "Sentry\Integration\FrameContextifierIntegration" integration instead. - */ - public function testCreateFromBacktraceThrowsDeprecationErrorIfLastArgumentIsNotSetToFalse(array ...$constructorArguments): void - { - Stacktrace::createFromBacktrace($this->options, $this->serializer, $this->representationSerializer, [], __FILE__, __LINE__, ...$constructorArguments); - } - - public function createFromBacktraceThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider(): \Generator - { - yield [[true]]; - - yield [[1]]; - - yield [['foo']]; - - yield [[new class() { - }]]; - - yield [[]]; - } - - public function testGetFrameArgumentsDoesNotModifyCapturedArgs(): void - { - // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. - // Modification of these would be really bad, since if control is returned (non-fatal error) we'll have altered the state of things! - $originalFoo = 'bloopblarp'; - $newFoo = $originalFoo; - $nestedArray = [ - 'key' => 'xxxxxxxxxx', - ]; - - $frame = [ - 'file' => __DIR__ . '/resources/a.php', - 'line' => 9, - 'args' => [ - &$newFoo, - &$nestedArray, - ], - 'function' => 'a_test', - ]; - - $stacktrace = new Stacktrace(new Options(['max_value_length' => 5]), $this->serializer, $this->representationSerializer, false); - $result = $stacktrace->getFrameArguments($frame); - - // Check we haven't modified our vars. - $this->assertEquals($originalFoo, 'bloopblarp'); - $this->assertEquals($nestedArray['key'], 'xxxxxxxxxx'); - - // Check that we did truncate the variable in our output - $this->assertEquals($result['param1'], 'bloop'); - $this->assertEquals($result['param2']['key'], 'xxxxx'); - } - - public function testPreserveXdebugFrameArgumentNames(): void - { - $frame = [ - 'file' => __DIR__ . '/resources/a.php', - 'line' => 9, - 'args' => [ - 'foo' => 'bar', - 'alice' => 'bob', ], - 'function' => 'a_test', ]; - - $stacktrace = new Stacktrace($this->options, $this->serializer, $this->representationSerializer, false); - $result = $stacktrace->getFrameArguments($frame); - - $this->assertEquals('bar', $result['foo']); - $this->assertEquals('bob', $result['alice']); - } - - private function getFixturePath(string $file): string - { - $filePath = realpath(__DIR__ . \DIRECTORY_SEPARATOR . 'Fixtures' . \DIRECTORY_SEPARATOR . $file); - - if (false === $filePath) { - throw new \RuntimeException(sprintf('The fixture file at path "%s" could not be found.', $file)); - } - - return $filePath; - } - - private function getFixture(string $file): string - { - $fileContent = file_get_contents($this->getFixturePath($file)); - - if (false === $fileContent) { - throw new \RuntimeException(sprintf('The fixture file at path "%s" could not be read.', $file)); - } - - return $fileContent; - } - - private function getJsonFixture(string $file): array - { - $decodedData = json_decode($this->getFixture($file), true); - - if (JSON_ERROR_NONE !== json_last_error()) { - throw new \RuntimeException(sprintf('Could not decode the fixture file at path "%s". Error was: %s', $this->getFixturePath($file), json_last_error_msg())); - } - - return $decodedData; } private function assertFrameEquals(Frame $frame, ?string $method, string $file, int $line): void diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt index 244106008..647969f04 100644 --- a/tests/phpt/error_handler_respects_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -1,5 +1,7 @@ --TEST-- Test that the error handler ignores silenced errors by default, but it reports them with the appropriate option enabled. +--INI-- +error_reporting=E_ALL --FILE-- Date: Wed, 19 Aug 2020 20:50:15 +0200 Subject: [PATCH 0585/1161] Switch from Httplug HTTP factories to PSR-17 factories and return a promise from the transport (#1066) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 4 + composer.json | 1 + src/Client.php | 13 +- src/ClientBuilder.php | 26 ++- src/HttpClient/HttpClientFactory.php | 6 +- src/HttpClient/Plugin/GzipEncoderPlugin.php | 16 +- src/Response.php | 45 ++++ src/ResponseStatus.php | 121 +++++++++++ src/Transport/DefaultTransportFactory.php | 29 ++- src/Transport/HttpTransport.php | 162 ++++----------- src/Transport/NullTransport.php | 9 +- src/Transport/SpoolTransport.php | 12 +- src/Transport/TransportInterface.php | 6 +- tests/ClientTest.php | 57 ++--- tests/HttpClient/HttpClientFactoryTest.php | 31 +-- .../Plugin/GzipEncoderPluginTest.php | 82 ++------ tests/ResponseStatusTest.php | 113 ++++++++++ .../Transport/DefaultTransportFactoryTest.php | 17 +- tests/Transport/HttpTransportTest.php | 196 ++++++------------ tests/Transport/NullTransportTest.php | 9 +- tests/Transport/SpoolTransportTest.php | 40 ++-- .../error_handler_captures_fatal_error.phpt | 9 +- ...rror_handler_respects_error_reporting.phpt | 9 +- ...tegration_respects_error_types_option.phpt | 12 +- ...rror_integration_captures_fatal_error.phpt | 9 +- ...tegration_respects_error_types_option.phpt | 9 +- .../http_transport_send_event_delayed.phpt | 59 ------ 28 files changed, 592 insertions(+), 511 deletions(-) create mode 100644 src/Response.php create mode 100644 src/ResponseStatus.php create mode 100644 tests/ResponseStatusTest.php delete mode 100644 tests/phpt/http_transport_send_event_delayed.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index d36b373ab..f887409cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [BC BREAK] Remove the deprecated methods from the `ClientBuilderInterface` interface and its implementations (#1047) - [BC BREAK] The `Scope::setUser()` method now always merges the given data with the existing one instead of replacing it as a whole (#1047) - [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) +- [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) ### 2.4.1 (2020-07-03) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 689822e70..20dd133a9 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -33,3 +33,7 @@ - `Options::setProjectRoot()` - Removed the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants - The signature of the `Scope::setUser()` method changed to not accept the `$merge` parameter anymore +- The signature of the `TransportInterface::send()` method changed to return a promise instead of the event ID +- The signature of the `HttpClientFactory::__construct()` method changed to accept instances of the PSR-17 factories in place of Httplug's ones +- The signature of the `DefaultTransportFactory::__construct()` method changed to accept instances of the PSR-17 factories in place of Httplug's ones +- The signature of the `GzipEncoderPlugin::__construct()` method changed to accept an instance of the `Psr\Http\Message\StreamFactoryInterface` interface only diff --git a/composer.json b/composer.json index f2f6a10d0..b7f0b3020 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.16", + "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.3|^2.0", "php-http/mock-client": "^1.3", "phpstan/extension-installer": "^1.0", diff --git a/src/Client.php b/src/Client.php index 1c91b6833..653d5f0df 100644 --- a/src/Client.php +++ b/src/Client.php @@ -115,7 +115,18 @@ public function captureEvent($payload, ?Scope $scope = null): ?EventId return null; } - return $this->transport->send($event); + try { + /** @var Response $response */ + $response = $this->transport->send($event)->wait(); + $event = $response->getEvent(); + + if (null !== $event) { + return $event->getId(); + } + } catch (\Throwable $exception) { + } + + return null; } /** diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index f2f044e8c..1281f450b 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -5,9 +5,7 @@ namespace Sentry; use Http\Client\HttpAsyncClient; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Discovery\StreamFactoryDiscovery; -use Http\Discovery\UriFactoryDiscovery; +use Http\Discovery\Psr17FactoryDiscovery; use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; @@ -151,21 +149,21 @@ public function setSdkVersion(string $sdkVersion): ClientBuilderInterface /** * {@inheritdoc} */ - public function getClient(): ClientInterface + public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface { - $this->transport = $this->transport ?? $this->createTransportInstance(); + $this->transportFactory = $transportFactory; - return new Client($this->options, $this->transport, $this->createEventFactory(), $this->logger); + return $this; } /** * {@inheritdoc} */ - public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface + public function getClient(): ClientInterface { - $this->transportFactory = $transportFactory; + $this->transport = $this->transport ?? $this->createTransportInstance(); - return $this; + return new Client($this->options, $this->transport, $this->createEventFactory(), $this->logger); } /** @@ -198,16 +196,16 @@ private function createEventFactory(): EventFactoryInterface */ private function createDefaultTransportFactory(): DefaultTransportFactory { - $messageFactory = MessageFactoryDiscovery::find(); + $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); $httpClientFactory = new HttpClientFactory( - UriFactoryDiscovery::find(), - $messageFactory, - StreamFactoryDiscovery::find(), + Psr17FactoryDiscovery::findUrlFactory(), + Psr17FactoryDiscovery::findResponseFactory(), + $streamFactory, $this->httpClient, $this->sdkIdentifier, $this->sdkVersion ); - return new DefaultTransportFactory($messageFactory, $httpClientFactory, $this->logger); + return new DefaultTransportFactory($streamFactory, Psr17FactoryDiscovery::findRequestFactory(), $httpClientFactory, $this->logger); } } diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index de2f28655..79f28ae5c 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -15,9 +15,9 @@ use Http\Client\Curl\Client as CurlHttpClient; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Discovery\HttpAsyncClientDiscovery; -use Http\Message\ResponseFactory as ResponseFactoryInterface; -use Http\Message\StreamFactory as StreamFactoryInterface; -use Http\Message\UriFactory as UriFactoryInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\HttpClient\Plugin\GzipEncoderPlugin; use Sentry\Options; diff --git a/src/HttpClient/Plugin/GzipEncoderPlugin.php b/src/HttpClient/Plugin/GzipEncoderPlugin.php index 658311c06..b5b9a84a8 100644 --- a/src/HttpClient/Plugin/GzipEncoderPlugin.php +++ b/src/HttpClient/Plugin/GzipEncoderPlugin.php @@ -5,8 +5,6 @@ namespace Sentry\HttpClient\Plugin; use Http\Client\Common\Plugin as PluginInterface; -use Http\Discovery\StreamFactoryDiscovery; -use Http\Message\StreamFactory as HttplugStreamFactoryInterface; use Http\Promise\Promise as PromiseInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamFactoryInterface; @@ -19,30 +17,24 @@ final class GzipEncoderPlugin implements PluginInterface { /** - * @var HttplugStreamFactoryInterface|StreamFactoryInterface The PSR-17 stream factory + * @var StreamFactoryInterface The PSR-17 stream factory */ private $streamFactory; /** * Constructor. * - * @param HttplugStreamFactoryInterface|StreamFactoryInterface|null $streamFactory The stream factory + * @param StreamFactoryInterface $streamFactory The stream factory * * @throws \RuntimeException If the zlib extension is not enabled */ - public function __construct($streamFactory = null) + public function __construct(StreamFactoryInterface $streamFactory) { if (!\extension_loaded('zlib')) { throw new \RuntimeException('The "zlib" extension must be enabled to use this plugin.'); } - if (null === $streamFactory) { - @trigger_error(sprintf('A PSR-17 stream factory is needed as argument of the constructor of the "%s" class since version 2.1.3 and will be required in 3.0.', self::class), E_USER_DEPRECATED); - } elseif (!$streamFactory instanceof HttplugStreamFactoryInterface && !$streamFactory instanceof StreamFactoryInterface) { - throw new \InvalidArgumentException(sprintf('The $streamFactory argument must be an instance of either the "%s" or the "%s" interface.', HttplugStreamFactoryInterface::class, StreamFactoryInterface::class)); - } - - $this->streamFactory = $streamFactory ?? StreamFactoryDiscovery::find(); + $this->streamFactory = $streamFactory; } /** diff --git a/src/Response.php b/src/Response.php new file mode 100644 index 000000000..3a2d54173 --- /dev/null +++ b/src/Response.php @@ -0,0 +1,45 @@ +status = $status; + $this->event = $event; + } + + /** + * Gets the status of the sending operation of the event. + */ + public function getStatus(): ResponseStatus + { + return $this->status; + } + + /** + * Gets the instance of the event being sent, or null if it was not available yet. + */ + public function getEvent(): ?Event + { + return $this->event; + } +} diff --git a/src/ResponseStatus.php b/src/ResponseStatus.php new file mode 100644 index 000000000..bd80f96fe --- /dev/null +++ b/src/ResponseStatus.php @@ -0,0 +1,121 @@ + + */ + private static $instances = []; + + /** + * Constructor. + * + * @param string $value The value of the enum instance + */ + private function __construct(string $value) + { + $this->value = $value; + } + + /** + * Returns an instance of this enum representing the fact that the event + * failed to be sent due to unknown reasons. + */ + public static function unknown(): self + { + return self::getInstance('UNKNOWN'); + } + + /** + * Returns an instance of this enum representing the fact that event was + * skipped from being sent. + */ + public static function skipped(): self + { + return self::getInstance('SKIPPED'); + } + + /** + * Returns an instance of this enum representing the fact that the event + * was sent successfully. + */ + public static function success(): self + { + return self::getInstance('SUCCESS'); + } + + /** + * Returns an instance of this enum representing the fact that the event + * failed to be sent because of API rate limiting. + */ + public static function rateLimit(): self + { + return self::getInstance('RATE_LIMIT'); + } + + /** + * Returns an instance of this enum representing the fact that the event + * failed to be sent because the server was not able to process the request. + */ + public static function invalid(): self + { + return self::getInstance('INVALID'); + } + + /** + * Returns an instance of this enum representing the fact that the event + * failed to be sent because the server returned a server error. + */ + public static function failed(): self + { + return self::getInstance('FAILED'); + } + + /** + * Returns an instance of this enum according to the given HTTP status code. + * + * @param int $statusCode The HTTP status code + */ + public static function createFromHttpStatusCode(int $statusCode): self + { + switch (true) { + case $statusCode >= 200 && $statusCode < 300: + return self::success(); + case 429 === $statusCode: + return self::rateLimit(); + case $statusCode >= 400 && $statusCode < 500: + return self::invalid(); + case $statusCode >= 500: + return self::failed(); + default: + return self::unknown(); + } + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php index 73c45c3fb..ea6d4a064 100644 --- a/src/Transport/DefaultTransportFactory.php +++ b/src/Transport/DefaultTransportFactory.php @@ -4,7 +4,8 @@ namespace Sentry\Transport; -use Http\Message\MessageFactory as MessageFactoryInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactoryInterface; use Sentry\Options; @@ -16,12 +17,17 @@ final class DefaultTransportFactory implements TransportFactoryInterface { /** - * @var MessageFactoryInterface The PSR-7 message factory + * @var StreamFactoryInterface A PSR-7 stream factory */ - private $messageFactory; + private $streamFactory; /** - * @var HttpClientFactoryInterface The factory to create the HTTP client + * @var RequestFactoryInterface A PSR-7 request factory + */ + private $requestFactory; + + /** + * @var HttpClientFactoryInterface A factory to create the HTTP client */ private $httpClientFactory; @@ -33,13 +39,15 @@ final class DefaultTransportFactory implements TransportFactoryInterface /** * Constructor. * - * @param MessageFactoryInterface $messageFactory The PSR-7 message factory + * @param StreamFactoryInterface $streamFactory The PSR-7 stream factory + * @param RequestFactoryInterface $requestFactory The PSR-7 request factory * @param HttpClientFactoryInterface $httpClientFactory The HTTP client factory - * @param LoggerInterface|null $logger A PSR-3 logger + * @param LoggerInterface|null $logger An optional PSR-3 logger */ - public function __construct(MessageFactoryInterface $messageFactory, HttpClientFactoryInterface $httpClientFactory, ?LoggerInterface $logger = null) + public function __construct(StreamFactoryInterface $streamFactory, RequestFactoryInterface $requestFactory, HttpClientFactoryInterface $httpClientFactory, ?LoggerInterface $logger = null) { - $this->messageFactory = $messageFactory; + $this->streamFactory = $streamFactory; + $this->requestFactory = $requestFactory; $this->httpClientFactory = $httpClientFactory; $this->logger = $logger; } @@ -56,9 +64,8 @@ public function create(Options $options): TransportInterface return new HttpTransport( $options, $this->httpClientFactory->create($options), - $this->messageFactory, - true, - false, + $this->streamFactory, + $this->requestFactory, $this->logger ); } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 7697e6c59..11818020f 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -4,16 +4,19 @@ namespace Sentry\Transport; -use GuzzleHttp\Promise\EachPromise; use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; +use GuzzleHttp\Promise\RejectedPromise; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Message\RequestFactory as RequestFactoryInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Event; -use Sentry\EventId; use Sentry\Options; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\Util\JSON; /** @@ -22,7 +25,7 @@ * * @author Stefano Arlandini */ -final class HttpTransport implements TransportInterface, ClosableTransportInterface +final class HttpTransport implements TransportInterface { /** * @var Options The Sentry client options @@ -35,22 +38,14 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterf private $httpClient; /** - * @var RequestFactoryInterface The PSR-7 request factory - */ - private $requestFactory; - - /** - * @var array> The list of pending requests - * - * @psalm-var array + * @var StreamFactoryInterface The PSR-7 stream factory */ - private $pendingRequests = []; + private $streamFactory; /** - * @var bool Flag indicating whether the sending of the events should be - * delayed until the shutdown of the application + * @var RequestFactoryInterface The PSR-7 request factory */ - private $delaySendingUntilShutdown = false; + private $requestFactory; /** * @var LoggerInterface A PSR-3 logger @@ -60,56 +55,30 @@ final class HttpTransport implements TransportInterface, ClosableTransportInterf /** * Constructor. * - * @param Options $options The Sentry client configuration - * @param HttpAsyncClientInterface $httpClient The HTTP client - * @param RequestFactoryInterface $requestFactory The PSR-7 request factory - * @param bool $delaySendingUntilShutdown This flag controls whether to delay - * sending of the events until the shutdown - * of the application - * @param bool $triggerDeprecation Flag controlling whether to throw - * a deprecation if the transport is - * used relying on the deprecated behavior - * of delaying the sending of the events - * until the shutdown of the application - * @param LoggerInterface|null $logger An instance of a PSR-3 logger + * @param Options $options The Sentry client configuration + * @param HttpAsyncClientInterface $httpClient The HTTP client + * @param StreamFactoryInterface $streamFactory The PSR-7 stream factory + * @param RequestFactoryInterface $requestFactory The PSR-7 request factory + * @param LoggerInterface|null $logger An instance of a PSR-3 logger */ public function __construct( Options $options, HttpAsyncClientInterface $httpClient, + StreamFactoryInterface $streamFactory, RequestFactoryInterface $requestFactory, - bool $delaySendingUntilShutdown = true, - bool $triggerDeprecation = true, ?LoggerInterface $logger = null ) { - if ($delaySendingUntilShutdown && $triggerDeprecation) { - @trigger_error(sprintf('Delaying the sending of the events using the "%s" class is deprecated since version 2.2 and will not work in 3.0.', __CLASS__), E_USER_DEPRECATED); - } - $this->options = $options; $this->httpClient = $httpClient; + $this->streamFactory = $streamFactory; $this->requestFactory = $requestFactory; - $this->delaySendingUntilShutdown = $delaySendingUntilShutdown; $this->logger = $logger ?? new NullLogger(); - - // By calling the cleanupPendingRequests function from a shutdown function - // registered inside another shutdown function we can be confident that it - // will be executed last - register_shutdown_function('register_shutdown_function', \Closure::fromCallable([$this, 'cleanupPendingRequests'])); - } - - /** - * Destructor. Ensures that all pending requests ends before destroying this - * object instance. - */ - public function __destruct() - { - $this->cleanupPendingRequests(); } /** * {@inheritdoc} */ - public function send(Event $event): ?EventId + public function send(Event $event): PromiseInterface { $dsn = $this->options->getDsn(); @@ -118,90 +87,33 @@ public function send(Event $event): ?EventId } if ('transaction' === $event->getType()) { - $request = $this->requestFactory->createRequest( - 'POST', - $dsn->getEnvelopeApiEndpointUrl(), - ['Content-Type' => 'application/x-sentry-envelope'], - $event->toEnvelope() - ); + $request = $this->requestFactory->createRequest('POST', $dsn->getEnvelopeApiEndpointUrl()) + ->withHeader('Content-Type', 'application/x-sentry-envelope') + ->withBody($this->streamFactory->createStream($event->toEnvelope())); } else { - $request = $this->requestFactory->createRequest( - 'POST', - $dsn->getStoreApiEndpointUrl(), - ['Content-Type' => 'application/json'], - JSON::encode($event->toArray()) - ); + $request = $this->requestFactory->createRequest('POST', $dsn->getStoreApiEndpointUrl()) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->streamFactory->createStream(JSON::encode($event->toArray()))); } - if ($this->delaySendingUntilShutdown) { - $this->pendingRequests[] = [$request, $event]; - } else { - try { - $this->httpClient->sendAsyncRequest($request)->wait(); - } catch (\Throwable $exception) { - $this->logger->error( - sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), - [ - 'exception' => $exception, - 'event' => $event, - ] - ); - - return null; - } - } - - return $event->getId(); - } - - /** - * {@inheritdoc} - */ - public function close(?int $timeout = null): PromiseInterface - { - $this->cleanupPendingRequests(); - - return new FulfilledPromise(true); - } - - /** - * Sends the pending requests. Any error that occurs will be ignored. - * - * @deprecated since version 2.2.3, to be removed in 3.0. Even though this - * method is `private` we cannot delete it because it's used - * in some old versions of the `sentry-laravel` package using - * tricky code involving reflection and Closure binding - */ - private function cleanupPendingRequests(): void - { - $requestGenerator = function (): \Generator { - foreach ($this->pendingRequests as $key => $data) { - yield $key => $this->httpClient->sendAsyncRequest($data[0]); - } - }; - try { - $eachPromise = new EachPromise($requestGenerator(), [ - 'concurrency' => 30, - 'rejected' => function (\Throwable $exception, int $requestIndex): void { - $this->logger->error( - sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), - [ - 'exception' => $exception, - 'event' => $this->pendingRequests[$requestIndex][1], - ] - ); - }, - ]); - - $eachPromise->promise()->wait(); + /** @var ResponseInterface $response */ + $response = $this->httpClient->sendAsyncRequest($request)->wait(); } catch (\Throwable $exception) { $this->logger->error( sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), - ['exception' => $exception] + ['exception' => $exception, 'event' => $event] ); + + return new RejectedPromise(new Response(ResponseStatus::failed(), $event)); + } + + $sendResponse = new Response(ResponseStatus::createFromHttpStatusCode($response->getStatusCode()), $event); + + if (ResponseStatus::success() === $sendResponse->getStatus()) { + return new FulfilledPromise($sendResponse); } - $this->pendingRequests = []; + return new RejectedPromise($sendResponse); } } diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index 3219044c7..087b7f9fd 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -4,8 +4,11 @@ namespace Sentry\Transport; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\Event; -use Sentry\EventId; +use Sentry\Response; +use Sentry\ResponseStatus; /** * This transport fakes the sending of events by just ignoring them. @@ -19,8 +22,8 @@ class NullTransport implements TransportInterface /** * {@inheritdoc} */ - public function send(Event $event): ?EventId + public function send(Event $event): PromiseInterface { - return $event->getId(); + return new FulfilledPromise(new Response(ResponseStatus::skipped(), $event)); } } diff --git a/src/Transport/SpoolTransport.php b/src/Transport/SpoolTransport.php index 65715b22a..1b816e15a 100644 --- a/src/Transport/SpoolTransport.php +++ b/src/Transport/SpoolTransport.php @@ -4,8 +4,12 @@ namespace Sentry\Transport; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; +use GuzzleHttp\Promise\RejectedPromise; use Sentry\Event; -use Sentry\EventId; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\Spool\SpoolInterface; /** @@ -41,12 +45,12 @@ public function getSpool(): SpoolInterface /** * {@inheritdoc} */ - public function send(Event $event): ?EventId + public function send(Event $event): PromiseInterface { if ($this->spool->queueEvent($event)) { - return $event->getId(); + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); } - return null; + return new RejectedPromise(new Response(ResponseStatus::skipped(), $event)); } } diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index 166714eb8..2a2608966 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -4,8 +4,8 @@ namespace Sentry\Transport; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\Event; -use Sentry\EventId; /** * This interface must be implemented by all classes willing to provide a way @@ -20,7 +20,7 @@ interface TransportInterface * * @param Event $event The event * - * @return EventId|null Returns the ID of the event or `null` if it failed to be sent + * @return PromiseInterface Returns the ID of the event or `null` if it failed to be sent */ - public function send(Event $event): ?EventId; + public function send(Event $event): PromiseInterface; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index d0d230889..93c679256 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -4,15 +4,17 @@ namespace Sentry\Tests; +use GuzzleHttp\Promise\FulfilledPromise; use PHPUnit\Framework\MockObject\Matcher\Invocation; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\EventId; use Sentry\Frame; use Sentry\Options; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; @@ -32,13 +34,16 @@ public function testCaptureMessage(): void $this->assertEquals(Severity::fatal(), $event->getLevel()); return true; - })); + })) + ->willReturnCallback(static function (Event $event): FulfilledPromise { + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + }); $client = ClientBuilder::create() ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $client->captureMessage('foo', Severity::fatal()); + $this->assertNotNull($client->captureMessage('foo', Severity::fatal())); } public function testCaptureException(): void @@ -58,24 +63,27 @@ public function testCaptureException(): void $this->assertSame($exception->getMessage(), $exceptionData->getValue()); return true; - })); + })) + ->willReturnCallback(static function (Event $event): FulfilledPromise { + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + }); $client = ClientBuilder::create() ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $client->captureException($exception); + $this->assertNotNull($client->captureException($exception)); } public function testCaptureEvent(): void { - $eventId = EventId::generate(); - /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') - ->willReturn($eventId); + ->willReturnCallback(static function (Event $event): FulfilledPromise { + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + }); $client = ClientBuilder::create() ->setTransportFactory($this->createTransportFactory($transport)) @@ -90,7 +98,7 @@ public function testCaptureEvent(): void 'user_context' => ['bar' => 'foo'], ]; - $this->assertSame($eventId, $client->captureEvent($inputData)); + $this->assertNotNull($client->captureEvent($inputData)); } /** @@ -98,8 +106,6 @@ public function testCaptureEvent(): void */ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOption(bool $attachStacktraceOption, array $payload, bool $shouldAttachStacktrace): void { - $eventId = EventId::generate(); - /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -115,13 +121,15 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt return true; })) - ->willReturn($eventId); + ->willReturnCallback(static function (Event $event): FulfilledPromise { + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + }); $client = ClientBuilder::create(['attach_stacktrace' => $attachStacktraceOption]) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertEquals($eventId, $client->captureEvent($payload)); + $this->assertNotNull($client->captureEvent($payload)); } public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider(): \Generator @@ -157,7 +165,6 @@ public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionD public function testCaptureEventPrefersExplicitStacktrace(): void { - $eventId = EventId::generate(); $stacktrace = new Stacktrace([ new Frame(__METHOD__, __FILE__, __LINE__), ]); @@ -169,19 +176,19 @@ public function testCaptureEventPrefersExplicitStacktrace(): void ->with($this->callback(static function (Event $event) use ($stacktrace): bool { return $stacktrace === $event->getStacktrace(); })) - ->willReturn($eventId); + ->willReturnCallback(static function (Event $event): FulfilledPromise { + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + }); $client = ClientBuilder::create(['attach_stacktrace' => true]) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertEquals($eventId, $client->captureEvent(['stacktrace' => $stacktrace])); + $this->assertNotNull($client->captureEvent(['stacktrace' => $stacktrace])); } public function testCaptureLastError(): void { - $eventId = EventId::generate(); - /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -194,7 +201,9 @@ public function testCaptureLastError(): void return true; })) - ->willReturn($eventId); + ->willReturnCallback(static function (Event $event): FulfilledPromise { + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + }); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) ->setTransportFactory($this->createTransportFactory($transport)) @@ -202,7 +211,7 @@ public function testCaptureLastError(): void @trigger_error('foo', E_USER_NOTICE); - $this->assertSame($eventId, $client->captureLastError()); + $this->assertNotNull($client->captureLastError()); $this->clearLastError(); } @@ -342,8 +351,6 @@ public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): vo public function testAttachStacktrace(): void { - $eventId = EventId::generate(); - /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -353,13 +360,15 @@ public function testAttachStacktrace(): void return null !== $result; })) - ->willReturn($eventId); + ->willReturnCallback(static function (Event $event): FulfilledPromise { + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + }); $client = ClientBuilder::create(['attach_stacktrace' => true]) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertSame($eventId, $client->captureMessage('test')); + $this->assertNotNull($client->captureMessage('test')); } /** diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php index 4c8c3c93d..8411245e5 100644 --- a/tests/HttpClient/HttpClientFactoryTest.php +++ b/tests/HttpClient/HttpClientFactoryTest.php @@ -5,9 +5,7 @@ namespace Sentry\Tests\HttpClient; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Discovery\StreamFactoryDiscovery; -use Http\Discovery\UriFactoryDiscovery; +use Http\Discovery\Psr17FactoryDiscovery; use Http\Mock\Client as HttpMockClient; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; @@ -17,15 +15,18 @@ final class HttpClientFactoryTest extends TestCase { /** + * @requires extension zlib * @dataProvider createDataProvider */ public function testCreate(bool $isCompressionEnabled, string $expectedRequestBody): void { + $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); + $mockHttpClient = new HttpMockClient(); $httpClientFactory = new HttpClientFactory( - UriFactoryDiscovery::find(), - MessageFactoryDiscovery::find(), - StreamFactoryDiscovery::find(), + Psr17FactoryDiscovery::findUrlFactory(), + Psr17FactoryDiscovery::findResponseFactory(), + $streamFactory, $mockHttpClient, 'sentry.php.test', '1.2.3' @@ -37,9 +38,11 @@ public function testCreate(bool $isCompressionEnabled, string $expectedRequestBo 'enable_compression' => $isCompressionEnabled, ])); - $httpClient->sendAsyncRequest(MessageFactoryDiscovery::find()->createRequest('POST', 'http://example.com/sentry/foo', [], 'foo bar')); + $request = Psr17FactoryDiscovery::findRequestFactory() + ->createRequest('POST', 'http://example.com/sentry/foo') + ->withBody($streamFactory->createStream('foo bar')); - /** @var RequestInterface|bool $httpRequest */ + $httpClient->sendAsyncRequest($request); $httpRequest = $mockHttpClient->getLastRequest(); $this->assertInstanceOf(RequestInterface::class, $httpRequest); @@ -65,9 +68,9 @@ public function createDataProvider(): \Generator public function testCreateThrowsIfDsnOptionIsNotConfigured(): void { $httpClientFactory = new HttpClientFactory( - UriFactoryDiscovery::find(), - MessageFactoryDiscovery::find(), - StreamFactoryDiscovery::find(), + Psr17FactoryDiscovery::findUrlFactory(), + Psr17FactoryDiscovery::findResponseFactory(), + Psr17FactoryDiscovery::findStreamFactory(), null, 'sentry.php.test', '1.2.3' @@ -82,9 +85,9 @@ public function testCreateThrowsIfDsnOptionIsNotConfigured(): void public function testCreateThrowsIfHttpProxyOptionIsUsedWithCustomHttpClient(): void { $httpClientFactory = new HttpClientFactory( - UriFactoryDiscovery::find(), - MessageFactoryDiscovery::find(), - StreamFactoryDiscovery::find(), + Psr17FactoryDiscovery::findUrlFactory(), + Psr17FactoryDiscovery::findResponseFactory(), + Psr17FactoryDiscovery::findStreamFactory(), $this->createMock(HttpAsyncClientInterface::class), 'sentry.php.test', '1.2.3' diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php index 4a88e8a57..f365adfb8 100644 --- a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php +++ b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php @@ -4,13 +4,10 @@ namespace Sentry\Tests\HttpClient\Plugin; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Discovery\StreamFactoryDiscovery; -use Http\Message\StreamFactory as HttplugStreamFactoryInterface; +use Http\Discovery\Psr17FactoryDiscovery; use Http\Promise\Promise as PromiseInterface; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\StreamFactoryInterface; use Sentry\HttpClient\Plugin\GzipEncoderPlugin; /** @@ -18,73 +15,24 @@ */ final class GzipEncoderPluginTest extends TestCase { - /** - * @dataProvider constructorThrowsIfArgumentsAreInvalidDataProvider - */ - public function testConstructorThrowsIfArgumentsAreInvalid($streamFactory, bool $shouldThrowException): void - { - if ($shouldThrowException) { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The $streamFactory argument must be an instance of either the "Http\Message\StreamFactory" or the "Psr\Http\Message\StreamFactoryInterface" interface.'); - } else { - $this->expectNotToPerformAssertions(); - } - - new GzipEncoderPlugin($streamFactory); - } - - public function constructorThrowsIfArgumentsAreInvalidDataProvider(): \Generator - { - yield [ - 'foo', - true, - ]; - - yield [ - $this->createMock(StreamFactoryInterface::class), - false, - ]; - - yield [ - $this->createMock(HttplugStreamFactoryInterface::class), - false, - ]; - } - - /** - * @group legacy - * - * @expectedDeprecation A PSR-17 stream factory is needed as argument of the constructor of the "Sentry\HttpClient\Plugin\GzipEncoderPlugin" class since version 2.1.3 and will be required in 3.0. - */ - public function testConstructorThrowsDeprecationErrorIfNoStreamFactoryIsProvided(): void - { - new GzipEncoderPlugin(); - } - public function testHandleRequest(): void { - $plugin = new GzipEncoderPlugin(StreamFactoryDiscovery::find()); + $plugin = new GzipEncoderPlugin(Psr17FactoryDiscovery::findStreamFactory()); $expectedPromise = $this->createMock(PromiseInterface::class); - $request = MessageFactoryDiscovery::find()->createRequest( - 'POST', - 'http://www.local.host', - [], - 'foo' - ); + $request = Psr17FactoryDiscovery::findRequestFactory() + ->createRequest('POST', 'http://www.example.com') + ->withBody(Psr17FactoryDiscovery::findStreamFactory()->createStream('foo')); $this->assertSame('foo', (string) $request->getBody()); - $this->assertSame( - $expectedPromise, - $plugin->handleRequest( - $request, - function (RequestInterface $requestArg) use ($expectedPromise): PromiseInterface { - $this->assertSame('gzip', $requestArg->getHeaderLine('Content-Encoding')); - $this->assertSame(gzcompress('foo', -1, ZLIB_ENCODING_GZIP), (string) $requestArg->getBody()); - - return $expectedPromise; - }, - static function (): void {} - ) - ); + $this->assertSame($expectedPromise, $plugin->handleRequest( + $request, + function (RequestInterface $requestArg) use ($expectedPromise): PromiseInterface { + $this->assertSame('gzip', $requestArg->getHeaderLine('Content-Encoding')); + $this->assertSame(gzcompress('foo', -1, ZLIB_ENCODING_GZIP), (string) $requestArg->getBody()); + + return $expectedPromise; + }, + static function (): void {} + )); } } diff --git a/tests/ResponseStatusTest.php b/tests/ResponseStatusTest.php new file mode 100644 index 000000000..2a9585f75 --- /dev/null +++ b/tests/ResponseStatusTest.php @@ -0,0 +1,113 @@ +assertSame($expectedStringRepresentation, (string) $responseStatus); + } + + public function toStringDataProvider(): iterable + { + yield [ + ResponseStatus::success(), + 'SUCCESS', + ]; + + yield [ + ResponseStatus::failed(), + 'FAILED', + ]; + + yield [ + ResponseStatus::invalid(), + 'INVALID', + ]; + + yield [ + ResponseStatus::skipped(), + 'SKIPPED', + ]; + + yield [ + ResponseStatus::rateLimit(), + 'RATE_LIMIT', + ]; + + yield [ + ResponseStatus::unknown(), + 'UNKNOWN', + ]; + } + + /** + * @dataProvider createFromHttpStatusCodeDataProvider + */ + public function testCreateFromHttpStatusCode(ResponseStatus $expectedResponseStatus, int $httpStatusCode): void + { + $this->assertSame($expectedResponseStatus, ResponseStatus::createFromHttpStatusCode($httpStatusCode)); + } + + public function createFromHttpStatusCodeDataProvider(): iterable + { + yield [ + ResponseStatus::success(), + 200, + ]; + + yield [ + ResponseStatus::success(), + 299, + ]; + + yield [ + ResponseStatus::rateLimit(), + 429, + ]; + + yield [ + ResponseStatus::invalid(), + 400, + ]; + + yield [ + ResponseStatus::invalid(), + 499, + ]; + + yield [ + ResponseStatus::failed(), + 500, + ]; + + yield [ + ResponseStatus::failed(), + 501, + ]; + + yield [ + ResponseStatus::unknown(), + 199, + ]; + } + + public function testStrictComparison(): void + { + $responseStatus1 = ResponseStatus::unknown(); + $responseStatus2 = ResponseStatus::unknown(); + $responseStatus3 = ResponseStatus::skipped(); + + $this->assertSame($responseStatus1, $responseStatus2); + $this->assertNotSame($responseStatus1, $responseStatus3); + } +} diff --git a/tests/Transport/DefaultTransportFactoryTest.php b/tests/Transport/DefaultTransportFactoryTest.php index f3ceee135..ad7c47160 100644 --- a/tests/Transport/DefaultTransportFactoryTest.php +++ b/tests/Transport/DefaultTransportFactoryTest.php @@ -5,7 +5,7 @@ namespace Sentry\Tests\Transport; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Discovery\MessageFactoryDiscovery; +use Http\Discovery\Psr17FactoryDiscovery; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\HttpClient\HttpClientFactoryInterface; @@ -19,7 +19,8 @@ final class DefaultTransportFactoryTest extends TestCase public function testCreateReturnsNullTransportWhenDsnOptionIsNotConfigured(): void { $factory = new DefaultTransportFactory( - MessageFactoryDiscovery::find(), + Psr17FactoryDiscovery::findStreamFactory(), + Psr17FactoryDiscovery::findRequestFactory(), $this->createMock(HttpClientFactoryInterface::class) ); @@ -30,14 +31,18 @@ public function testCreateReturnsHttpTransportWhenDsnOptionIsConfigured(): void { $options = new Options(['dsn' => 'http://public@example.com/sentry/1']); - /** @var HttpClientFactoryInterface&MockObject $clientFactory */ - $clientFactory = $this->createMock(HttpClientFactoryInterface::class); - $clientFactory->expects($this->once()) + /** @var HttpClientFactoryInterface&MockObject $httpClientFactory */ + $httpClientFactory = $this->createMock(HttpClientFactoryInterface::class); + $httpClientFactory->expects($this->once()) ->method('create') ->with($options) ->willReturn($this->createMock(HttpAsyncClientInterface::class)); - $factory = new DefaultTransportFactory(MessageFactoryDiscovery::find(), $clientFactory); + $factory = new DefaultTransportFactory( + Psr17FactoryDiscovery::findStreamFactory(), + Psr17FactoryDiscovery::findRequestFactory(), + $httpClientFactory + ); $this->assertInstanceOf(HttpTransport::class, $factory->create($options)); } diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index bcac41746..219de3465 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -4,20 +4,21 @@ namespace Sentry\Tests\Transport; +use GuzzleHttp\Promise\PromiseInterface; +use GuzzleHttp\Promise\RejectionException; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Discovery\StreamFactoryDiscovery; -use Http\Discovery\UriFactoryDiscovery; +use Http\Discovery\Psr17FactoryDiscovery; use Http\Mock\Client as HttpMockClient; -use Http\Promise\FulfilledPromise; +use Http\Promise\FulfilledPromise as HttpFullfilledPromise; use Http\Promise\RejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; use Sentry\Event; use Sentry\HttpClient\HttpClientFactory; use Sentry\Options; +use Sentry\ResponseStatus; use Sentry\Transport\HttpTransport; final class HttpTransportTest extends TestCase @@ -27,8 +28,8 @@ public function testSendThrowsIfDsnOptionIsNotSet(): void $transport = new HttpTransport( new Options(), $this->createMock(HttpAsyncClientInterface::class), - MessageFactoryDiscovery::find(), - false + Psr17FactoryDiscovery::findStreamFactory(), + Psr17FactoryDiscovery::findRequestFactory() ); $this->expectException(\RuntimeException::class); @@ -40,11 +41,10 @@ public function testSendThrowsIfDsnOptionIsNotSet(): void public function testSendTransactionAsEnvelope(): void { $mockHttpClient = new HttpMockClient(); - $httpClientFactory = new HttpClientFactory( - UriFactoryDiscovery::find(), - MessageFactoryDiscovery::find(), - StreamFactoryDiscovery::find(), + Psr17FactoryDiscovery::findUrlFactory(), + Psr17FactoryDiscovery::findResponseFactory(), + Psr17FactoryDiscovery::findStreamFactory(), $mockHttpClient, 'sentry.php.test', '1.2.3' @@ -56,78 +56,76 @@ public function testSendTransactionAsEnvelope(): void ])); $transport = new HttpTransport( - new Options([ - 'dsn' => 'http://public@example.com/sentry/1', - ]), + new Options(['dsn' => 'http://public@example.com/sentry/1']), $httpClient, - MessageFactoryDiscovery::find(), - false + Psr17FactoryDiscovery::findStreamFactory(), + Psr17FactoryDiscovery::findRequestFactory() ); $event = new Event(); $event->setType('transaction'); + $transport->send($event); - /** @var RequestInterface|bool $httpRequest */ $httpRequest = $mockHttpClient->getLastRequest(); - $this->assertSame('application/x-sentry-envelope', $httpRequest->getHeaders()['Content-Type'][0]); + $this->assertSame('application/x-sentry-envelope', $httpRequest->getHeaderLine('Content-Type')); } /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. + * @dataProvider sendDataProvider */ - public function testSendDelaysExecutionUntilShutdown(): void + public function testSend(int $httpStatusCode, string $expectedPromiseStatus, ResponseStatus $expectedResponseStatus): void { - $promise = new FulfilledPromise('foo'); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once()) + ->method('getStatusCode') + ->willReturn($httpStatusCode); + + $event = new Event(); /** @var HttpAsyncClientInterface&MockObject $httpClient */ $httpClient = $this->createMock(HttpAsyncClientInterface::class); $httpClient->expects($this->once()) ->method('sendAsyncRequest') - ->willReturn($promise); + ->willReturn(new HttpFullfilledPromise($response)); $transport = new HttpTransport( new Options(['dsn' => 'http://public@example.com/sentry/1']), $httpClient, - MessageFactoryDiscovery::find(), - true + Psr17FactoryDiscovery::findStreamFactory(), + Psr17FactoryDiscovery::findRequestFactory() ); - $this->assertAttributeEmpty('pendingRequests', $transport); - - $transport->send(new Event()); - - $this->assertAttributeNotEmpty('pendingRequests', $transport); + $promise = $transport->send($event); - $transport->close(); + try { + $promiseResult = $promise->wait(); + } catch (RejectionException $exception) { + $promiseResult = $exception->getReason(); + } - $this->assertAttributeEmpty('pendingRequests', $transport); + $this->assertSame($expectedPromiseStatus, $promise->getState()); + $this->assertSame($expectedResponseStatus, $promiseResult->getStatus()); + $this->assertSame($event, $promiseResult->getEvent()); } - public function testSendDoesNotDelayExecutionUntilShutdownWhenConfiguredToNotDoIt(): void + public function sendDataProvider(): iterable { - $promise = new RejectedPromise(new \Exception()); - - /** @var HttpAsyncClientInterface&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClientInterface::class); - $httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn($promise); - - $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $httpClient, - MessageFactoryDiscovery::find(), - false - ); - - $transport->send(new Event()); + yield [ + 200, + PromiseInterface::FULFILLED, + ResponseStatus::success(), + ]; + + yield [ + 500, + PromiseInterface::REJECTED, + ResponseStatus::failed(), + ]; } - public function testSendLogsErrorMessageIfSendingFailed(): void + public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientException(): void { $exception = new \Exception('foo'); $event = new Event(); @@ -147,95 +145,21 @@ public function testSendLogsErrorMessageIfSendingFailed(): void $transport = new HttpTransport( new Options(['dsn' => 'http://public@example.com/sentry/1']), $httpClient, - MessageFactoryDiscovery::find(), - false, - true, - $logger - ); - - $transport->send($event); - } - - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ - public function testCloseLogsErrorMessageIfSendingFailed(): void - { - $exception = new \Exception('foo'); - $event1 = new Event(); - $event2 = new Event(); - - /** @var LoggerInterface&MockObject $logger */ - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->exactly(2)) - ->method('error') - ->withConsecutive([ - 'Failed to send the event to Sentry. Reason: "foo".', - ['exception' => $exception, 'event' => $event1], - ], - [ - 'Failed to send the event to Sentry. Reason: "foo".', - ['exception' => $exception, 'event' => $event2], - ]); - - /** @var HttpAsyncClientInterface&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClientInterface::class); - $httpClient->expects($this->exactly(2)) - ->method('sendAsyncRequest') - ->willReturnOnConsecutiveCalls( - new RejectedPromise($exception), - new RejectedPromise($exception) - ); - - $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $httpClient, - MessageFactoryDiscovery::find(), - true, - true, + Psr17FactoryDiscovery::findStreamFactory(), + Psr17FactoryDiscovery::findRequestFactory(), $logger ); - // Send multiple events to assert that they all gets the chance of - // being sent regardless of which fails - $transport->send($event1); - $transport->send($event2); - $transport->close(); - } - - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ - public function testCloseLogsErrorMessageIfExceptionIsThrownWhileProcessingTheHttpRequest(): void - { - $exception = new \Exception('foo'); - - /** @var LoggerInterface&MockObject $logger */ - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once()) - ->method('error') - ->with('Failed to send the event to Sentry. Reason: "foo".', ['exception' => $exception]); - - /** @var HttpAsyncClientInterface&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClientInterface::class); - $httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willThrowException($exception); + $promise = $transport->send($event); - $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $httpClient, - MessageFactoryDiscovery::find(), - true, - true, - $logger - ); + try { + $promiseResult = $promise->wait(); + } catch (RejectionException $exception) { + $promiseResult = $exception->getReason(); + } - $transport->send(new Event()); - $transport->close(); + $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); + $this->assertSame(ResponseStatus::failed(), $promiseResult->getStatus()); + $this->assertSame($event, $promiseResult->getEvent()); } } diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php index 3d95c81ff..a7281384c 100644 --- a/tests/Transport/NullTransportTest.php +++ b/tests/Transport/NullTransportTest.php @@ -4,8 +4,10 @@ namespace Sentry\Tests\Transport; +use GuzzleHttp\Promise\PromiseInterface; use PHPUnit\Framework\TestCase; use Sentry\Event; +use Sentry\ResponseStatus; use Sentry\Transport\NullTransport; final class NullTransportTest extends TestCase @@ -15,6 +17,11 @@ public function testSend(): void $transport = new NullTransport(); $event = new Event(); - $this->assertSame($event->getId(), $transport->send($event)); + $promise = $transport->send($event); + $promiseResult = $promise->wait(); + + $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); + $this->assertSame(ResponseStatus::skipped(), $promiseResult->getStatus()); + $this->assertSame($event, $promiseResult->getEvent()); } } diff --git a/tests/Transport/SpoolTransportTest.php b/tests/Transport/SpoolTransportTest.php index a65699a2c..bbd637493 100644 --- a/tests/Transport/SpoolTransportTest.php +++ b/tests/Transport/SpoolTransportTest.php @@ -4,9 +4,12 @@ namespace Sentry\Tests\Transport; +use GuzzleHttp\Promise\PromiseInterface; +use GuzzleHttp\Promise\RejectionException; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\Event; +use Sentry\ResponseStatus; use Sentry\Spool\SpoolInterface; use Sentry\Transport\SpoolTransport; @@ -15,12 +18,12 @@ final class SpoolTransportTest extends TestCase /** * @var SpoolInterface&MockObject */ - protected $spool; + private $spool; /** * @var SpoolTransport */ - protected $transport; + private $transport; protected function setUp(): void { @@ -36,27 +39,40 @@ public function testGetSpool(): void /** * @dataProvider sendDataProvider */ - public function testSend(bool $isSendingSuccessful): void + public function testSend(bool $shouldQueueEvent, string $expectedPromiseStatus, ResponseStatus $expectedResponseStatus): void { $event = new Event(); $this->spool->expects($this->once()) ->method('queueEvent') ->with($event) - ->willReturn($isSendingSuccessful); + ->willReturn($shouldQueueEvent); - $eventId = $this->transport->send($event); + $promise = $this->transport->send($event); - if ($isSendingSuccessful) { - $this->assertSame($event->getId(), $eventId); - } else { - $this->assertNull($eventId); + try { + $promiseResult = $promise->wait(); + } catch (RejectionException $exception) { + $promiseResult = $exception->getReason(); } + + $this->assertSame($expectedPromiseStatus, $promise->getState()); + $this->assertSame($expectedResponseStatus, $promiseResult->getStatus()); + $this->assertSame($event, $promiseResult->getEvent()); } - public function sendDataProvider(): \Generator + public function sendDataProvider(): iterable { - yield [true]; - yield [false]; + yield [ + true, + PromiseInterface::FULFILLED, + ResponseStatus::success(), + ]; + + yield [ + false, + PromiseInterface::REJECTED, + ResponseStatus::skipped(), + ]; } } diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index 6e89f2092..61eaaa2c2 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -7,11 +7,14 @@ declare(strict_types=1); namespace Sentry\Tests; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\ErrorHandler; use Sentry\Event; -use Sentry\EventId; use Sentry\Options; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\SentrySdk; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; @@ -28,11 +31,11 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?EventId + public function send(Event $event): PromiseInterface { echo 'Transport called' . PHP_EOL; - return null; + return new FulfilledPromise(new Response(ResponseStatus::success())); } }; } diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt index 647969f04..acd2232cb 100644 --- a/tests/phpt/error_handler_respects_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -9,10 +9,13 @@ declare(strict_types=1); namespace Sentry\Tests; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\EventId; use Sentry\Options; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\SentrySdk; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; @@ -29,11 +32,11 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?EventId + public function send(Event $event): PromiseInterface { echo 'Transport called' . PHP_EOL; - return null; + return new FulfilledPromise(new Response(ResponseStatus::success())); } }; } diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt index fb1a71d6c..b6cd91c3e 100644 --- a/tests/phpt/error_listener_integration_respects_error_types_option.phpt +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -9,11 +9,14 @@ declare(strict_types=1); namespace Sentry\Tests; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\EventId; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Options; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\SentrySdk; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; @@ -30,11 +33,11 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?EventId + public function send(Event $event): PromiseInterface { - echo 'Transport called'; + echo 'Transport called' . PHP_EOL; - return null; + return new FulfilledPromise(new Response(ResponseStatus::success())); } }; } @@ -59,6 +62,7 @@ trigger_error('Error thrown', E_USER_WARNING); ?> --EXPECTF-- Transport called + Notice: Error thrown in %s on line %d Warning: Error thrown in %s on line %d diff --git a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt index d159b594e..de62b55f5 100644 --- a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt @@ -7,11 +7,14 @@ declare(strict_types=1); namespace Sentry\Tests; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\EventId; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\SentrySdk; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; @@ -28,11 +31,11 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?EventId + public function send(Event $event): PromiseInterface { echo 'Transport called' . PHP_EOL; - return null; + return new FulfilledPromise(new Response(ResponseStatus::success())); } }; } diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index c1c90aa00..749d4b13f 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -7,11 +7,14 @@ declare(strict_types=1); namespace Sentry\Tests; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\EventId; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\SentrySdk; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; @@ -28,11 +31,11 @@ $transportFactory = new class implements TransportFactoryInterface { public function create(Options $options): TransportInterface { return new class implements TransportInterface { - public function send(Event $event): ?EventId + public function send(Event $event): PromiseInterface { echo 'Transport called (it should not have been)' . PHP_EOL; - return null; + return new FulfilledPromise(new Response(ResponseStatus::success())); } }; } diff --git a/tests/phpt/http_transport_send_event_delayed.phpt b/tests/phpt/http_transport_send_event_delayed.phpt deleted file mode 100644 index b64a44028..000000000 --- a/tests/phpt/http_transport_send_event_delayed.phpt +++ /dev/null @@ -1,59 +0,0 @@ ---TEST-- -Test that the HttpTransport transport delays the event sending until the shutdown ---FILE-- -httpClientInvokationCount = &$httpClientInvokationCount; - } - - public function sendAsyncRequest(RequestInterface $request) - { - ++$this->httpClientInvokationCount; - - return new RejectedPromise(new \Exception()); - } -}; - -$transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $httpClient, - MessageFactoryDiscovery::find() -); - -$transport->send(new Event()); - -var_dump($httpClientInvokationCount); - -register_shutdown_function('register_shutdown_function', static function () use (&$httpClientInvokationCount): void { - var_dump($httpClientInvokationCount); -}); -?> ---EXPECT-- -int(0) -int(1) From 7781b585e7b31a82b488c2abcb90924b586ba87f Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 24 Aug 2020 09:46:43 +0200 Subject: [PATCH 0586/1161] Cleanup the Monolog handler from deprecated features (#1068) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 47 +++++++++++++++++ src/Monolog/Handler.php | 17 +------ tests/Monolog/HandlerTest.php | 95 ++--------------------------------- 4 files changed, 54 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f887409cf..0ed5fd7a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [BC BREAK] The `Scope::setUser()` method now always merges the given data with the existing one instead of replacing it as a whole (#1047) - [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) - [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) +- [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) ### 2.4.1 (2020-07-03) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 20dd133a9..9ccf13ae9 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -37,3 +37,50 @@ - The signature of the `HttpClientFactory::__construct()` method changed to accept instances of the PSR-17 factories in place of Httplug's ones - The signature of the `DefaultTransportFactory::__construct()` method changed to accept instances of the PSR-17 factories in place of Httplug's ones - The signature of the `GzipEncoderPlugin::__construct()` method changed to accept an instance of the `Psr\Http\Message\StreamFactoryInterface` interface only +- The Monolog handler does not set anymore the tags and extras on the event by extracting automatically the data from the record payload. You can decorate the + class and set such data on the scope as shown below: + + ```php + use Monolog\Handler\HandlerInterface; + use Sentry\State\Scope; + use function Sentry\withScope; + + final class MonologHandler implements HandlerInterface + { + private $decoratedHandler; + + public function __construct(HandlerInterface $decoratedHandler) + { + $this->decoratedHandler = $decoratedHandler; + } + + public function isHandling(array $record): bool + { + return $this->decoratedHandler->isHandling($record); + } + + public function handle(array $record): bool + { + $result = false; + + withScope(function (Scope $scope) use ($record, &$result): void { + $scope->setTags(...); + $scope->setExtras(...); + + $result = $this->decoratedHandler->handle($record); + }); + + return $result; + } + + public function handleBatch(array $records): void + { + $this->decoratedHandler->handleBatch($records); + } + + public function close(): void + { + $this->decoratedHandler->close(); + } + } + ``` diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index ed3c02e37..93dddd490 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -58,18 +58,6 @@ protected function write(array $record): void $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); - if (isset($record['context']['extra']) && \is_array($record['context']['extra'])) { - foreach ($record['context']['extra'] as $key => $value) { - $scope->setExtra((string) $key, $value); - } - } - - if (isset($record['context']['tags']) && \is_array($record['context']['tags'])) { - foreach ($record['context']['tags'] as $key => $value) { - $scope->setTag($key, $value); - } - } - $this->hub->captureEvent($payload); }); } @@ -84,9 +72,6 @@ private static function getSeverityFromLevel(int $level): Severity switch ($level) { case Logger::DEBUG: return Severity::debug(); - case Logger::INFO: - case Logger::NOTICE: - return Severity::info(); case Logger::WARNING: return Severity::warning(); case Logger::ERROR: @@ -95,6 +80,8 @@ private static function getSeverityFromLevel(int $level): Severity case Logger::ALERT: case Logger::EMERGENCY: return Severity::fatal(); + case Logger::INFO: + case Logger::NOTICE: default: return Severity::info(); } diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index 2d8a50bf5..f7ba79b1c 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -19,29 +19,26 @@ final class HandlerTest extends TestCase /** * @dataProvider handleDataProvider */ - public function testHandle(array $record, array $expectedPayload, array $expectedExtra, array $expectedTags): void + public function testHandle(array $record, array $expectedPayload, array $expectedExtra): void { - $scope = new Scope(); - /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') - ->with($expectedPayload, $this->callback(function (Scope $scopeArg) use ($expectedExtra, $expectedTags): bool { + ->with($expectedPayload, $this->callback(function (Scope $scopeArg) use ($expectedExtra): bool { $event = $scopeArg->applyToEvent(new Event(), []); $this->assertNotNull($event); $this->assertSame($expectedExtra, $event->getExtraContext()->toArray()); - $this->assertSame($expectedTags, $event->getTagsContext()->toArray()); return true; })); - $handler = new Handler(new Hub($client, $scope)); + $handler = new Handler(new Hub($client, new Scope())); $handler->handle($record); } - public function handleDataProvider(): \Generator + public function handleDataProvider(): iterable { yield [ [ @@ -61,7 +58,6 @@ public function handleDataProvider(): \Generator 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::DEBUG), ], - [], ]; yield [ @@ -82,7 +78,6 @@ public function handleDataProvider(): \Generator 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::INFO), ], - [], ]; yield [ @@ -103,7 +98,6 @@ public function handleDataProvider(): \Generator 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::NOTICE), ], - [], ]; yield [ @@ -124,7 +118,6 @@ public function handleDataProvider(): \Generator 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::WARNING), ], - [], ]; yield [ @@ -145,7 +138,6 @@ public function handleDataProvider(): \Generator 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::ERROR), ], - [], ]; yield [ @@ -166,7 +158,6 @@ public function handleDataProvider(): \Generator 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::CRITICAL), ], - [], ]; yield [ @@ -187,7 +178,6 @@ public function handleDataProvider(): \Generator 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::ALERT), ], - [], ]; yield [ @@ -208,43 +198,6 @@ public function handleDataProvider(): \Generator 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::EMERGENCY), ], - [], - ]; - - yield [ - [ - 'message' => 'foo bar', - 'level' => Logger::WARNING, - 'level_name' => Logger::getLevelName(Logger::WARNING), - 'context' => [ - 'extra' => [ - 'foo.extra' => 'foo extra value', - 'bar.extra' => 'bar extra value', - ], - 'tags' => [ - 'foo.tag' => 'foo tag value', - 'bar.tag' => 'bar tag value', - ], - ], - 'channel' => 'channel.foo', - 'datetime' => new \DateTimeImmutable(), - 'extra' => [], - ], - [ - 'level' => Severity::warning(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], - [ - 'monolog.channel' => 'channel.foo', - 'monolog.level' => Logger::getLevelName(Logger::WARNING), - 'foo.extra' => 'foo extra value', - 'bar.extra' => 'bar extra value', - ], - [ - 'foo.tag' => 'foo tag value', - 'bar.tag' => 'bar tag value', - ], ]; yield [ @@ -254,14 +207,6 @@ public function handleDataProvider(): \Generator 'level_name' => Logger::getLevelName(Logger::WARNING), 'context' => [ 'exception' => new \Exception('exception message'), - 'extra' => [ - 'foo.extra' => 'foo extra value', - 'bar.extra' => 'bar extra value', - ], - 'tags' => [ - 'foo.tag' => 'foo tag value', - 'bar.tag' => 'bar tag value', - ], ], 'channel' => 'channel.foo', 'datetime' => new \DateTimeImmutable(), @@ -276,39 +221,7 @@ public function handleDataProvider(): \Generator [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::WARNING), - 'foo.extra' => 'foo extra value', - 'bar.extra' => 'bar extra value', - ], - [ - 'foo.tag' => 'foo tag value', - 'bar.tag' => 'bar tag value', - ], - ]; - - yield [ - [ - 'message' => 'foo bar', - 'level' => Logger::INFO, - 'level_name' => Logger::getLevelName(Logger::INFO), - 'channel' => 'channel.foo', - 'context' => [ - 'extra' => [ - 1 => 'numeric key', - ], - ], - 'extra' => [], - ], - [ - 'level' => Severity::info(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], - [ - 'monolog.channel' => 'channel.foo', - 'monolog.level' => Logger::getLevelName(Logger::INFO), - '0' => 'numeric key', ], - [], ]; } } From b15e2bb32f6696ea44e1fd709a6eb7484b96040e Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 26 Aug 2020 10:46:07 +0200 Subject: [PATCH 0587/1161] fix: Request tests --- src/Integration/RequestIntegration.php | 2 +- tests/Integration/RequestIntegrationTest.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index af290ea2f..b3eef674d 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -153,7 +153,7 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re $maxRequestBodySize = $options->getMaxRequestBodySize(); $requestBody = $request->getBody(); - $requestBodySize = $request->getSize(); + $requestBodySize = $request->getBody()->getSize(); if ( null === $requestBodySize || diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 42d892ad0..893c21666 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -465,6 +465,9 @@ public function invokeDataProvider(): \Generator 'Content-Type' => ['application/json'], ], ], + [ + 'foo' => 'bar', + ], ]; } From 0f495b039ea844e0d280b514b6e5936417f2367d Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 26 Aug 2020 13:14:35 +0200 Subject: [PATCH 0588/1161] feat: Tracing getter/setters (#1074) * feat: Tracing getter/setters Move Span/Transaction to hub instead of scope * fix: Formatting errros * meta: Changelog --- CHANGELOG.md | 7 ++ src/State/Hub.php | 53 +++++++++++-- src/State/HubAdapter.php | 25 ++++++ src/State/HubInterface.php | 21 +++++ src/State/Scope.php | 46 ----------- src/Tracing/Span.php | 40 ++++++++++ src/Tracing/SpanContext.php | 122 +++++++++++++++++++++++++++++ src/Tracing/TransactionContext.php | 10 +++ 8 files changed, 272 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48529fbed..4059495a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +**Tracing API** + +In this version we released API for Tracing. `\Sentry\startTransaction` is your entry point for manual instrumentation. +More information can be found in our [Performance](https://docs.sentry.io/product/performance/) docs or specific +[PHP SDK](https://docs.sentry.io/platforms/php/) docs. + + - [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) - [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) - [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) diff --git a/src/State/Hub.php b/src/State/Hub.php index 0a6804f51..848d16c2f 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -9,6 +9,7 @@ use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; +use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -27,6 +28,11 @@ final class Hub implements HubInterface */ private $lastEventId; + /** + * @var Span|null Set a Span on the Scope + */ + private $span; + /** * Hub constructor. * @@ -207,6 +213,22 @@ public function getIntegration(string $className): ?IntegrationInterface return null; } + /** + * Gets the scope bound to the top of the stack. + */ + private function getScope(): Scope + { + return $this->getStackTop()->getScope(); + } + + /** + * Gets the topmost client/layer pair in the stack. + */ + private function getStackTop(): Layer + { + return $this->stack[\count($this->stack) - 1]; + } + /** * {@inheritdoc} */ @@ -241,18 +263,37 @@ public function startTransaction(TransactionContext $context): Transaction } /** - * Gets the scope bound to the top of the stack. + * {@inheritdoc} + * + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement */ - private function getScope(): Scope + public function getTransaction(): ?Transaction { - return $this->getStackTop()->getScope(); + $span = $this->span; + if (null !== $span && null !== $span->spanRecorder) { + /** @phpstan-ignore-next-line */ + return $span->spanRecorder->getSpans()[0]; + } + + return null; } /** - * Gets the topmost client/layer pair in the stack. + * {@inheritdoc} */ - private function getStackTop(): Layer + public function getSpan(): ?Span { - return $this->stack[\count($this->stack) - 1]; + return $this->span; + } + + /** + * {@inheritdoc} + */ + public function setSpan(?Span $span): HubInterface + { + $this->span = $span; + + return $this; } } diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 17d6e4bf0..76123e829 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -10,6 +10,7 @@ use Sentry\Integration\IntegrationInterface; use Sentry\SentrySdk; use Sentry\Severity; +use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -156,6 +157,30 @@ public function startTransaction(TransactionContext $context): Transaction return SentrySdk::getCurrentHub()->startTransaction($context); } + /** + * {@inheritdoc} + */ + public function getTransaction(): ?Transaction + { + return SentrySdk::getCurrentHub()->getTransaction(); + } + + /** + * {@inheritdoc} + */ + public function getSpan(): ?Span + { + return SentrySdk::getCurrentHub()->getSpan(); + } + + /** + * {@inheritdoc} + */ + public function setSpan(?Span $span): HubInterface + { + return SentrySdk::getCurrentHub()->setSpan($span); + } + /** * @see https://www.php.net/manual/en/language.oop5.cloning.php#object.clone */ diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index cb58b349b..ea36970af 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -10,6 +10,7 @@ use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; +use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -138,4 +139,24 @@ public function getIntegration(string $className): ?IntegrationInterface; * @param TransactionContext $context properties of the new `Transaction` */ public function startTransaction(TransactionContext $context): Transaction; + + /** + * Returns the Transaction that is on the Hub. + * + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ + public function getTransaction(): ?Transaction; + + /** + * Returns the Span that is on the Hub. + */ + public function getSpan(): ?Span; + + /** + * Sets the Span on the Hub. + * + * @param Span|null $span The Span + */ + public function setSpan(?Span $span): HubInterface; } diff --git a/src/State/Scope.php b/src/State/Scope.php index fba249579..83819256a 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -10,8 +10,6 @@ use Sentry\Context\UserContext; use Sentry\Event; use Sentry\Severity; -use Sentry\Tracing\Span; -use Sentry\Tracing\Transaction; /** * The scope holds data that should implicitly be sent with Sentry events. It @@ -56,11 +54,6 @@ final class Scope */ private $level; - /** - * @var Span|null Set a Span on the Scope - */ - private $span; - /** * @var callable[] List of event processors */ @@ -211,45 +204,6 @@ public function setLevel(?Severity $level): self return $this; } - /** - * Returns the Span that is on the Scope. - */ - public function getSpan(): ?Span - { - return $this->span; - } - - /** - * Sets the Span on the Scope. - * - * @param Span|null $span The Span - * - * @return $this - */ - public function setSpan(?Span $span): self - { - $this->span = $span; - - return $this; - } - - /** - * Returns the Transaction that is on the Scope. - * - * @psalm-suppress MoreSpecificReturnType - * @psalm-suppress LessSpecificReturnStatement - */ - public function getTransaction(): ?Transaction - { - $span = $this->span; - if (null !== $span && null !== $span->spanRecorder) { - /** @phpstan-ignore-next-line */ - return $span->spanRecorder->getSpans()[0]; - } - - return null; - } - /** * Add the given breadcrumb to the scope. * diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 5d0904ae6..8655c88f0 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -261,6 +261,46 @@ public function setTags(array $tags): void $this->tags->merge($tags); } + public function getSpanId(): SpanId + { + return $this->spanId; + } + + public function setSpanId(SpanId $spanId): void + { + $this->spanId = $spanId; + } + + public function getTraceId(): TraceId + { + return $this->traceId; + } + + public function setTraceId(TraceId $traceId): void + { + $this->traceId = $traceId; + } + + public function getParentSpanId(): ?SpanId + { + return $this->parentSpanId; + } + + public function setParentSpanId(?SpanId $parentSpanId): void + { + $this->parentSpanId = $parentSpanId; + } + + public function getSampled(): ?bool + { + return $this->sampled; + } + + public function setSampled(?bool $sampled): void + { + $this->sampled = $sampled; + } + /** * @param array $data */ diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index e412358e9..2e5e2bff0 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -63,6 +63,128 @@ class SpanContext */ public $endTimestamp; + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): void + { + $this->description = $description; + } + + public function getOp(): ?string + { + return $this->op; + } + + public function setOp(?string $op): void + { + $this->op = $op; + } + + public function getStatus(): ?string + { + return $this->status; + } + + public function setStatus(?string $status): void + { + $this->status = $status; + } + + public function getParentSpanId(): ?SpanId + { + return $this->parentSpanId; + } + + public function setParentSpanId(?SpanId $parentSpanId): void + { + $this->parentSpanId = $parentSpanId; + } + + public function getSampled(): ?bool + { + return $this->sampled; + } + + public function setSampled(?bool $sampled): void + { + $this->sampled = $sampled; + } + + public function getSpanId(): ?SpanId + { + return $this->spanId; + } + + public function setSpanId(?SpanId $spanId): void + { + $this->spanId = $spanId; + } + + public function getTraceId(): ?TraceId + { + return $this->traceId; + } + + public function setTraceId(?TraceId $traceId): void + { + $this->traceId = $traceId; + } + + /** + * @return array + */ + public function getTags(): ?array + { + return $this->tags; + } + + /** + * @param array|null $tags + */ + public function setTags(?array $tags): void + { + $this->tags = $tags; + } + + /** + * @return array + */ + public function getData(): ?array + { + return $this->data; + } + + /** + * @param array|null $data + */ + public function setData(?array $data): void + { + $this->data = $data; + } + + public function getStartTimestamp(): ?float + { + return $this->startTimestamp; + } + + public function setStartTimestamp(?float $startTimestamp): void + { + $this->startTimestamp = $startTimestamp; + } + + public function getEndTimestamp(): ?float + { + return $this->endTimestamp; + } + + public function setEndTimestamp(?float $endTimestamp): void + { + $this->endTimestamp = $endTimestamp; + } + /** * Returns a context depending on the header given. Containing trace_id, parent_span_id and sampled. * diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index a5058c484..889b36620 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -10,4 +10,14 @@ final class TransactionContext extends SpanContext * @var string|null Name of the transaction */ public $name; + + public function getName(): ?string + { + return $this->name; + } + + public function setName(?string $name): void + { + $this->name = $name; + } } From 14ad56b7cd4f58a6368def5a070c2c5bb9ce49c5 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 27 Aug 2020 11:53:04 +0200 Subject: [PATCH 0589/1161] fix: Add additional check for spans --- src/State/Hub.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/State/Hub.php b/src/State/Hub.php index 848d16c2f..8ff007dd9 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -271,7 +271,8 @@ public function startTransaction(TransactionContext $context): Transaction public function getTransaction(): ?Transaction { $span = $this->span; - if (null !== $span && null !== $span->spanRecorder) { + if (null !== $span && null !== $span->spanRecorder && !empty($span->spanRecorder->getSpans())) { + // The first span in the recorder is considered to be a Transaction /** @phpstan-ignore-next-line */ return $span->spanRecorder->getSpans()[0]; } From c33704877b22f5f6c29854f8f53281b2e23d2780 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 27 Aug 2020 18:52:10 +0200 Subject: [PATCH 0590/1161] Refactor the context classes and add the EnvironmentIntegration integration (#1072) --- CHANGELOG.md | 2 + UPGRADE-3.0.md | 21 +- phpstan.neon | 3 - src/Context/Context.php | 155 ------------ src/Context/OsContext.php | 129 ++++++++++ src/Context/RuntimeContext.php | 130 ++-------- src/Context/ServerOsContext.php | 196 --------------- src/Context/TagsContext.php | 87 ------- src/Context/UserContext.php | 86 ------- src/Event.php | 141 +++++++---- src/EventFactory.php | 2 +- src/Integration/EnvironmentIntegration.php | 71 ++++++ src/Integration/RequestIntegration.php | 16 +- src/Options.php | 2 + src/State/Scope.php | 100 +++++--- src/Tracing/Span.php | 47 ++-- src/Tracing/Transaction.php | 2 +- src/UserDataBag.php | 235 ++++++++++++++++++ src/Util/JSON.php | 2 + src/Util/PHPVersion.php | 2 +- tests/ClientBuilderTest.php | 8 +- tests/Context/AbstractContextTest.php | 125 ---------- tests/Context/ContextTest.php | 115 --------- tests/Context/OsContextTest.php | 66 +++++ tests/Context/RuntimeContextTest.php | 126 ++-------- tests/Context/ServerOsContextTest.php | 213 ---------------- tests/Context/TagsContextTest.php | 143 ----------- tests/Context/UserContextTest.php | 92 ------- tests/EventFactoryTest.php | 2 +- tests/EventTest.php | 16 +- tests/FunctionsTest.php | 3 - .../EnvironmentIntegrationTest.php | 101 ++++++++ .../FrameContextifierIntegrationTest.php | 2 - tests/Integration/RequestIntegrationTest.php | 116 ++++----- tests/Monolog/HandlerTest.php | 3 +- tests/OptionsTest.php | 13 +- tests/State/ScopeTest.php | 88 ++++--- tests/UserDataBagTest.php | 152 +++++++++++ 38 files changed, 1145 insertions(+), 1668 deletions(-) delete mode 100644 src/Context/Context.php create mode 100644 src/Context/OsContext.php delete mode 100644 src/Context/ServerOsContext.php delete mode 100644 src/Context/TagsContext.php delete mode 100644 src/Context/UserContext.php create mode 100644 src/Integration/EnvironmentIntegration.php create mode 100644 src/UserDataBag.php delete mode 100644 tests/Context/AbstractContextTest.php delete mode 100644 tests/Context/ContextTest.php create mode 100644 tests/Context/OsContextTest.php delete mode 100644 tests/Context/ServerOsContextTest.php delete mode 100644 tests/Context/TagsContextTest.php delete mode 100644 tests/Context/UserContextTest.php create mode 100644 tests/Integration/EnvironmentIntegrationTest.php create mode 100644 tests/UserDataBagTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4059495a1..e4a0c29da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ More information can be found in our [Performance](https://docs.sentry.io/produc - [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) - [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) - [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) +- [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) +- Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) ### 2.4.3 (2020-08-13) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 9ccf13ae9..bd0cf8c03 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -32,7 +32,7 @@ - `Options::getProjectRoot()` - `Options::setProjectRoot()` - Removed the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants -- The signature of the `Scope::setUser()` method changed to not accept the `$merge` parameter anymore +- The signature of the `Scope::setUser()` method changed to `setUser(array|Sentry\UserDataBag $user)` - The signature of the `TransportInterface::send()` method changed to return a promise instead of the event ID - The signature of the `HttpClientFactory::__construct()` method changed to accept instances of the PSR-17 factories in place of Httplug's ones - The signature of the `DefaultTransportFactory::__construct()` method changed to accept instances of the PSR-17 factories in place of Httplug's ones @@ -84,3 +84,22 @@ } } ``` + +- Removed the `TagsContext`, `ExtraContext` and `Context` classes, data is now stored in a plain array +- Renamed the `ServerOsContext` class to `OsContext` +- The `OsContext` and `RuntimeContext` classes do not implement anymore the `ArrayAccess`, `IteratorAggregate` and `JsonSerializable` interfaces and became `final` +- The following methods have been removed from the `OsContext` and `RuntimeContext` classes: + - `*Context::merge()` + - `*Context::setData()` + - `*Context::replaceData()` + - `*Context::clear()` + - `*Context::isEmpty()` + - `*Context::toArray()` +- Removed the `UserContext` class, use `UserDataBag` instead +- The signature of the constructor of the `RuntimeContext` class changed to `RuntimeContext::__construct(string $name, ?string $version = null)` +- The signature of the constructor of the `OsContext` class changed to `OsContext::__construct(string $name, ?string $version = null, ?string $build = null, ?string $kernelVersion = null)` +- Removed the `Event::getExtraContext()` method, use `Event::getExtra()` instead +- Removed the `Event::getTagsContext()` method, use `Event::getTags()` instead +- Removed the `Event::getUserContext()` method, use `Event::getUser()` instead +- Renamed the `Event::getServerOsContext()` method to `Event::getOsContext()` +- The signature of the `Scope::setUser()` method changed ot accept a plain array diff --git a/phpstan.neon b/phpstan.neon index a71bdc3b2..56efdb91c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -27,9 +27,6 @@ parameters: - message: '/^Method Sentry\\Monolog\\Handler::write\(\) has parameter \$record with no value type specified in iterable type array\.$/' path: src/Monolog/Handler.php - - - message: '/^Cannot cast array\|bool\|float\|int\|string\|null to string\.$/' - path: src/Serializer/RepresentationSerializer.php - message: '/^Method Sentry\\Client::getIntegration\(\) should return T of Sentry\\Integration\\IntegrationInterface\|null but returns Sentry\\Integration\\IntegrationInterface\|null\.$/' path: src/Client.php diff --git a/src/Context/Context.php b/src/Context/Context.php deleted file mode 100644 index 652be6497..000000000 --- a/src/Context/Context.php +++ /dev/null @@ -1,155 +0,0 @@ - - * - * @psalm-template T - * - * @template-implements \ArrayAccess - * @template-implements \IteratorAggregate - */ -class Context implements \ArrayAccess, \JsonSerializable, \IteratorAggregate -{ - /** - * @var array The data stored in this object - * - * @psalm-var array - */ - protected $data = []; - - /** - * Constructor. - * - * @param array $data The initial data to store - * - * @psalm-param array $data - */ - public function __construct(array $data = []) - { - $this->data = $data; - } - - /** - * Merges the given data with the existing one, recursively or not, according - * to the value of the `$recursive` parameter. - * - * @param array $data The data to merge - * @param bool $recursive Whether to merge the data recursively or not - * - * @psalm-param array $data - */ - public function merge(array $data, bool $recursive = false): void - { - $this->data = $recursive ? array_merge_recursive($this->data, $data) : array_merge($this->data, $data); - } - - /** - * Sets each element of the array to the value of the corresponding key in - * the given input data. - * - * @param array $data The data to set - * - * @psalm-param array $data - */ - public function setData(array $data): void - { - foreach ($data as $index => $value) { - $this->data[$index] = $value; - } - } - - /** - * Replaces all the data contained in this object with the given one. - * - * @param array $data The data to set - * - * @psalm-param array $data - */ - public function replaceData(array $data): void - { - $this->data = $data; - } - - /** - * Clears the entire data contained in this object. - */ - public function clear(): void - { - $this->data = []; - } - - /** - * Checks whether the object is not storing any data. - */ - public function isEmpty(): bool - { - return empty($this->data); - } - - /** - * Returns an array representation of the data stored by the object. - * - * @psalm-return array - */ - public function toArray(): array - { - return $this->data; - } - - /** - * {@inheritdoc} - */ - public function offsetExists($offset): bool - { - return isset($this->data[$offset]) || \array_key_exists($offset, $this->data); - } - - /** - * {@inheritdoc} - */ - public function offsetGet($offset) - { - return $this->data[$offset]; - } - - /** - * {@inheritdoc} - */ - public function offsetSet($offset, $value): void - { - $this->data[$offset] = $value; - } - - /** - * {@inheritdoc} - */ - public function offsetUnset($offset): void - { - unset($this->data[$offset]); - } - - /** - * {@inheritdoc} - * - * @psalm-return array - */ - public function jsonSerialize(): array - { - return $this->toArray(); - } - - /** - * {@inheritdoc} - */ - public function getIterator(): \Traversable - { - return new \ArrayIterator($this->data); - } -} diff --git a/src/Context/OsContext.php b/src/Context/OsContext.php new file mode 100644 index 000000000..7e6928f77 --- /dev/null +++ b/src/Context/OsContext.php @@ -0,0 +1,129 @@ + + */ +final class OsContext +{ + /** + * @var string The name of the operating system + */ + private $name; + + /** + * @var string|null The version of the operating system + */ + private $version; + + /** + * @var string|null The internal build revision of the operating system + */ + private $build; + + /** + * @var string|null An independent kernel version string + */ + private $kernelVersion; + + /** + * Constructor. + * + * @param string $name The name of the operating system + * @param string|null $version The version of the operating system + * @param string|null $build The internal build revision of the operating system + * @param string|null $kernelVersion An independent kernel version string + */ + public function __construct(string $name, ?string $version = null, ?string $build = null, ?string $kernelVersion = null) + { + if (0 === \strlen(trim($name))) { + throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); + } + + $this->name = $name; + $this->version = $version; + $this->build = $build; + $this->kernelVersion = $kernelVersion; + } + + /** + * Gets the name of the operating system. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Sets the name of the operating system. + * + * @param string $name The name + */ + public function setName(string $name): void + { + if (0 === \strlen(trim($name))) { + throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); + } + + $this->name = $name; + } + + /** + * Gets the version of the operating system. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Sets the version of the operating system. + * + * @param string|null $version The version + */ + public function setVersion(?string $version): void + { + $this->version = $version; + } + + /** + * Gets the build of the operating system. + */ + public function getBuild(): ?string + { + return $this->build; + } + + /** + * Sets the build of the operating system. + * + * @param string|null $build The build + */ + public function setBuild(?string $build): void + { + $this->build = $build; + } + + /** + * Gets the version of the kernel of the operating system. + */ + public function getKernelVersion(): ?string + { + return $this->kernelVersion; + } + + /** + * Sets the version of the kernel of the operating system. + * + * @param string|null $kernelVersion The kernel version + */ + public function setKernelVersion(?string $kernelVersion): void + { + $this->kernelVersion = $kernelVersion; + } +} diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index b67b080cb..9801e9ee3 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -4,103 +4,37 @@ namespace Sentry\Context; -use Sentry\Util\PHPVersion; -use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; -use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; -use Symfony\Component\OptionsResolver\OptionsResolver; - /** - * This class is a specialized implementation of the {@see Context} class that - * stores information about the current runtime. + * This class stores information about the current runtime. * * @author Stefano Arlandini - * - * @final since version 2.3 - * - * @template-extends Context */ -class RuntimeContext extends Context +final class RuntimeContext { /** - * @var OptionsResolver The options resolver + * @var string The name of the runtime */ - private $resolver; + private $name; /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules + * @var string|null The version of the runtime */ - public function __construct(array $data = []) - { - $this->resolver = new OptionsResolver(); - - $this->configureOptions($this->resolver); - - parent::__construct($this->resolver->resolve($data)); - } + private $version; /** - * {@inheritdoc} + * Constructor. * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules + * @param string $name The name of the runtime + * @param string|null $version The version of the runtime */ - public function merge(array $data, bool $recursive = false): void + public function __construct(string $name, ?string $version = null) { - $data = $this->resolver->resolve($data); + if (0 === \strlen(trim($name))) { + throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); + } - parent::merge($data, $recursive); - } - - /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules - */ - public function setData(array $data): void - { - $data = $this->resolver->resolve($data); - - parent::setData($data); - } - - /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules - */ - public function replaceData(array $data): void - { - $data = $this->resolver->resolve($data); - - parent::replaceData($data); - } - - /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules - */ - public function offsetSet($offset, $value): void - { - $data = $this->resolver->resolve([$offset => $value]); - - parent::offsetSet($offset, $data[$offset]); + $this->name = $name; + $this->version = $version; } /** @@ -108,7 +42,7 @@ public function offsetSet($offset, $value): void */ public function getName(): string { - return $this->data['name']; + return $this->name; } /** @@ -118,40 +52,28 @@ public function getName(): string */ public function setName(string $name): void { - $this->offsetSet('name', $name); + if (0 === \strlen(trim($name))) { + throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); + } + + $this->name = $name; } /** * Gets the version of the runtime. */ - public function getVersion(): string + public function getVersion(): ?string { - return $this->data['version']; + return $this->version; } /** * Sets the version of the runtime. * - * @param string $version The version + * @param string|null $version The version */ - public function setVersion(string $version): void + public function setVersion(?string $version): void { - $this->offsetSet('version', $version); - } - - /** - * Configures the options of the context. - * - * @param OptionsResolver $resolver The resolver for the options - */ - private function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'name' => 'php', - 'version' => PHPVersion::parseVersion(), - ]); - - $resolver->setAllowedTypes('name', 'string'); - $resolver->setAllowedTypes('version', 'string'); + $this->version = $version; } } diff --git a/src/Context/ServerOsContext.php b/src/Context/ServerOsContext.php deleted file mode 100644 index 3fc3151fe..000000000 --- a/src/Context/ServerOsContext.php +++ /dev/null @@ -1,196 +0,0 @@ - - * - * @final since version 2.3 - * - * @template-extends Context - */ -class ServerOsContext extends Context -{ - /** - * @var OptionsResolver The options resolver - */ - private $resolver; - - /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules - */ - public function __construct(array $data = []) - { - $this->resolver = new OptionsResolver(); - - $this->configureOptions($this->resolver); - - parent::__construct($this->resolver->resolve($data)); - } - - /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules - */ - public function merge(array $data, bool $recursive = false): void - { - $data = $this->resolver->resolve($data); - - parent::merge($data, $recursive); - } - - /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules - */ - public function setData(array $data): void - { - $data = $this->resolver->resolve($data); - - parent::setData($data); - } - - /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules - */ - public function replaceData(array $data): void - { - $data = $this->resolver->resolve($data); - - parent::replaceData($data); - } - - /** - * {@inheritdoc} - * - * @throws UndefinedOptionsException If any of the options are not supported - * by the context - * @throws InvalidOptionsException If any of the options don't fulfill the - * specified validation rules - */ - public function offsetSet($offset, $value): void - { - $data = $this->resolver->resolve([$offset => $value]); - - parent::offsetSet($offset, $data[$offset]); - } - - /** - * Gets the name of the operating system. - */ - public function getName(): string - { - return $this->data['name']; - } - - /** - * Sets the name of the operating system. - * - * @param string $name The name - */ - public function setName(string $name): void - { - $this->offsetSet('name', $name); - } - - /** - * Gets the version of the operating system. - */ - public function getVersion(): string - { - return $this->data['version']; - } - - /** - * Sets the version of the operating system. - * - * @param string $version The version - */ - public function setVersion(string $version): void - { - $this->offsetSet('version', $version); - } - - /** - * Gets the build of the operating system. - */ - public function getBuild(): string - { - return $this->data['build']; - } - - /** - * Sets the build of the operating system. - * - * @param string $build The build - */ - public function setBuild(string $build): void - { - $this->offsetSet('build', $build); - } - - /** - * Gets the version of the kernel of the operating system. - */ - public function getKernelVersion(): string - { - return $this->data['kernel_version']; - } - - /** - * Sets the version of the kernel of the operating system. - * - * @param string $kernelVersion The kernel version - */ - public function setKernelVersion(string $kernelVersion): void - { - $this->offsetSet('kernel_version', $kernelVersion); - } - - /** - * Configures the options of the context. - * - * @param OptionsResolver $resolver The resolver for the options - */ - private function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'name' => php_uname('s'), - 'version' => php_uname('r'), - 'build' => php_uname('v'), - 'kernel_version' => php_uname('a'), - ]); - - $resolver->setAllowedTypes('name', 'string'); - $resolver->setAllowedTypes('version', 'string'); - $resolver->setAllowedTypes('build', 'string'); - $resolver->setAllowedTypes('kernel_version', 'string'); - } -} diff --git a/src/Context/TagsContext.php b/src/Context/TagsContext.php deleted file mode 100644 index 2a892d334..000000000 --- a/src/Context/TagsContext.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * @final since version 2.3 - * - * @template-extends Context - */ -class TagsContext extends Context -{ - /** - * {@inheritdoc} - */ - public function merge(array $data, bool $recursive = false): void - { - if ($recursive) { - throw new \InvalidArgumentException('The tags context does not allow recursive merging of its data.'); - } - - parent::merge(self::sanitizeData($data)); - } - - /** - * {@inheritdoc} - */ - public function setData(array $data): void - { - parent::setData(self::sanitizeData($data)); - } - - /** - * {@inheritdoc} - */ - public function replaceData(array $data): void - { - parent::replaceData(self::sanitizeData($data)); - } - - /** - * {@inheritdoc} - */ - public function offsetSet($offset, $value): void - { - if (is_numeric($value)) { - $value = (string) $value; - } - - if (!\is_string($value)) { - throw new \InvalidArgumentException('The $value argument must be a string.'); - } - - parent::offsetSet($offset, $value); - } - - /** - * Sanitizes the given data by converting numeric values to strings. - * - * @param array $data The data to sanitize - * - * @return array - * - * @throws \InvalidArgumentException If any of the values of the input data - * is not a number or a string - */ - private static function sanitizeData(array $data): array - { - foreach ($data as &$value) { - if (is_numeric($value)) { - $value = (string) $value; - } - - if (!\is_string($value)) { - throw new \InvalidArgumentException('The $data argument must contains a simple array of string values.'); - } - } - - return $data; - } -} diff --git a/src/Context/UserContext.php b/src/Context/UserContext.php deleted file mode 100644 index fe3667f62..000000000 --- a/src/Context/UserContext.php +++ /dev/null @@ -1,86 +0,0 @@ - - */ -final class UserContext extends Context -{ - /** - * Gets the ID of the user. - */ - public function getId(): ?string - { - return $this->data['id'] ?? null; - } - - /** - * Sets the ID of the user. - * - * @param string|null $id The ID - */ - public function setId(?string $id): void - { - $this->data['id'] = $id; - } - - /** - * Gets the username of the user. - */ - public function getUsername(): ?string - { - return $this->data['username'] ?? null; - } - - /** - * Sets the username of the user. - * - * @param string|null $username The username - */ - public function setUsername(?string $username): void - { - $this->data['username'] = $username; - } - - /** - * Gets the email of the user. - */ - public function getEmail(): ?string - { - return $this->data['email'] ?? null; - } - - /** - * Sets the email of the user. - * - * @param string|null $email The email - */ - public function setEmail(?string $email): void - { - $this->data['email'] = $email; - } - - /** - * Gets the ip address of the user. - */ - public function getIpAddress(): ?string - { - return $this->data['ip_address'] ?? null; - } - - /** - * Sets the ip address of the user. - * - * @param string|null $ipAddress The ip address - */ - public function setIpAddress(?string $ipAddress): void - { - $this->data['ip_address'] = $ipAddress; - } -} diff --git a/src/Event.php b/src/Event.php index b9a416692..65c277145 100644 --- a/src/Event.php +++ b/src/Event.php @@ -5,11 +5,8 @@ namespace Sentry; use Jean85\PrettyVersions; -use Sentry\Context\Context; +use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; -use Sentry\Context\ServerOsContext; -use Sentry\Context\TagsContext; -use Sentry\Context\UserContext; use Sentry\Tracing\Span; use Sentry\Util\JSON; @@ -93,34 +90,34 @@ final class Event implements \JsonSerializable private $request = []; /** - * @var ServerOsContext The server OS context data + * @var array A list of tags associated to this event */ - private $serverOsContext; + private $tags = []; /** - * @var RuntimeContext The runtime context data + * @var OsContext|null The server OS context data */ - private $runtimeContext; + private $osContext; /** - * @var UserContext The user context data + * @var RuntimeContext|null The runtime context data */ - private $userContext; + private $runtimeContext; /** - * @var array> An arbitrary mapping of additional contexts associated to this event + * @var UserDataBag|null The user context data */ - private $contexts = []; + private $user; /** - * @var Context An arbitrary mapping of additional metadata + * @var array> An arbitrary mapping of additional contexts associated to this event */ - private $extraContext; + private $contexts = []; /** - * @var TagsContext A List of tags associated to this event + * @var array An arbitrary mapping of additional metadata */ - private $tagsContext; + private $extra = []; /** * @var string[] An array of strings used to dictate the deduplication of this event @@ -172,11 +169,6 @@ public function __construct(?EventId $eventId = null) $this->id = $eventId ?? EventId::generate(); $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); $this->level = Severity::error(); - $this->serverOsContext = new ServerOsContext(); - $this->runtimeContext = new RuntimeContext(); - $this->userContext = new UserContext(); - $this->extraContext = new Context(); - $this->tagsContext = new TagsContext(); $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); } @@ -446,45 +438,97 @@ public function setContext(string $name, array $data): self /** * Gets an arbitrary mapping of additional metadata. * - * @return Context + * @return array */ - public function getExtraContext(): Context + public function getExtra(): array { - return $this->extraContext; + return $this->extra; } /** - * Gets a list of tags. + * Sets an arbitrary mapping of additional metadata. + * + * @param array $extra The context object + */ + public function setExtra(array $extra): void + { + $this->extra = $extra; + } + + /** + * Gets a list of tags associated to this event. + * + * @return array + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * Sets a list of tags associated to this event. + * + * @param array $tags The tags to set */ - public function getTagsContext(): TagsContext + public function setTags(array $tags): void { - return $this->tagsContext; + $this->tags = $tags; } /** * Gets the user context. */ - public function getUserContext(): UserContext + public function getUser(): ?UserDataBag { - return $this->userContext; + return $this->user; + } + + /** + * Sets the user context. + * + * @param UserDataBag|null $user The context object + */ + public function setUser(?UserDataBag $user): void + { + $this->user = $user; } /** * Gets the server OS context. */ - public function getServerOsContext(): ServerOsContext + public function getOsContext(): ?OsContext + { + return $this->osContext; + } + + /** + * Sets the server OS context. + * + * @param OsContext|null $osContext The context object + */ + public function setOsContext(?OsContext $osContext): void { - return $this->serverOsContext; + $this->osContext = $osContext; } /** * Gets the runtime context data. */ - public function getRuntimeContext(): RuntimeContext + public function getRuntimeContext(): ?RuntimeContext { return $this->runtimeContext; } + /** + * Sets the runtime context data. + * + * @param RuntimeContext|null $runtimeContext The context object + */ + public function setRuntimeContext(?RuntimeContext $runtimeContext): void + { + $this->runtimeContext = $runtimeContext; + } + /** * Gets an array of strings used to dictate the deduplication of this * event. @@ -675,24 +719,37 @@ public function toArray(): array $data['modules'] = $this->modules; } - if (!$this->extraContext->isEmpty()) { - $data['extra'] = $this->extraContext->toArray(); + if (!empty($this->extra)) { + $data['extra'] = $this->extra; } - if (!$this->tagsContext->isEmpty()) { - $data['tags'] = $this->tagsContext->toArray(); + if (!empty($this->tags)) { + $data['tags'] = $this->tags; } - if (!$this->userContext->isEmpty()) { - $data['user'] = $this->userContext->toArray(); + if (null !== $this->user) { + $data['user'] = array_merge($this->user->getMetadata(), [ + 'id' => $this->user->getId(), + 'email' => $this->user->getEmail(), + 'ip_address' => $this->user->getIpAddress(), + 'username' => $this->user->getUsername(), + ]); } - if (!$this->serverOsContext->isEmpty()) { - $data['contexts']['os'] = $this->serverOsContext->toArray(); + if (null !== $this->osContext) { + $data['contexts']['os'] = [ + 'name' => $this->osContext->getName(), + 'version' => $this->osContext->getVersion(), + 'build' => $this->osContext->getBuild(), + 'kernel_version' => $this->osContext->getKernelVersion(), + ]; } - if (!$this->runtimeContext->isEmpty()) { - $data['contexts']['runtime'] = $this->runtimeContext->toArray(); + if (null !== $this->runtimeContext) { + $data['contexts']['runtime'] = [ + 'name' => $this->runtimeContext->getName(), + 'version' => $this->runtimeContext->getVersion(), + ]; } if (!empty($this->contexts)) { diff --git a/src/EventFactory.php b/src/EventFactory.php index f19a16174..03b134e94 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -118,7 +118,7 @@ public function create($payload): Event $event->setSdkVersion($this->sdkVersion); $event->setServerName($this->options->getServerName()); $event->setRelease($this->options->getRelease()); - $event->getTagsContext()->merge($this->options->getTags()); + $event->setTags($this->options->getTags()); $event->setEnvironment($this->options->getEnvironment()); return $event; diff --git a/src/Integration/EnvironmentIntegration.php b/src/Integration/EnvironmentIntegration.php new file mode 100644 index 000000000..9bbc236c4 --- /dev/null +++ b/src/Integration/EnvironmentIntegration.php @@ -0,0 +1,71 @@ + + */ +final class EnvironmentIntegration implements IntegrationInterface +{ + /** + * {@inheritdoc} + */ + public function setupOnce(): void + { + Scope::addGlobalEventProcessor(static function (Event $event): Event { + $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); + + if (null !== $integration) { + $event->setRuntimeContext($integration->updateRuntimeContext($event->getRuntimeContext())); + $event->setOsContext($integration->updateServerOsContext($event->getOsContext())); + } + + return $event; + }); + } + + private function updateRuntimeContext(?RuntimeContext $runtimeContext): RuntimeContext + { + if (null === $runtimeContext) { + $runtimeContext = new RuntimeContext('php'); + } + + if (null === $runtimeContext->getVersion()) { + $runtimeContext->setVersion(PHPVersion::parseVersion()); + } + + return $runtimeContext; + } + + private function updateServerOsContext(?OsContext $osContext): OsContext + { + if (null === $osContext) { + $osContext = new OsContext(php_uname('s')); + } + + if (null === $osContext->getVersion()) { + $osContext->setVersion(php_uname('r')); + } + + if (null === $osContext->getBuild()) { + $osContext->setBuild(php_uname('v')); + } + + if (null === $osContext->getKernelVersion()) { + $osContext->setKernelVersion(php_uname('a')); + } + + return $osContext; + } +} diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index b3eef674d..3995498ed 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -11,6 +11,7 @@ use Sentry\Options; use Sentry\SentrySdk; use Sentry\State\Scope; +use Sentry\UserDataBag; use Sentry\Util\JSON; /** @@ -93,17 +94,20 @@ private function processEvent(Event $event, Options $options): void $serverParams = $request->getServerParams(); if (isset($serverParams['REMOTE_ADDR'])) { + $user = $event->getUser(); $requestData['env']['REMOTE_ADDR'] = $serverParams['REMOTE_ADDR']; + + if (null === $user) { + $user = UserDataBag::createFromUserIpAddress($serverParams['REMOTE_ADDR']); + } elseif (null === $user->getIpAddress()) { + $user->setIpAddress($serverParams['REMOTE_ADDR']); + } + + $event->setUser($user); } $requestData['cookies'] = $request->getCookieParams(); $requestData['headers'] = $request->getHeaders(); - - $userContext = $event->getUserContext(); - - if (null === $userContext->getIpAddress() && isset($serverParams['REMOTE_ADDR'])) { - $userContext->setIpAddress($serverParams['REMOTE_ADDR']); - } } else { $requestData['headers'] = $this->removePiiFromHeaders($request->getHeaders()); } diff --git a/src/Options.php b/src/Options.php index c5aea28e5..fbee57477 100644 --- a/src/Options.php +++ b/src/Options.php @@ -4,6 +4,7 @@ namespace Sentry; +use Sentry\Integration\EnvironmentIntegration; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -927,6 +928,7 @@ private function getDefaultIntegrations(): array new RequestIntegration(), new TransactionIntegration(), new FrameContextifierIntegration(), + new EnvironmentIntegration(), ]; } diff --git a/src/State/Scope.php b/src/State/Scope.php index 83819256a..521dc036f 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -5,11 +5,9 @@ namespace Sentry\State; use Sentry\Breadcrumb; -use Sentry\Context\Context; -use Sentry\Context\TagsContext; -use Sentry\Context\UserContext; use Sentry\Event; use Sentry\Severity; +use Sentry\UserDataBag; /** * The scope holds data that should implicitly be sent with Sentry events. It @@ -23,7 +21,7 @@ final class Scope private $breadcrumbs = []; /** - * @var UserContext The user data associated to this scope + * @var UserDataBag|null The user data associated to this scope */ private $user; @@ -33,14 +31,14 @@ final class Scope private $contexts = []; /** - * @var TagsContext The list of tags associated to this scope + * @var array The list of tags associated to this scope */ - private $tags; + private $tags = []; /** - * @var Context A set of extra data associated to this scope + * @var array A set of extra data associated to this scope */ - private $extra; + private $extra = []; /** * @var string[] List of fingerprints used to group events together in @@ -64,17 +62,6 @@ final class Scope */ private static $globalEventProcessors = []; - /** - * Constructor. - */ - public function __construct() - { - $this->user = new UserContext(); - $this->tags = new TagsContext(); - $this->extra = new Context(); - $this->contexts = []; - } - /** * Sets a new tag in the tags context. * @@ -99,7 +86,7 @@ public function setTag(string $key, string $value): self */ public function setTags(array $tags): self { - $this->tags->merge($tags); + $this->tags = array_merge($this->tags, $tags); return $this; } @@ -157,21 +144,45 @@ public function setExtra(string $key, $value): self */ public function setExtras(array $extras): self { - $this->extra->merge($extras); + $this->extra = array_merge($this->extra, $extras); return $this; } /** - * Sets the given data in the user context. + * Merges the given data in the user context. * - * @param array $data The data + * @param array|UserDataBag $user The user data * * @return $this */ - public function setUser(array $data): self + public function setUser($user): self { - $this->user->merge($data); + if (!\is_array($user) && !$user instanceof UserDataBag) { + throw new \TypeError(sprintf('The $user argument must be either an array or an instance of the "%s" class. Got: "%s".', UserDataBag::class, get_debug_type($user))); + } + + if (\is_array($user)) { + $user = UserDataBag::createFromArray($user); + } + + if (null === $this->user) { + $this->user = $user; + } else { + $this->user = $this->user->merge($user); + } + + return $this; + } + + /** + * Removes all data of the user context. + * + * @return $this + */ + public function removeUser(): self + { + $this->user = null; return $this; } @@ -265,13 +276,12 @@ public static function addGlobalEventProcessor(callable $eventProcessor): void */ public function clear(): self { - $this->tags->clear(); - $this->extra->clear(); - $this->user->clear(); - + $this->user = null; $this->level = null; $this->fingerprint = []; $this->breadcrumbs = []; + $this->tags = []; + $this->extra = []; $this->contexts = []; return $this; @@ -286,9 +296,7 @@ public function clear(): self */ public function applyToEvent(Event $event, $payload): ?Event { - if (empty($event->getFingerprint())) { - $event->setFingerprint($this->fingerprint); - } + $event->setFingerprint(array_merge($event->getFingerprint(), $this->fingerprint)); if (empty($event->getBreadcrumbs())) { $event->setBreadcrumb($this->breadcrumbs); @@ -298,9 +306,25 @@ public function applyToEvent(Event $event, $payload): ?Event $event->setLevel($this->level); } - $event->getTagsContext()->merge($this->tags->toArray()); - $event->getExtraContext()->merge($this->extra->toArray()); - $event->getUserContext()->merge($this->user->toArray()); + if (!empty($this->tags)) { + $event->setTags(array_merge($this->tags, $event->getTags())); + } + + if (!empty($this->extra)) { + $event->setExtra(array_merge($this->extra, $event->getExtra())); + } + + if (null !== $this->user) { + $user = $event->getUser(); + + if (null === $user) { + $user = $this->user; + } else { + $user = $this->user->merge($user); + } + + $event->setUser($user); + } foreach (array_merge($this->contexts, $event->getContexts()) as $name => $data) { $event->setContext($name, $data); @@ -323,8 +347,8 @@ public function applyToEvent(Event $event, $payload): ?Event public function __clone() { - $this->user = clone $this->user; - $this->tags = clone $this->tags; - $this->extra = clone $this->extra; + if (null !== $this->user) { + $this->user = clone $this->user; + } } } diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 8655c88f0..91405d6c7 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -4,8 +4,6 @@ namespace Sentry\Tracing; -use Sentry\Context\Context; -use Sentry\Context\TagsContext; use Sentry\EventId; /** @@ -49,14 +47,14 @@ class Span implements \JsonSerializable protected $sampled; /** - * @var TagsContext A List of tags associated to this Span + * @var array A List of tags associated to this Span */ - protected $tags; + protected $tags = []; /** - * @var Context An arbitrary mapping of additional metadata + * @var array An arbitrary mapping of additional metadata */ - protected $data; + protected $data = []; /** * @var float Timestamp in seconds (epoch time) indicating when the span started @@ -92,16 +90,12 @@ public function __construct(?SpanContext $context = null) $this->status = $context->status ?? null; $this->sampled = $context->sampled ?? null; - if ($context && $context->tags) { - $this->tags = new TagsContext($context->tags); - } else { - $this->tags = new TagsContext(); + if (null !== $context && $context->tags) { + $this->tags = $context->tags; } - if ($context && $context->data) { - $this->data = new Context($context->data); - } else { - $this->data = new Context(); + if (null !== $context && $context->data) { + $this->data = $context->data; } $this->startTimestamp = $context->startTimestamp ?? microtime(true); @@ -197,12 +191,12 @@ public function toArray(): array $data['op'] = $this->op; } - if (!$this->data->isEmpty()) { - $data['data'] = $this->data->toArray(); + if (!empty($this->data)) { + $data['data'] = $this->data; } - if (!$this->tags->isEmpty()) { - $data['tags'] = $this->tags->toArray(); + if (!empty($this->tags)) { + $data['tags'] = $this->tags; } return $data; @@ -248,17 +242,24 @@ public function setStatus(?string $status): void $this->status = $status; } - public function getTags(): TagsContext + /** + * Gets a map of tags for this event. + * + * @return array + */ + public function getTags(): array { return $this->tags; } /** - * @param array $tags + * Sets a map of tags for this event. + * + * @param array $tags The tags */ public function setTags(array $tags): void { - $this->tags->merge($tags); + $this->tags = array_merge($this->tags, $tags); } public function getSpanId(): SpanId @@ -302,11 +303,11 @@ public function setSampled(?bool $sampled): void } /** - * @param array $data + * @param array $data */ public function setData(array $data): void { - $this->data->merge($data); + $this->data = array_merge($this->data, $data); } public function setStartTimestamp(float $startTimestamp): void diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index b56e0f478..ed05ebc74 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -105,7 +105,7 @@ public function toEvent(): Event { $event = new Event(); $event->setType('transaction'); - $event->getTagsContext()->merge($this->tags->toArray()); + $event->setTags(array_merge($event->getTags(), $this->tags)); $event->setTransaction($this->name); $event->setStartTimestamp($this->startTimestamp); $event->setContext('trace', $this->getTraceContext()); diff --git a/src/UserDataBag.php b/src/UserDataBag.php new file mode 100644 index 000000000..d142dcde5 --- /dev/null +++ b/src/UserDataBag.php @@ -0,0 +1,235 @@ + Additional data + */ + private $metadata = []; + + private function __construct() + { + } + + /** + * Creates an instance of this object from a user ID. + * + * @param string $id The ID of the user + */ + public static function createFromUserIdentifier(string $id): self + { + $instance = new self(); + $instance->setId($id); + + return $instance; + } + + /** + * Creates an instance of this object from an IP address. + * + * @param string $ipAddress The IP address of the user + */ + public static function createFromUserIpAddress(string $ipAddress): self + { + $instance = new self(); + $instance->setIpAddress($ipAddress); + + return $instance; + } + + /** + * Creates an instance of this object from the given data. + * + * @param array $data The raw data + */ + public static function createFromArray(array $data): self + { + if (!isset($data['id']) && !isset($data['ip_address'])) { + throw new \InvalidArgumentException('Either the "id" or the "ip_address" field must be set.'); + } + + $instance = new self(); + + foreach ($data as $field => $value) { + switch ($field) { + case 'id': + $instance->setId($data['id']); + break; + case 'ip_address': + $instance->setIpAddress($data['ip_address']); + break; + case 'email': + $instance->setEmail($data['email']); + break; + case 'username': + $instance->setUsername($data['username']); + break; + default: + $instance->setMetadata($field, $value); + break; + } + } + + return $instance; + } + + /** + * Gets the ID of the user. + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * Sets the ID of the user. + * + * @param string|null $id The ID + */ + public function setId(?string $id): void + { + if (null === $id && null === $this->ipAddress) { + throw new \BadMethodCallException('Either the IP address or the ID must be set.'); + } + + $this->id = $id; + } + + /** + * Gets the username of the user. + */ + public function getUsername(): ?string + { + return $this->username; + } + + /** + * Sets the username of the user. + * + * @param string|null $username The username + */ + public function setUsername(?string $username): void + { + $this->username = $username; + } + + /** + * Gets the email of the user. + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * Sets the email of the user. + * + * @param string|null $email The email + */ + public function setEmail(?string $email): void + { + $this->email = $email; + } + + /** + * Gets the ip address of the user. + */ + public function getIpAddress(): ?string + { + return $this->ipAddress; + } + + /** + * Sets the ip address of the user. + * + * @param string|null $ipAddress The ip address + */ + public function setIpAddress(?string $ipAddress): void + { + if (null !== $ipAddress && false === filter_var($ipAddress, FILTER_VALIDATE_IP)) { + throw new \InvalidArgumentException(sprintf('The "%s" value is not a valid IP address.', $ipAddress)); + } + + if (null === $ipAddress && null === $this->id) { + throw new \BadMethodCallException('Either the IP address or the ID must be set.'); + } + + $this->ipAddress = $ipAddress; + } + + /** + * Gets additional metadata. + * + * @return array + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * Sets the given field in the additional metadata. + * + * @param string $name The name of the field + * @param mixed $value The value + */ + public function setMetadata(string $name, $value): void + { + $this->metadata[$name] = $value; + } + + /** + * Removes the given field from the additional metadata. + * + * @param string $name The name of the field + */ + public function removeMetadata(string $name): void + { + unset($this->metadata[$name]); + } + + /** + * Merges the given context with this one. + * + * @param UserDataBag $other The context to merge the data with + * + * @return $this + */ + public function merge(self $other): self + { + $this->id = $other->id; + $this->email = $other->email; + $this->ipAddress = $other->ipAddress; + $this->username = $other->username; + $this->metadata = array_merge($this->metadata, $other->metadata); + + return $this; + } +} diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 03b435f7d..1cd4685dd 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -10,6 +10,8 @@ * This class provides some utility methods to encode/decode JSON data. * * @author Stefano Arlandini + * + * @internal */ final class JSON { diff --git a/src/Util/PHPVersion.php b/src/Util/PHPVersion.php index 22b32ff0d..c4dfecb07 100644 --- a/src/Util/PHPVersion.php +++ b/src/Util/PHPVersion.php @@ -8,7 +8,7 @@ * This class is an helper utility to parse the version of PHP and convert it * to a normalized form. * - * @internal since version 2.4 + * @internal */ final class PHPVersion { diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 9de42004c..2ac4113ab 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -9,6 +9,7 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\Integration\EnvironmentIntegration; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -22,11 +23,6 @@ final class ClientBuilderTest extends TestCase { - /** - * @group legacy - * - * @expectedDeprecationMessage Delaying the sending of the events using the "Sentry\Transport\HttpTransport" class is deprecated since version 2.2 and will not work in 3.0. - */ public function testHttpTransportIsUsedWhenServerIsConfigured(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); @@ -84,6 +80,7 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array RequestIntegration::class, TransactionIntegration::class, FrameContextifierIntegration::class, + EnvironmentIntegration::class, ], ], [ @@ -96,6 +93,7 @@ public function integrationsAreAddedToClientCorrectlyDataProvider(): array RequestIntegration::class, TransactionIntegration::class, FrameContextifierIntegration::class, + EnvironmentIntegration::class, StubIntegration::class, ], ], diff --git a/tests/Context/AbstractContextTest.php b/tests/Context/AbstractContextTest.php deleted file mode 100644 index 67df457a1..000000000 --- a/tests/Context/AbstractContextTest.php +++ /dev/null @@ -1,125 +0,0 @@ -expectException($expectedExceptionClass); - } - - if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessageRegExp($expectedExceptionMessage); - } - - $context = $this->createContext($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testMerge(array $initialData, array $expectedData, ?string $expectedExceptionClass, ?string $expectedExceptionMessage): void - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - } - - if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessageRegExp($expectedExceptionMessage); - } - - $context = $this->createContext(); - $context->merge($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testSetData(array $initialData, array $expectedData, ?string $expectedExceptionClass, ?string $expectedExceptionMessage): void - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - } - - if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessageRegExp($expectedExceptionMessage); - } - - $context = $this->createContext(); - $context->setData($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider valuesDataProvider - */ - public function testReplaceData(array $initialData, array $expectedData, ?string $expectedExceptionClass, ?string $expectedExceptionMessage): void - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - } - - if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessageRegExp($expectedExceptionMessage); - } - - $context = $this->createContext(); - $context->replaceData($initialData); - - $this->assertEquals($expectedData, $context->toArray()); - } - - /** - * @dataProvider offsetSetDataProvider - */ - public function testOffsetSet(string $key, $value, ?string $expectedExceptionClass, ?string $expectedExceptionMessage): void - { - if (null !== $expectedExceptionClass) { - $this->expectException($expectedExceptionClass); - } - - if (null !== $expectedExceptionMessage) { - $this->expectExceptionMessageRegExp($expectedExceptionMessage); - } - - $context = $this->createContext(); - $context[$key] = $value; - - $this->assertArrayHasKey($key, $context); - $this->assertSame($value, $context[$key]); - } - - /** - * @dataProvider gettersAndSettersDataProvider - */ - public function testGettersAndSetters(string $getterMethod, string $setterMethod, $value): void - { - $context = $this->createContext(); - $context->$setterMethod($value); - - $this->assertEquals($value, $context->$getterMethod()); - } - - abstract public function valuesDataProvider(): array; - - abstract public function offsetSetDataProvider(): array; - - abstract public function gettersAndSettersDataProvider(): array; - - abstract protected function createContext(array $initialData = []): Context; -} diff --git a/tests/Context/ContextTest.php b/tests/Context/ContextTest.php deleted file mode 100644 index 76a418776..000000000 --- a/tests/Context/ContextTest.php +++ /dev/null @@ -1,115 +0,0 @@ - 'bar']); - - $this->assertSame(['foo' => 'bar'], $context->toArray()); - } - - public function testMerge(): void - { - $context = new Context([ - 'foo' => 'bar', - 'bar' => [ - 'foobar' => 'barfoo', - ], - ]); - - $context->merge(['bar' => ['barfoo' => 'foobar']], true); - - $this->assertSame(['foo' => 'bar', 'bar' => ['foobar' => 'barfoo', 'barfoo' => 'foobar']], $context->toArray()); - - $context->merge(['bar' => 'foo']); - - $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $context->toArray()); - } - - public function testSetData(): void - { - $context = new Context(['foo' => 'bar']); - $context->setData(['bar' => 'foo']); - - $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $context->toArray()); - - $context->setData(['foo' => ['bar' => 'baz']]); - - $this->assertSame(['foo' => ['bar' => 'baz'], 'bar' => 'foo'], $context->toArray()); - } - - public function testReplaceData(): void - { - $context = new Context(['foo' => 'bar']); - $context->replaceData(['bar' => 'foo']); - - $this->assertSame(['bar' => 'foo'], $context->toArray()); - } - - public function testClear(): void - { - $context = new Context(['foo' => 'bar']); - - $this->assertSame(['foo' => 'bar'], $context->toArray()); - - $context->clear(); - - $this->assertSame([], $context->toArray()); - } - - public function testIsEmpty(): void - { - $context = new Context(); - - $this->assertTrue($context->isEmpty()); - - $context->setData(['foo' => 'bar']); - - $this->assertFalse($context->isEmpty()); - } - - public function testJsonSerialize(): void - { - $context = new Context(['foo' => 'bar']); - - $this->assertSame('{"foo":"bar"}', json_encode($context)); - } - - public function testArrayLikeBehaviour(): void - { - $context = new Context(); - - // Accessing a key that does not exists in the data object should behave - // like accessing a non-existent key of an array - @$context['foo']; - - $error = error_get_last(); - - $this->assertIsArray($error); - $this->assertSame('Undefined index: foo', $error['message']); - - $context['foo'] = 'bar'; - - $this->assertTrue(isset($context['foo'])); - $this->assertSame('bar', $context['foo']); - - unset($context['foo']); - - $this->assertArrayNotHasKey('foo', $context); - } - - public function testGetIterator(): void - { - $context = new Context(['foo' => 'bar', 'bar' => 'foo']); - - $this->assertSame($context->toArray(), iterator_to_array($context)); - } -} diff --git a/tests/Context/OsContextTest.php b/tests/Context/OsContextTest.php new file mode 100644 index 000000000..3ec5b3f6e --- /dev/null +++ b/tests/Context/OsContextTest.php @@ -0,0 +1,66 @@ +assertSame($expectedName, $context->getName()); + $this->assertSame($expectedVersion, $context->getVersion()); + $this->assertSame($expectedBuild, $context->getBuild()); + $this->assertSame($expectedKernelVersion, $context->getKernelVersion()); + } + + public function testConstructorThrowsOnInvalidArgument(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $name argument cannot be an empty string.'); + + new OsContext(''); + } + + /** + * @dataProvider valuesDataProvider + */ + public function testGettersAndSetters(string $expectedName, ?string $expectedVersion, ?string $expectedBuild, ?string $expectedKernelVersion): void + { + $context = new OsContext('Windows'); + $context->setName($expectedName); + $context->setVersion($expectedVersion); + $context->setBuild($expectedBuild); + $context->setKernelVersion($expectedKernelVersion); + + $this->assertSame($expectedName, $context->getName()); + $this->assertSame($expectedVersion, $context->getVersion()); + $this->assertSame($expectedBuild, $context->getBuild()); + $this->assertSame($expectedKernelVersion, $context->getKernelVersion()); + } + + public function valuesDataProvider(): iterable + { + yield [ + 'Linux', + '4.19.104-microsoft-standard', + '#1 SMP Wed Feb 19 06:37:35 UTC 2020', + 'Linux c03a247f5e13 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64', + ]; + + yield [ + 'Linux', + null, + null, + null, + ]; + } +} diff --git a/tests/Context/RuntimeContextTest.php b/tests/Context/RuntimeContextTest.php index 5a1c86331..59464a83e 100644 --- a/tests/Context/RuntimeContextTest.php +++ b/tests/Context/RuntimeContextTest.php @@ -4,124 +4,34 @@ namespace Sentry\Tests\Context; -use Sentry\Context\Context; +use PHPUnit\Framework\TestCase; use Sentry\Context\RuntimeContext; -use Sentry\Util\PHPVersion; -use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; -use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; -class RuntimeContextTest extends AbstractContextTest +final class RuntimeContextTest extends TestCase { - public function valuesDataProvider(): array + public function testConstructor(): void { - return [ - [ - [], - [ - 'name' => 'php', - 'version' => PHPVersion::parseVersion(), - ], - null, - null, - ], - [ - [ - 'name' => 'foo', - ], - [ - 'name' => 'foo', - 'version' => PHPVersion::parseVersion(), - ], - null, - null, - ], - [ - [ - 'name' => 'foo', - 'version' => 'bar', - ], - [ - 'name' => 'foo', - 'version' => 'bar', - ], - null, - null, - ], - [ - [ - 'foo' => 'bar', - ], - [], - UndefinedOptionsException::class, - '/^The option "foo" does not exist\. Defined options are: "name", "version"\.$/', - ], - [ - [ - 'name' => 1, - ], - [], - InvalidOptionsException::class, - '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - [ - 'version' => 1, - ], - [], - InvalidOptionsException::class, - '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - ]; - } + $context = new RuntimeContext('php', '7.4'); - public function offsetSetDataProvider(): array - { - return [ - [ - 'name', - 'foo', - null, - null, - ], - [ - 'name', - 1, - InvalidOptionsException::class, - '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - 'version', - 1, - InvalidOptionsException::class, - '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - 'foo', - 'bar', - UndefinedOptionsException::class, - '/^The option "foo" does not exist\. Defined options are: "name", "version"\.$/', - ], - ]; + $this->assertSame('php', $context->getName()); + $this->assertSame('7.4', $context->getVersion()); } - public function gettersAndSettersDataProvider(): array + public function testConstructorThrowsOnInvalidArgument(): void { - return [ - [ - 'getName', - 'setName', - 'foo', - ], - [ - 'getVersion', - 'setVersion', - 'bar', - ], - ]; + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $name argument cannot be an empty string.'); + + new RuntimeContext(''); } - protected function createContext(array $initialData = []): Context + public function testGettersAndSetters(): void { - return new RuntimeContext($initialData); + $context = new RuntimeContext('php'); + $context->setName('go'); + $context->setVersion('1.15'); + + $this->assertSame('go', $context->getName()); + $this->assertSame('1.15', $context->getVersion()); } } diff --git a/tests/Context/ServerOsContextTest.php b/tests/Context/ServerOsContextTest.php deleted file mode 100644 index 644aa621d..000000000 --- a/tests/Context/ServerOsContextTest.php +++ /dev/null @@ -1,213 +0,0 @@ - php_uname('s'), - 'version' => php_uname('r'), - 'build' => php_uname('v'), - 'kernel_version' => php_uname('a'), - ], - null, - null, - ], - [ - [ - 'name' => 'foo', - ], - [ - 'name' => 'foo', - 'version' => php_uname('r'), - 'build' => php_uname('v'), - 'kernel_version' => php_uname('a'), - ], - null, - null, - ], - [ - [ - 'version' => 'bar', - ], - [ - 'name' => php_uname('s'), - 'version' => 'bar', - 'build' => php_uname('v'), - 'kernel_version' => php_uname('a'), - ], - null, - null, - ], - [ - [ - 'build' => 'baz', - ], - [ - 'name' => php_uname('s'), - 'version' => php_uname('r'), - 'build' => 'baz', - 'kernel_version' => php_uname('a'), - ], - null, - null, - ], - [ - [ - 'kernel_version' => 'foobarbaz', - ], - [ - 'name' => php_uname('s'), - 'version' => php_uname('r'), - 'build' => php_uname('v'), - 'kernel_version' => 'foobarbaz', - ], - null, - null, - ], - [ - [ - 'foo' => 'bar', - ], - [], - UndefinedOptionsException::class, - '/^The option "foo" does not exist\. Defined options are: "build", "kernel_version", "name", "version"\.$/', - ], - [ - [ - 'name' => 1, - ], - [], - InvalidOptionsException::class, - '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - [ - 'version' => 1, - ], - [], - InvalidOptionsException::class, - '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - [ - 'build' => 1, - ], - [], - InvalidOptionsException::class, - '/^The option "build" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - [ - 'kernel_version' => 1, - ], - [], - InvalidOptionsException::class, - '/^The option "kernel_version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - ]; - } - - public function offsetSetDataProvider(): array - { - return [ - [ - 'name', - 'foo', - null, - null, - ], - [ - 'name', - 1, - InvalidOptionsException::class, - '/^The option "name" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - 'version', - 'foo', - null, - null, - ], - [ - 'version', - 1, - InvalidOptionsException::class, - '/^The option "version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - 'build', - 'foo', - null, - null, - ], - [ - 'build', - 1, - InvalidOptionsException::class, - '/^The option "build" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - 'kernel_version', - 'foobarbaz', - null, - null, - ], - [ - 'kernel_version', - 1, - InvalidOptionsException::class, - '/^The option "kernel_version" with value 1 is expected to be of type "string", but is of type "(integer|int)"\.$/', - ], - [ - 'foo', - 'bar', - UndefinedOptionsException::class, - '/^The option "foo" does not exist\. Defined options are: "build", "kernel_version", "name", "version"\.$/', - ], - ]; - } - - public function gettersAndSettersDataProvider(): array - { - return [ - [ - 'getName', - 'setName', - 'foo', - ], - [ - 'getVersion', - 'setVersion', - 'bar', - ], - [ - 'getBuild', - 'setBuild', - 'baz', - ], - [ - 'getKernelVersion', - 'setKernelVersion', - 'foobarbaz', - ], - ]; - } - - protected function createContext(array $initialData = []): Context - { - return new ServerOsContext($initialData); - } -} diff --git a/tests/Context/TagsContextTest.php b/tests/Context/TagsContextTest.php deleted file mode 100644 index d3b756dac..000000000 --- a/tests/Context/TagsContextTest.php +++ /dev/null @@ -1,143 +0,0 @@ -expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new TagsContext(['foo' => 'bar', 'bar' => 'foo']); - $context->merge($data, $recursive); - - $this->assertEquals($expectedData, $context->toArray()); - } - - public function mergeDataProvider() - { - return [ - [ - ['foo' => 'baz', 'baz' => 'foo', 'int' => 1, 'float' => 1.1], - false, - ['foo' => 'baz', 'bar' => 'foo', 'baz' => 'foo', 'int' => '1', 'float' => '1.1'], - null, - ], - [ - ['foo' => 'bar'], - true, - null, - 'The tags context does not allow recursive merging of its data.', - ], - [ - ['foo' => new \stdClass()], - false, - null, - 'The $data argument must contains a simple array of string values.', - ], - ]; - } - - /** - * @dataProvider setDataDataProvider - */ - public function testSetData($data, $expectException) - { - if ($expectException) { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The $data argument must contains a simple array of string values.'); - } - - $context = new TagsContext(); - $context->setData($data); - - $this->assertEquals(['foo' => 'bar'], $context->toArray()); - } - - public function setDataDataProvider() - { - return [ - [ - ['foo' => 'bar'], - false, - ], - [ - [new \stdClass()], - true, - ], - ]; - } - - /** - * @dataProvider replaceDataDataProvider - */ - public function testReplaceData($data, $expectException) - { - if ($expectException) { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The $data argument must contains a simple array of string values.'); - } - - $context = new TagsContext(['foo', 'bar']); - $context->replaceData($data); - - $this->assertEquals(['bar', 'foo'], $context->toArray()); - } - - public function replaceDataDataProvider() - { - return [ - [ - ['bar', 'foo'], - false, - ], - [ - [new \stdClass()], - true, - ], - ]; - } - - /** - * @dataProvider offsetSetDataProvider - */ - public function testOffsetSet($offset, $value, $expectedExceptionMessage) - { - if (null !== $expectedExceptionMessage) { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage($expectedExceptionMessage); - } - - $context = new TagsContext(); - $context[$offset] = $value; - - $this->assertEquals(['foo' => 'bar'], $context->toArray()); - } - - public function offsetSetDataProvider() - { - return [ - [ - 'foo', - 'bar', - null, - ], - [ - 'foo', - new \stdClass(), - 'The $value argument must be a string.', - ], - ]; - } -} diff --git a/tests/Context/UserContextTest.php b/tests/Context/UserContextTest.php deleted file mode 100644 index fe740a0aa..000000000 --- a/tests/Context/UserContextTest.php +++ /dev/null @@ -1,92 +0,0 @@ - 'foo', - 'username' => 'bar', - 'email' => 'foo@bar.baz', - ], - [ - 'id' => 'foo', - 'username' => 'bar', - 'email' => 'foo@bar.baz', - ], - null, - null, - ], - ]; - } - - public function offsetSetDataProvider(): array - { - return [ - [ - 'id', - 'foo', - null, - null, - ], - [ - 'username', - 'bar', - null, - null, - ], - [ - 'email', - 'foo@bar.baz', - null, - null, - ], - [ - 'ip_address', - '127.0.0.1', - null, - null, - ], - ]; - } - - public function gettersAndSettersDataProvider(): array - { - return [ - [ - 'getId', - 'setId', - 'foo', - ], - [ - 'getUsername', - 'setUsername', - 'bar', - ], - [ - 'getEmail', - 'setEmail', - 'foo@bar.baz', - ], - [ - 'getIpAddress', - 'setIpAddress', - '127.0.0.1', - ], - ]; - } - - protected function createContext(array $initialData = []): Context - { - return new UserContext($initialData); - } -} diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index 44e6a5af5..5fa0a24b6 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -42,7 +42,7 @@ public function testCreateEventWithDefaultValues(): void $this->assertSame('1.2.3', $event->getSdkVersion()); $this->assertSame($options->getServerName(), $event->getServerName()); $this->assertSame($options->getRelease(), $event->getRelease()); - $this->assertSame($options->getTags(), $event->getTagsContext()->toArray()); + $this->assertSame($options->getTags(), $event->getTags()); $this->assertSame($options->getEnvironment(), $event->getEnvironment()); $this->assertNull($event->getStacktrace()); } diff --git a/tests/EventTest.php b/tests/EventTest.php index 467d27050..c8a236a7d 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -8,6 +8,8 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; use Sentry\Client; +use Sentry\Context\OsContext; +use Sentry\Context\RuntimeContext; use Sentry\Event; use Sentry\Severity; use Sentry\Util\PHPVersion; @@ -41,18 +43,6 @@ public function testToArray(): void 'version' => PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(), ], 'level' => 'error', - 'contexts' => [ - 'os' => [ - 'name' => php_uname('s'), - 'version' => php_uname('r'), - 'build' => php_uname('v'), - 'kernel_version' => php_uname('a'), - ], - 'runtime' => [ - 'name' => 'php', - 'version' => PHPVersion::parseVersion(), - ], - ], ]; $this->assertSame($expected, $event->toArray()); @@ -63,6 +53,8 @@ public function testToArrayMergesCustomContextsWithDefaultContexts(): void ClockMock::register(Event::class); $event = new Event(); + $event->setOsContext(new OsContext(php_uname('s'), php_uname('r'), php_uname('v'), php_uname('a'))); + $event->setRuntimeContext(new RuntimeContext('php', PHPVersion::parseVersion())); $event->setContext('foo', ['foo' => 'bar']); $event->setContext('bar', ['bar' => 'foo']); $event->setContext('runtime', ['baz' => 'baz']); diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 15aee4ce2..30cf5b603 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -23,9 +23,6 @@ use function Sentry\init; use function Sentry\withScope; -/** - * @group legacy - */ final class FunctionsTest extends TestCase { public function testInit(): void diff --git a/tests/Integration/EnvironmentIntegrationTest.php b/tests/Integration/EnvironmentIntegrationTest.php new file mode 100644 index 000000000..8e005f616 --- /dev/null +++ b/tests/Integration/EnvironmentIntegrationTest.php @@ -0,0 +1,101 @@ +setupOnce(); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getIntegration') + ->willReturn($isIntegrationEnabled ? $integration : null); + + SentrySdk::getCurrentHub()->bindClient($client); + + withScope(function (Scope $scope) use ($expectedRuntimeContext, $expectedOsContext, $initialRuntimeContext, $initialOsContext): void { + $event = new Event(); + $event->setRuntimeContext($initialRuntimeContext); + $event->setOsContext($initialOsContext); + + $event = $scope->applyToEvent($event, []); + + $this->assertNotNull($event); + + $runtimeContext = $event->getRuntimeContext(); + $osContext = $event->getOsContext(); + + if (null === $expectedRuntimeContext) { + $this->assertNull($runtimeContext); + } else { + $this->assertSame($expectedRuntimeContext->getName(), $runtimeContext->getName()); + $this->assertSame($expectedRuntimeContext->getVersion(), $runtimeContext->getVersion()); + } + + if (null === $expectedOsContext) { + $this->assertNull($expectedOsContext); + } else { + $this->assertSame($expectedOsContext->getName(), $osContext->getName()); + $this->assertSame($expectedOsContext->getVersion(), $osContext->getVersion()); + $this->assertSame($expectedOsContext->getBuild(), $osContext->getBuild()); + $this->assertSame($expectedOsContext->getKernelVersion(), $osContext->getKernelVersion()); + } + }); + } + + public function invokeDataProvider(): iterable + { + yield 'Integration disabled => do nothing' => [ + false, + null, + null, + null, + null, + ]; + + yield 'Integration enabled && event context data not filled => replace whole context data' => [ + true, + null, + null, + new RuntimeContext('php', PHPVersion::parseVersion()), + new OsContext(php_uname('s'), php_uname('r'), php_uname('v'), php_uname('a')), + ]; + + yield 'Integration enabled && event context data filled => do nothing' => [ + true, + new RuntimeContext('go', '1.15'), + new OsContext('iOS', '13.5.1', '17F80', 'Darwin Kernel Version 19.5.0: Tue May 26 20:56:31 PDT 2020; root:xnu-6153.122.2~1/RELEASE_ARM64_T8015'), + new RuntimeContext('go', '1.15'), + new OsContext('iOS', '13.5.1', '17F80', 'Darwin Kernel Version 19.5.0: Tue May 26 20:56:31 PDT 2020; root:xnu-6153.122.2~1/RELEASE_ARM64_T8015'), + ]; + + yield 'Integration enabled && event context data partially filled => fill remaining data' => [ + true, + new RuntimeContext('php'), + new OsContext('Linux'), + new RuntimeContext('php', PHPVersion::parseVersion()), + new OsContext('Linux', php_uname('r'), php_uname('v'), php_uname('a')), + ]; + } +} diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index e8ddded9d..2fd7b85da 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -38,8 +38,6 @@ protected function setUp(): void } /** - * @group legacy - * * @dataProvider invokeDataProvider */ public function testInvoke(string $fixtureFilePath, int $lineNumber, int $contextLines, int $preContextCount, int $postContextCount): void diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 893c21666..efd507a26 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -18,6 +18,7 @@ use Sentry\Options; use Sentry\SentrySdk; use Sentry\State\Scope; +use Sentry\UserDataBag; use function Sentry\withScope; final class RequestIntegrationTest extends TestCase @@ -25,17 +26,16 @@ final class RequestIntegrationTest extends TestCase /** * @dataProvider invokeDataProvider */ - public function testInvoke(array $options, ServerRequestInterface $request, array $expectedRequestContextData, array $expectedUserContextData): void + public function testInvoke(array $options, ServerRequestInterface $request, array $expectedRequestContextData, ?UserDataBag $initialUser, ?UserDataBag $expectedUser): void { $event = new Event(); - $event->getUserContext()->setData(['foo' => 'bar']); + $event->setUser($initialUser); $integration = new RequestIntegration($this->createRequestFetcher($request)); $integration->setupOnce(); /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) ->method('getIntegration') ->willReturn($integration); @@ -46,16 +46,24 @@ public function testInvoke(array $options, ServerRequestInterface $request, arra SentrySdk::getCurrentHub()->bindClient($client); - withScope(function (Scope $scope) use ($event, $expectedRequestContextData, $expectedUserContextData): void { + withScope(function (Scope $scope) use ($event, $expectedRequestContextData, $initialUser, $expectedUser): void { $event = $scope->applyToEvent($event, []); $this->assertNotNull($event); - $this->assertEquals($expectedRequestContextData, $event->getRequest()); - $this->assertEquals($expectedUserContextData, $event->getUserContext()->toArray()); + $this->assertSame($expectedRequestContextData, $event->getRequest()); + + $user = $event->getUser(); + + if (null !== $expectedUser) { + $this->assertNotNull($user); + $this->assertEquals($expectedUser, $user); + } else { + $this->assertNull($user); + } }); } - public function invokeDataProvider(): \Generator + public function invokeDataProvider(): iterable { yield [ [ @@ -73,9 +81,8 @@ public function invokeDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], - [ - 'foo' => 'bar', - ], + UserDataBag::createFromUserIdentifier('unique_id'), + UserDataBag::createFromUserIdentifier('unique_id'), ]; yield [ @@ -91,9 +98,8 @@ public function invokeDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -109,9 +115,8 @@ public function invokeDataProvider(): \Generator 'Host' => ['www.example.com:1234'], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -126,9 +131,8 @@ public function invokeDataProvider(): \Generator 'Host' => ['www.example.com:1234'], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -144,6 +148,9 @@ public function invokeDataProvider(): \Generator 'url' => 'http://www.example.com/foo?foo=bar&bar=baz', 'method' => 'GET', 'query_string' => 'foo=bar&bar=baz', + 'env' => [ + 'REMOTE_ADDR' => '127.0.0.1', + ], 'cookies' => [], 'headers' => [ 'Host' => ['www.example.com'], @@ -151,14 +158,9 @@ public function invokeDataProvider(): \Generator 'Cookie' => ['bar'], 'Set-Cookie' => ['baz'], ], - 'env' => [ - 'REMOTE_ADDR' => '127.0.0.1', - ], - ], - [ - 'ip_address' => '127.0.0.1', - 'foo' => 'bar', ], + null, + UserDataBag::createFromUserIpAddress('127.0.0.1'), ]; yield [ @@ -178,9 +180,8 @@ public function invokeDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -200,9 +201,8 @@ public function invokeDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -226,9 +226,8 @@ public function invokeDataProvider(): \Generator 'bar' => 'bar value', ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -248,9 +247,8 @@ public function invokeDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -274,9 +272,8 @@ public function invokeDataProvider(): \Generator 'bar' => 'bar value', ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -296,9 +293,8 @@ public function invokeDataProvider(): \Generator 'Host' => ['www.example.com'], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -323,9 +319,8 @@ public function invokeDataProvider(): \Generator ], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -360,9 +355,8 @@ public function invokeDataProvider(): \Generator ], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -401,9 +395,8 @@ public function invokeDataProvider(): \Generator ], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -424,9 +417,8 @@ public function invokeDataProvider(): \Generator 'foo' => 'bar', ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -445,9 +437,8 @@ public function invokeDataProvider(): \Generator ], 'data' => '{', ], - [ - 'foo' => 'bar', - ], + null, + null, ]; yield [ @@ -465,15 +456,14 @@ public function invokeDataProvider(): \Generator 'Content-Type' => ['application/json'], ], ], - [ - 'foo' => 'bar', - ], + null, + null, ]; } private function getStreamMock(?int $size, string $content = ''): StreamInterface { - /** @var MockObject|StreamInterface $stream */ + /** @var MockObject&StreamInterface $stream */ $stream = $this->createMock(StreamInterface::class); $stream->expects($this->any()) ->method('getSize') diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index f7ba79b1c..0c1cc3fd7 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -29,7 +29,7 @@ public function testHandle(array $record, array $expectedPayload, array $expecte $event = $scopeArg->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame($expectedExtra, $event->getExtraContext()->toArray()); + $this->assertSame($expectedExtra, $event->getExtra()); return true; })); @@ -209,7 +209,6 @@ public function handleDataProvider(): iterable 'exception' => new \Exception('exception message'), ], 'channel' => 'channel.foo', - 'datetime' => new \DateTimeImmutable(), 'extra' => [], ], [ diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 20cbc0766..aa7c2c275 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Sentry\Dsn; +use Sentry\Integration\EnvironmentIntegration; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\ExceptionListenerIntegration; use Sentry\Integration\FatalErrorListenerIntegration; @@ -19,8 +20,6 @@ final class OptionsTest extends TestCase { /** - * @group legacy - * * @dataProvider optionsDataProvider */ public function testConstructor($option, $value, $getterMethod): void @@ -31,8 +30,6 @@ public function testConstructor($option, $value, $getterMethod): void } /** - * @group legacy - * * @dataProvider optionsDataProvider */ public function testGettersAndSetters(string $option, $value, string $getterMethod, ?string $setterMethod = null): void @@ -235,8 +232,6 @@ public function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array } /** - * @group legacy - * * @dataProvider contextLinesOptionValidatesInputValueDataProvider */ public function testContextLinesOptionValidatesInputValue(?int $value, ?string $expectedExceptionMessage): void @@ -339,11 +334,12 @@ public function setupOnce(): void ], [ new ExceptionListenerIntegration(), - new ErrorListenerIntegration(null, false), + new ErrorListenerIntegration(), new FatalErrorListenerIntegration(), new RequestIntegration(), new TransactionIntegration(), new FrameContextifierIntegration(), + new EnvironmentIntegration(), $integration, ], ]; @@ -394,11 +390,12 @@ static function (array $defaultIntegrations): array { }, [ new ExceptionListenerIntegration(), - new ErrorListenerIntegration(null, false), + new ErrorListenerIntegration(), new FatalErrorListenerIntegration(), new RequestIntegration(), new TransactionIntegration(), new FrameContextifierIntegration(), + new EnvironmentIntegration(), ], ]; } diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 4efc4eb79..87673202f 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -9,6 +9,7 @@ use Sentry\Event; use Sentry\Severity; use Sentry\State\Scope; +use Sentry\UserDataBag; final class ScopeTest extends TestCase { @@ -18,7 +19,7 @@ public function testSetTag(): void $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertTrue($event->getTagsContext()->isEmpty()); + $this->assertEmpty($event->getTags()); $scope->setTag('foo', 'bar'); $scope->setTag('bar', 'baz'); @@ -26,7 +27,7 @@ public function testSetTag(): void $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTagsContext()->toArray()); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); } public function testSetTags(): void @@ -37,14 +38,14 @@ public function testSetTags(): void $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar'], $event->getTagsContext()->toArray()); + $this->assertSame(['foo' => 'bar'], $event->getTags()); $scope->setTags(['bar' => 'baz']); $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTagsContext()->toArray()); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); } public function testSetAndRemoveContext(): void @@ -71,7 +72,7 @@ public function testSetExtra(): void $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertTrue($event->getExtraContext()->isEmpty()); + $this->assertEmpty($event->getExtra()); $scope->setExtra('foo', 'bar'); $scope->setExtra('bar', 'baz'); @@ -79,7 +80,7 @@ public function testSetExtra(): void $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtraContext()->toArray()); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtra()); } public function testSetExtras(): void @@ -90,38 +91,71 @@ public function testSetExtras(): void $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar'], $event->getExtraContext()->toArray()); + $this->assertSame(['foo' => 'bar'], $event->getExtra()); $scope->setExtras(['bar' => 'baz']); $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtraContext()->toArray()); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtra()); } public function testSetUser(): void { $scope = new Scope(); + $event = $scope->applyToEvent(new Event(), []); + + $this->assertNotNull($event); + $this->assertNull($event->getUser()); + + $user = UserDataBag::createFromUserIdentifier('unique_id'); + $user->setMetadata('subscription', 'basic'); + + $scope->setUser($user); $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame([], $event->getUserContext()->toArray()); + $this->assertSame($user, $event->getUser()); + + $user = UserDataBag::createFromUserIpAddress('127.0.0.1'); + $user->setMetadata('subscription', 'basic'); + $user->setMetadata('subscription_expires_at', '2020-08-26'); + + $scope->setUser(['ip_address' => '127.0.0.1', 'subscription_expires_at' => '2020-08-26']); - $scope->setUser(['foo' => 'bar']); + $event = $scope->applyToEvent($event, []); + + $this->assertNotNull($event); + $this->assertEquals($user, $event->getUser()); + } + + public function testSetUserThrowsOnInvalidArgument(): void + { + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('The $user argument must be either an array or an instance of the "Sentry\UserDataBag" class. Got: "string".'); + + $scope = new Scope(); + $scope->setUser('foo'); + } + + public function testRemoveUser(): void + { + $scope = new Scope(); + $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar'], $event->getUserContext()->toArray()); + $this->assertNotNull($event->getUser()); - $scope->setUser(['bar' => 'baz']); + $scope->removeUser(); $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getUserContext()->toArray()); + $this->assertNull($event->getUser()); } public function testSetFingerprint(): void @@ -255,18 +289,7 @@ public function testClear(): void $scope->setFingerprint(['foo']); $scope->setExtras(['foo' => 'bar']); $scope->setTags(['bar' => 'foo']); - $scope->setUser(['foobar' => 'barfoo']); - - $event = $scope->applyToEvent(new Event(), []); - - $this->assertNotNull($event); - $this->assertEquals(Severity::info(), $event->getLevel()); - $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); - $this->assertSame(['foo'], $event->getFingerprint()); - $this->assertSame(['foo' => 'bar'], $event->getExtraContext()->toArray()); - $this->assertSame(['bar' => 'foo'], $event->getTagsContext()->toArray()); - $this->assertSame(['foobar' => 'barfoo'], $event->getUserContext()->toArray()); - + $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); $scope->clear(); $event = $scope->applyToEvent(new Event(), []); @@ -275,14 +298,15 @@ public function testClear(): void $this->assertEquals(Severity::error(), $event->getLevel()); $this->assertEmpty($event->getBreadcrumbs()); $this->assertEmpty($event->getFingerprint()); - $this->assertEmpty($event->getExtraContext()->toArray()); - $this->assertEmpty($event->getTagsContext()->toArray()); - $this->assertEmpty($event->getUserContext()->toArray()); + $this->assertEmpty($event->getExtra()); + $this->assertEmpty($event->getTags()); + $this->assertEmpty($event->getUser()); } public function testApplyToEvent(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); + $user = UserDataBag::createFromUserIdentifier('unique_id'); $event = new Event(); $event->setContext('foocontext', ['foo' => 'foo', 'bar' => 'bar']); @@ -293,7 +317,7 @@ public function testApplyToEvent(): void $scope->addBreadcrumb($breadcrumb); $scope->setTag('foo', 'bar'); $scope->setExtra('bar', 'foo'); - $scope->setUser(['foo' => 'baz']); + $scope->setUser($user); $scope->setContext('foocontext', ['foo' => 'bar']); $scope->setContext('barcontext', ['bar' => 'foo']); @@ -301,9 +325,9 @@ public function testApplyToEvent(): void $this->assertTrue($event->getLevel()->isEqualTo(Severity::warning())); $this->assertSame(['foo'], $event->getFingerprint()); $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); - $this->assertSame(['foo' => 'bar'], $event->getTagsContext()->toArray()); - $this->assertSame(['bar' => 'foo'], $event->getExtraContext()->toArray()); - $this->assertSame(['foo' => 'baz'], $event->getUserContext()->toArray()); + $this->assertSame(['foo' => 'bar'], $event->getTags()); + $this->assertSame(['bar' => 'foo'], $event->getExtra()); + $this->assertSame($user, $event->getUser()); $this->assertSame(['foocontext' => ['foo' => 'foo', 'bar' => 'bar'], 'barcontext' => ['bar' => 'foo']], $event->getContexts()); } } diff --git a/tests/UserDataBagTest.php b/tests/UserDataBagTest.php new file mode 100644 index 000000000..26477fa06 --- /dev/null +++ b/tests/UserDataBagTest.php @@ -0,0 +1,152 @@ +expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "foo" value is not a valid IP address.'); + + UserDataBag::createFromUserIpAddress('foo'); + } + + public function testGettersAndSetters(): void + { + $userDataBag = UserDataBag::createFromUserIdentifier('unique_id'); + $userDataBag->setIpAddress('127.0.0.1'); + $userDataBag->setEmail('foo@example.com'); + $userDataBag->setUsername('my_user'); + $userDataBag->setMetadata('subscription', 'basic'); + + $this->assertSame('unique_id', $userDataBag->getId()); + $this->assertSame('127.0.0.1', $userDataBag->getIpAddress()); + $this->assertSame('foo@example.com', $userDataBag->getEmail()); + $this->assertSame('my_user', $userDataBag->getUsername()); + $this->assertSame(['subscription' => 'basic'], $userDataBag->getMetadata()); + } + + /** + * @dataProvider createFromArrayDataProvider + */ + public function testCreateFromArray(array $data, ?string $expectedId, ?string $expectedIpAddress, ?string $expectedEmail, ?string $expectedUsername, array $expectedMetadata): void + { + $userDataBag = UserDataBag::createFromArray($data); + + $this->assertSame($expectedId, $userDataBag->getId()); + $this->assertSame($expectedIpAddress, $userDataBag->getIpAddress()); + $this->assertSame($expectedEmail, $userDataBag->getEmail()); + $this->assertSame($expectedUsername, $userDataBag->getUsername()); + $this->assertSame($expectedMetadata, $userDataBag->getMetadata()); + } + + public function createFromArrayDataProvider(): iterable + { + yield [ + ['id' => 'unique_id'], + 'unique_id', + null, + null, + null, + [], + ]; + + yield [ + ['ip_address' => '127.0.0.1'], + null, + '127.0.0.1', + null, + null, + [], + ]; + + yield [ + [ + 'id' => 'unique_id', + 'email' => 'foo@example.com', + ], + 'unique_id', + null, + 'foo@example.com', + null, + [], + ]; + + yield [ + [ + 'id' => 'unique_id', + 'username' => 'my_user', + ], + 'unique_id', + null, + null, + 'my_user', + [], + ]; + + yield [ + [ + 'id' => 'unique_id', + 'subscription' => 'basic', + ], + 'unique_id', + null, + null, + null, + ['subscription' => 'basic'], + ]; + } + + public function testSetIdThrowsIfBothArgumentAndIpAddressAreNull(): void + { + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage('Either the IP address or the ID must be set.'); + + $userDataBag = UserDataBag::createFromUserIdentifier('unique_id'); + $userDataBag->setId(null); + } + + public function testSetIpAddressThrowsIfBothArgumentAndIdAreNull(): void + { + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage('Either the IP address or the ID must be set.'); + + $userDataBag = UserDataBag::createFromUserIpAddress('127.0.0.1'); + $userDataBag->setIpAddress(null); + } + + public function testSetIpAddressThrowsIfArgumentIsInvalid(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "foo" value is not a valid IP address.'); + + $userDataBag = UserDataBag::createFromUserIpAddress('127.0.0.1'); + $userDataBag->setIpAddress('foo'); + } + + public function testMerge(): void + { + $userDataBag = UserDataBag::createFromUserIdentifier('unique_id'); + $userDataBag->setMetadata('subscription', 'basic'); + + $userDataBagToMergeWith = UserDataBag::createFromUserIpAddress('127.0.0.1'); + $userDataBagToMergeWith->setEmail('foo@example.com'); + $userDataBagToMergeWith->setUsername('my_user'); + $userDataBagToMergeWith->setMetadata('subscription', 'lifetime'); + $userDataBagToMergeWith->setMetadata('subscription_expires_at', '2020-08-20'); + + $userDataBag = $userDataBag->merge($userDataBagToMergeWith); + + $this->assertNull($userDataBag->getId()); + $this->assertSame('127.0.0.1', $userDataBag->getIpAddress()); + $this->assertSame('foo@example.com', $userDataBag->getEmail()); + $this->assertSame('my_user', $userDataBag->getUsername()); + $this->assertSame(['subscription' => 'lifetime', 'subscription_expires_at' => '2020-08-20'], $userDataBag->getMetadata()); + } +} From 67f15ce3af1f2e7316fa891be68d8ad315f674d0 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 28 Aug 2020 12:01:55 +0200 Subject: [PATCH 0591/1161] fix: Wrap reflection methods into try/catch --- src/FrameBuilder.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 91ad97cd5..0cc72bd78 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -169,16 +169,20 @@ private function getFunctionArguments(array $backtraceFrame): array $reflectionFunction = null; - if (isset($backtraceFrame['class'], $backtraceFrame['function'])) { - if (method_exists($backtraceFrame['class'], $backtraceFrame['function'])) { - $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], $backtraceFrame['function']); - } elseif (isset($backtraceFrame['type']) && '::' === $backtraceFrame['type']) { - $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__callStatic'); - } else { - $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); + try { + if (isset($backtraceFrame['class'], $backtraceFrame['function'])) { + if (method_exists($backtraceFrame['class'], $backtraceFrame['function'])) { + $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], $backtraceFrame['function']); + } elseif (isset($backtraceFrame['type']) && '::' === $backtraceFrame['type']) { + $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__callStatic'); + } else { + $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); + } + } elseif (isset($backtraceFrame['function']) && !\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { + $reflectionFunction = new \ReflectionFunction($backtraceFrame['function']); } - } elseif (isset($backtraceFrame['function']) && !\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { - $reflectionFunction = new \ReflectionFunction($backtraceFrame['function']); + } catch (\ReflectionException $e) { + // Reflection failed, we do nothing instead } $argumentValues = []; From 9a88d192675e38797eede9d933cf0c4e16deec09 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 28 Aug 2020 12:05:12 +0200 Subject: [PATCH 0592/1161] fix: phpstan --- src/Serializer/RepresentationSerializer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index 2d91446f6..fb70b1008 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -21,6 +21,7 @@ public function representationSerialize($value) $value = $this->serializeRecursively($value); if (is_numeric($value)) { + /** @phpstan-ignore-next-line */ return (string) $value; } From 51ac40c15db37359928621d5ef37bd6a4f3188f8 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 1 Sep 2020 12:38:07 +0200 Subject: [PATCH 0593/1161] ref: Move Span to Scope --- src/State/Hub.php | 26 +++++------------- src/State/Scope.php | 54 +++++++++++++++++++++++++++++++++++++ src/Tracing/Span.php | 13 +++++++++ src/Tracing/Transaction.php | 13 --------- 4 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/State/Hub.php b/src/State/Hub.php index 8ff007dd9..ec9415315 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -28,11 +28,6 @@ final class Hub implements HubInterface */ private $lastEventId; - /** - * @var Span|null Set a Span on the Scope - */ - private $span; - /** * Hub constructor. * @@ -270,31 +265,24 @@ public function startTransaction(TransactionContext $context): Transaction */ public function getTransaction(): ?Transaction { - $span = $this->span; - if (null !== $span && null !== $span->spanRecorder && !empty($span->spanRecorder->getSpans())) { - // The first span in the recorder is considered to be a Transaction - /** @phpstan-ignore-next-line */ - return $span->spanRecorder->getSpans()[0]; - } - - return null; + return $this->getScope()->getTransaction(); } /** * {@inheritdoc} */ - public function getSpan(): ?Span + public function setSpan(?Span $span): HubInterface { - return $this->span; + $this->getScope()->setSpan($span); + + return $this; } /** * {@inheritdoc} */ - public function setSpan(?Span $span): HubInterface + public function getSpan(): ?Span { - $this->span = $span; - - return $this; + return $this->getScope()->getSpan(); } } diff --git a/src/State/Scope.php b/src/State/Scope.php index 521dc036f..ef255ba5d 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -7,6 +7,8 @@ use Sentry\Breadcrumb; use Sentry\Event; use Sentry\Severity; +use Sentry\Tracing\Span; +use Sentry\Tracing\Transaction; use Sentry\UserDataBag; /** @@ -57,6 +59,11 @@ final class Scope */ private $eventProcessors = []; + /** + * @var Span|null Set a Span on the Scope + */ + private $span; + /** * @var callable[] List of event processors */ @@ -278,6 +285,7 @@ public function clear(): self { $this->user = null; $this->level = null; + $this->span = null; $this->fingerprint = []; $this->breadcrumbs = []; $this->tags = []; @@ -326,6 +334,11 @@ public function applyToEvent(Event $event, $payload): ?Event $event->setUser($user); } + // We do this here to also apply the trace context to errors if there is a Span on the Scope + if (null !== $this->span) { + $event->setContext('trace', $this->span->getTraceContext()); + } + foreach (array_merge($this->contexts, $event->getContexts()) as $name => $data) { $event->setContext($name, $data); } @@ -345,6 +358,47 @@ public function applyToEvent(Event $event, $payload): ?Event return $event; } + /** + * Returns the Span that is on the Scope. + */ + public function getSpan(): ?Span + { + return $this->span; + } + + /** + * Sets the Span on the Scope. + * + * @param Span|null $span The Span + * + * @return $this + */ + public function setSpan(?Span $span): self + { + $this->span = $span; + + return $this; + } + + /** + * Returns the Transaction that is on the Scope. + * + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ + public function getTransaction(): ?Transaction + { + $span = $this->span; + + if (null !== $span && null !== $span->spanRecorder && !empty($span->spanRecorder->getSpans())) { + // The first span in the recorder is considered to be a Transaction + /** @phpstan-ignore-next-line */ + return $span->spanRecorder->getSpans()[0]; + } + + return null; + } + public function __clone() { if (null !== $this->user) { diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 91405d6c7..d68e5bfa3 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -202,6 +202,19 @@ public function toArray(): array return $data; } + /** + * @return array Returns the trace context + */ + public function getTraceContext(): array + { + $data = $this->toArray(); + + unset($data['start_timestamp']); + unset($data['timestamp']); + + return $data; + } + /** * {@inheritdoc} * diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index ed05ebc74..d632f814b 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -48,19 +48,6 @@ public function setName(string $name): void $this->name = $name; } - /** - * @return array Returns the trace context - */ - public function getTraceContext(): array - { - $data = $this->toArray(); - - unset($data['start_timestamp']); - unset($data['timestamp']); - - return $data; - } - /** * Attaches SpanRecorder to the transaction itself. */ From 310fccbf0a33eb6725372282810102bd6eef7f7b Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 1 Sep 2020 12:47:37 +0200 Subject: [PATCH 0594/1161] fix: PHPstan --- src/Serializer/RepresentationSerializer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index fb70b1008..2d91446f6 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -21,7 +21,6 @@ public function representationSerialize($value) $value = $this->serializeRecursively($value); if (is_numeric($value)) { - /** @phpstan-ignore-next-line */ return (string) $value; } From 5b7489057c13118f3cf780200005d139b8232f01 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 1 Sep 2020 14:39:12 +0200 Subject: [PATCH 0595/1161] Allow the user ID in the event payload to be either a string or an integer (#1078) --- src/UserDataBag.php | 18 ++++++++++++------ tests/UserDataBagTest.php | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/UserDataBag.php b/src/UserDataBag.php index d142dcde5..5849cbcaa 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -10,7 +10,7 @@ final class UserDataBag { /** - * @var string|null The unique ID of the user + * @var string|int|null The unique ID of the user */ private $id; @@ -41,9 +41,9 @@ private function __construct() /** * Creates an instance of this object from a user ID. * - * @param string $id The ID of the user + * @param string|int $id The ID of the user */ - public static function createFromUserIdentifier(string $id): self + public static function createFromUserIdentifier($id): self { $instance = new self(); $instance->setId($id); @@ -102,8 +102,10 @@ public static function createFromArray(array $data): self /** * Gets the ID of the user. + * + * @return string|int|null */ - public function getId(): ?string + public function getId() { return $this->id; } @@ -111,14 +113,18 @@ public function getId(): ?string /** * Sets the ID of the user. * - * @param string|null $id The ID + * @param string|int|null $id The ID */ - public function setId(?string $id): void + public function setId($id): void { if (null === $id && null === $this->ipAddress) { throw new \BadMethodCallException('Either the IP address or the ID must be set.'); } + if (!\is_string($id) && !\is_int($id)) { + throw new \UnexpectedValueException(sprintf('Expected an integer or string value for the $id argument. Got: "%s".', get_debug_type($id))); + } + $this->id = $id; } diff --git a/tests/UserDataBagTest.php b/tests/UserDataBagTest.php index 26477fa06..ba9ace823 100644 --- a/tests/UserDataBagTest.php +++ b/tests/UserDataBagTest.php @@ -35,7 +35,7 @@ public function testGettersAndSetters(): void /** * @dataProvider createFromArrayDataProvider */ - public function testCreateFromArray(array $data, ?string $expectedId, ?string $expectedIpAddress, ?string $expectedEmail, ?string $expectedUsername, array $expectedMetadata): void + public function testCreateFromArray(array $data, $expectedId, ?string $expectedIpAddress, ?string $expectedEmail, ?string $expectedUsername, array $expectedMetadata): void { $userDataBag = UserDataBag::createFromArray($data); @@ -48,6 +48,15 @@ public function testCreateFromArray(array $data, ?string $expectedId, ?string $e public function createFromArrayDataProvider(): iterable { + yield [ + ['id' => 1234], + 1234, + null, + null, + null, + [], + ]; + yield [ ['id' => 'unique_id'], 'unique_id', @@ -103,6 +112,30 @@ public function createFromArrayDataProvider(): iterable ]; } + /** + * @dataProvider setIdThrowsIfValueIsUnexpectedValueDataProvider + */ + public function testSetIdThrowsIfValueIsUnexpectedValue($value, string $expectedExceptionMessage): void + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + UserDataBag::createFromUserIdentifier($value); + } + + public function setIdThrowsIfValueIsUnexpectedValueDataProvider(): iterable + { + yield [ + 12.34, + 'Expected an integer or string value for the $id argument. Got: "float".', + ]; + + yield [ + new \stdClass(), + 'Expected an integer or string value for the $id argument. Got: "stdClass".', + ]; + } + public function testSetIdThrowsIfBothArgumentAndIpAddressAreNull(): void { $this->expectException(\BadMethodCallException::class); From 4862e4437bced05db05a5205d629aeb830cb89a7 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 2 Sep 2020 11:53:04 +0200 Subject: [PATCH 0596/1161] Remove the FlushableClientInterface and ClosableTransportInterface interfaces (#1079) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 1 + src/Client.php | 8 +------ src/ClientInterface.php | 9 +++++++ src/FlushableClientInterface.php | 24 ------------------- src/Transport/ClosableTransportInterface.php | 23 ------------------ src/Transport/HttpTransport.php | 8 +++++++ src/Transport/NullTransport.php | 8 +++++++ src/Transport/SpoolTransport.php | 8 +++++++ src/Transport/TransportInterface.php | 8 +++++++ tests/ClientTest.php | 20 ++++++++++++++++ tests/Transport/HttpTransportTest.php | 17 +++++++++++++ tests/Transport/NullTransportTest.php | 21 ++++++++++++++-- tests/Transport/SpoolTransportTest.php | 8 +++++++ .../error_handler_captures_fatal_error.phpt | 5 ++++ ...rror_handler_respects_error_reporting.phpt | 5 ++++ ...tegration_respects_error_types_option.phpt | 5 ++++ ...rror_integration_captures_fatal_error.phpt | 5 ++++ ...tegration_respects_error_types_option.phpt | 5 ++++ 19 files changed, 133 insertions(+), 56 deletions(-) delete mode 100644 src/FlushableClientInterface.php delete mode 100644 src/Transport/ClosableTransportInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a0c29da..1cf521295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ More information can be found in our [Performance](https://docs.sentry.io/produc - [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) - [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) - [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) +- [BC BREAK] Remove the `FlushableClientInterface` and the `ClosableTransportInterface` interfaces (#1079) - Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) ### 2.4.3 (2020-08-13) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index bd0cf8c03..f2e68062d 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -103,3 +103,4 @@ - Removed the `Event::getUserContext()` method, use `Event::getUser()` instead - Renamed the `Event::getServerOsContext()` method to `Event::getOsContext()` - The signature of the `Scope::setUser()` method changed ot accept a plain array +- Removed the `FlushableClientInterface` and `ClosableTransportInterface` interfaces. Their methods have been moved to the corresponding `ClientInterface` and `TransportInterface` interfaces diff --git a/src/Client.php b/src/Client.php index 653d5f0df..6b82fe6af 100644 --- a/src/Client.php +++ b/src/Client.php @@ -4,14 +4,12 @@ namespace Sentry; -use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Integration\Handler; use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; -use Sentry\Transport\ClosableTransportInterface; use Sentry\Transport\TransportInterface; /** @@ -19,7 +17,7 @@ * * @author Stefano Arlandini */ -final class Client implements FlushableClientInterface +final class Client implements ClientInterface { /** * The version of the protocol to communicate with the Sentry server. @@ -161,10 +159,6 @@ public function getIntegration(string $className): ?IntegrationInterface */ public function flush(?int $timeout = null): PromiseInterface { - if (!$this->transport instanceof ClosableTransportInterface) { - return new FulfilledPromise(true); - } - return $this->transport->close($timeout); } diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 39dcbaf02..82d2aedc5 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -4,6 +4,7 @@ namespace Sentry; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; @@ -63,4 +64,12 @@ public function captureEvent($payload, ?Scope $scope = null): ?EventId; * @psalm-return T|null */ public function getIntegration(string $className): ?IntegrationInterface; + + /** + * Flushes the queue of events pending to be sent. If a timeout is provided + * and the queue takes longer to drain, the promise resolves with `false`. + * + * @param int|null $timeout Maximum time in seconds the client should wait + */ + public function flush(?int $timeout = null): PromiseInterface; } diff --git a/src/FlushableClientInterface.php b/src/FlushableClientInterface.php deleted file mode 100644 index b68c5e6ea..000000000 --- a/src/FlushableClientInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ -interface FlushableClientInterface extends ClientInterface -{ - /** - * Flushes the queue of events pending to be sent. If a timeout is provided - * and the queue takes longer to drain, the promise resolves with `false`. - * - * @param int|null $timeout Maximum time in seconds the client should wait - */ - public function flush(?int $timeout = null): PromiseInterface; -} diff --git a/src/Transport/ClosableTransportInterface.php b/src/Transport/ClosableTransportInterface.php deleted file mode 100644 index 65d758d51..000000000 --- a/src/Transport/ClosableTransportInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - - */ -interface ClosableTransportInterface -{ - /** - * Waits until all pending requests have been sent or the timeout expires. - * - * @param int|null $timeout Maximum time in seconds before the sending - * operation is interrupted - */ - public function close(?int $timeout = null): PromiseInterface; -} diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 11818020f..a60095268 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -116,4 +116,12 @@ public function send(Event $event): PromiseInterface return new RejectedPromise($sendResponse); } + + /** + * {@inheritdoc} + */ + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } } diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index 087b7f9fd..7aeef1728 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -26,4 +26,12 @@ public function send(Event $event): PromiseInterface { return new FulfilledPromise(new Response(ResponseStatus::skipped(), $event)); } + + /** + * {@inheritdoc} + */ + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } } diff --git a/src/Transport/SpoolTransport.php b/src/Transport/SpoolTransport.php index 1b816e15a..465ef6e39 100644 --- a/src/Transport/SpoolTransport.php +++ b/src/Transport/SpoolTransport.php @@ -53,4 +53,12 @@ public function send(Event $event): PromiseInterface return new RejectedPromise(new Response(ResponseStatus::skipped(), $event)); } + + /** + * {@inheritdoc} + */ + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } } diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index 2a2608966..b7d56962e 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -23,4 +23,12 @@ interface TransportInterface * @return PromiseInterface Returns the ID of the event or `null` if it failed to be sent */ public function send(Event $event): PromiseInterface; + + /** + * Waits until all pending requests have been sent or the timeout expires. + * + * @param int|null $timeout Maximum time in seconds before the sending + * operation is interrupted + */ + public function close(?int $timeout = null): PromiseInterface; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 93c679256..79e6497f3 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -5,6 +5,7 @@ namespace Sentry\Tests; use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use PHPUnit\Framework\MockObject\Matcher\Invocation; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -371,6 +372,25 @@ public function testAttachStacktrace(): void $this->assertNotNull($client->captureMessage('test')); } + public function testFlush(): void + { + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('close') + ->with(10) + ->willReturn(new FulfilledPromise(true)); + + $client = ClientBuilder::create() + ->setTransportFactory($this->createTransportFactory($transport)) + ->getClient(); + + $promise = $client->flush(10); + + $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); + $this->assertTrue($promise->wait()); + } + /** * @see https://github.com/symfony/polyfill/blob/52332f49d18c413699d2dccf465234356f8e0b2c/src/Php70/Php70.php#L52-L61 */ diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 219de3465..957a1da21 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -13,7 +13,9 @@ use Http\Promise\RejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Log\LoggerInterface; use Sentry\Event; use Sentry\HttpClient\HttpClientFactory; @@ -162,4 +164,19 @@ public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientExce $this->assertSame(ResponseStatus::failed(), $promiseResult->getStatus()); $this->assertSame($event, $promiseResult->getEvent()); } + + public function testClose(): void + { + $transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/sentry/1']), + $this->createMock(HttpAsyncClientInterface::class), + $this->createMock(StreamFactoryInterface::class), + $this->createMock(RequestFactoryInterface::class) + ); + + $promise = $transport->close(); + + $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); + $this->assertTrue($promise->wait()); + } } diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php index a7281384c..f83f022c5 100644 --- a/tests/Transport/NullTransportTest.php +++ b/tests/Transport/NullTransportTest.php @@ -12,16 +12,33 @@ final class NullTransportTest extends TestCase { + /** + * @var NullTransport + */ + private $transport; + + protected function setUp(): void + { + $this->transport = new NullTransport(); + } + public function testSend(): void { - $transport = new NullTransport(); $event = new Event(); - $promise = $transport->send($event); + $promise = $this->transport->send($event); $promiseResult = $promise->wait(); $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); $this->assertSame(ResponseStatus::skipped(), $promiseResult->getStatus()); $this->assertSame($event, $promiseResult->getEvent()); } + + public function testClose(): void + { + $promise = $this->transport->close(); + + $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); + $this->assertTrue($promise->wait()); + } } diff --git a/tests/Transport/SpoolTransportTest.php b/tests/Transport/SpoolTransportTest.php index bbd637493..c795f7445 100644 --- a/tests/Transport/SpoolTransportTest.php +++ b/tests/Transport/SpoolTransportTest.php @@ -75,4 +75,12 @@ public function sendDataProvider(): iterable ResponseStatus::skipped(), ]; } + + public function testClose(): void + { + $promise = $this->transport->close(); + + $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); + $this->assertTrue($promise->wait()); + } } diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index 61eaaa2c2..48f079d3a 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -37,6 +37,11 @@ $transportFactory = new class implements TransportFactoryInterface { return new FulfilledPromise(new Response(ResponseStatus::success())); } + + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } }; } }; diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_error_reporting.phpt index acd2232cb..b03612bf7 100644 --- a/tests/phpt/error_handler_respects_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_reporting.phpt @@ -38,6 +38,11 @@ $transportFactory = new class implements TransportFactoryInterface { return new FulfilledPromise(new Response(ResponseStatus::success())); } + + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } }; } }; diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt index b6cd91c3e..870a0696f 100644 --- a/tests/phpt/error_listener_integration_respects_error_types_option.phpt +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -39,6 +39,11 @@ $transportFactory = new class implements TransportFactoryInterface { return new FulfilledPromise(new Response(ResponseStatus::success())); } + + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } }; } }; diff --git a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt index de62b55f5..b5705cd74 100644 --- a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt @@ -37,6 +37,11 @@ $transportFactory = new class implements TransportFactoryInterface { return new FulfilledPromise(new Response(ResponseStatus::success())); } + + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } }; } }; diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index 749d4b13f..b57487945 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -37,6 +37,11 @@ $transportFactory = new class implements TransportFactoryInterface { return new FulfilledPromise(new Response(ResponseStatus::success())); } + + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } }; } }; From bbd0c337ed92d8f7186244096412a05956069982 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 3 Sep 2020 10:18:48 +0200 Subject: [PATCH 0597/1161] Implement a payload serializer to serialize events, transactions, sessions, etc (#1077) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 4 + composer.json | 1 + src/Breadcrumb.php | 29 +- src/Client.php | 2 +- src/ClientBuilder.php | 15 +- src/Event.php | 201 +-------- src/EventFactory.php | 1 + src/EventType.php | 59 +++ src/Frame.php | 65 +-- src/FrameBuilder.php | 5 +- .../FrameContextifierIntegration.php | 2 +- src/Serializer/PayloadSerializer.php | 354 +++++++++++++++ src/Serializer/PayloadSerializerInterface.php | 23 + src/Stacktrace.php | 25 +- src/State/Scope.php | 4 + src/Tracing/Span.php | 291 +++++++----- src/Tracing/Transaction.php | 16 +- src/Transport/DefaultTransportFactory.php | 2 + src/Transport/HttpTransport.php | 27 +- src/functions.php | 4 +- tests/BreadcrumbTest.php | 99 ++-- tests/EventFactoryTest.php | 67 ++- tests/EventTest.php | 194 +------- tests/FrameBuilderTest.php | 4 +- tests/FrameTest.php | 30 -- tests/HttpClient/HttpClientFactoryTest.php | 3 +- .../FrameContextifierIntegrationTest.php | 4 +- tests/Serializer/PayloadSerializerTest.php | 427 ++++++++++++++++++ tests/StacktraceTest.php | 16 - tests/State/ScopeTest.php | 4 +- tests/Tracing/SpanTest.php | 57 ++- tests/Tracing/TransactionTest.php | 65 ++- .../Transport/DefaultTransportFactoryTest.php | 11 +- tests/Transport/HttpTransportTest.php | 194 +++++--- tests/bootstrap.php | 19 + 36 files changed, 1437 insertions(+), 888 deletions(-) create mode 100644 src/EventType.php create mode 100644 src/Serializer/PayloadSerializer.php create mode 100644 src/Serializer/PayloadSerializerInterface.php create mode 100644 tests/Serializer/PayloadSerializerTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cf521295..7d6b25bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ More information can be found in our [Performance](https://docs.sentry.io/produc - [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) - [BC BREAK] Remove the `FlushableClientInterface` and the `ClosableTransportInterface` interfaces (#1079) - Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) +- Refactor how the event data gets serialized to JSON (#1077) ### 2.4.3 (2020-08-13) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index f2e68062d..ccdeab9f3 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -104,3 +104,7 @@ - Renamed the `Event::getServerOsContext()` method to `Event::getOsContext()` - The signature of the `Scope::setUser()` method changed ot accept a plain array - Removed the `FlushableClientInterface` and `ClosableTransportInterface` interfaces. Their methods have been moved to the corresponding `ClientInterface` and `TransportInterface` interfaces +- Removed the `Event::toArray()` and `Event::jsonSerialize()` methods +- Removed the `Breadcrumb::toArray()` and `Breadcrumb::jsonSerialize()` methods +- Removed the `Frame::toArray()` and `Frame::jsonSerialize()` methods +- Removed the `Stacktrace::toArray()` and `Stacktrace::jsonSerialize()` methods diff --git a/composer.json b/composer.json index b7f0b3020..f5421589b 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "guzzlehttp/promises": "^1.3", "guzzlehttp/psr7": "^1.6", "jean85/pretty-package-versions": "^1.2", + "ocramius/package-versions": "^1.8", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", "php-http/discovery": "^1.6.1", diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index b8fe14d95..3b65be03f 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -11,7 +11,7 @@ * * @author Stefano Arlandini */ -final class Breadcrumb implements \JsonSerializable +final class Breadcrumb { /** * This constant defines the default breadcrumb type. @@ -300,23 +300,6 @@ public function getTimestamp(): float return $this->timestamp; } - /** - * Gets the breadcrumb as an array. - * - * @return array - */ - public function toArray(): array - { - return [ - 'type' => $this->type, - 'category' => $this->category, - 'level' => $this->level, - 'message' => $this->message, - 'timestamp' => $this->timestamp, - 'data' => $this->metadata, - ]; - } - /** * Helper method to create an instance of this class from an array of data. * @@ -340,14 +323,4 @@ public static function fromArray(array $data): self $data['data'] ?? [] ); } - - /** - * {@inheritdoc} - * - * @return array - */ - public function jsonSerialize(): array - { - return $this->toArray(); - } } diff --git a/src/Client.php b/src/Client.php index 6b82fe6af..6b9aadfeb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -180,7 +180,7 @@ private function prepareEvent($payload, ?Scope $scope = null): ?Event $sampleRate = $this->options->getSampleRate(); - if ('transaction' !== $event->getType() && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { + if (EventType::transaction() !== $event->getType() && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]); return null; diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 1281f450b..a5e78224c 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -188,7 +188,13 @@ private function createEventFactory(): EventFactoryInterface $this->serializer = $this->serializer ?? new Serializer($this->options); $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer($this->options); - return new EventFactory($this->serializer, $this->representationSerializer, $this->options, $this->sdkIdentifier, $this->sdkVersion); + return new EventFactory( + $this->serializer, + $this->representationSerializer, + $this->options, + $this->sdkIdentifier, + $this->sdkVersion + ); } /** @@ -206,6 +212,11 @@ private function createDefaultTransportFactory(): DefaultTransportFactory $this->sdkVersion ); - return new DefaultTransportFactory($streamFactory, Psr17FactoryDiscovery::findRequestFactory(), $httpClientFactory, $this->logger); + return new DefaultTransportFactory( + $streamFactory, + Psr17FactoryDiscovery::findRequestFactory(), + $httpClientFactory, + $this->logger + ); } } diff --git a/src/Event.php b/src/Event.php index 65c277145..300a40467 100644 --- a/src/Event.php +++ b/src/Event.php @@ -8,14 +8,13 @@ use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; use Sentry\Tracing\Span; -use Sentry\Util\JSON; /** * This is the base class for classes containing event data. * * @author Stefano Arlandini */ -final class Event implements \JsonSerializable +final class Event { /** * @var EventId The ID @@ -30,7 +29,7 @@ final class Event implements \JsonSerializable /** * This property is used if it's a Transaction event together with $timestamp it's the duration of the transaction. * - * @var string|float|null The date and time of when this event was generated + * @var float|null The date and time of when this event was generated */ private $startTimestamp; @@ -155,7 +154,7 @@ final class Event implements \JsonSerializable private $sdkVersion; /** - * @var string|null The type of the Event "default" | "transaction" + * @var EventType The type of the Event */ private $type; @@ -168,8 +167,8 @@ public function __construct(?EventId $eventId = null) { $this->id = $eventId ?? EventId::generate(); $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); - $this->level = Severity::error(); $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + $this->type = EventType::default(); } /** @@ -633,209 +632,51 @@ public function setStacktrace(?Stacktrace $stacktrace): void $this->stacktrace = $stacktrace; } - public function getType(): ?string + public function getType(): EventType { return $this->type; } - public function setType(?string $type): void + public function setType(EventType $type): void { - if ('default' !== $type && 'transaction' !== $type) { - $type = null; - } $this->type = $type; } /** - * @param string|float|null $startTimestamp The start time of the event + * Gets a timestamp representing when the measuring of a transaction started. */ - public function setStartTimestamp($startTimestamp): void + public function getStartTimestamp(): ?float { - $this->startTimestamp = $startTimestamp; - } - - /** - * @param Span[] $spans Array of spans - */ - public function setSpans(array $spans): void - { - $this->spans = $spans; + return $this->startTimestamp; } /** - * Gets the event as an array. + * Sets a timestamp representing when the measuring of a transaction started. * - * @return array + * @param float|null $startTimestamp The start time of the measurement */ - public function toArray(): array + public function setStartTimestamp(?float $startTimestamp): void { - $data = [ - 'event_id' => (string) $this->id, - 'timestamp' => $this->timestamp, - 'platform' => 'php', - 'sdk' => [ - 'name' => $this->sdkIdentifier, - 'version' => $this->getSdkVersion(), - ], - ]; - - if (null !== $this->level) { - $data['level'] = (string) $this->level; - } - - if (null !== $this->startTimestamp) { - $data['start_timestamp'] = $this->startTimestamp; - } - - if (null !== $this->type) { - $data['type'] = $this->type; - } - - if (null !== $this->logger) { - $data['logger'] = $this->logger; - } - - if (null !== $this->transaction) { - $data['transaction'] = $this->transaction; - } - - if (null !== $this->serverName) { - $data['server_name'] = $this->serverName; - } - - if (null !== $this->release) { - $data['release'] = $this->release; - } - - if (null !== $this->environment) { - $data['environment'] = $this->environment; - } - - if (!empty($this->fingerprint)) { - $data['fingerprint'] = $this->fingerprint; - } - - if (!empty($this->modules)) { - $data['modules'] = $this->modules; - } - - if (!empty($this->extra)) { - $data['extra'] = $this->extra; - } - - if (!empty($this->tags)) { - $data['tags'] = $this->tags; - } - - if (null !== $this->user) { - $data['user'] = array_merge($this->user->getMetadata(), [ - 'id' => $this->user->getId(), - 'email' => $this->user->getEmail(), - 'ip_address' => $this->user->getIpAddress(), - 'username' => $this->user->getUsername(), - ]); - } - - if (null !== $this->osContext) { - $data['contexts']['os'] = [ - 'name' => $this->osContext->getName(), - 'version' => $this->osContext->getVersion(), - 'build' => $this->osContext->getBuild(), - 'kernel_version' => $this->osContext->getKernelVersion(), - ]; - } - - if (null !== $this->runtimeContext) { - $data['contexts']['runtime'] = [ - 'name' => $this->runtimeContext->getName(), - 'version' => $this->runtimeContext->getVersion(), - ]; - } - - if (!empty($this->contexts)) { - $data['contexts'] = array_merge($data['contexts'] ?? [], $this->contexts); - } - - if (!empty($this->breadcrumbs)) { - $data['breadcrumbs']['values'] = $this->breadcrumbs; - } - - if ('transaction' === $this->getType()) { - $data['spans'] = array_values(array_map(function (Span $span): array { - return $span->toArray(); - }, $this->spans)); - } - - foreach (array_reverse($this->exceptions) as $exception) { - $exceptionMechanism = $exception->getMechanism(); - $exceptionStacktrace = $exception->getStacktrace(); - $exceptionValue = [ - 'type' => $exception->getType(), - 'value' => $exception->getValue(), - ]; - - if (null !== $exceptionStacktrace) { - $exceptionValue['stacktrace'] = [ - 'frames' => $exceptionStacktrace->toArray(), - ]; - } - - if (null !== $exceptionMechanism) { - $exceptionValue['mechanism'] = [ - 'type' => $exceptionMechanism->getType(), - 'handled' => $exceptionMechanism->isHandled(), - ]; - } - - $data['exception']['values'][] = $exceptionValue; - } - - if (null !== $this->stacktrace) { - $data['stacktrace'] = [ - 'frames' => $this->stacktrace->toArray(), - ]; - } - - if (!empty($this->request)) { - $data['request'] = $this->request; - } - - if (null !== $this->message) { - if (empty($this->messageParams)) { - $data['message'] = $this->message; - } else { - $data['message'] = [ - 'message' => $this->message, - 'params' => $this->messageParams, - 'formatted' => $this->messageFormatted ?? vsprintf($this->message, $this->messageParams), - ]; - } - } - - return $data; + $this->startTimestamp = $startTimestamp; } /** - * {@inheritdoc} + * A list of timed application events that have a start and end time. * - * @return array + * @return Span[] */ - public function jsonSerialize(): array + public function getSpans(): array { - return $this->toArray(); + return $this->spans; } /** - * Converts an Event to an Envelope. + * Sets a list of timed application events that have a start and end time. * - * @throws Exception\JsonException + * @param Span[] $spans The list of spans */ - public function toEnvelope(): string + public function setSpans(array $spans): void { - $rawEvent = $this->jsonSerialize(); - $envelopeHeader = JSON::encode(['event_id' => $rawEvent['event_id'], 'sent_at' => gmdate('Y-m-d\TH:i:s\Z')]); - $itemHeader = JSON::encode(['type' => $rawEvent['type'] ?? 'event', 'content_type' => 'application/json']); - - return vsprintf("%s\n%s\n%s", [$envelopeHeader, $itemHeader, JSON::encode($rawEvent)]); + $this->spans = $spans; } } diff --git a/src/EventFactory.php b/src/EventFactory.php index 03b134e94..c3b917953 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -86,6 +86,7 @@ public function create($payload): Event $event = $payload; } else { $event = new Event(); + if (isset($payload['logger'])) { $event->setLogger($payload['logger']); } diff --git a/src/EventType.php b/src/EventType.php new file mode 100644 index 000000000..99e9053cb --- /dev/null +++ b/src/EventType.php @@ -0,0 +1,59 @@ + + */ +final class EventType +{ + /** + * @var string The value of the enum instance + */ + private $value; + + /** + * @var array A list of cached enum instances + */ + private static $instances = []; + + private function __construct(string $value) + { + $this->value = $value; + } + + /** + * Creates an instance of this enum for the "default" value. + */ + public static function default(): self + { + return self::getInstance('default'); + } + + /** + * Creates an instance of this enum for the "transaction" value. + */ + public static function transaction(): self + { + return self::getInstance('transaction'); + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Frame.php b/src/Frame.php index 91a01b801..ade6ac811 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -9,7 +9,7 @@ * * @author Stefano Arlandini */ -final class Frame implements \JsonSerializable +final class Frame { public const INTERNAL_FRAME_FILENAME = '[internal]'; @@ -32,7 +32,7 @@ final class Frame implements \JsonSerializable private $file; /** - * @var string The absolute path to the source file + * @var string|null The absolute path to the source file */ private $absoluteFilePath; @@ -92,7 +92,7 @@ public function __construct(?string $functionName, string $file, int $line, ?str $this->file = $file; $this->line = $line; $this->rawFunctionName = $rawFunctionName; - $this->absoluteFilePath = $absoluteFilePath ?? $file; + $this->absoluteFilePath = $absoluteFilePath; $this->vars = $vars; $this->inApp = $inApp; } @@ -125,7 +125,7 @@ public function getFile(): string /** * Gets the absolute path to the source file. */ - public function getAbsoluteFilePath(): string + public function getAbsoluteFilePath(): ?string { return $this->absoluteFilePath; } @@ -247,61 +247,4 @@ public function isInternal(): bool { return self::INTERNAL_FRAME_FILENAME === $this->file; } - - /** - * Returns an array representation of the data of this frame modeled according - * to the specifications of the Sentry SDK Stacktrace Interface. - * - * @psalm-return array{ - * function: string|null, - * raw_function: string|null, - * filename: string, - * lineno: int, - * in_app: bool, - * abs_path: string, - * pre_context?: string[], - * context_line?: string, - * post_context?: string[], - * vars?: array - * } - */ - public function toArray(): array - { - $result = [ - 'function' => $this->functionName, - 'raw_function' => $this->rawFunctionName, - 'filename' => $this->file, - 'lineno' => $this->line, - 'in_app' => $this->inApp, - 'abs_path' => $this->absoluteFilePath, - ]; - - if (0 !== \count($this->preContext)) { - $result['pre_context'] = $this->preContext; - } - - if (null !== $this->contextLine) { - $result['context_line'] = $this->contextLine; - } - - if (0 !== \count($this->postContext)) { - $result['post_context'] = $this->postContext; - } - - if (!empty($this->vars)) { - $result['vars'] = $this->vars; - } - - return $result; - } - - /** - * {@inheritdoc} - * - * @return array - */ - public function jsonSerialize(): array - { - return $this->toArray(); - } } diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 0cc72bd78..5bbb40b80 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -64,6 +64,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac $functionName = null; $rawFunctionName = null; + $strippedFilePath = $this->stripPrefixFromFilePath($file); if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['class']; @@ -80,10 +81,10 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac return new Frame( $functionName, - $this->stripPrefixFromFilePath($file), + $strippedFilePath, $line, $rawFunctionName, - Frame::INTERNAL_FRAME_FILENAME !== $file ? $file : null, + Frame::INTERNAL_FRAME_FILENAME !== $file && $strippedFilePath !== $file ? $file : null, $this->getFunctionArguments($backtraceFrame), $this->isFrameInApp($file, $functionName) ); diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 36fee0e78..129ddfd3a 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -78,7 +78,7 @@ public function setupOnce(): void private function addContextToStacktraceFrames(int $maxContextLines, Stacktrace $stacktrace): void { foreach ($stacktrace->getFrames() as $frame) { - if ($frame->isInternal()) { + if ($frame->isInternal() || null === $frame->getAbsoluteFilePath()) { continue; } diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php new file mode 100644 index 000000000..57faf5df2 --- /dev/null +++ b/src/Serializer/PayloadSerializer.php @@ -0,0 +1,354 @@ +getType()) { + return $this->serializeAsEnvelope($event); + } + + return $this->serializeAsEvent($event); + } + + private function serializeAsEvent(Event $event): string + { + $result = [ + 'event_id' => (string) $event->getId(), + 'timestamp' => $event->getTimestamp(), + 'platform' => 'php', + 'sdk' => [ + 'name' => $event->getSdkIdentifier(), + 'version' => $event->getSdkVersion(), + ], + ]; + + if (null !== $event->getStartTimestamp()) { + $result['start_timestamp'] = $event->getStartTimestamp(); + } + + if (null !== $event->getLevel()) { + $result['level'] = (string) $event->getLevel(); + } + + if (null !== $event->getLogger()) { + $result['logger'] = $event->getLogger(); + } + + if (null !== $event->getTransaction()) { + $result['transaction'] = $event->getTransaction(); + } + + if (null !== $event->getServerName()) { + $result['server_name'] = $event->getServerName(); + } + + if (null !== $event->getRelease()) { + $result['release'] = $event->getRelease(); + } + + if (null !== $event->getEnvironment()) { + $result['environment'] = $event->getEnvironment(); + } + + if (!empty($event->getFingerprint())) { + $result['fingerprint'] = $event->getFingerprint(); + } + + if (!empty($event->getModules())) { + $result['modules'] = $event->getModules(); + } + + if (!empty($event->getExtra())) { + $result['extra'] = $event->getExtra(); + } + + if (!empty($event->getTags())) { + $result['tags'] = $event->getTags(); + } + + $user = $event->getUser(); + + if (null !== $user) { + $result['user'] = array_merge($user->getMetadata(), [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + 'ip_address' => $user->getIpAddress(), + ]); + } + + $osContext = $event->getOsContext(); + $runtimeContext = $event->getRuntimeContext(); + + if (null !== $osContext) { + $result['contexts']['os'] = [ + 'name' => $osContext->getName(), + 'version' => $osContext->getVersion(), + 'build' => $osContext->getBuild(), + 'kernel_version' => $osContext->getKernelVersion(), + ]; + } + + if (null !== $runtimeContext) { + $result['contexts']['runtime'] = [ + 'name' => $runtimeContext->getName(), + 'version' => $runtimeContext->getVersion(), + ]; + } + + if (!empty($event->getContexts())) { + $result['contexts'] = array_merge($result['contexts'] ?? [], $event->getContexts()); + } + + if (!empty($event->getBreadcrumbs())) { + $result['breadcrumbs']['values'] = array_map([$this, 'serializeBreadcrumb'], $event->getBreadcrumbs()); + } + + if (!empty($event->getRequest())) { + $result['request'] = $event->getRequest(); + } + + if (null !== $event->getMessage()) { + if (empty($event->getMessageParams())) { + $result['message'] = $event->getMessage(); + } else { + $result['message'] = [ + 'message' => $event->getMessage(), + 'params' => $event->getMessageParams(), + 'formatted' => $event->getMessageFormatted() ?? vsprintf($event->getMessage(), $event->getMessageParams()), + ]; + } + } + + $exceptions = $event->getExceptions(); + + for ($i = \count($exceptions) - 1; $i >= 0; --$i) { + $result['exception']['values'][] = $this->serializeException($exceptions[$i]); + } + + if (EventType::transaction() === $event->getType()) { + $result['spans'] = array_map([$this, 'serializeSpan'], $event->getSpans()); + } + + return JSON::encode($result); + } + + private function serializeAsEnvelope(Event $event): string + { + $envelopeHeader = JSON::encode([ + 'event_id' => (string) $event->getId(), + 'sent_at' => gmdate('Y-m-d\TH:i:s\Z'), + ]); + + $itemHeader = JSON::encode([ + 'type' => (string) $event->getType(), + 'content_type' => 'application/json', + ]); + + return sprintf("%s\n%s\n%s", $envelopeHeader, $itemHeader, $this->serializeAsEvent($event)); + } + + /** + * @return array + * + * @psalm-return array{ + * type: string, + * category: string, + * level: string, + * timestamp: float, + * message?: string, + * data?: array + * } + */ + private function serializeBreadcrumb(Breadcrumb $breadcrumb): array + { + $result = [ + 'type' => $breadcrumb->getType(), + 'category' => $breadcrumb->getCategory(), + 'level' => $breadcrumb->getLevel(), + 'timestamp' => $breadcrumb->getTimestamp(), + ]; + + if (null !== $breadcrumb->getMessage()) { + $result['message'] = $breadcrumb->getMessage(); + } + + if (!empty($breadcrumb->getMetadata())) { + $result['data'] = $breadcrumb->getMetadata(); + } + + return $result; + } + + /** + * @return array + * + * @psalm-return array{ + * type: string, + * value: string, + * stacktrace?: array{ + * frames: array> + * }, + * mechanism?: array{ + * type: string, + * handled: boolean + * } + * } + */ + private function serializeException(ExceptionDataBag $exception): array + { + $exceptionMechanism = $exception->getMechanism(); + $exceptionStacktrace = $exception->getStacktrace(); + $result = [ + 'type' => $exception->getType(), + 'value' => $exception->getValue(), + ]; + + if (null !== $exceptionStacktrace) { + $result['stacktrace'] = [ + 'frames' => array_map([$this, 'serializeStacktraceFrame'], $exceptionStacktrace->getFrames()), + ]; + } + + if (null !== $exceptionMechanism) { + $result['mechanism'] = [ + 'type' => $exceptionMechanism->getType(), + 'handled' => $exceptionMechanism->isHandled(), + ]; + } + + return $result; + } + + /** + * @return array + * + * @psalm-return array{ + * filename: string, + * lineno: int, + * in_app: bool, + * abs_path?: string, + * function?: string, + * raw_function?: string, + * pre_context?: string[], + * context_line?: string, + * post_context?: string[], + * vars?: array + * } + */ + private function serializeStacktraceFrame(Frame $frame): array + { + $result = [ + 'filename' => $frame->getFile(), + 'lineno' => $frame->getLine(), + 'in_app' => $frame->isInApp(), + ]; + + if (null !== $frame->getAbsoluteFilePath()) { + $result['abs_path'] = $frame->getAbsoluteFilePath(); + } + + if (null !== $frame->getFunctionName()) { + $result['function'] = $frame->getFunctionName(); + } + + if (null !== $frame->getRawFunctionName()) { + $result['raw_function'] = $frame->getRawFunctionName(); + } + + if (!empty($frame->getPreContext())) { + $result['pre_context'] = $frame->getPreContext(); + } + + if (null !== $frame->getContextLine()) { + $result['context_line'] = $frame->getContextLine(); + } + + if (!empty($frame->getPostContext())) { + $result['post_context'] = $frame->getPostContext(); + } + + if (!empty($frame->getVars())) { + $result['vars'] = $frame->getVars(); + } + + return $result; + } + + /** + * @return array + * + * @psalm-return array{ + * span_id: string, + * trace_id: string, + * parent_span_id?: string, + * start_timestamp: float, + * timestamp?: float, + * status?: string, + * description?: string, + * op?: string, + * data?: array, + * tags?: array + * } + */ + private function serializeSpan(Span $span): array + { + $result = [ + 'span_id' => (string) $span->getSpanId(), + 'trace_id' => (string) $span->getTraceId(), + 'start_timestamp' => $span->getStartTimestamp(), + ]; + + if (null !== $span->getParentSpanId()) { + $result['parent_span_id'] = (string) $span->getParentSpanId(); + } + + if (null !== $span->getEndTimestamp()) { + $result['timestamp'] = $span->getEndTimestamp(); + } + + if (null !== $span->getStatus()) { + $result['status'] = $span->getStatus(); + } + + if (null !== $span->getDescription()) { + $result['description'] = $span->getDescription(); + } + + if (null !== $span->getOp()) { + $result['op'] = $span->getOp(); + } + + if (!empty($span->getData())) { + $result['data'] = $span->getData(); + } + + if (!empty($span->getTags())) { + $result['tags'] = $span->getTags(); + } + + return $result; + } +} diff --git a/src/Serializer/PayloadSerializerInterface.php b/src/Serializer/PayloadSerializerInterface.php new file mode 100644 index 000000000..f1cabf15a --- /dev/null +++ b/src/Serializer/PayloadSerializerInterface.php @@ -0,0 +1,23 @@ + */ -final class Stacktrace implements \JsonSerializable +final class Stacktrace { /** * @var Frame[] The frames that compose the stacktrace @@ -93,27 +93,4 @@ public function removeFrame(int $index): void array_splice($this->frames, $index, 1); } - - /** - * Gets the stacktrace frames (this is the same as calling the getFrames - * method). - * - * @return array - */ - public function toArray(): array - { - return array_map(static function (Frame $frame): array { - return $frame->toArray(); - }, $this->frames); - } - - /** - * {@inheritdoc} - * - * @return array - */ - public function jsonSerialize() - { - return $this->toArray(); - } } diff --git a/src/State/Scope.php b/src/State/Scope.php index ef255ba5d..da84a55b7 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -56,6 +56,8 @@ final class Scope /** * @var callable[] List of event processors + * + * @psalm-var array */ private $eventProcessors = []; @@ -66,6 +68,8 @@ final class Scope /** * @var callable[] List of event processors + * + * @psalm-var array */ private static $globalEventProcessors = []; diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index d68e5bfa3..61dc924d1 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -9,7 +9,7 @@ /** * This class stores all the information about a Span. */ -class Span implements \JsonSerializable +class Span { /** * @var SpanId Span ID @@ -103,153 +103,141 @@ public function __construct(?SpanContext $context = null) } /** - * Sets the finish timestamp on the current span. - * - * @param float|null $endTimestamp Takes an endTimestamp if the end should not be the time when you call this function + * Sets the ID of the span. * - * @return EventId|null Finish for a span always returns null + * @param SpanId $spanId The ID */ - public function finish($endTimestamp = null): ?EventId + public function setSpanId(SpanId $spanId): void { - $this->endTimestamp = $endTimestamp ?? microtime(true); + $this->spanId = $spanId; + } - return null; + /** + * Gets the ID that determines which trace the span belongs to. + */ + public function getTraceId(): TraceId + { + return $this->traceId; } /** - * Creates a new `Span` while setting the current `Span.id` as `parentSpanId`. - * Also the `sampled` decision will be inherited. - * - * @param SpanContext $context The Context of the child span + * Sets the ID that determines which trace the span belongs to. * - * @return Span Instance of the newly created Span + * @param TraceId $traceId The ID */ - public function startChild(SpanContext $context): self + public function setTraceId(TraceId $traceId): void { - $context->sampled = $this->sampled; - $context->parentSpanId = $this->spanId; - $context->traceId = $this->traceId; - - $span = new self($context); - - $span->spanRecorder = $this->spanRecorder; - if (null != $span->spanRecorder) { - $span->spanRecorder->add($span); - } - - return $span; + $this->traceId = $traceId; } - public function getStartTimestamp(): float + /** + * Gets the ID that determines which span is the parent of the current one. + */ + public function getParentSpanId(): ?SpanId { - return $this->startTimestamp; + return $this->parentSpanId; } /** - * Returns `sentry-trace` header content. + * Sets the ID that determines which span is the parent of the current one. + * + * @param SpanId|null $parentSpanId The ID */ - public function toTraceparent(): string + public function setParentSpanId(?SpanId $parentSpanId): void { - $sampled = ''; - if (null !== $this->sampled) { - $sampled = $this->sampled ? '-1' : '-0'; - } + $this->parentSpanId = $parentSpanId; + } - return $this->traceId . '-' . $this->spanId . $sampled; + /** + * Gets the timestamp representing when the measuring started. + */ + public function getStartTimestamp(): float + { + return $this->startTimestamp; } /** - * Gets the event as an array. + * Sets the timestamp representing when the measuring started. * - * @return array + * @param float $startTimestamp The timestamp */ - public function toArray(): array + public function setStartTimestamp(float $startTimestamp): void { - $data = [ - 'span_id' => (string) $this->spanId, - 'trace_id' => (string) $this->traceId, - 'start_timestamp' => $this->startTimestamp, - ]; - - if (null !== $this->parentSpanId) { - $data['parent_span_id'] = (string) $this->parentSpanId; - } - - if (null !== $this->endTimestamp) { - $data['timestamp'] = $this->endTimestamp; - } - - if (null !== $this->status) { - $data['status'] = $this->status; - } - - if (null !== $this->description) { - $data['description'] = $this->description; - } - - if (null !== $this->op) { - $data['op'] = $this->op; - } - - if (!empty($this->data)) { - $data['data'] = $this->data; - } - - if (!empty($this->tags)) { - $data['tags'] = $this->tags; - } - - return $data; + $this->startTimestamp = $startTimestamp; } /** - * @return array Returns the trace context + * Gets the timestamp representing when the measuring finished. */ - public function getTraceContext(): array + public function getEndTimestamp(): ?float { - $data = $this->toArray(); - - unset($data['start_timestamp']); - unset($data['timestamp']); - - return $data; + return $this->endTimestamp; } /** - * {@inheritdoc} - * - * @return array + * Returns `sentry-trace` header content. */ - public function jsonSerialize(): array + public function toTraceparent(): string { - return $this->toArray(); + $sampled = ''; + if (null !== $this->sampled) { + $sampled = $this->sampled ? '-1' : '-0'; + } + + return $this->traceId . '-' . $this->spanId . $sampled; } + /** + * Gets a description of the span's operation, which uniquely identifies + * the span but is consistent across instances of the span. + */ public function getDescription(): ?string { return $this->description; } + /** + * Sets a description of the span's operation, which uniquely identifies + * the span but is consistent across instances of the span. + * + * @param string|null $description The description + */ public function setDescription(?string $description): void { $this->description = $description; } + /** + * Gets a short code identifying the type of operation the span is measuring. + */ public function getOp(): ?string { return $this->op; } + /** + * Sets a short code identifying the type of operation the span is measuring. + * + * @param string|null $op The short code + */ public function setOp(?string $op): void { $this->op = $op; } + /** + * Gets the status of the span/transaction. + */ public function getStatus(): ?string { return $this->status; } + /** + * Sets the status of the span/transaction. + * + * @param string|null $status The status + */ public function setStatus(?string $status): void { $this->status = $status; @@ -275,61 +263,138 @@ public function setTags(array $tags): void $this->tags = array_merge($this->tags, $tags); } + /** + * Gets the ID of the span. + */ public function getSpanId(): SpanId { return $this->spanId; } - public function setSpanId(SpanId $spanId): void + /** + * Gets the flag determining whether this span should be sampled or not. + */ + public function getSampled(): ?bool { - $this->spanId = $spanId; + return $this->sampled; } - public function getTraceId(): TraceId + /** + * Sets the flag determining whether this span should be sampled or not. + * + * @param bool $sampled Whether to sample or not this span + */ + public function setSampled(?bool $sampled): void { - return $this->traceId; + $this->sampled = $sampled; } - public function setTraceId(TraceId $traceId): void + /** + * Gets a map of arbitrary data. + * + * @return array + */ + public function getData(): array { - $this->traceId = $traceId; + return $this->data; } - public function getParentSpanId(): ?SpanId + /** + * Sets a map of arbitrary data. This method will merge the given data with + * the existing one. + * + * @param array $data The data + */ + public function setData(array $data): void { - return $this->parentSpanId; + $this->data = array_merge($this->data, $data); } - public function setParentSpanId(?SpanId $parentSpanId): void + /** + * Gets the data in a format suitable for storage in the "trace" context. + * + * @return array + * + * @psalm-return array{ + * data?: array, + * description?: string, + * op?: string, + * parent_span_id?: string, + * span_id: string, + * status?: string, + * tags?: array, + * trace_id: string + * } + */ + public function getTraceContext(): array { - $this->parentSpanId = $parentSpanId; - } + $result = [ + 'span_id' => (string) $this->spanId, + 'trace_id' => (string) $this->traceId, + ]; - public function getSampled(): ?bool - { - return $this->sampled; - } + if (null !== $this->parentSpanId) { + $result['parent_span_id'] = (string) $this->parentSpanId; + } - public function setSampled(?bool $sampled): void - { - $this->sampled = $sampled; + if (null !== $this->description) { + $result['description'] = $this->description; + } + + if (null !== $this->op) { + $result['op'] = $this->op; + } + + if (null !== $this->status) { + $result['status'] = $this->status; + } + + if (!empty($this->data)) { + $result['data'] = $this->data; + } + + if (!empty($this->tags)) { + $result['tags'] = $this->tags; + } + + return $result; } /** - * @param array $data + * Sets the finish timestamp on the current span. + * + * @param float|null $endTimestamp Takes an endTimestamp if the end should not be the time when you call this function + * + * @return EventId|null Finish for a span always returns null */ - public function setData(array $data): void + public function finish(?float $endTimestamp = null): ?EventId { - $this->data = array_merge($this->data, $data); - } + $this->endTimestamp = $endTimestamp ?? microtime(true); - public function setStartTimestamp(float $startTimestamp): void - { - $this->startTimestamp = $startTimestamp; + return null; } - public function getEndTimestamp(): ?float + /** + * Creates a new `Span` while setting the current `Span.id` as `parentSpanId`. + * Also the `sampled` decision will be inherited. + * + * @param SpanContext $context The Context of the child span + * + * @return Span Instance of the newly created Span + */ + public function startChild(SpanContext $context): self { - return $this->endTimestamp; + $context->sampled = $this->sampled; + $context->parentSpanId = $this->spanId; + $context->traceId = $this->traceId; + + $span = new self($context); + + $span->spanRecorder = $this->spanRecorder; + if (null != $span->spanRecorder) { + $span->spanRecorder->add($span); + } + + return $span; } } diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index d632f814b..49c0476d2 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -6,6 +6,7 @@ use Sentry\Event; use Sentry\EventId; +use Sentry\EventType; use Sentry\Severity; use Sentry\State\HubInterface; @@ -64,7 +65,7 @@ public function initSpanRecorder(): void * * @return EventId|null Finish for a transaction returns the eventId or null in case we didn't send it */ - public function finish($endTimestamp = null): ?EventId + public function finish(?float $endTimestamp = null): ?EventId { if (null !== $this->endTimestamp) { // Transaction was already finished once and we don't want to re-flush it @@ -74,7 +75,6 @@ public function finish($endTimestamp = null): ?EventId parent::finish($endTimestamp); if (true !== $this->sampled) { - // At this point if `sampled !== true` we want to discard the transaction. return null; } @@ -91,7 +91,7 @@ public function finish($endTimestamp = null): ?EventId public function toEvent(): Event { $event = new Event(); - $event->setType('transaction'); + $event->setType(EventType::transaction()); $event->setTags(array_merge($event->getTags(), $this->tags)); $event->setTransaction($this->name); $event->setStartTimestamp($this->startTimestamp); @@ -114,14 +114,4 @@ public function toEvent(): Event return $event; } - - /** - * {@inheritdoc} - * - * @return array - */ - public function jsonSerialize(): array - { - return $this->toEvent()->toArray(); - } } diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php index ea6d4a064..a4b56ecc4 100644 --- a/src/Transport/DefaultTransportFactory.php +++ b/src/Transport/DefaultTransportFactory.php @@ -9,6 +9,7 @@ use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactoryInterface; use Sentry\Options; +use Sentry\Serializer\PayloadSerializer; /** * This class is the default implementation of the {@see TransportFactoryInterface} @@ -66,6 +67,7 @@ public function create(Options $options): TransportInterface $this->httpClientFactory->create($options), $this->streamFactory, $this->requestFactory, + new PayloadSerializer(), $this->logger ); } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index a60095268..1b018e884 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -14,10 +14,11 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Event; +use Sentry\EventType; use Sentry\Options; use Sentry\Response; use Sentry\ResponseStatus; -use Sentry\Util\JSON; +use Sentry\Serializer\PayloadSerializerInterface; /** * This transport sends the events using a syncronous HTTP client that will @@ -47,6 +48,11 @@ final class HttpTransport implements TransportInterface */ private $requestFactory; + /** + * @var PayloadSerializerInterface The event serializer + */ + private $payloadSerializer; + /** * @var LoggerInterface A PSR-3 logger */ @@ -55,23 +61,26 @@ final class HttpTransport implements TransportInterface /** * Constructor. * - * @param Options $options The Sentry client configuration - * @param HttpAsyncClientInterface $httpClient The HTTP client - * @param StreamFactoryInterface $streamFactory The PSR-7 stream factory - * @param RequestFactoryInterface $requestFactory The PSR-7 request factory - * @param LoggerInterface|null $logger An instance of a PSR-3 logger + * @param Options $options The Sentry client configuration + * @param HttpAsyncClientInterface $httpClient The HTTP client + * @param StreamFactoryInterface $streamFactory The PSR-7 stream factory + * @param RequestFactoryInterface $requestFactory The PSR-7 request factory + * @param PayloadSerializerInterface $payloadSerializer The event serializer + * @param LoggerInterface|null $logger An instance of a PSR-3 logger */ public function __construct( Options $options, HttpAsyncClientInterface $httpClient, StreamFactoryInterface $streamFactory, RequestFactoryInterface $requestFactory, + PayloadSerializerInterface $payloadSerializer, ?LoggerInterface $logger = null ) { $this->options = $options; $this->httpClient = $httpClient; $this->streamFactory = $streamFactory; $this->requestFactory = $requestFactory; + $this->payloadSerializer = $payloadSerializer; $this->logger = $logger ?? new NullLogger(); } @@ -86,14 +95,14 @@ public function send(Event $event): PromiseInterface throw new \RuntimeException(sprintf('The DSN option must be set to use the "%s" transport.', self::class)); } - if ('transaction' === $event->getType()) { + if (EventType::transaction() === $event->getType()) { $request = $this->requestFactory->createRequest('POST', $dsn->getEnvelopeApiEndpointUrl()) ->withHeader('Content-Type', 'application/x-sentry-envelope') - ->withBody($this->streamFactory->createStream($event->toEnvelope())); + ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); } else { $request = $this->requestFactory->createRequest('POST', $dsn->getStoreApiEndpointUrl()) ->withHeader('Content-Type', 'application/json') - ->withBody($this->streamFactory->createStream(JSON::encode($event->toArray()))); + ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); } try { diff --git a/src/functions.php b/src/functions.php index dfd13842a..382d226c7 100644 --- a/src/functions.php +++ b/src/functions.php @@ -22,8 +22,8 @@ function init(array $options = []): void /** * Captures a message event and sends it to Sentry. * - * @param string $message The message - * @param Severity $level The severity level of the message + * @param string $message The message + * @param Severity|null $level The severity level of the message */ function captureMessage(string $message, ?Severity $level = null): ?EventId { diff --git a/tests/BreadcrumbTest.php b/tests/BreadcrumbTest.php index df69a53c2..ec0684d12 100644 --- a/tests/BreadcrumbTest.php +++ b/tests/BreadcrumbTest.php @@ -35,57 +35,55 @@ public function testConstructor(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); - $this->assertEquals('foo', $breadcrumb->getCategory()); - $this->assertEquals(Breadcrumb::LEVEL_INFO, $breadcrumb->getLevel()); - $this->assertEquals('foo bar', $breadcrumb->getMessage()); - $this->assertEquals(Breadcrumb::TYPE_USER, $breadcrumb->getType()); - $this->assertEquals(['baz'], $breadcrumb->getMetadata()); - $this->assertEquals(microtime(true), $breadcrumb->getTimestamp()); + $this->assertSame('foo', $breadcrumb->getCategory()); + $this->assertSame(Breadcrumb::LEVEL_INFO, $breadcrumb->getLevel()); + $this->assertSame('foo bar', $breadcrumb->getMessage()); + $this->assertSame(Breadcrumb::TYPE_USER, $breadcrumb->getType()); + $this->assertSame(['baz'], $breadcrumb->getMetadata()); + $this->assertSame(microtime(true), $breadcrumb->getTimestamp()); } /** * @dataProvider fromArrayDataProvider */ - public function testFromArray(array $requestData, array $expectedResult): void + public function testFromArray(array $requestData, string $expectedLevel, string $expectedType, string $expectedCategory, ?string $expectedMessage, array $expectedMetadata): void { - $expectedResult['timestamp'] = microtime(true); $breadcrumb = Breadcrumb::fromArray($requestData); - $this->assertEquals($expectedResult, $breadcrumb->toArray()); + $this->assertSame($expectedLevel, $breadcrumb->getLevel()); + $this->assertSame($expectedType, $breadcrumb->getType()); + $this->assertSame($expectedCategory, $breadcrumb->getCategory()); + $this->assertSame($expectedMessage, $breadcrumb->getMessage()); + $this->assertSame($expectedMetadata, $breadcrumb->getMetadata()); } - public function fromArrayDataProvider(): array + public function fromArrayDataProvider(): iterable { - return [ + yield [ [ - [ - 'level' => Breadcrumb::LEVEL_INFO, - 'type' => Breadcrumb::TYPE_USER, - 'category' => 'foo', - 'message' => 'foo bar', - 'data' => ['baz'], - ], - [ - 'level' => Breadcrumb::LEVEL_INFO, - 'type' => Breadcrumb::TYPE_USER, - 'category' => 'foo', - 'message' => 'foo bar', - 'data' => ['baz'], - ], + 'level' => Breadcrumb::LEVEL_INFO, + 'type' => Breadcrumb::TYPE_USER, + 'category' => 'foo', + 'message' => 'foo bar', + 'data' => ['baz'], ], + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_USER, + 'foo', + 'foo bar', + ['baz'], + ]; + + yield [ [ - [ - 'level' => Breadcrumb::LEVEL_INFO, - 'category' => 'foo', - ], - [ - 'level' => Breadcrumb::LEVEL_INFO, - 'type' => Breadcrumb::TYPE_DEFAULT, - 'category' => 'foo', - 'message' => null, - 'data' => [], - ], + 'level' => Breadcrumb::LEVEL_INFO, + 'category' => 'foo', ], + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_DEFAULT, + 'foo', + null, + [], ]; } @@ -95,7 +93,7 @@ public function testWithCategory(): void $newBreadcrumb = $breadcrumb->withCategory('bar'); $this->assertNotSame($breadcrumb, $newBreadcrumb); - $this->assertEquals('bar', $newBreadcrumb->getCategory()); + $this->assertSame('bar', $newBreadcrumb->getCategory()); $this->assertSame($newBreadcrumb, $newBreadcrumb->withCategory('bar')); } @@ -105,7 +103,7 @@ public function testWithLevel(): void $newBreadcrumb = $breadcrumb->withLevel(Breadcrumb::LEVEL_WARNING); $this->assertNotSame($breadcrumb, $newBreadcrumb); - $this->assertEquals(Breadcrumb::LEVEL_WARNING, $newBreadcrumb->getLevel()); + $this->assertSame(Breadcrumb::LEVEL_WARNING, $newBreadcrumb->getLevel()); $this->assertSame($newBreadcrumb, $newBreadcrumb->withLevel(Breadcrumb::LEVEL_WARNING)); } @@ -115,7 +113,7 @@ public function testWithType(): void $newBreadcrumb = $breadcrumb->withType(Breadcrumb::TYPE_ERROR); $this->assertNotSame($breadcrumb, $newBreadcrumb); - $this->assertEquals(Breadcrumb::TYPE_ERROR, $newBreadcrumb->getType()); + $this->assertSame(Breadcrumb::TYPE_ERROR, $newBreadcrumb->getType()); $this->assertSame($newBreadcrumb, $newBreadcrumb->withType(Breadcrumb::TYPE_ERROR)); } @@ -125,7 +123,7 @@ public function testWithMessage(): void $newBreadcrumb = $breadcrumb->withMessage('foo bar'); $this->assertNotSame($breadcrumb, $newBreadcrumb); - $this->assertEquals('foo bar', $newBreadcrumb->getMessage()); + $this->assertSame('foo bar', $newBreadcrumb->getMessage()); $this->assertSame($newBreadcrumb, $newBreadcrumb->withMessage('foo bar')); } @@ -148,25 +146,4 @@ public function testWithoutMetadata(): void $this->assertSame(['foo' => 'bar'], $breadcrumb->getMetadata()); $this->assertArrayNotHasKey('foo', $newBreadcrumb->getMetadata()); } - - public function testJsonSerialize(): void - { - $type = Breadcrumb::TYPE_USER; - $level = Breadcrumb::LEVEL_INFO; - $category = 'foo'; - $data = ['baz' => 'bar']; - $message = 'message'; - $breadcrumb = new Breadcrumb($level, $type, $category, $message, $data); - - $expected = [ - 'type' => $type, - 'category' => $category, - 'message' => $message, - 'level' => $level, - 'timestamp' => microtime(true), - 'data' => $data, - ]; - - $this->assertEquals($expected, $breadcrumb->jsonSerialize()); - } } diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php index 5fa0a24b6..f86bdf697 100644 --- a/tests/EventFactoryTest.php +++ b/tests/EventFactoryTest.php @@ -50,7 +50,7 @@ public function testCreateEventWithDefaultValues(): void /** * @dataProvider createWithPayloadDataProvider */ - public function testCreateWithPayload(array $payload, array $expectedSubset): void + public function testCreateWithPayload(array $payload, ?string $expectedLogger, ?string $expectedMessage, array $expectedMessageParams, ?string $expectedFormattedMessage): void { $eventFactory = new EventFactory( $this->createMock(SerializerInterface::class), @@ -62,47 +62,40 @@ public function testCreateWithPayload(array $payload, array $expectedSubset): vo $event = $eventFactory->create($payload); - $this->assertArraySubset($expectedSubset, $event->toArray()); + $this->assertSame($expectedLogger, $event->getLogger()); + $this->assertSame($expectedMessage, $event->getMessage()); + $this->assertSame($expectedMessageParams, $event->getMessageParams()); + $this->assertSame($expectedFormattedMessage, $event->getMessageFormatted()); } - public function createWithPayloadDataProvider() + public function createWithPayloadDataProvider(): iterable { - return [ - [ - ['logger' => 'testLogger'], - ['logger' => 'testLogger'], - ], - [ - ['message' => 'testMessage'], - ['message' => 'testMessage'], - ], - [ - [ - 'message' => 'testMessage %s', - 'message_params' => ['param'], - ], - [ - 'message' => [ - 'message' => 'testMessage %s', - 'params' => ['param'], - 'formatted' => 'testMessage param', - ], - ], - ], + yield [ + ['logger' => 'app.php'], + 'app.php', + null, + [], + null, + ]; + + yield [ + ['message' => 'My raw message with interpreted strings like this'], + null, + 'My raw message with interpreted strings like this', + [], + null, + ]; + + yield [ [ - [ - 'message' => 'testMessage %foo', - 'message_params' => ['%foo' => 'param'], - 'message_formatted' => 'testMessage param', - ], - [ - 'message' => [ - 'message' => 'testMessage %foo', - 'params' => ['%foo' => 'param'], - 'formatted' => 'testMessage param', - ], - ], + 'message' => 'My raw message with interpreted strings like that', + 'message_params' => ['this'], + 'message_formatted' => 'My raw message with interpreted strings like %s', ], + null, + 'My raw message with interpreted strings like that', + ['this'], + 'My raw message with interpreted strings like %s', ]; } diff --git a/tests/EventTest.php b/tests/EventTest.php index c8a236a7d..ae343df6a 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -4,16 +4,9 @@ namespace Sentry\Tests; -use Jean85\PrettyVersions; use PHPUnit\Framework\TestCase; -use Sentry\Breadcrumb; -use Sentry\Client; -use Sentry\Context\OsContext; -use Sentry\Context\RuntimeContext; use Sentry\Event; use Sentry\Severity; -use Sentry\Util\PHPVersion; -use Symfony\Bridge\PhpUnit\ClockMock; /** * @group time-sensitive @@ -28,144 +21,6 @@ public function testEventIsGeneratedWithUniqueIdentifier(): void $this->assertNotEquals($event1->getId(), $event2->getId()); } - public function testToArray(): void - { - ClockMock::register(Event::class); - - $event = new Event(); - - $expected = [ - 'event_id' => (string) $event->getId(), - 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), - 'platform' => 'php', - 'sdk' => [ - 'name' => Client::SDK_IDENTIFIER, - 'version' => PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(), - ], - 'level' => 'error', - ]; - - $this->assertSame($expected, $event->toArray()); - } - - public function testToArrayMergesCustomContextsWithDefaultContexts(): void - { - ClockMock::register(Event::class); - - $event = new Event(); - $event->setOsContext(new OsContext(php_uname('s'), php_uname('r'), php_uname('v'), php_uname('a'))); - $event->setRuntimeContext(new RuntimeContext('php', PHPVersion::parseVersion())); - $event->setContext('foo', ['foo' => 'bar']); - $event->setContext('bar', ['bar' => 'foo']); - $event->setContext('runtime', ['baz' => 'baz']); - - $expected = [ - 'event_id' => (string) $event->getId(), - 'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), - 'platform' => 'php', - 'sdk' => [ - 'name' => Client::SDK_IDENTIFIER, - 'version' => PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(), - ], - 'level' => 'error', - 'contexts' => [ - 'os' => [ - 'name' => php_uname('s'), - 'version' => php_uname('r'), - 'build' => php_uname('v'), - 'kernel_version' => php_uname('a'), - ], - 'runtime' => [ - 'baz' => 'baz', - ], - 'foo' => [ - 'foo' => 'bar', - ], - 'bar' => [ - 'bar' => 'foo', - ], - ], - ]; - - $this->assertSame($expected, $event->toArray()); - } - - /** - * @dataProvider toArrayWithMessageDataProvider - */ - public function testToArrayWithMessage(array $setMessageArguments, $expectedValue): void - { - $event = new Event(); - - \call_user_func_array([$event, 'setMessage'], $setMessageArguments); - - $data = $event->toArray(); - - $this->assertArrayHasKey('message', $data); - $this->assertSame($expectedValue, $data['message']); - } - - public function toArrayWithMessageDataProvider(): array - { - return [ - [ - [ - 'foo bar', - ], - 'foo bar', - ], - [ - [ - 'foo %s', - [ - 'bar', - ], - ], - [ - 'message' => 'foo %s', - 'params' => [ - 'bar', - ], - 'formatted' => 'foo bar', - ], - ], - [ - [ - 'foo %bar', - [ - '%bar' => 'baz', - ], - 'foo baz', - ], - [ - 'message' => 'foo %bar', - 'params' => [ - '%bar' => 'baz', - ], - 'formatted' => 'foo baz', - ], - ], - ]; - } - - public function testToArrayWithBreadcrumbs(): void - { - $breadcrumbs = [ - new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'foo'), - new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'bar'), - ]; - - $event = new Event(); - $event->setBreadcrumb($breadcrumbs); - - $this->assertSame($breadcrumbs, $event->getBreadcrumbs()); - - $data = $event->toArray(); - - $this->assertArrayHasKey('breadcrumbs', $data); - $this->assertSame($breadcrumbs, $data['breadcrumbs']['values']); - } - /** * @dataProvider getMessageDataProvider */ @@ -220,7 +75,7 @@ public function getMessageDataProvider(): array /** * @dataProvider gettersAndSettersDataProvider */ - public function testGettersAndSetters(string $propertyName, $propertyValue, $expectedValue): void + public function testGettersAndSetters(string $propertyName, $propertyValue): void { $getterMethod = 'get' . ucfirst($propertyName); $setterMethod = 'set' . ucfirst($propertyName); @@ -229,48 +84,21 @@ public function testGettersAndSetters(string $propertyName, $propertyValue, $exp $event->$setterMethod($propertyValue); $this->assertEquals($event->$getterMethod(), $propertyValue); - $this->assertArraySubset($expectedValue, $event->toArray()); } public function gettersAndSettersDataProvider(): array { return [ - ['sdkIdentifier', 'sentry.sdk.test-identifier', ['sdk' => ['name' => 'sentry.sdk.test-identifier']]], - ['sdkVersion', '1.2.3', ['sdk' => ['version' => '1.2.3']]], - ['level', Severity::info(), ['level' => Severity::info()]], - ['logger', 'ruby', ['logger' => 'ruby']], - ['transaction', 'foo', ['transaction' => 'foo']], - ['serverName', 'local.host', ['server_name' => 'local.host']], - ['release', '0.0.1', ['release' => '0.0.1']], - ['modules', ['foo' => '0.0.1', 'bar' => '0.0.2'], ['modules' => ['foo' => '0.0.1', 'bar' => '0.0.2']]], - ['fingerprint', ['foo', 'bar'], ['fingerprint' => ['foo', 'bar']]], - ['environment', 'foo', ['environment' => 'foo']], + ['sdkIdentifier', 'sentry.sdk.test-identifier'], + ['sdkVersion', '1.2.3'], + ['level', Severity::info()], + ['logger', 'ruby'], + ['transaction', 'foo'], + ['serverName', 'local.host'], + ['release', '0.0.1'], + ['modules', ['foo' => '0.0.1', 'bar' => '0.0.2']], + ['fingerprint', ['foo', 'bar']], + ['environment', 'foo'], ]; } - - public function testEventJsonSerialization(): void - { - $event = new Event(); - - $encodingOfToArray = json_encode($event->toArray()); - $serializedEvent = json_encode($event); - - $this->assertNotFalse($encodingOfToArray); - $this->assertNotFalse($serializedEvent); - $this->assertJsonStringEqualsJsonString($encodingOfToArray, $serializedEvent); - } - - public function testToEnvelope(): void - { - $event = new Event(); - - $envelope = $event->toEnvelope(); - $envelopeLines = explode("\n", $envelope); - - $this->assertContains('event_id', $envelopeLines[0]); - $this->assertContains('sent_at', $envelopeLines[0]); - $this->assertContains('type', $envelopeLines[1]); - $this->assertContains('event', $envelopeLines[1]); - $this->assertContains($event->getId(false)->__toString(), $envelopeLines[2]); - } } diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php index e219798d5..b73dd3696 100644 --- a/tests/FrameBuilderTest.php +++ b/tests/FrameBuilderTest.php @@ -133,7 +133,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'file' => 'path/not/of/app/path/to/file', 'line' => 10, ], - new Frame(null, 'path/not/of/app/path/to/file', 10, null, 'path/not/of/app/path/to/file'), + new Frame(null, 'path/not/of/app/path/to/file', 10, null), ]; yield [ @@ -147,7 +147,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'file' => 'path/not/of/app/to/file', 'line' => 10, ], - new Frame(null, 'path/not/of/app/to/file', 10, null, 'path/not/of/app/to/file'), + new Frame(null, 'path/not/of/app/to/file', 10, null), ]; } diff --git a/tests/FrameTest.php b/tests/FrameTest.php index 4ff159263..a18f44925 100644 --- a/tests/FrameTest.php +++ b/tests/FrameTest.php @@ -39,34 +39,4 @@ public function gettersAndSettersDataProvider(): array ['getVars', 'setVars', ['foo' => 'bar']], ]; } - - /** - * @dataProvider toArrayAndJsonSerializeDataProvider - */ - public function testToArrayAndJsonSerialize(string $setterMethod, string $expectedDataKey, $expectedData): void - { - $frame = new Frame('foo', 'bar', 10); - $frame->$setterMethod($expectedData); - - $expectedResult = [ - 'function' => 'foo', - 'filename' => 'bar', - 'lineno' => 10, - $expectedDataKey => $expectedData, - ]; - - $this->assertArraySubset($expectedResult, $frame->toArray()); - $this->assertArraySubset($expectedResult, $frame->jsonSerialize()); - } - - public function toArrayAndJsonSerializeDataProvider(): array - { - return [ - ['setPreContext', 'pre_context', ['foo' => 'bar']], - ['setContextLine', 'context_line', 'foo bar'], - ['setPostContext', 'post_context', ['bar' => 'foo']], - ['setIsInApp', 'in_app', true], - ['setVars', 'vars', ['baz' => 'bar']], - ]; - } } diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php index 8411245e5..625a36554 100644 --- a/tests/HttpClient/HttpClientFactoryTest.php +++ b/tests/HttpClient/HttpClientFactoryTest.php @@ -8,7 +8,6 @@ use Http\Discovery\Psr17FactoryDiscovery; use Http\Mock\Client as HttpMockClient; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestInterface; use Sentry\HttpClient\HttpClientFactory; use Sentry\Options; @@ -43,9 +42,9 @@ public function testCreate(bool $isCompressionEnabled, string $expectedRequestBo ->withBody($streamFactory->createStream('foo bar')); $httpClient->sendAsyncRequest($request); + $httpRequest = $mockHttpClient->getLastRequest(); - $this->assertInstanceOf(RequestInterface::class, $httpRequest); $this->assertSame('http://example.com/sentry/foo', (string) $httpRequest->getUri()); $this->assertSame('sentry.php.test/1.2.3', $httpRequest->getHeaderLine('User-Agent')); $this->assertSame('Sentry sentry_version=7, sentry_client=sentry.php.test/1.2.3, sentry_key=public', $httpRequest->getHeaderLine('X-Sentry-Auth')); diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index 2fd7b85da..9a555b693 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -60,7 +60,7 @@ public function testInvoke(string $fixtureFilePath, int $lineNumber, int $contex SentrySdk::getCurrentHub()->bindClient($client); $stacktrace = new Stacktrace([ - new Frame('[unknown]', $fixtureFilePath, $lineNumber), + new Frame('[unknown]', $fixtureFilePath, $lineNumber, null, $fixtureFilePath), ]); $event = new Event(); @@ -154,7 +154,7 @@ public function testInvokeLogsWarningMessageIfSourceCodeExcerptCannotBeRetrieved $stacktrace = new Stacktrace([ new Frame(null, '[internal]', 0), - new Frame(null, 'file.ext', 10), + new Frame(null, 'file.ext', 10, null, 'file.ext'), ]); $event = new Event(); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php new file mode 100644 index 000000000..2926b2845 --- /dev/null +++ b/tests/Serializer/PayloadSerializerTest.php @@ -0,0 +1,427 @@ +serializer = new PayloadSerializer(); + } + + /** + * @dataProvider serializeDataProvider + */ + public function testSerialize(Event $event, string $expectedResult, bool $isOutputJson): void + { + ClockMock::withClockMock(1597790835); + + $result = $this->serializer->serialize($event); + + if ($isOutputJson) { + $this->assertJsonStringEqualsJsonString($expectedResult, $result); + } else { + $this->assertSame($expectedResult, $result); + } + } + + public function serializeDataProvider(): iterable + { + ClockMock::withClockMock(1597790835); + + $sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + + yield [ + new Event(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), + <<setLevel(Severity::error()); + $event->setLogger('app.php'); + $event->setTransaction('/users//'); + $event->setServerName('foo.example.com'); + $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); + $event->setEnvironment('production'); + $event->setFingerprint(['myrpc', 'POST', '/foo.bar']); + $event->setModules(['my.module.name' => '1.0']); + $event->setStartTimestamp(1597790835); + $event->setBreadcrumb([ + new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'log'), + new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_NAVIGATION, 'log', null, ['from' => '/login', 'to' => '/dashboard']), + ]); + + $event->setUser(UserDataBag::createFromArray([ + 'id' => 'unique_id', + 'username' => 'my_user', + 'email' => 'foo@example.com', + 'ip_address' => '127.0.0.1', + ])); + + $event->setTags([ + 'ios_version' => '4.0', + 'context' => 'production', + ]); + + $event->setExtra([ + 'my_key' => 1, + 'some_other_value' => 'foo bar', + ]); + + $event->setRequest([ + 'method' => 'POST', + 'url' => 'http://absolute.uri/foo', + 'query_string' => 'query=foobar&page=2', + 'data' => [ + 'foo' => 'bar', + ], + 'cookies' => [ + 'PHPSESSID' => '298zf09hf012fh2', + ], + 'headers' => [ + 'content-type' => 'text/html', + ], + 'env' => [ + 'REMOTE_ADDR' => '127.0.0.1', + ], + ]); + + $event->setOsContext(new OsContext( + 'Linux', + '4.19.104-microsoft-standard', + '#1 SMP Wed Feb 19 06:37:35 UTC 2020', + 'Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64' + )); + + $event->setRuntimeContext(new RuntimeContext( + 'php', + '7.4.3' + )); + + $event->setContext('electron', [ + 'type' => 'runtime', + 'name' => 'Electron', + 'version' => '4.0', + ]); + + $frame1 = new Frame(null, 'file/name.py', 3); + $frame2 = new Frame('myfunction', 'file/name.py', 3, 'raw_function_name', 'absolute/file/name.py', ['my_var' => 'value'], false); + $frame2->setContextLine(' raise ValueError()'); + $frame2->setPreContext([ + 'def foo():', + ' my_var = \'foo\'', + ]); + + $frame2->setPostContext([ + '', + 'def main():', + ]); + + $event->setExceptions([ + new ExceptionDataBag(new \Exception('initial exception')), + new ExceptionDataBag( + new \Exception('chained exception'), + new Stacktrace([ + $frame1, + $frame2, + ]), + new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true) + ), + ]); + + yield [ + $event, + <</", + "server_name": "foo.example.com", + "release": "721e41770371db95eee98ca2707686226b993eda", + "environment": "production", + "fingerprint": [ + "myrpc", + "POST", + "/foo.bar" + ], + "modules": { + "my.module.name": "1.0" + }, + "extra": { + "my_key": 1, + "some_other_value": "foo bar" + }, + "tags": { + "ios_version": "4.0", + "context": "production" + }, + "user": { + "id": "unique_id", + "username": "my_user", + "email": "foo@example.com", + "ip_address": "127.0.0.1" + }, + "contexts": { + "os": { + "name": "Linux", + "version": "4.19.104-microsoft-standard", + "build": "#1 SMP Wed Feb 19 06:37:35 UTC 2020", + "kernel_version": "Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64" + }, + "runtime": { + "name": "php", + "version": "7.4.3" + }, + "electron": { + "type": "runtime", + "name": "Electron", + "version": "4.0" + } + }, + "breadcrumbs": { + "values": [ + { + "type": "user", + "category": "log", + "level": "info", + "timestamp": 1597790835 + }, + { + "type": "navigation", + "category": "log", + "level": "info", + "timestamp": 1597790835, + "data": { + "from": "/login", + "to": "/dashboard" + } + } + ] + }, + "request": { + "method": "POST", + "url": "http://absolute.uri/foo", + "query_string": "query=foobar&page=2", + "data": { + "foo": "bar" + }, + "cookies": { + "PHPSESSID": "298zf09hf012fh2" + }, + "headers": { + "content-type": "text/html" + }, + "env": { + "REMOTE_ADDR": "127.0.0.1" + } + }, + "exception": { + "values": [ + { + "type": "Exception", + "value": "chained exception", + "stacktrace": { + "frames": [ + { + "filename": "file/name.py", + "lineno": 3, + "in_app": true + }, + { + "filename": "file/name.py", + "lineno": 3, + "in_app": false, + "abs_path": "absolute/file/name.py", + "function": "myfunction", + "raw_function": "raw_function_name", + "pre_context": [ + "def foo():", + " my_var = 'foo'" + ], + "context_line": " raise ValueError()", + "post_context": [ + "", + "def main():" + ], + "vars": { + "my_var": "value" + } + } + ] + }, + "mechanism": { + "type": "generic", + "handled": true + } + }, + { + "type": "Exception", + "value": "initial exception" + } + ] + } +} +JSON + , + true, + ]; + + $event = new Event(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setMessage('My raw message with interpreted strings like this', []); + + yield [ + $event, + <<setMessage('My raw message with interpreted strings like %s', ['this']); + + yield [ + $event, + <<setMessage('My raw message with interpreted strings like %s', ['this'], 'My raw message with interpreted strings like that'); + + yield [ + $event, + <<setSpanId(new SpanId('5dd538dc297544cc')); + $span1->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); + + $span2 = new Span(); + $span2->setSpanId(new SpanId('b01b9f6349558cd1')); + $span2->setParentSpanId(new SpanId('b0e6f15b45c36b12')); + $span2->setTraceId(new TraceId('1e57b752bc6e4544bbaa246cd1d05dee')); + $span2->setOp('http'); + $span2->setDescription('GET /sockjs-node/info'); + $span2->setStatus('ok'); + $span2->setStartTimestamp(1597790835); + $span2->setTags(['http.status_code' => '200']); + $span2->setData([ + 'url' => 'http://localhost:8080/sockjs-node/info?t=1588601703755', + 'status_code' => 200, + 'type' => 'xhr', + 'method' => 'GET', + ]); + + $span2->finish(1598659060); + + $event = new Event(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setType(EventType::transaction()); + $event->setSpans([$span1, $span2]); + + yield [ + $event, + <<getFrames()); - $serializedStacktrace = json_encode($stacktrace); - - $this->assertNotFalse($frames); - $this->assertNotFalse($serializedStacktrace); - $this->assertJsonStringEqualsJsonString($frames, $serializedStacktrace); - } - public function testAddFrame(): void { $stacktrace = new Stacktrace([ diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 87673202f..f9a43d8ed 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -180,7 +180,7 @@ public function testSetLevel(): void $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertEquals(Severity::error(), $event->getLevel()); + $this->assertNull($event->getLevel()); $scope->setLevel(Severity::debug()); @@ -295,7 +295,7 @@ public function testClear(): void $event = $scope->applyToEvent(new Event(), []); $this->assertNotNull($event); - $this->assertEquals(Severity::error(), $event->getLevel()); + $this->assertNull($event->getLevel()); $this->assertEmpty($event->getBreadcrumbs()); $this->assertEmpty($event->getFingerprint()); $this->assertEmpty($event->getExtra()); diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index bc0cb8772..af99f3965 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -2,13 +2,14 @@ declare(strict_types=1); -namespace Sentry\Tests; +namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; +use Symfony\Bridge\PhpUnit\ClockMock; /** * @group time-sensitive @@ -33,31 +34,47 @@ public function testConstructor(): void $context->data = $data; $context->startTimestamp = microtime(true); $context->endTimestamp = microtime(true); + $span = new Span($context); - $data = $span->jsonSerialize(); - - $this->assertEquals($context->op, $data['op']); - $this->assertEquals($context->traceId->__toString(), $data['trace_id']); - $this->assertEquals($context->spanId->__toString(), $data['span_id']); - $this->assertEquals($context->parentSpanId->__toString(), $data['parent_span_id']); - $this->assertEquals($context->description, $data['description']); - $this->assertEquals($context->status, $data['status']); - $this->assertEquals($context->tags, $data['tags']); - $this->assertEquals($context->data, $data['data']); - $this->assertEquals($context->startTimestamp, $data['start_timestamp']); - $this->assertEquals($context->endTimestamp, $data['timestamp']); + + $this->assertEquals($context->traceId, $span->getTraceId()); + $this->assertEquals($context->spanId, $span->getSpanId()); + $this->assertEquals($context->parentSpanId, $span->getParentSpanId()); + $this->assertSame($context->op, $span->getOp()); + $this->assertSame($context->description, $span->getDescription()); + $this->assertSame($context->status, $span->getStatus()); + $this->assertSame($context->tags, $span->getTags()); + $this->assertSame($context->data, $span->getData()); + $this->assertSame($context->startTimestamp, $span->getStartTimestamp()); + $this->assertSame($context->endTimestamp, $span->getEndTimestamp()); } - public function testFinish(): void + /** + * @dataProvider finishDataProvider + */ + public function testFinish(?float $currentTimestamp, ?float $timestamp, float $expectedTimestamp): void { - $span = new Span(); - $span->finish(); - $this->assertIsFloat($span->jsonSerialize()['timestamp']); + ClockMock::withClockMock($currentTimestamp); - $time = microtime(true); $span = new Span(); - $span->finish($time); - $this->assertEquals($span->jsonSerialize()['timestamp'], $time); + $span->finish($timestamp); + + $this->assertSame($expectedTimestamp, $span->getEndTimestamp()); + } + + public function finishDataProvider(): iterable + { + yield [ + 1598660006, + null, + 1598660006, + ]; + + yield [ + 1598660006, + 1598660332, + 1598660332, + ]; } public function testTraceparentHeader(): void diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index 5575a6458..345f26d5e 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -2,11 +2,13 @@ declare(strict_types=1); -namespace Sentry\Tests; +namespace Sentry\Tests\Tracing; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; +use Sentry\Event; +use Sentry\EventType; use Sentry\Options; use Sentry\State\Hub; use Sentry\Tracing\SpanContext; @@ -41,19 +43,20 @@ public function testConstructor(): void $context->startTimestamp = microtime(true); $context->endTimestamp = microtime(true); $transaction = new Transaction($context); - $data = $transaction->jsonSerialize(); + $data = $transaction->toEvent(); + $traceContext = $data->getContexts()['trace']; - $this->assertEquals($context->op, $data['contexts']['trace']['op']); - $this->assertEquals($context->name, $data['transaction']); - $this->assertEquals($context->traceId->__toString(), $data['contexts']['trace']['trace_id']); - $this->assertEquals($context->spanId->__toString(), $data['contexts']['trace']['span_id']); - $this->assertEquals($context->parentSpanId->__toString(), $data['contexts']['trace']['parent_span_id']); - $this->assertEquals($context->description, $data['contexts']['trace']['description']); - $this->assertEquals($context->status, $data['contexts']['trace']['status']); - $this->assertEquals($context->tags, $data['tags']); - $this->assertEquals($context->data, $data['contexts']['trace']['data']); - $this->assertEquals($context->startTimestamp, $data['start_timestamp']); - $this->assertEquals($context->endTimestamp, $data['timestamp']); + $this->assertEquals($context->op, $traceContext['op']); + $this->assertEquals($context->name, $data->getTransaction()); + $this->assertEquals($context->traceId->__toString(), $traceContext['trace_id']); + $this->assertEquals($context->spanId->__toString(), $traceContext['span_id']); + $this->assertEquals($context->parentSpanId->__toString(), $traceContext['parent_span_id']); + $this->assertEquals($context->description, $traceContext['description']); + $this->assertEquals($context->status, $traceContext['status']); + $this->assertEquals($context->tags, $data->getTags()); + $this->assertEquals($context->data, $traceContext['data']); + $this->assertEquals($context->startTimestamp, $data->getStartTimestamp()); + $this->assertEquals($context->endTimestamp, $data->getTimestamp()); } public function testShouldContainFinishSpans(): void @@ -67,8 +70,8 @@ public function testShouldContainFinishSpans(): void $span2->finish(); // $span3 is not finished and therefore not included $transaction->finish(); - $data = $transaction->jsonSerialize(); - $this->assertCount(2, $data['spans']); + $data = $transaction->toEvent(); + $this->assertCount(2, $data->getSpans()); } public function testStartAndSendTransaction(): void @@ -85,36 +88,20 @@ public function testStartAndSendTransaction(): void $span2 = $transaction->startChild(new SpanContext()); $span1->finish(); $span2->finish(); - $endTimestamp = microtime(true); - $data = $transaction->jsonSerialize(); - // We fake the endtime here - $data['timestamp'] = $endTimestamp; + $data = $transaction->toEvent(); + $client->expects($this->once()) ->method('captureEvent') - ->with($this->callback(function ($event) use ($data): bool { - $this->assertEqualWithIgnore($data, $event->toArray(), ['event_id']); + ->with($this->callback(function (Event $eventArg) use ($data): bool { + $this->assertSame(EventType::transaction(), $eventArg->getType()); + $this->assertSame($data->getStartTimestamp(), $eventArg->getStartTimestamp()); + $this->assertSame(microtime(true), $eventArg->getTimestamp()); + $this->assertCount(2, $data->getSpans()); return true; })); - $transaction->finish($endTimestamp); - - $this->assertCount(2, $data['spans']); - } - - private function assertEqualWithIgnore($expected, $actual, $ignoreKeys = [], $currentKey = null): void - { - if (\is_object($expected)) { - foreach ($expected as $key => $value) { - $this->assertEqualWithIgnore($expected->$key, $actual->$key, $ignoreKeys, $key); - } - } elseif (\is_array($expected)) { - foreach ($expected as $key => $value) { - $this->assertEqualWithIgnore($expected[$key], $actual[$key], $ignoreKeys, $key); - } - } elseif (null !== $currentKey && !\in_array($currentKey, $ignoreKeys)) { - $this->assertEquals($expected, $actual); - } + $transaction->finish(microtime(true)); } } diff --git a/tests/Transport/DefaultTransportFactoryTest.php b/tests/Transport/DefaultTransportFactoryTest.php index ad7c47160..8ef47ddd4 100644 --- a/tests/Transport/DefaultTransportFactoryTest.php +++ b/tests/Transport/DefaultTransportFactoryTest.php @@ -5,9 +5,10 @@ namespace Sentry\Tests\Transport; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Discovery\Psr17FactoryDiscovery; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; use Sentry\HttpClient\HttpClientFactoryInterface; use Sentry\Options; use Sentry\Transport\DefaultTransportFactory; @@ -19,8 +20,8 @@ final class DefaultTransportFactoryTest extends TestCase public function testCreateReturnsNullTransportWhenDsnOptionIsNotConfigured(): void { $factory = new DefaultTransportFactory( - Psr17FactoryDiscovery::findStreamFactory(), - Psr17FactoryDiscovery::findRequestFactory(), + $this->createMock(StreamFactoryInterface::class), + $this->createMock(RequestFactoryInterface::class), $this->createMock(HttpClientFactoryInterface::class) ); @@ -39,8 +40,8 @@ public function testCreateReturnsHttpTransportWhenDsnOptionIsConfigured(): void ->willReturn($this->createMock(HttpAsyncClientInterface::class)); $factory = new DefaultTransportFactory( - Psr17FactoryDiscovery::findStreamFactory(), - Psr17FactoryDiscovery::findRequestFactory(), + $this->createMock(StreamFactoryInterface::class), + $this->createMock(RequestFactoryInterface::class), $httpClientFactory ); diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 957a1da21..cf79691bc 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -6,32 +6,64 @@ use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Promise\RejectionException; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Discovery\Psr17FactoryDiscovery; -use Http\Mock\Client as HttpMockClient; use Http\Promise\FulfilledPromise as HttpFullfilledPromise; -use Http\Promise\RejectedPromise; +use Http\Promise\RejectedPromise as HttpRejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; use Psr\Log\LoggerInterface; +use Sentry\Dsn; use Sentry\Event; -use Sentry\HttpClient\HttpClientFactory; +use Sentry\EventType; use Sentry\Options; use Sentry\ResponseStatus; +use Sentry\Serializer\PayloadSerializerInterface; use Sentry\Transport\HttpTransport; +use function GuzzleHttp\Psr7\stream_for; final class HttpTransportTest extends TestCase { + /** + * @var MockObject&HttpAsyncClientInterface + */ + private $httpClient; + + /** + * @var MockObject&StreamFactoryInterface + */ + private $streamFactory; + + /** + * @var MockObject&RequestFactoryInterface + */ + private $requestFactory; + + /** + * @var MockObject&PayloadSerializerInterface + */ + private $payloadSerializer; + + protected function setUp(): void + { + $this->httpClient = $this->createMock(HttpAsyncClientInterface::class); + $this->streamFactory = $this->createMock(StreamFactoryInterface::class); + $this->requestFactory = $this->createMock(RequestFactoryInterface::class); + $this->payloadSerializer = $this->createMock(PayloadSerializerInterface::class); + } + public function testSendThrowsIfDsnOptionIsNotSet(): void { $transport = new HttpTransport( new Options(), - $this->createMock(HttpAsyncClientInterface::class), - Psr17FactoryDiscovery::findStreamFactory(), - Psr17FactoryDiscovery::findRequestFactory() + $this->httpClient, + $this->streamFactory, + $this->requestFactory, + $this->payloadSerializer ); $this->expectException(\RuntimeException::class); @@ -42,36 +74,51 @@ public function testSendThrowsIfDsnOptionIsNotSet(): void public function testSendTransactionAsEnvelope(): void { - $mockHttpClient = new HttpMockClient(); - $httpClientFactory = new HttpClientFactory( - Psr17FactoryDiscovery::findUrlFactory(), - Psr17FactoryDiscovery::findResponseFactory(), - Psr17FactoryDiscovery::findStreamFactory(), - $mockHttpClient, - 'sentry.php.test', - '1.2.3' - ); + $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); + $event = new Event(); + $event->setType(EventType::transaction()); + + $this->payloadSerializer->expects($this->once()) + ->method('serialize') + ->with($event) + ->willReturn('{"foo":"bar"}'); + + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with('POST', $dsn->getEnvelopeApiEndpointUrl()) + ->willReturn(new Request('POST', 'http://www.example.com')); + + $this->streamFactory->expects($this->once()) + ->method('createStream') + ->with('{"foo":"bar"}') + ->willReturnCallback(static function (string $content): StreamInterface { + return stream_for($content); + }); + + $this->httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->with($this->callback(function (Request $requestArg): bool { + if ('application/x-sentry-envelope' !== $requestArg->getHeaderLine('Content-Type')) { + return false; + } + + if ('{"foo":"bar"}' !== $requestArg->getBody()->getContents()) { + return false; + } - $httpClient = $httpClientFactory->create(new Options([ - 'dsn' => 'http://public@example.com/sentry/1', - 'default_integrations' => false, - ])); + return true; + })) + ->willReturn(new HttpFullfilledPromise(new Response())); $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $httpClient, - Psr17FactoryDiscovery::findStreamFactory(), - Psr17FactoryDiscovery::findRequestFactory() + new Options(['dsn' => $dsn]), + $this->httpClient, + $this->streamFactory, + $this->requestFactory, + $this->payloadSerializer ); - $event = new Event(); - $event->setType('transaction'); - $transport->send($event); - - $httpRequest = $mockHttpClient->getLastRequest(); - - $this->assertSame('application/x-sentry-envelope', $httpRequest->getHeaderLine('Content-Type')); } /** @@ -79,24 +126,47 @@ public function testSendTransactionAsEnvelope(): void */ public function testSend(int $httpStatusCode, string $expectedPromiseStatus, ResponseStatus $expectedResponseStatus): void { - $response = $this->createMock(ResponseInterface::class); - $response->expects($this->once()) - ->method('getStatusCode') - ->willReturn($httpStatusCode); - + $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $event = new Event(); - /** @var HttpAsyncClientInterface&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClientInterface::class); - $httpClient->expects($this->once()) + $this->payloadSerializer->expects($this->once()) + ->method('serialize') + ->with($event) + ->willReturn('{"foo":"bar"}'); + + $this->streamFactory->expects($this->once()) + ->method('createStream') + ->with('{"foo":"bar"}') + ->willReturnCallback(static function (string $content): StreamInterface { + return stream_for($content); + }); + + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with('POST', $dsn->getStoreApiEndpointUrl()) + ->willReturn(new Request('POST', 'http://www.example.com')); + + $this->httpClient->expects($this->once()) ->method('sendAsyncRequest') - ->willReturn(new HttpFullfilledPromise($response)); + ->with($this->callback(function (Request $requestArg): bool { + if ('application/json' !== $requestArg->getHeaderLine('Content-Type')) { + return false; + } + + if ('{"foo":"bar"}' !== $requestArg->getBody()->getContents()) { + return false; + } + + return true; + })) + ->willReturn(new HttpFullfilledPromise(new Response($httpStatusCode))); $transport = new HttpTransport( new Options(['dsn' => 'http://public@example.com/sentry/1']), - $httpClient, - Psr17FactoryDiscovery::findStreamFactory(), - Psr17FactoryDiscovery::findRequestFactory() + $this->httpClient, + $this->streamFactory, + $this->requestFactory, + $this->payloadSerializer ); $promise = $transport->send($event); @@ -129,6 +199,7 @@ public function sendDataProvider(): iterable public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientException(): void { + $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $exception = new \Exception('foo'); $event = new Event(); @@ -138,17 +209,33 @@ public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientExce ->method('error') ->with('Failed to send the event to Sentry. Reason: "foo".', ['exception' => $exception, 'event' => $event]); - /** @var HttpAsyncClientInterface&MockObject $httpClient */ - $httpClient = $this->createMock(HttpAsyncClientInterface::class); - $httpClient->expects($this->once()) + $this->payloadSerializer->expects($this->once()) + ->method('serialize') + ->with($event) + ->willReturn('{"foo":"bar"}'); + + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with('POST', $dsn->getStoreApiEndpointUrl()) + ->willReturn(new Request('POST', 'http://www.example.com')); + + $this->streamFactory->expects($this->once()) + ->method('createStream') + ->with('{"foo":"bar"}') + ->willReturnCallback(static function (string $content): StreamInterface { + return stream_for($content); + }); + + $this->httpClient->expects($this->once()) ->method('sendAsyncRequest') - ->willReturn(new RejectedPromise($exception)); + ->willReturn(new HttpRejectedPromise($exception)); $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $httpClient, - Psr17FactoryDiscovery::findStreamFactory(), - Psr17FactoryDiscovery::findRequestFactory(), + new Options(['dsn' => $dsn]), + $this->httpClient, + $this->streamFactory, + $this->requestFactory, + $this->payloadSerializer, $logger ); @@ -171,7 +258,8 @@ public function testClose(): void new Options(['dsn' => 'http://public@example.com/sentry/1']), $this->createMock(HttpAsyncClientInterface::class), $this->createMock(StreamFactoryInterface::class), - $this->createMock(RequestFactoryInterface::class) + $this->createMock(RequestFactoryInterface::class), + $this->createMock(PayloadSerializerInterface::class) ); $promise = $transport->close(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index b3985f2b4..98f7fd129 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -4,7 +4,26 @@ use Http\Discovery\ClassDiscovery; use Http\Discovery\Strategy\MockClientStrategy; +use Sentry\Breadcrumb; +use Sentry\Event; +use Sentry\Tracing\Span; +use Symfony\Bridge\PhpUnit\ClockMock; require_once __DIR__ . '/../vendor/autoload.php'; ClassDiscovery::appendStrategy(MockClientStrategy::class); + +// According to the Symfony documentation the proper way to register the mocked +// functions for a certain class would be to configure the listener in the +// phpunit.xml file, however in our case it doesn't work because PHPUnit loads +// the data providers of the tests long before instantiating the listeners. In +// turn, PHP caches the functions to call to avoid looking up in the function +// table again and again, therefore if for any reason the method that should use +// a mocked function gets called before the mock itself gets created it will not +// use the mocked methods. +// +// See https://symfony.com/doc/current/components/phpunit_bridge.html#troubleshooting +// See https://bugs.php.net/bug.php?id=64346 +ClockMock::register(Event::class); +ClockMock::register(Breadcrumb::class); +ClockMock::register(Span::class); From b2cee6df7f41979e8a0acf326665d8ae1882bc60 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 3 Sep 2020 10:41:47 +0200 Subject: [PATCH 0598/1161] Remove the SpoolTransport transport (#1080) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 1 + src/Spool/MemorySpool.php | 45 -------------- src/Spool/SpoolInterface.php | 33 ---------- src/Transport/SpoolTransport.php | 64 ------------------- tests/Spool/MemorySpoolTest.php | 42 ------------- tests/Transport/SpoolTransportTest.php | 86 -------------------------- 7 files changed, 2 insertions(+), 270 deletions(-) delete mode 100644 src/Spool/MemorySpool.php delete mode 100644 src/Spool/SpoolInterface.php delete mode 100644 src/Transport/SpoolTransport.php delete mode 100644 tests/Spool/MemorySpoolTest.php delete mode 100644 tests/Transport/SpoolTransportTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6b25bf9..6459073be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ More information can be found in our [Performance](https://docs.sentry.io/produc - [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) - [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) - [BC BREAK] Remove the `FlushableClientInterface` and the `ClosableTransportInterface` interfaces (#1079) +- [BC BREAK] Remove the `SpoolTransport` transport and all its related classes (#1080) - Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) - Refactor how the event data gets serialized to JSON (#1077) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index ccdeab9f3..d946559dd 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -108,3 +108,4 @@ - Removed the `Breadcrumb::toArray()` and `Breadcrumb::jsonSerialize()` methods - Removed the `Frame::toArray()` and `Frame::jsonSerialize()` methods - Removed the `Stacktrace::toArray()` and `Stacktrace::jsonSerialize()` methods +- Removed the `SpoolTransport` class and the `SpoolInterface` interface with related implementation diff --git a/src/Spool/MemorySpool.php b/src/Spool/MemorySpool.php deleted file mode 100644 index bce365874..000000000 --- a/src/Spool/MemorySpool.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ -final class MemorySpool implements SpoolInterface -{ - /** - * @var Event[] List of enqueued events - */ - private $events = []; - - /** - * {@inheritdoc} - */ - public function queueEvent(Event $event): bool - { - $this->events[] = $event; - - return true; - } - - /** - * {@inheritdoc} - */ - public function flushQueue(TransportInterface $transport): void - { - if (empty($this->events)) { - return; - } - - while ($event = array_pop($this->events)) { - $transport->send($event); - } - } -} diff --git a/src/Spool/SpoolInterface.php b/src/Spool/SpoolInterface.php deleted file mode 100644 index bacb31eb2..000000000 --- a/src/Spool/SpoolInterface.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ -interface SpoolInterface -{ - /** - * Queues an event. - * - * @param Event $event The event to store - * - * @return bool Whether the operation has succeeded - */ - public function queueEvent(Event $event): bool; - - /** - * Sends the events that are in the queue using the given transport. - * - * @param TransportInterface $transport The transport instance - */ - public function flushQueue(TransportInterface $transport): void; -} diff --git a/src/Transport/SpoolTransport.php b/src/Transport/SpoolTransport.php deleted file mode 100644 index 465ef6e39..000000000 --- a/src/Transport/SpoolTransport.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ -final class SpoolTransport implements TransportInterface -{ - /** - * @var SpoolInterface The spool instance - */ - private $spool; - - /** - * Constructor. - * - * @param SpoolInterface $spool The spool instance - */ - public function __construct(SpoolInterface $spool) - { - $this->spool = $spool; - } - - /** - * Gets the spool. - */ - public function getSpool(): SpoolInterface - { - return $this->spool; - } - - /** - * {@inheritdoc} - */ - public function send(Event $event): PromiseInterface - { - if ($this->spool->queueEvent($event)) { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); - } - - return new RejectedPromise(new Response(ResponseStatus::skipped(), $event)); - } - - /** - * {@inheritdoc} - */ - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } -} diff --git a/tests/Spool/MemorySpoolTest.php b/tests/Spool/MemorySpoolTest.php deleted file mode 100644 index ee01c2d0f..000000000 --- a/tests/Spool/MemorySpoolTest.php +++ /dev/null @@ -1,42 +0,0 @@ -spool = new MemorySpool(); - } - - public function testFlushQueue(): void - { - $event1 = new Event(); - $event2 = new Event(); - - /** @var TransportInterface|MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->exactly(2)) - ->method('send') - ->withConsecutive([$event2], [$event1]); - - $this->spool->queueEvent($event1); - $this->spool->queueEvent($event2); - - $this->spool->flushQueue($transport); - $this->spool->flushQueue($transport); - } -} diff --git a/tests/Transport/SpoolTransportTest.php b/tests/Transport/SpoolTransportTest.php deleted file mode 100644 index c795f7445..000000000 --- a/tests/Transport/SpoolTransportTest.php +++ /dev/null @@ -1,86 +0,0 @@ -spool = $this->createMock(SpoolInterface::class); - $this->transport = new SpoolTransport($this->spool); - } - - public function testGetSpool(): void - { - $this->assertSame($this->spool, $this->transport->getSpool()); - } - - /** - * @dataProvider sendDataProvider - */ - public function testSend(bool $shouldQueueEvent, string $expectedPromiseStatus, ResponseStatus $expectedResponseStatus): void - { - $event = new Event(); - - $this->spool->expects($this->once()) - ->method('queueEvent') - ->with($event) - ->willReturn($shouldQueueEvent); - - $promise = $this->transport->send($event); - - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); - } - - $this->assertSame($expectedPromiseStatus, $promise->getState()); - $this->assertSame($expectedResponseStatus, $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); - } - - public function sendDataProvider(): iterable - { - yield [ - true, - PromiseInterface::FULFILLED, - ResponseStatus::success(), - ]; - - yield [ - false, - PromiseInterface::REJECTED, - ResponseStatus::skipped(), - ]; - } - - public function testClose(): void - { - $promise = $this->transport->close(); - - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertTrue($promise->wait()); - } -} From 69bf460dcd739a40acf3eb9403da31f45aa82cbe Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 3 Sep 2020 11:27:37 +0200 Subject: [PATCH 0599/1161] fix: JSON serialization of spans --- src/Serializer/PayloadSerializer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 57faf5df2..dcd143731 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -149,7 +149,7 @@ private function serializeAsEvent(Event $event): string } if (EventType::transaction() === $event->getType()) { - $result['spans'] = array_map([$this, 'serializeSpan'], $event->getSpans()); + $result['spans'] = array_values(array_map([$this, 'serializeSpan'], $event->getSpans())); } return JSON::encode($result); From 53e8aeeef6677e23db55c63899ba01c1f4ba2265 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 3 Sep 2020 13:19:07 +0200 Subject: [PATCH 0600/1161] meta: Changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6459073be..ae723c073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## Unreleased +## 3.0.0-beta1 (2020-09-03) **Tracing API** @@ -8,6 +8,9 @@ In this version we released API for Tracing. `\Sentry\startTransaction` is your More information can be found in our [Performance](https://docs.sentry.io/product/performance/) docs or specific [PHP SDK](https://docs.sentry.io/platforms/php/) docs. +**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are +using an on-premise installation it requires Sentry version `>= v20.6.0` to work. If you are using +[sentry.io](https://sentry.io) nothing will change and no action is needed. - [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) - [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) From bf3f0ab8458f2a24f43ca6c497b53d2debc9668d Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 10 Sep 2020 11:44:03 +0200 Subject: [PATCH 0601/1161] feat: Add TracesSampler (#1083) * feat: Add TracesSampler * ref: Codereview * ref: Remove Interface only allow callable for traces_sampler * fix: tests * ref: CodeReview * ref: Tests * ref: Add typehint --- CHANGELOG.md | 4 ++ src/Options.php | 27 ++++++++++ src/State/Hub.php | 30 +++++++---- src/Tracing/SamplingContext.php | 34 ++++++++++++ tests/OptionsTest.php | 2 + tests/Tracing/TransactionTest.php | 88 ++++++++++++++++++++++++++++--- 6 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 src/Tracing/SamplingContext.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ae723c073..6910f55cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Unreleased + +- Add `traces_sampler` option to set custom sample rate callback (#1083) + ## 3.0.0-beta1 (2020-09-03) **Tracing API** diff --git a/src/Options.php b/src/Options.php index fbee57477..730cf919b 100644 --- a/src/Options.php +++ b/src/Options.php @@ -658,6 +658,31 @@ public function setClassSerializers(array $serializers): void $this->options = $this->resolver->resolve($options); } + /** + * Gets a callback that will be invoked when we sample a Transaction. + * + * @psalm-return ?callable(\Sentry\Tracing\SamplingContext): float + */ + public function getTracesSampler(): ?callable + { + return $this->options['traces_sampler']; + } + + /** + * Sets a callback that will be invoked when we take the sampling decision for Transactions. + * Return a number between 0 and 1 to define the sample rate for the provided SamplingContext. + * + * @param ?callable $sampler The sampler + * + * @psalm-param ?callable(\Sentry\Tracing\SamplingContext): float $sampler + */ + public function setTracesSampler(?callable $sampler): void + { + $options = array_merge($this->options, ['traces_sampler' => $sampler]); + + $this->options = $this->resolver->resolve($options); + } + /** * Configures the options of the client. * @@ -675,6 +700,7 @@ private function configureOptions(OptionsResolver $resolver): void 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), 'sample_rate' => 1, 'traces_sample_rate' => 0, + 'traces_sampler' => null, 'attach_stacktrace' => false, 'context_lines' => 5, 'enable_compression' => true, @@ -706,6 +732,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('prefixes', 'array'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('traces_sample_rate', ['int', 'float']); + $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); $resolver->setAllowedTypes('enable_compression', 'bool'); diff --git a/src/State/Hub.php b/src/State/Hub.php index ec9415315..a46f2b636 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -9,6 +9,7 @@ use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; +use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -230,22 +231,33 @@ private function getStackTop(): Layer public function startTransaction(TransactionContext $context): Transaction { $client = $this->getClient(); + $sampleRate = null; + + if (null !== $client) { + $sampler = $client->getOptions()->getTracesSampler(); + + if (null !== $sampler) { + $sampleRate = $sampler(SamplingContext::getDefault($context)); + } + } // Roll the dice for sampling transaction, all child spans inherit the sampling decision. - if (null === $context->sampled) { + // Only if $sampleRate is `null` (which can only be because then the traces_sampler wasn't defined) + // we need to roll the dice. + if (null === $context->sampled && null === $sampleRate) { if (null !== $client) { $sampleRate = $client->getOptions()->getTracesSampleRate(); - - if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { - // if true = we want to have the transaction - // if false = we don't want to have it - $context->sampled = false; - } else { - $context->sampled = true; - } } } + if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { + // if true = we want to have the transaction + // if false = we don't want to have it + $context->sampled = false; + } else { + $context->sampled = true; + } + $transaction = new Transaction($context, $this); // We only want to create a span list if we sampled the transaction diff --git a/src/Tracing/SamplingContext.php b/src/Tracing/SamplingContext.php new file mode 100644 index 000000000..0210195b8 --- /dev/null +++ b/src/Tracing/SamplingContext.php @@ -0,0 +1,34 @@ +setTransactionContext($transactionContext); + + return $context; + } + + public function getTransactionContext(): ?TransactionContext + { + return $this->transactionContext; + } + + public function setTransactionContext(?TransactionContext $transactionContext): void + { + $this->transactionContext = $transactionContext; + } +} diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index aa7c2c275..67af2d459 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -49,6 +49,8 @@ public function optionsDataProvider(): array ['send_attempts', 1, 'getSendAttempts', 'setSendAttempts'], ['prefixes', ['foo', 'bar'], 'getPrefixes', 'setPrefixes'], ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], + ['traces_sample_rate', 0.5, 'getTracesSampleRate', 'setTracesSampleRate'], + ['traces_sampler', static function (): void {}, 'getTracesSampler', 'setTracesSampler'], ['attach_stacktrace', false, 'shouldAttachStacktrace', 'setAttachStacktrace'], ['context_lines', 3, 'getContextLines', 'setContextLines'], ['enable_compression', false, 'isCompressionEnabled', 'setEnableCompression'], diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index 345f26d5e..12afb08f1 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -11,6 +11,7 @@ use Sentry\EventType; use Sentry\Options; use Sentry\State\Hub; +use Sentry\Tracing\SamplingContext; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\SpanRecorder; @@ -74,20 +75,18 @@ public function testShouldContainFinishSpans(): void $this->assertCount(2, $data->getSpans()); } - public function testStartAndSendTransaction(): void + public function testStartAndSendTransactionSampleRateOne(): void { /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) + $client->expects($this->any()) ->method('getOptions') ->willReturn(new Options(['traces_sample_rate' => 1])); $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $span1 = $transaction->startChild(new SpanContext()); - $span2 = $transaction->startChild(new SpanContext()); $span1->finish(); - $span2->finish(); $data = $transaction->toEvent(); @@ -97,11 +96,88 @@ public function testStartAndSendTransaction(): void $this->assertSame(EventType::transaction(), $eventArg->getType()); $this->assertSame($data->getStartTimestamp(), $eventArg->getStartTimestamp()); $this->assertSame(microtime(true), $eventArg->getTimestamp()); - $this->assertCount(2, $data->getSpans()); + $this->assertCount(1, $data->getSpans()); return true; })); - $transaction->finish(microtime(true)); + $transaction->finish(); + } + + public function testStartAndSendTransactionSampleRateZero(): void + { + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 0])); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext()); + $span1 = $transaction->startChild(new SpanContext()); + $span1->finish(); + + $client->expects($this->never()) + ->method('captureEvent'); + + $transaction->finish(); + } + + public function testStartAndSendTransactionSamplerShouldBeStrongerThanRateOne(): void + { + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 0, + 'traces_sampler' => static function (SamplingContext $context): float { + return 1; + }, + ])); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext()); + $span1 = $transaction->startChild(new SpanContext()); + $span1->finish(); + + $data = $transaction->toEvent(); + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(function (Event $eventArg) use ($data): bool { + $this->assertSame(EventType::transaction(), $eventArg->getType()); + $this->assertSame($data->getStartTimestamp(), $eventArg->getStartTimestamp()); + $this->assertSame(microtime(true), $eventArg->getTimestamp()); + $this->assertCount(1, $data->getSpans()); + + return true; + })); + + $transaction->finish(); + } + + public function testStartAndSendTransactionSamplerShouldBeStrongerThanRateZero(): void + { + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1, + 'traces_sampler' => static function (SamplingContext $context): float { + return 0; + }, + ])); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext()); + $span1 = $transaction->startChild(new SpanContext()); + $span1->finish(); + + $client->expects($this->never()) + ->method('captureEvent'); + + $transaction->finish(); } } From eba52ad8725bf581f01b0a96218ff3f3e187067b Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Sun, 13 Sep 2020 21:41:40 +0200 Subject: [PATCH 0602/1161] Symfony HTTP client support (#1084) --- CHANGELOG.md | 2 ++ phpstan.neon | 6 +++--- src/HttpClient/HttpClientFactory.php | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ac5bb8c..20c416364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Support the `timeout` and `proxy` options for the Symfony HTTP Client (#1084) + ### 2.4.3 (2020-08-13) - Fix `Options::setEnvironment` method not accepting `null` values (#1057) diff --git a/phpstan.neon b/phpstan.neon index bbdace8dc..aabc4f7d3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,6 +12,9 @@ parameters: - message: '/^Access to constant (?:PROXY|TIMEOUT|CONNECT_TIMEOUT) on an unknown class GuzzleHttp\\RequestOptions\.$/' path: src/HttpClient/HttpClientFactory.php + - + message: '/^Call to static method create\(\) on an unknown class Symfony\\Component\\HttpClient\\HttpClient\.$/' + path: src/HttpClient/HttpClientFactory.php - message: '/^Parameter #1 \$c of function ctype_digit expects int\|string, string\|null given\.$/' path: src/Dsn.php @@ -27,9 +30,6 @@ parameters: - message: '/^Method Sentry\\Monolog\\Handler::write\(\) has parameter \$record with no value type specified in iterable type array\.$/' path: src/Monolog/Handler.php - - - message: '/^Cannot cast array\|bool\|float\|int\|string\|null to string\.$/' - path: src/Serializer/RepresentationSerializer.php - message: '/^Method Sentry\\Client::getIntegration\(\) should return T of Sentry\\Integration\\IntegrationInterface\|null but returns Sentry\\Integration\\IntegrationInterface\|null\.$/' path: src/Client.php diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 9ce606feb..83ec7f70a 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -21,6 +21,8 @@ use Sentry\HttpClient\Authentication\SentryAuthentication; use Sentry\HttpClient\Plugin\GzipEncoderPlugin; use Sentry\Options; +use Symfony\Component\HttpClient\HttpClient as SymfonyHttpClient; +use Symfony\Component\HttpClient\HttplugClient as SymfonyHttplugClient; /** * Default implementation of the {@HttpClientFactoryInterface} interface that uses @@ -111,7 +113,20 @@ public function create(Options $options): HttpAsyncClientInterface } if (null === $httpClient) { - if (class_exists(GuzzleHttpClient::class)) { + if (class_exists(SymfonyHttplugClient::class)) { + $symfonyConfig = [ + 'max_duration' => self::DEFAULT_HTTP_TIMEOUT, + ]; + + if (null !== $options->getHttpProxy()) { + $symfonyConfig['proxy'] = $options->getHttpProxy(); + } + + /** @psalm-suppress UndefinedClass */ + $httpClient = new SymfonyHttplugClient( + SymfonyHttpClient::create($symfonyConfig) + ); + } elseif (class_exists(GuzzleHttpClient::class)) { /** @psalm-suppress UndefinedClass */ $guzzleConfig = [ GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, From 24cf09e245333c43d9df948eacfaad67573363e9 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 13 Sep 2020 22:17:51 +0200 Subject: [PATCH 0603/1161] Add named constructor to the Event class (#1085) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 1 + src/Event.php | 32 ++++++++---- src/EventFactory.php | 2 +- src/Tracing/Transaction.php | 4 +- tests/EventTest.php | 8 +-- tests/FunctionsTest.php | 2 +- .../EnvironmentIntegrationTest.php | 2 +- .../FrameContextifierIntegrationTest.php | 4 +- .../IgnoreErrorsIntegrationTest.php | 12 ++--- tests/Integration/ModulesIntegrationTest.php | 2 +- tests/Integration/RequestIntegrationTest.php | 2 +- .../TransactionIntegrationTest.php | 12 ++--- tests/Monolog/HandlerTest.php | 2 +- tests/Serializer/PayloadSerializerTest.php | 14 +++-- tests/State/HubTest.php | 14 ++--- tests/State/ScopeTest.php | 52 +++++++++---------- tests/Transport/HttpTransportTest.php | 10 ++-- tests/Transport/NullTransportTest.php | 2 +- 19 files changed, 92 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6910f55cc..a7e7edf5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Add `traces_sampler` option to set custom sample rate callback (#1083) +- [BC BREAK] Add named constructors to the `Event` class (#1085) ## 3.0.0-beta1 (2020-09-03) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index d946559dd..007cb76fa 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -109,3 +109,4 @@ - Removed the `Frame::toArray()` and `Frame::jsonSerialize()` methods - Removed the `Stacktrace::toArray()` and `Stacktrace::jsonSerialize()` methods - Removed the `SpoolTransport` class and the `SpoolInterface` interface with related implementation +- Made the `Event::__construct()` method `private`, use the named constructors instead diff --git a/src/Event.php b/src/Event.php index 300a40467..b06b5c5fd 100644 --- a/src/Event.php +++ b/src/Event.php @@ -158,17 +158,32 @@ final class Event */ private $type; + private function __construct(?EventId $eventId, EventType $eventType) + { + $this->id = $eventId ?? EventId::generate(); + $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); + $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + $this->type = $eventType; + } + /** - * Class constructor. + * Creates a new event. * * @param EventId|null $eventId The ID of the event */ - public function __construct(?EventId $eventId = null) + public static function createEvent(?EventId $eventId = null): self { - $this->id = $eventId ?? EventId::generate(); - $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); - $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); - $this->type = EventType::default(); + return new self($eventId, EventType::default()); + } + + /** + * Creates a new transaction event. + * + * @param EventId|null $eventId The ID of the event + */ + public static function createTransaction(EventId $eventId = null): self + { + return new self($eventId, EventType::transaction()); } /** @@ -637,11 +652,6 @@ public function getType(): EventType return $this->type; } - public function setType(EventType $type): void - { - $this->type = $type; - } - /** * Gets a timestamp representing when the measuring of a transaction started. */ diff --git a/src/EventFactory.php b/src/EventFactory.php index c3b917953..964e95b64 100644 --- a/src/EventFactory.php +++ b/src/EventFactory.php @@ -85,7 +85,7 @@ public function create($payload): Event if ($payload instanceof Event) { $event = $payload; } else { - $event = new Event(); + $event = Event::createEvent(); if (isset($payload['logger'])) { $event->setLogger($payload['logger']); diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index 49c0476d2..5576a3544 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -6,7 +6,6 @@ use Sentry\Event; use Sentry\EventId; -use Sentry\EventType; use Sentry\Severity; use Sentry\State\HubInterface; @@ -90,8 +89,7 @@ public function finish(?float $endTimestamp = null): ?EventId */ public function toEvent(): Event { - $event = new Event(); - $event->setType(EventType::transaction()); + $event = Event::createTransaction(); $event->setTags(array_merge($event->getTags(), $this->tags)); $event->setTransaction($this->name); $event->setStartTimestamp($this->startTimestamp); diff --git a/tests/EventTest.php b/tests/EventTest.php index ae343df6a..26713e253 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -15,8 +15,8 @@ final class EventTest extends TestCase { public function testEventIsGeneratedWithUniqueIdentifier(): void { - $event1 = new Event(); - $event2 = new Event(); + $event1 = Event::createEvent(); + $event2 = Event::createEvent(); $this->assertNotEquals($event1->getId(), $event2->getId()); } @@ -26,7 +26,7 @@ public function testEventIsGeneratedWithUniqueIdentifier(): void */ public function testGetMessage(array $setMessageArguments, array $expectedValue): void { - $event = new Event(); + $event = Event::createEvent(); \call_user_func_array([$event, 'setMessage'], $setMessageArguments); @@ -80,7 +80,7 @@ public function testGettersAndSetters(string $propertyName, $propertyValue): voi $getterMethod = 'get' . ucfirst($propertyName); $setterMethod = 'set' . ucfirst($propertyName); - $event = new Event(); + $event = Event::createEvent(); $event->$setterMethod($propertyValue); $this->assertEquals($event->$getterMethod(), $propertyValue); diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 30cf5b603..3c26129e4 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -112,7 +112,7 @@ public function testAddBreadcrumb(): void addBreadcrumb($breadcrumb); configureScope(function (Scope $scope) use ($breadcrumb): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); diff --git a/tests/Integration/EnvironmentIntegrationTest.php b/tests/Integration/EnvironmentIntegrationTest.php index 8e005f616..13d2a1c04 100644 --- a/tests/Integration/EnvironmentIntegrationTest.php +++ b/tests/Integration/EnvironmentIntegrationTest.php @@ -35,7 +35,7 @@ public function testInvoke(bool $isIntegrationEnabled, ?RuntimeContext $initialR SentrySdk::getCurrentHub()->bindClient($client); withScope(function (Scope $scope) use ($expectedRuntimeContext, $expectedOsContext, $initialRuntimeContext, $initialOsContext): void { - $event = new Event(); + $event = Event::createEvent(); $event->setRuntimeContext($initialRuntimeContext); $event->setOsContext($initialOsContext); diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index 9a555b693..5a65b3113 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -63,7 +63,7 @@ public function testInvoke(string $fixtureFilePath, int $lineNumber, int $contex new Frame('[unknown]', $fixtureFilePath, $lineNumber, null, $fixtureFilePath), ]); - $event = new Event(); + $event = Event::createEvent(); $event->setStacktrace($stacktrace); withScope(static function (Scope $scope) use (&$event): void { @@ -157,7 +157,7 @@ public function testInvokeLogsWarningMessageIfSourceCodeExcerptCannotBeRetrieved new Frame(null, 'file.ext', 10, null, 'file.ext'), ]); - $event = new Event(); + $event = Event::createEvent(); $event->setStacktrace($stacktrace); withScope(static function (Scope $scope) use (&$event): void { diff --git a/tests/Integration/IgnoreErrorsIntegrationTest.php b/tests/Integration/IgnoreErrorsIntegrationTest.php index 88b58bfe1..2bc9130be 100644 --- a/tests/Integration/IgnoreErrorsIntegrationTest.php +++ b/tests/Integration/IgnoreErrorsIntegrationTest.php @@ -45,11 +45,11 @@ public function testInvoke(Event $event, bool $isIntegrationEnabled, array $inte public function invokeDataProvider(): \Generator { - $event = new Event(); + $event = Event::createEvent(); $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); yield 'Integration disabled' => [ - new Event(), + Event::createEvent(), false, [ 'ignore_exceptions' => [], @@ -57,11 +57,11 @@ public function invokeDataProvider(): \Generator false, ]; - $event = new Event(); + $event = Event::createEvent(); $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); yield 'No exceptions to check' => [ - new Event(), + Event::createEvent(), true, [ 'ignore_exceptions' => [], @@ -69,7 +69,7 @@ public function invokeDataProvider(): \Generator false, ]; - $event = new Event(); + $event = Event::createEvent(); $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); yield 'The exception is matching exactly the "ignore_exceptions" option' => [ @@ -83,7 +83,7 @@ public function invokeDataProvider(): \Generator true, ]; - $event = new Event(); + $event = Event::createEvent(); $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); yield 'The exception is matching the "ignore_exceptions" option' => [ diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index 81c4d92db..b9457be19 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -32,7 +32,7 @@ public function testInvoke(bool $isIntegrationEnabled, bool $expectedEmptyModule SentrySdk::getCurrentHub()->bindClient($client); withScope(function (Scope $scope) use ($expectedEmptyModules): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index efd507a26..ed5371a8b 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -28,7 +28,7 @@ final class RequestIntegrationTest extends TestCase */ public function testInvoke(array $options, ServerRequestInterface $request, array $expectedRequestContextData, ?UserDataBag $initialUser, ?UserDataBag $expectedUser): void { - $event = new Event(); + $event = Event::createEvent(); $event->setUser($initialUser); $integration = new RequestIntegration($this->createRequestFetcher($request)); diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index 213c36952..85151c677 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -46,7 +46,7 @@ public function testSetupOnce(Event $event, bool $isIntegrationEnabled, array $p public function setupOnceDataProvider(): \Generator { yield [ - new Event(), + Event::createEvent(), true, [], [], @@ -54,7 +54,7 @@ public function setupOnceDataProvider(): \Generator ]; yield [ - new Event(), + Event::createEvent(), true, ['transaction' => '/foo/bar'], [], @@ -62,14 +62,14 @@ public function setupOnceDataProvider(): \Generator ]; yield [ - new Event(), + Event::createEvent(), true, [], ['PATH_INFO' => '/foo/bar'], '/foo/bar', ]; - $event = new Event(); + $event = Event::createEvent(); $event->setTransaction('/foo/bar'); yield [ @@ -80,7 +80,7 @@ public function setupOnceDataProvider(): \Generator '/foo/bar', ]; - $event = new Event(); + $event = Event::createEvent(); $event->setTransaction('/foo/bar'); yield [ @@ -92,7 +92,7 @@ public function setupOnceDataProvider(): \Generator ]; yield [ - new Event(), + Event::createEvent(), false, ['transaction' => '/foo/bar'], [], diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index 0c1cc3fd7..2d177dd3a 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -26,7 +26,7 @@ public function testHandle(array $record, array $expectedPayload, array $expecte $client->expects($this->once()) ->method('captureEvent') ->with($expectedPayload, $this->callback(function (Scope $scopeArg) use ($expectedExtra): bool { - $event = $scopeArg->applyToEvent(new Event(), []); + $event = $scopeArg->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame($expectedExtra, $event->getExtra()); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 2926b2845..f1e93bfdf 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -11,7 +11,6 @@ use Sentry\Context\RuntimeContext; use Sentry\Event; use Sentry\EventId; -use Sentry\EventType; use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; @@ -62,7 +61,7 @@ public function serializeDataProvider(): iterable $sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); yield [ - new Event(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), + Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), <<setLevel(Severity::error()); $event->setLogger('app.php'); $event->setTransaction('/users//'); @@ -316,7 +315,7 @@ public function serializeDataProvider(): iterable true, ]; - $event = new Event(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setMessage('My raw message with interpreted strings like this', []); yield [ @@ -337,7 +336,7 @@ public function serializeDataProvider(): iterable true, ]; - $event = new Event(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setMessage('My raw message with interpreted strings like %s', ['this']); yield [ @@ -362,7 +361,7 @@ public function serializeDataProvider(): iterable true, ]; - $event = new Event(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setMessage('My raw message with interpreted strings like %s', ['this'], 'My raw message with interpreted strings like that'); yield [ @@ -409,8 +408,7 @@ public function serializeDataProvider(): iterable $span2->finish(1598659060); - $event = new Event(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); - $event->setType(EventType::transaction()); + $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setSpans([$span1, $span2]); yield [ diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 6650a0bf7..8b84f72fc 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -289,7 +289,7 @@ public function testAddBreadcrumb(): void $hub->addBreadcrumb($breadcrumb); $hub->configureScope(function (Scope $scope): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -298,7 +298,7 @@ public function testAddBreadcrumb(): void $hub->bindClient($client); $hub->addBreadcrumb($breadcrumb); $hub->configureScope(function (Scope $scope) use (&$callbackInvoked, $breadcrumb): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); @@ -321,7 +321,7 @@ public function testAddBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsZero(): void $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); $hub->configureScope(function (Scope $scope): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -345,7 +345,7 @@ public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void $hub->addBreadcrumb($breadcrumb2); $hub->configureScope(function (Scope $scope) use ($breadcrumb1, $breadcrumb2): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame([$breadcrumb1, $breadcrumb2], $event->getBreadcrumbs()); @@ -354,7 +354,7 @@ public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void $hub->addBreadcrumb($breadcrumb3); $hub->configureScope(function (Scope $scope) use ($breadcrumb2, $breadcrumb3): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame([$breadcrumb2, $breadcrumb3], $event->getBreadcrumbs()); @@ -377,7 +377,7 @@ public function testAddBreadcrumbDoesNothingWhenBeforeBreadcrumbCallbackReturnsN $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); $hub->configureScope(function (Scope $scope): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -404,7 +404,7 @@ public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallb $hub->addBreadcrumb($breadcrumb1); $hub->configureScope(function (Scope $scope) use (&$callbackInvoked, $breadcrumb2): void { - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame([$breadcrumb2], $event->getBreadcrumbs()); diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index f9a43d8ed..95ced8739 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -16,7 +16,7 @@ final class ScopeTest extends TestCase public function testSetTag(): void { $scope = new Scope(); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEmpty($event->getTags()); @@ -24,7 +24,7 @@ public function testSetTag(): void $scope->setTag('foo', 'bar'); $scope->setTag('bar', 'baz'); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); @@ -35,14 +35,14 @@ public function testSetTags(): void $scope = new Scope(); $scope->setTags(['foo' => 'bar']); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar'], $event->getTags()); $scope->setTags(['bar' => 'baz']); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); @@ -53,14 +53,14 @@ public function testSetAndRemoveContext(): void $scope = new Scope(); $scope->setContext('foo', ['foo' => 'bar']); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame(['foo' => ['foo' => 'bar']], $event->getContexts()); $scope->removeContext('foo'); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame([], $event->getContexts()); @@ -69,7 +69,7 @@ public function testSetAndRemoveContext(): void public function testSetExtra(): void { $scope = new Scope(); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEmpty($event->getExtra()); @@ -77,7 +77,7 @@ public function testSetExtra(): void $scope->setExtra('foo', 'bar'); $scope->setExtra('bar', 'baz'); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtra()); @@ -88,14 +88,14 @@ public function testSetExtras(): void $scope = new Scope(); $scope->setExtras(['foo' => 'bar']); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar'], $event->getExtra()); $scope->setExtras(['bar' => 'baz']); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtra()); @@ -104,7 +104,7 @@ public function testSetExtras(): void public function testSetUser(): void { $scope = new Scope(); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertNull($event->getUser()); @@ -114,7 +114,7 @@ public function testSetUser(): void $scope->setUser($user); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame($user, $event->getUser()); @@ -145,14 +145,14 @@ public function testRemoveUser(): void $scope = new Scope(); $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertNotNull($event->getUser()); $scope->removeUser(); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertNull($event->getUser()); @@ -161,14 +161,14 @@ public function testRemoveUser(): void public function testSetFingerprint(): void { $scope = new Scope(); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEmpty($event->getFingerprint()); $scope->setFingerprint(['foo', 'bar']); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame(['foo', 'bar'], $event->getFingerprint()); @@ -177,14 +177,14 @@ public function testSetFingerprint(): void public function testSetLevel(): void { $scope = new Scope(); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertNull($event->getLevel()); $scope->setLevel(Severity::debug()); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEquals(Severity::debug(), $event->getLevel()); @@ -197,7 +197,7 @@ public function testAddBreadcrumb(): void $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -205,14 +205,14 @@ public function testAddBreadcrumb(): void $scope->addBreadcrumb($breadcrumb1); $scope->addBreadcrumb($breadcrumb2); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame([$breadcrumb1, $breadcrumb2], $event->getBreadcrumbs()); $scope->addBreadcrumb($breadcrumb3, 2); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertSame([$breadcrumb2, $breadcrumb3], $event->getBreadcrumbs()); @@ -225,14 +225,14 @@ public function testClearBreadcrumbs(): void $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertNotEmpty($event->getBreadcrumbs()); $scope->clearBreadcrumbs(); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -244,7 +244,7 @@ public function testAddEventProcessor(): void $callback2Called = false; $callback3Called = false; - $event = new Event(); + $event = Event::createEvent(); $scope = new Scope(); $scope->addEventProcessor(function (Event $eventArg) use (&$callback1Called, $callback2Called, $callback3Called): ?Event { @@ -292,7 +292,7 @@ public function testClear(): void $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); $scope->clear(); - $event = $scope->applyToEvent(new Event(), []); + $event = $scope->applyToEvent(Event::createEvent(), []); $this->assertNotNull($event); $this->assertNull($event->getLevel()); @@ -308,7 +308,7 @@ public function testApplyToEvent(): void $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $user = UserDataBag::createFromUserIdentifier('unique_id'); - $event = new Event(); + $event = Event::createEvent(); $event->setContext('foocontext', ['foo' => 'foo', 'bar' => 'bar']); $scope = new Scope(); diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index cf79691bc..b54fa8e6e 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -19,7 +19,6 @@ use Psr\Log\LoggerInterface; use Sentry\Dsn; use Sentry\Event; -use Sentry\EventType; use Sentry\Options; use Sentry\ResponseStatus; use Sentry\Serializer\PayloadSerializerInterface; @@ -69,14 +68,13 @@ public function testSendThrowsIfDsnOptionIsNotSet(): void $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('The DSN option must be set to use the "Sentry\Transport\HttpTransport" transport.'); - $transport->send(new Event()); + $transport->send(Event::createEvent()); } public function testSendTransactionAsEnvelope(): void { $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); - $event = new Event(); - $event->setType(EventType::transaction()); + $event = Event::createTransaction(); $this->payloadSerializer->expects($this->once()) ->method('serialize') @@ -127,7 +125,7 @@ public function testSendTransactionAsEnvelope(): void public function testSend(int $httpStatusCode, string $expectedPromiseStatus, ResponseStatus $expectedResponseStatus): void { $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); - $event = new Event(); + $event = Event::createEvent(); $this->payloadSerializer->expects($this->once()) ->method('serialize') @@ -201,7 +199,7 @@ public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientExce { $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $exception = new \Exception('foo'); - $event = new Event(); + $event = Event::createEvent(); /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php index f83f022c5..954e3ee0b 100644 --- a/tests/Transport/NullTransportTest.php +++ b/tests/Transport/NullTransportTest.php @@ -24,7 +24,7 @@ protected function setUp(): void public function testSend(): void { - $event = new Event(); + $event = Event::createEvent(); $promise = $this->transport->send($event); $promiseResult = $promise->wait(); From bab5b73dbaf5f0ff62317e1611d952764d5514a9 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 14 Sep 2020 09:02:40 +0200 Subject: [PATCH 0604/1161] prepare: 2.5.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20c416364..4fc0882a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +### 2.5.0 (2020-09-14) + - Support the `timeout` and `proxy` options for the Symfony HTTP Client (#1084) ### 2.4.3 (2020-08-13) From dc9e6255e8deaab8096a298a05de981bc114e37d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 16 Sep 2020 10:07:32 +0200 Subject: [PATCH 0605/1161] Update some deps and require at least PHP 7.2 (#1088) --- .appveyor.yml | 6 ------ .travis.yml | 1 - CHANGELOG.md | 1 + composer.json | 4 ++-- src/Options.php | 49 +++++-------------------------------------------- 5 files changed, 8 insertions(+), 53 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3c9772209..abb6aefe0 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,12 +7,6 @@ image: Visual Studio 2019 environment: matrix: - - PHP_VERSION: 7.1 - XDEBUG_VERSION: 2.9.2-7.1-vc14-nts - DEPENDENCIES: lowest - - PHP_VERSION: 7.1 - XDEBUG_VERSION: 2.9.2-7.1-vc14-nts - DEPENDENCIES: highest - PHP_VERSION: 7.2 XDEBUG_VERSION: 2.9.2-7.2-vc15-nts DEPENDENCIES: lowest diff --git a/.travis.yml b/.travis.yml index 2216d439c..ebe4ca7e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 7.1 - 7.2 - 7.3 - 7.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 82c7fba90..0af7d5891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Add `traces_sampler` option to set custom sample rate callback (#1083) - [BC BREAK] Add named constructors to the `Event` class (#1085) +- Raise the minimum version of PHP to `7.2` and the minimum version of some dependencies (#1088) ## 3.0.0-beta1 (2020-09-03) diff --git a/composer.json b/composer.json index f5421589b..833590ee7 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": "^7.1", + "php": "^7.2", "ext-json": "*", "ext-mbstring": "*", "guzzlehttp/promises": "^1.3", @@ -35,7 +35,7 @@ "psr/http-factory": "^1.0", "psr/http-message-implementation": "^1.0", "psr/log": "^1.0", - "symfony/options-resolver": "^2.7|^3.0|^4.0|^5.0", + "symfony/options-resolver": "^3.4.4|^4.0|^5.0", "symfony/polyfill-php80": "^1.17", "symfony/polyfill-uuid": "^1.13.1" }, diff --git a/src/Options.php b/src/Options.php index 730cf919b..c5971ff58 100644 --- a/src/Options.php +++ b/src/Options.php @@ -729,7 +729,7 @@ private function configureOptions(OptionsResolver $resolver): void ]); $resolver->setAllowedTypes('send_attempts', 'int'); - $resolver->setAllowedTypes('prefixes', 'array'); + $resolver->setAllowedTypes('prefixes', 'string[]'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('traces_sample_rate', ['int', 'float']); $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); @@ -737,18 +737,18 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('context_lines', ['null', 'int']); $resolver->setAllowedTypes('enable_compression', 'bool'); $resolver->setAllowedTypes('environment', ['null', 'string']); - $resolver->setAllowedTypes('in_app_exclude', 'array'); - $resolver->setAllowedTypes('in_app_include', 'array'); + $resolver->setAllowedTypes('in_app_exclude', 'string[]'); + $resolver->setAllowedTypes('in_app_include', 'string[]'); $resolver->setAllowedTypes('logger', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); - $resolver->setAllowedTypes('tags', 'array'); + $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); $resolver->setAllowedTypes('before_breadcrumb', ['callable']); - $resolver->setAllowedTypes('integrations', ['array', 'callable']); + $resolver->setAllowedTypes('integrations', ['Sentry\\Integration\\IntegrationInterface[]', 'callable']); $resolver->setAllowedTypes('send_default_pii', 'bool'); $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); @@ -759,10 +759,8 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedValues('max_request_body_size', ['none', 'small', 'medium', 'always']); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); - $resolver->setAllowedValues('integrations', \Closure::fromCallable([$this, 'validateIntegrationsOption'])); $resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions'])); $resolver->setAllowedValues('class_serializers', \Closure::fromCallable([$this, 'validateClassSerializersOption'])); - $resolver->setAllowedValues('tags', \Closure::fromCallable([$this, 'validateTagsOption'])); $resolver->setAllowedValues('context_lines', \Closure::fromCallable([$this, 'validateContextLinesOption'])); $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); @@ -863,27 +861,6 @@ private function validateDsnOption($dsn): bool } } - /** - * Validates that the elements of this option are all class instances that - * implements the {@see IntegrationInterface} interface. - * - * @param IntegrationInterface[]|callable $integrations The value to validate - */ - private function validateIntegrationsOption($integrations): bool - { - if (\is_callable($integrations)) { - return true; - } - - foreach ($integrations as $integration) { - if (!$integration instanceof IntegrationInterface) { - return false; - } - } - - return true; - } - /** * Validates if the value of the max_breadcrumbs option is in range. * @@ -910,22 +887,6 @@ private function validateClassSerializersOption(array $serializers): bool return true; } - /** - * Validates that the values passed to the `tags` option are valid. - * - * @param mixed[] $tags The value to validate - */ - private function validateTagsOption(array $tags): bool - { - foreach ($tags as $tagName => $tagValue) { - if (!\is_string($tagValue)) { - return false; - } - } - - return true; - } - /** * Validates that the value passed to the "context_lines" option is valid. * From 0f2e068041eebebd07e0a04a7ff6e0ed7e716849 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 18 Sep 2020 09:43:04 +0200 Subject: [PATCH 0606/1161] Refactor the setup process of the integrations (#1086) --- src/Client.php | 8 +- src/Integration/Handler.php | 53 ---- src/Integration/IntegrationRegistry.php | 141 +++++++++ src/Options.php | 61 +--- src/State/HubInterface.php | 13 +- src/functions.php | 2 +- tests/ClientBuilderTest.php | 66 ---- tests/ClientTest.php | 59 +++- tests/Integration/IntegrationRegistryTest.php | 285 ++++++++++++++++++ tests/OptionsTest.php | 109 ------- tests/SentrySdkExtension.php | 6 +- 11 files changed, 499 insertions(+), 304 deletions(-) delete mode 100644 src/Integration/Handler.php create mode 100644 src/Integration/IntegrationRegistry.php create mode 100644 tests/Integration/IntegrationRegistryTest.php diff --git a/src/Client.php b/src/Client.php index 6b9aadfeb..e3e09ce0b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -7,8 +7,8 @@ use GuzzleHttp\Promise\PromiseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Sentry\Integration\Handler; use Sentry\Integration\IntegrationInterface; +use Sentry\Integration\IntegrationRegistry; use Sentry\State\Scope; use Sentry\Transport\TransportInterface; @@ -69,8 +69,8 @@ public function __construct(Options $options, TransportInterface $transport, Eve $this->options = $options; $this->transport = $transport; $this->eventFactory = $eventFactory; - $this->integrations = Handler::setupIntegrations($options->getIntegrations()); $this->logger = $logger ?? new NullLogger(); + $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); } /** @@ -191,7 +191,7 @@ private function prepareEvent($payload, ?Scope $scope = null): ?Event $event = $scope->applyToEvent($event, $payload); if (null === $event) { - $this->logger->info('The event will be discarded because one of the event processors returned `null`.', ['event' => $previousEvent]); + $this->logger->info('The event will be discarded because one of the event processors returned "null".', ['event' => $previousEvent]); return null; } @@ -201,7 +201,7 @@ private function prepareEvent($payload, ?Scope $scope = null): ?Event $event = ($this->options->getBeforeSendCallback())($event); if (null === $event) { - $this->logger->info('The event will be discarded because the "before_send" callback returned `null`.', ['event' => $previousEvent]); + $this->logger->info('The event will be discarded because the "before_send" callback returned "null".', ['event' => $previousEvent]); } return $event; diff --git a/src/Integration/Handler.php b/src/Integration/Handler.php deleted file mode 100644 index afd15420f..000000000 --- a/src/Integration/Handler.php +++ /dev/null @@ -1,53 +0,0 @@ - The registered integrations - */ - private static $integrations = []; - - /** - * Calls {@link IntegrationInterface::setupOnce} for all passed integrations - * if it hasn't been called yet. - * - * @param IntegrationInterface[] $integrations The integrations - * - * @return array - * - * @psalm-return array, IntegrationInterface> - */ - public static function setupIntegrations(array $integrations): array - { - $integrationIndex = []; - - /* @var IntegrationInterface $integration */ - foreach ($integrations as $integration) { - $className = \get_class($integration); - - if (!$integration instanceof IntegrationInterface) { - throw new \InvalidArgumentException(sprintf('Expecting integration implementing %s interface, got %s', IntegrationInterface::class, $className)); - } - - if (!isset(self::$integrations[$className])) { - self::$integrations[$className] = true; - - $integration->setupOnce(); - } - - $integrationIndex[$className] = $integration; - } - - return $integrationIndex; - } -} diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php new file mode 100644 index 000000000..731c4469c --- /dev/null +++ b/src/Integration/IntegrationRegistry.php @@ -0,0 +1,141 @@ +, bool> The registered integrations + */ + private $integrations = []; + + private function __construct() + { + } + + /** + * Gets the current singleton instance or creates a new one if it didn't + * exists yet. + */ + public static function getInstance(): self + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Setups the integrations according to the given options. For each integration + * the {@see IntegrationInterface::setupOnce()} method will be called only once + * during the application lifetime. + * + * @param Options $options The SDK client options + * + * @return array, IntegrationInterface> + */ + public function setupIntegrations(Options $options, LoggerInterface $logger): array + { + $integrations = []; + + foreach ($this->getIntegrationsToSetup($options) as $integration) { + $integrations[\get_class($integration)] = $integration; + + $this->setupIntegration($integration, $logger); + } + + return $integrations; + } + + private function setupIntegration(IntegrationInterface $integration, LoggerInterface $logger): void + { + $integrationName = \get_class($integration); + + if (isset($this->integrations[$integrationName])) { + return; + } + + $integration->setupOnce(); + + $this->integrations[$integrationName] = true; + + $logger->debug(sprintf('The "%s" integration has been installed.', $integrationName)); + } + + /** + * @return IntegrationInterface[] + */ + private function getIntegrationsToSetup(Options $options): array + { + $integrations = []; + $defaultIntegrations = $this->getDefaultIntegrations($options); + $userIntegrations = $options->getIntegrations(); + + if (\is_array($userIntegrations)) { + /** @psalm-suppress PossiblyInvalidArgument */ + $userIntegrationsClasses = array_map('get_class', $userIntegrations); + $pickedIntegrationsClasses = []; + + foreach ($defaultIntegrations as $defaultIntegration) { + $integrationClassName = \get_class($defaultIntegration); + + if (!\in_array($integrationClassName, $userIntegrationsClasses, true) && !isset($pickedIntegrationsClasses[$integrationClassName])) { + $integrations[] = $defaultIntegration; + $pickedIntegrationsClasses[$integrationClassName] = true; + } + } + + foreach ($userIntegrations as $userIntegration) { + /** @psalm-suppress PossiblyInvalidArgument */ + $integrationClassName = \get_class($userIntegration); + + if (!isset($pickedIntegrationsClasses[$integrationClassName])) { + $integrations[] = $userIntegration; + $pickedIntegrationsClasses[$integrationClassName] = true; + } + } + } else { + $integrations = $userIntegrations($defaultIntegrations); + + if (!\is_array($integrations)) { + throw new \UnexpectedValueException(sprintf('Expected the callback set for the "integrations" option to return a list of integrations. Got: "%s".', get_debug_type($integrations))); + } + } + + return $integrations; + } + + /** + * @return IntegrationInterface[] + */ + private function getDefaultIntegrations(Options $options): array + { + if (!$options->hasDefaultIntegrations()) { + return []; + } + + return [ + new ExceptionListenerIntegration(), + new ErrorListenerIntegration(), + new FatalErrorListenerIntegration(), + new RequestIntegration(), + new TransactionIntegration(), + new FrameContextifierIntegration(), + new EnvironmentIntegration(), + ]; + } +} diff --git a/src/Options.php b/src/Options.php index c5971ff58..af43551cc 100644 --- a/src/Options.php +++ b/src/Options.php @@ -4,14 +4,8 @@ namespace Sentry; -use Sentry\Integration\EnvironmentIntegration; use Sentry\Integration\ErrorListenerIntegration; -use Sentry\Integration\ExceptionListenerIntegration; -use Sentry\Integration\FatalErrorListenerIntegration; -use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\IntegrationInterface; -use Sentry\Integration\RequestIntegration; -use Sentry\Integration\TransactionIntegration; use Symfony\Component\OptionsResolver\Options as SymfonyOptions; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -38,11 +32,6 @@ final class Options */ private $resolver; - /** - * @var IntegrationInterface[]|null The list of default integrations - */ - private $defaultIntegrations; - /** * Class constructor. * @@ -460,7 +449,7 @@ public function setBeforeBreadcrumbCallback(callable $callback): void * initialized or a function that receives default integrations and returns * a new, updated list. * - * @param IntegrationInterface[]|callable $integrations The list or callable + * @param IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[] $integrations The list or callable */ public function setIntegrations($integrations): void { @@ -472,27 +461,11 @@ public function setIntegrations($integrations): void /** * Returns all configured integrations that will be used by the Client. * - * @return IntegrationInterface[] + * @return IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[] */ - public function getIntegrations(): array + public function getIntegrations() { - $defaultIntegrations = $this->getDefaultIntegrations(); - $userIntegrations = $this->options['integrations']; - $integrations = []; - - if (\is_callable($userIntegrations)) { - return $userIntegrations($defaultIntegrations); - } - - foreach ($defaultIntegrations as $defaultIntegration) { - $integrations[\get_class($defaultIntegration)] = $defaultIntegration; - } - - foreach ($userIntegrations as $userIntegration) { - $integrations[\get_class($userIntegration)] = $userIntegration; - } - - return array_values($integrations); + return $this->options['integrations']; } /** @@ -896,30 +869,4 @@ private function validateContextLinesOption(?int $contextLines): bool { return null === $contextLines || $contextLines >= 0; } - - /** - * Gets the list of default integrations. - * - * @return IntegrationInterface[] - */ - private function getDefaultIntegrations(): array - { - if (!$this->options['default_integrations']) { - return []; - } - - if (null === $this->defaultIntegrations) { - $this->defaultIntegrations = [ - new ExceptionListenerIntegration(), - new ErrorListenerIntegration(), - new FatalErrorListenerIntegration(), - new RequestIntegration(), - new TransactionIntegration(), - new FrameContextifierIntegration(), - new EnvironmentIntegration(), - ]; - } - - return $this->defaultIntegrations; - } } diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index ea36970af..01185b0e6 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -136,27 +136,24 @@ public function getIntegration(string $className): ?IntegrationInterface; * which point the transaction with all its finished child spans will be sent to * Sentry. * - * @param TransactionContext $context properties of the new `Transaction` + * @param TransactionContext $context Properties of the new transaction */ public function startTransaction(TransactionContext $context): Transaction; /** - * Returns the Transaction that is on the Hub. - * - * @psalm-suppress MoreSpecificReturnType - * @psalm-suppress LessSpecificReturnStatement + * Returns the transaction that is on the Hub. */ public function getTransaction(): ?Transaction; /** - * Returns the Span that is on the Hub. + * Returns the span that is on the Hub. */ public function getSpan(): ?Span; /** - * Sets the Span on the Hub. + * Sets the span on the Hub. * - * @param Span|null $span The Span + * @param Span|null $span The span */ public function setSpan(?Span $span): HubInterface; } diff --git a/src/functions.php b/src/functions.php index 382d226c7..089cbdb40 100644 --- a/src/functions.php +++ b/src/functions.php @@ -107,7 +107,7 @@ function withScope(callable $callback): void * which point the transaction with all its finished child spans will be sent to * Sentry. * - * @param TransactionContext $context properties of the new `Transaction` + * @param TransactionContext $context Properties of the new transaction */ function startTransaction(TransactionContext $context): Transaction { diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 2ac4113ab..7e95f8caa 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -9,14 +9,7 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\Integration\EnvironmentIntegration; -use Sentry\Integration\ErrorListenerIntegration; -use Sentry\Integration\ExceptionListenerIntegration; -use Sentry\Integration\FatalErrorListenerIntegration; -use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\IntegrationInterface; -use Sentry\Integration\RequestIntegration; -use Sentry\Integration\TransactionIntegration; use Sentry\Options; use Sentry\Transport\HttpTransport; use Sentry\Transport\NullTransport; @@ -41,65 +34,6 @@ public function testNullTransportIsUsedWhenNoServerIsConfigured(): void $this->assertInstanceOf(NullTransport::class, $transport); } - /** - * @dataProvider integrationsAreAddedToClientCorrectlyDataProvider - */ - public function testIntegrationsAreAddedToClientCorrectly(bool $defaultIntegrations, array $integrations, array $expectedIntegrations): void - { - $options = new Options(); - $options->setDefaultIntegrations($defaultIntegrations); - $options->setIntegrations($integrations); - - $client = (new ClientBuilder($options))->getClient(); - - $actualIntegrationsClassNames = array_map('\get_class', $client->getOptions()->getIntegrations()); - - $this->assertEquals($expectedIntegrations, $actualIntegrationsClassNames, '', 0, 10, true); - } - - public function integrationsAreAddedToClientCorrectlyDataProvider(): array - { - return [ - [ - false, - [], - [], - ], - [ - false, - [new StubIntegration()], - [StubIntegration::class], - ], - [ - true, - [], - [ - ErrorListenerIntegration::class, - FatalErrorListenerIntegration::class, - ExceptionListenerIntegration::class, - RequestIntegration::class, - TransactionIntegration::class, - FrameContextifierIntegration::class, - EnvironmentIntegration::class, - ], - ], - [ - true, - [new StubIntegration()], - [ - ErrorListenerIntegration::class, - FatalErrorListenerIntegration::class, - ExceptionListenerIntegration::class, - RequestIntegration::class, - TransactionIntegration::class, - FrameContextifierIntegration::class, - EnvironmentIntegration::class, - StubIntegration::class, - ], - ], - ]; - } - public function testClientBuilderFallbacksToDefaultSdkIdentifierAndVersion(): void { $callbackCalled = false; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 79e6497f3..f026257b4 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,9 +10,12 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventFactoryInterface; use Sentry\Frame; +use Sentry\Integration\IntegrationInterface; use Sentry\Options; use Sentry\Response; use Sentry\ResponseStatus; @@ -22,8 +25,58 @@ use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; -class ClientTest extends TestCase +final class ClientTest extends TestCase { + public function testConstructorSetupsIntegrations(): void + { + $integrationCalled = false; + + $eventFactory = $this->createMock(EventFactoryInterface::class); + $eventFactory->expects($this->once()) + ->method('create') + ->willReturn(Event::createEvent()); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('debug'); + + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because one of the event processors returned "null".'); + + $integration = new class($integrationCalled) implements IntegrationInterface { + private $integrationCalled; + + public function __construct(bool &$integrationCalled) + { + $this->integrationCalled = &$integrationCalled; + } + + public function setupOnce(): void + { + Scope::addGlobalEventProcessor(function (): ?Event { + $this->integrationCalled = true; + + return null; + }); + } + }; + + $client = new Client( + new Options([ + 'default_integrations' => false, + 'integrations' => [$integration], + ]), + $this->createMock(TransportInterface::class), + $eventFactory, + $logger + ); + + $client->captureEvent([], new Scope()); + + $this->assertTrue($integrationCalled); + } + public function testCaptureMessage(): void { /** @var TransportInterface&MockObject $transport */ @@ -311,7 +364,7 @@ public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull() $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) ->method('info') - ->with('The event will be discarded because the "before_send" callback returned `null`.', $this->callback(static function (array $context): bool { + ->with('The event will be discarded because the "before_send" callback returned "null".', $this->callback(static function (array $context): bool { return isset($context['event']) && $context['event'] instanceof Event; })); @@ -334,7 +387,7 @@ public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): vo $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) ->method('info') - ->with('The event will be discarded because one of the event processors returned `null`.', $this->callback(static function (array $context): bool { + ->with('The event will be discarded because one of the event processors returned "null".', $this->callback(static function (array $context): bool { return isset($context['event']) && $context['event'] instanceof Event; })); diff --git a/tests/Integration/IntegrationRegistryTest.php b/tests/Integration/IntegrationRegistryTest.php new file mode 100644 index 000000000..d25ba992d --- /dev/null +++ b/tests/Integration/IntegrationRegistryTest.php @@ -0,0 +1,285 @@ +assertSame($instance1, $instance2); + } + + /** + * @dataProvider setupIntegrationsDataProvider + */ + public function testSetupIntegrations(Options $options, array $expectedDebugMessages, array $expectedIntegrations): void + { + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->exactly(\count($expectedDebugMessages))) + ->method('debug') + ->withConsecutive(...array_map( + static function (string $debugMessage): array { + return [ + $debugMessage, + [], + ]; + }, + $expectedDebugMessages + )); + + $this->assertEquals($expectedIntegrations, IntegrationRegistry::getInstance()->setupIntegrations($options, $logger)); + } + + public function setupIntegrationsDataProvider(): iterable + { + $integration1 = new class() implements IntegrationInterface { + public function setupOnce(): void + { + } + }; + + $integration2 = new class() implements IntegrationInterface { + public function setupOnce(): void + { + } + }; + + $integration1ClassName = \get_class($integration1); + $integration2ClassName = \get_class($integration2); + + yield 'No default integrations and no user integrations' => [ + new Options([ + 'default_integrations' => false, + ]), + [], + [], + ]; + + yield 'Default integrations and no user integrations' => [ + new Options([ + 'default_integrations' => true, + ]), + [ + 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', + 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', + 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + ], + [ + ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), + ErrorListenerIntegration::class => new ErrorListenerIntegration(), + FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), + RequestIntegration::class => new RequestIntegration(), + TransactionIntegration::class => new TransactionIntegration(), + FrameContextifierIntegration::class => new FrameContextifierIntegration(), + EnvironmentIntegration::class => new EnvironmentIntegration(), + ], + ]; + + yield 'No default integrations and some user integrations' => [ + new Options([ + 'default_integrations' => false, + 'integrations' => [ + $integration1, + $integration2, + ], + ]), + [ + "The \"$integration1ClassName\" integration has been installed.", + "The \"$integration2ClassName\" integration has been installed.", + ], + [ + $integration1ClassName => $integration1, + $integration2ClassName => $integration2, + ], + ]; + + yield 'Default integrations and some user integrations' => [ + new Options([ + 'default_integrations' => true, + 'integrations' => [ + $integration1, + $integration2, + ], + ]), + [ + 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', + 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', + 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + "The \"$integration1ClassName\" integration has been installed.", + "The \"$integration2ClassName\" integration has been installed.", + ], + [ + ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), + ErrorListenerIntegration::class => new ErrorListenerIntegration(), + FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), + RequestIntegration::class => new RequestIntegration(), + TransactionIntegration::class => new TransactionIntegration(), + FrameContextifierIntegration::class => new FrameContextifierIntegration(), + EnvironmentIntegration::class => new EnvironmentIntegration(), + $integration1ClassName => $integration1, + $integration2ClassName => $integration2, + ], + ]; + + yield 'Default integrations and some user integrations, one of which is also a default integration' => [ + new Options([ + 'default_integrations' => true, + 'integrations' => [ + new TransactionIntegration(), + $integration1, + ], + ]), + [ + 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', + 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', + "The \"$integration1ClassName\" integration has been installed.", + ], + [ + ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), + ErrorListenerIntegration::class => new ErrorListenerIntegration(), + FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), + RequestIntegration::class => new RequestIntegration(), + FrameContextifierIntegration::class => new FrameContextifierIntegration(), + EnvironmentIntegration::class => new EnvironmentIntegration(), + TransactionIntegration::class => new TransactionIntegration(), + $integration1ClassName => $integration1, + ], + ]; + + yield 'No default integrations and some user integrations are repeated twice' => [ + new Options([ + 'default_integrations' => false, + 'integrations' => [ + $integration1, + $integration1, + ], + ]), + [ + "The \"$integration1ClassName\" integration has been installed.", + ], + [ + $integration1ClassName => $integration1, + ], + ]; + + yield 'No default integrations and a callable as user integrations' => [ + new Options([ + 'default_integrations' => false, + 'integrations' => static function (array $defaultIntegrations): array { + return $defaultIntegrations; + }, + ]), + [], + [], + ]; + + yield 'Default integrations and a callable as user integrations' => [ + new Options([ + 'default_integrations' => true, + 'integrations' => static function (array $defaultIntegrations): array { + return $defaultIntegrations; + }, + ]), + [ + 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', + 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', + 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + ], + [ + ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), + ErrorListenerIntegration::class => new ErrorListenerIntegration(), + FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), + RequestIntegration::class => new RequestIntegration(), + TransactionIntegration::class => new TransactionIntegration(), + FrameContextifierIntegration::class => new FrameContextifierIntegration(), + EnvironmentIntegration::class => new EnvironmentIntegration(), + ], + ]; + } + + /** + * @dataProvider setupIntegrationsThrowsExceptionIfValueReturnedFromOptionIsNotValidDataProvider + */ + public function testSetupIntegrationsThrowsExceptionIfValueReturnedFromOptionIsNotValid($value, string $expectedExceptionMessage): void + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + IntegrationRegistry::getInstance()->setupIntegrations( + new Options([ + 'default_integrations' => false, + 'integrations' => static function () use ($value) { + return $value; + }, + ]), + $this->createMock(LoggerInterface::class) + ); + } + + public function setupIntegrationsThrowsExceptionIfValueReturnedFromOptionIsNotValidDataProvider(): iterable + { + yield [ + 12.34, + 'Expected the callback set for the "integrations" option to return a list of integrations. Got: "float".', + ]; + + yield [ + new \stdClass(), + 'Expected the callback set for the "integrations" option to return a list of integrations. Got: "stdClass".', + ]; + } + + public function testSetupIntegrationsIsIdempotent(): void + { + $integration = $this->createMock(IntegrationInterface::class); + $integration->expects($this->once()) + ->method('setupOnce'); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('debug'); + + $options = new Options([ + 'default_integrations' => false, + 'integrations' => [$integration], + ]); + + IntegrationRegistry::getInstance()->setupIntegrations($options, $logger); + IntegrationRegistry::getInstance()->setupIntegrations($options, $logger); + } +} diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 67af2d459..418856073 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -6,14 +6,6 @@ use PHPUnit\Framework\TestCase; use Sentry\Dsn; -use Sentry\Integration\EnvironmentIntegration; -use Sentry\Integration\ErrorListenerIntegration; -use Sentry\Integration\ExceptionListenerIntegration; -use Sentry\Integration\FatalErrorListenerIntegration; -use Sentry\Integration\FrameContextifierIntegration; -use Sentry\Integration\IntegrationInterface; -use Sentry\Integration\RequestIntegration; -use Sentry\Integration\TransactionIntegration; use Sentry\Options; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; @@ -300,105 +292,4 @@ public function testReleaseOptionDefaultValueIsGotFromEnvironmentVariable(): voi $this->assertSame('0.0.1', (new Options())->getRelease()); } - - /** - * @dataProvider integrationsOptionAsCallableDataProvider - */ - public function testIntegrationsOptionAsCallable(bool $useDefaultIntegrations, $integrations, array $expectedResult): void - { - $options = new Options([ - 'default_integrations' => $useDefaultIntegrations, - 'integrations' => $integrations, - ]); - - $this->assertEquals($expectedResult, $options->getIntegrations()); - } - - public function integrationsOptionAsCallableDataProvider(): \Generator - { - yield 'No default integrations && no user integrations' => [ - false, - [], - [], - ]; - - $integration = new class() implements IntegrationInterface { - public function setupOnce(): void - { - } - }; - - yield 'User integration added && default integration appearing only once' => [ - true, - [ - $integration, - new ExceptionListenerIntegration(), - ], - [ - new ExceptionListenerIntegration(), - new ErrorListenerIntegration(), - new FatalErrorListenerIntegration(), - new RequestIntegration(), - new TransactionIntegration(), - new FrameContextifierIntegration(), - new EnvironmentIntegration(), - $integration, - ], - ]; - - $integration = new class() implements IntegrationInterface { - public function setupOnce(): void - { - } - }; - - yield 'User integration added twice' => [ - false, - [ - $integration, - $integration, - ], - [ - $integration, - ], - ]; - - yield 'User integrations as callable returning empty list' => [ - true, - static function (): array { - return []; - }, - [], - ]; - - $integration = new class() implements IntegrationInterface { - public function setupOnce(): void - { - } - }; - - yield 'User integrations as callable returning custom list' => [ - true, - static function () use ($integration): array { - return [$integration]; - }, - [$integration], - ]; - - yield 'User integrations as callable returning $defaultIntegrations argument' => [ - true, - static function (array $defaultIntegrations): array { - return $defaultIntegrations; - }, - [ - new ExceptionListenerIntegration(), - new ErrorListenerIntegration(), - new FatalErrorListenerIntegration(), - new RequestIntegration(), - new TransactionIntegration(), - new FrameContextifierIntegration(), - new EnvironmentIntegration(), - ], - ]; - } } diff --git a/tests/SentrySdkExtension.php b/tests/SentrySdkExtension.php index f6d1b9bc3..ee6bef9dc 100644 --- a/tests/SentrySdkExtension.php +++ b/tests/SentrySdkExtension.php @@ -5,7 +5,7 @@ namespace Sentry\Tests; use PHPUnit\Runner\BeforeTestHook as BeforeTestHookInterface; -use Sentry\Integration\Handler; +use Sentry\Integration\IntegrationRegistry; use Sentry\SentrySdk; use Sentry\State\Scope; @@ -23,9 +23,9 @@ public function executeBeforeTest(string $test): void $reflectionProperty->setValue(null, []); $reflectionProperty->setAccessible(false); - $reflectionProperty = new \ReflectionProperty(Handler::class, 'integrations'); + $reflectionProperty = new \ReflectionProperty(IntegrationRegistry::class, 'integrations'); $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue(null, []); + $reflectionProperty->setValue(IntegrationRegistry::getInstance(), []); $reflectionProperty->setAccessible(false); } } From 91ffb88788012a869a055c9aec53d5c333dca6f9 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 22 Sep 2020 11:52:45 +0200 Subject: [PATCH 0607/1161] ref: Remove EventFactory (#1091) * ref: Remove EventFactory * fix: 7.2 * Update src/Client.php Co-authored-by: Alessandro Lai * fix: Codefix * ref: Use pretty version * ref: Remove dead code * ref: CR * ref: Update composer Co-authored-by: Alessandro Lai --- composer.json | 2 +- src/Client.php | 164 +++++++++++++++++++++-- src/ClientBuilder.php | 20 +-- src/EventFactory.php | 154 ---------------------- src/EventFactoryInterface.php | 25 ---- tests/ClientTest.php | 241 +++++++++++++++++++++++++++++++++- tests/EventFactoryTest.php | 202 ---------------------------- 7 files changed, 386 insertions(+), 422 deletions(-) delete mode 100644 src/EventFactory.php delete mode 100644 src/EventFactoryInterface.php delete mode 100644 tests/EventFactoryTest.php diff --git a/composer.json b/composer.json index 833590ee7..772fdb2b3 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ext-mbstring": "*", "guzzlehttp/promises": "^1.3", "guzzlehttp/psr7": "^1.6", - "jean85/pretty-package-versions": "^1.2", + "jean85/pretty-package-versions": "^1.5", "ocramius/package-versions": "^1.8", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", diff --git a/src/Client.php b/src/Client.php index e3e09ce0b..3261285d3 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,10 +5,16 @@ namespace Sentry; use GuzzleHttp\Promise\PromiseInterface; +use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Sentry\Exception\EventCreationException; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\IntegrationRegistry; +use Sentry\Serializer\RepresentationSerializer; +use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\Serializer\Serializer; +use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; use Sentry\Transport\TransportInterface; @@ -39,11 +45,6 @@ final class Client implements ClientInterface */ private $transport; - /** - * @var EventFactoryInterface The factory to create {@see Event} from raw data - */ - private $eventFactory; - /** * @var LoggerInterface The PSR-3 logger */ @@ -56,21 +57,60 @@ final class Client implements ClientInterface */ private $integrations; + /** + * @var SerializerInterface The serializer of the client + */ + private $serializer; + + /** + * @var RepresentationSerializerInterface The representation serializer of the client + */ + private $representationSerializer; + + /** + * @var StacktraceBuilder + */ + private $stacktraceBuilder; + + /** + * @var string The Sentry SDK identifier + */ + private $sdkIdentifier; + + /** + * @var string The SDK version of the Client + */ + private $sdkVersion; + /** * Constructor. * - * @param Options $options The client configuration - * @param TransportInterface $transport The transport - * @param EventFactoryInterface $eventFactory The factory for events - * @param LoggerInterface|null $logger The PSR-3 logger + * @param Options $options The client configuration + * @param TransportInterface $transport The transport + * @param string|null $sdkIdentifier The Sentry SDK identifier + * @param string|null $sdkVersion The Sentry SDK version + * @param SerializerInterface|null $serializer The serializer + * @param RepresentationSerializerInterface|null $representationSerializer The serializer for function arguments + * @param LoggerInterface|null $logger The PSR-3 logger */ - public function __construct(Options $options, TransportInterface $transport, EventFactoryInterface $eventFactory, ?LoggerInterface $logger = null) - { + public function __construct( + Options $options, + TransportInterface $transport, + ?string $sdkIdentifier = null, + ?string $sdkVersion = null, + ?SerializerInterface $serializer = null, + ?RepresentationSerializerInterface $representationSerializer = null, + ?LoggerInterface $logger = null + ) { $this->options = $options; $this->transport = $transport; - $this->eventFactory = $eventFactory; $this->logger = $logger ?? new NullLogger(); $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); + $this->serializer = $serializer ?? new Serializer($this->options); + $this->representationSerializer = $representationSerializer ?? new RepresentationSerializer($this->options); + $this->stacktraceBuilder = new StacktraceBuilder($options, $this->representationSerializer); + $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; + $this->sdkVersion = $sdkVersion ?? PrettyVersions::getVersion(PrettyVersions::getRootPackageName())->getPrettyVersion(); } /** @@ -173,9 +213,9 @@ public function flush(?int $timeout = null): PromiseInterface private function prepareEvent($payload, ?Scope $scope = null): ?Event { if ($this->options->shouldAttachStacktrace() && !($payload instanceof Event) && !isset($payload['exception']) && !isset($payload['stacktrace'])) { - $event = $this->eventFactory->createWithStacktrace($payload); + $event = $this->buildEventWithStacktrace($payload); } else { - $event = $this->eventFactory->create($payload); + $event = $this->buildEvent($payload); } $sampleRate = $this->options->getSampleRate(); @@ -206,4 +246,100 @@ private function prepareEvent($payload, ?Scope $scope = null): ?Event return $event; } + + /** + * Create an {@see Event} with a stacktrace attached to it. + * + * @param array $payload The data to be attached to the Event + */ + private function buildEventWithStacktrace($payload): Event + { + if (!isset($payload['stacktrace']) || !$payload['stacktrace'] instanceof Stacktrace) { + $payload['stacktrace'] = $this->stacktraceBuilder->buildFromBacktrace( + debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), + __FILE__, + __LINE__ - 3 + ); + } + + return $this->buildEvent($payload); + } + + /** + * Create an {@see Event} from a data payload. + * + * @param array|Event $payload The data to be attached to the Event + */ + private function buildEvent($payload): Event + { + try { + if ($payload instanceof Event) { + $event = $payload; + } else { + $event = Event::createEvent(); + + if (isset($payload['logger'])) { + $event->setLogger($payload['logger']); + } + + $message = isset($payload['message']) ? mb_substr($payload['message'], 0, $this->options->getMaxValueLength()) : null; + $messageParams = $payload['message_params'] ?? []; + $messageFormatted = isset($payload['message_formatted']) ? mb_substr($payload['message_formatted'], 0, $this->options->getMaxValueLength()) : null; + + if (null !== $message) { + $event->setMessage($message, $messageParams, $messageFormatted); + } + + if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { + $this->addThrowableToEvent($event, $payload['exception']); + } + + if (isset($payload['level']) && $payload['level'] instanceof Severity) { + $event->setLevel($payload['level']); + } + + if (isset($payload['stacktrace']) && $payload['stacktrace'] instanceof Stacktrace) { + $event->setStacktrace($payload['stacktrace']); + } + } + } catch (\Throwable $exception) { + throw new EventCreationException($exception); + } + + $event->setSdkIdentifier($this->sdkIdentifier); + $event->setSdkVersion($this->sdkVersion); + $event->setServerName($this->options->getServerName()); + $event->setRelease($this->options->getRelease()); + $event->setTags($this->options->getTags()); + $event->setEnvironment($this->options->getEnvironment()); + + return $event; + } + + /** + * Stores the given exception in the passed event. + * + * @param Event $event The event that will be enriched with the + * exception + * @param \Throwable $exception The exception that will be processed and + * added to the event + */ + private function addThrowableToEvent(Event $event, \Throwable $exception): void + { + if ($exception instanceof \ErrorException) { + $event->setLevel(Severity::fromError($exception->getSeverity())); + } + + $exceptions = []; + + do { + $exceptions[] = new ExceptionDataBag( + $exception, + $this->stacktraceBuilder->buildFromException($exception), + new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true) + ); + } while ($exception = $exception->getPrevious()); + + $event->setExceptions($exceptions); + } } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index a5e78224c..0108ad5b3 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -9,7 +9,6 @@ use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; -use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; @@ -163,7 +162,7 @@ public function getClient(): ClientInterface { $this->transport = $this->transport ?? $this->createTransportInstance(); - return new Client($this->options, $this->transport, $this->createEventFactory(), $this->logger); + return new Client($this->options, $this->transport, $this->sdkIdentifier, $this->sdkVersion, $this->serializer, $this->representationSerializer, $this->logger); } /** @@ -180,23 +179,6 @@ private function createTransportInstance(): TransportInterface return $transportFactory->create($this->options); } - /** - * Instantiate the {@see EventFactory} with the configured serializers. - */ - private function createEventFactory(): EventFactoryInterface - { - $this->serializer = $this->serializer ?? new Serializer($this->options); - $this->representationSerializer = $this->representationSerializer ?? new RepresentationSerializer($this->options); - - return new EventFactory( - $this->serializer, - $this->representationSerializer, - $this->options, - $this->sdkIdentifier, - $this->sdkVersion - ); - } - /** * Creates a new instance of the {@see DefaultTransportFactory} factory. */ diff --git a/src/EventFactory.php b/src/EventFactory.php deleted file mode 100644 index 964e95b64..000000000 --- a/src/EventFactory.php +++ /dev/null @@ -1,154 +0,0 @@ -serializer = $serializer; - $this->options = $options; - $this->sdkIdentifier = $sdkIdentifier; - $this->sdkVersion = $sdkVersion; - $this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer); - } - - /** - * {@inheritdoc} - */ - public function createWithStacktrace($payload): Event - { - if ($payload instanceof Event) { - return $this->create($payload); - } - - if (!isset($payload['stacktrace']) || !$payload['stacktrace'] instanceof Stacktrace) { - $payload['stacktrace'] = $this->stacktraceBuilder->buildFromBacktrace( - debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), - __FILE__, - __LINE__ - 3 - ); - } - - return $this->create($payload); - } - - /** - * {@inheritdoc} - */ - public function create($payload): Event - { - try { - if ($payload instanceof Event) { - $event = $payload; - } else { - $event = Event::createEvent(); - - if (isset($payload['logger'])) { - $event->setLogger($payload['logger']); - } - - $message = isset($payload['message']) ? mb_substr($payload['message'], 0, $this->options->getMaxValueLength()) : null; - $messageParams = $payload['message_params'] ?? []; - $messageFormatted = isset($payload['message_formatted']) ? mb_substr($payload['message_formatted'], 0, $this->options->getMaxValueLength()) : null; - - if (null !== $message) { - $event->setMessage($message, $messageParams, $messageFormatted); - } - - if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { - $this->addThrowableToEvent($event, $payload['exception']); - } - - if (isset($payload['level']) && $payload['level'] instanceof Severity) { - $event->setLevel($payload['level']); - } - - if (isset($payload['stacktrace']) && $payload['stacktrace'] instanceof Stacktrace) { - $event->setStacktrace($payload['stacktrace']); - } - } - } catch (\Throwable $exception) { - throw new EventCreationException($exception); - } - - $event->setSdkIdentifier($this->sdkIdentifier); - $event->setSdkVersion($this->sdkVersion); - $event->setServerName($this->options->getServerName()); - $event->setRelease($this->options->getRelease()); - $event->setTags($this->options->getTags()); - $event->setEnvironment($this->options->getEnvironment()); - - return $event; - } - - /** - * Stores the given exception in the passed event. - * - * @param Event $event The event that will be enriched with the - * exception - * @param \Throwable $exception The exception that will be processed and - * added to the event - */ - private function addThrowableToEvent(Event $event, \Throwable $exception): void - { - if ($exception instanceof \ErrorException) { - $event->setLevel(Severity::fromError($exception->getSeverity())); - } - - $exceptions = []; - - do { - $exceptions[] = new ExceptionDataBag( - $exception, - $this->stacktraceBuilder->buildFromException($exception), - new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true) - ); - } while ($exception = $exception->getPrevious()); - - $event->setExceptions($exceptions); - } -} diff --git a/src/EventFactoryInterface.php b/src/EventFactoryInterface.php deleted file mode 100644 index cd949767d..000000000 --- a/src/EventFactoryInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -|Event $payload The data to be attached to the Event - */ - public function createWithStacktrace($payload): Event; - - /** - * Create an {@see Event} from a data payload. - * - * @param array|Event $payload The data to be attached to the Event - */ - public function create($payload): Event; -} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f026257b4..5efac8696 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -13,12 +13,15 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; -use Sentry\EventFactoryInterface; +use Sentry\ExceptionMechanism; use Sentry\Frame; use Sentry\Integration\IntegrationInterface; use Sentry\Options; use Sentry\Response; use Sentry\ResponseStatus; +use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\Serializer\Serializer; +use Sentry\Serializer\SerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; @@ -31,11 +34,6 @@ public function testConstructorSetupsIntegrations(): void { $integrationCalled = false; - $eventFactory = $this->createMock(EventFactoryInterface::class); - $eventFactory->expects($this->once()) - ->method('create') - ->willReturn(Event::createEvent()); - $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) ->method('debug'); @@ -68,7 +66,10 @@ public function setupOnce(): void 'integrations' => [$integration], ]), $this->createMock(TransportInterface::class), - $eventFactory, + null, + null, + null, + null, $logger ); @@ -477,4 +478,230 @@ public function create(Options $options): TransportInterface } }; } + + /** + * @backupGlobals + */ + public function testBuildEventWithDefaultValues(): void + { + $options = new Options(); + $options->setServerName('testServerName'); + $options->setRelease('testRelease'); + $options->setTags(['test' => 'tag']); + $options->setEnvironment('testEnvironment'); + + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event) use ($options): bool { + $this->assertSame('sentry.sdk.identifier', $event->getSdkIdentifier()); + $this->assertSame('1.2.3', $event->getSdkVersion()); + $this->assertSame($options->getServerName(), $event->getServerName()); + $this->assertSame($options->getRelease(), $event->getRelease()); + $this->assertSame($options->getTags(), $event->getTags()); + $this->assertSame($options->getEnvironment(), $event->getEnvironment()); + $this->assertNull($event->getStacktrace()); + + return true; + })); + + $client = new Client( + $options, + $transport, + 'sentry.sdk.identifier', + '1.2.3', + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $client->captureEvent([]); + } + + /** + * @dataProvider buildWithPayloadDataProvider + */ + public function testBuildWithPayload(array $payload, ?string $expectedLogger, ?string $expectedMessage, array $expectedMessageParams, ?string $expectedFormattedMessage): void + { + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event) use ($expectedLogger, $expectedMessage, $expectedFormattedMessage, $expectedMessageParams): bool { + $this->assertSame($expectedLogger, $event->getLogger()); + $this->assertSame($expectedMessage, $event->getMessage()); + $this->assertSame($expectedMessageParams, $event->getMessageParams()); + $this->assertSame($expectedFormattedMessage, $event->getMessageFormatted()); + + return true; + })); + + $client = new Client( + new Options(), + $transport, + 'sentry.sdk.identifier', + '1.2.3', + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $client->captureEvent($payload); + } + + public function buildWithPayloadDataProvider(): iterable + { + yield [ + ['logger' => 'app.php'], + 'app.php', + null, + [], + null, + ]; + + yield [ + ['message' => 'My raw message with interpreted strings like this'], + null, + 'My raw message with interpreted strings like this', + [], + null, + ]; + + yield [ + [ + 'message' => 'My raw message with interpreted strings like that', + 'message_params' => ['this'], + 'message_formatted' => 'My raw message with interpreted strings like %s', + ], + null, + 'My raw message with interpreted strings like that', + ['this'], + 'My raw message with interpreted strings like %s', + ]; + } + + public function testBuildEventInCLIDoesntSetTransaction(): void + { + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertNull($event->getTransaction()); + + return true; + })); + + $client = new Client( + new Options(), + $transport, + 'sentry.sdk.identifier', + '1.2.3', + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $client->captureEvent([]); + } + + public function testBuildEventWithException(): void + { + $options = new Options(); + $previousException = new \RuntimeException('testMessage2'); + $exception = new \Exception('testMessage', 0, $previousException); + + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $capturedExceptions = $event->getExceptions(); + + $this->assertCount(2, $capturedExceptions); + $this->assertNotNull($capturedExceptions[0]->getStacktrace()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), $capturedExceptions[0]->getMechanism()); + $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); + $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); + + $this->assertNotNull($capturedExceptions[1]->getStacktrace()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), $capturedExceptions[1]->getMechanism()); + $this->assertSame(\RuntimeException::class, $capturedExceptions[1]->getType()); + $this->assertSame('testMessage2', $capturedExceptions[1]->getValue()); + + return true; + })); + + $client = new Client( + $options, + $transport, + 'sentry.sdk.identifier', + '1.2.3', + new Serializer($options), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $client->captureEvent(['exception' => $exception]); + } + + public function testBuildWithErrorException(): void + { + $options = new Options(); + $exception = new \ErrorException('testMessage', 0, E_USER_ERROR); + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertTrue(Severity::error()->isEqualTo($event->getLevel())); + + return true; + })); + + $client = new Client( + $options, + $transport, + 'sentry.sdk.identifier', + '1.2.3', + new Serializer($options), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $client->captureEvent(['exception' => $exception]); + } + + public function testBuildWithStacktrace(): void + { + $options = new Options(); + $options->setAttachStacktrace(true); + + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $stacktrace = $event->getStacktrace(); + + $this->assertInstanceOf(Stacktrace::class, $stacktrace); + + /** @var Frame $lastFrame */ + $lastFrame = array_reverse($stacktrace->getFrames())[0]; + + $this->assertSame( + 'Client.php', + basename($lastFrame->getFile()) + ); + + return true; + })); + + $client = new Client( + $options, + $transport, + 'sentry.sdk.identifier', + '1.2.3', + new Serializer($options), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $client->captureEvent([]); + } } diff --git a/tests/EventFactoryTest.php b/tests/EventFactoryTest.php deleted file mode 100644 index f86bdf697..000000000 --- a/tests/EventFactoryTest.php +++ /dev/null @@ -1,202 +0,0 @@ -setServerName('testServerName'); - $options->setRelease('testRelease'); - $options->setTags(['test' => 'tag']); - $options->setEnvironment('testEnvironment'); - - $eventFactory = new EventFactory( - $this->createMock(SerializerInterface::class), - $this->createMock(RepresentationSerializerInterface::class), - $options, - 'sentry.sdk.identifier', - '1.2.3' - ); - - $event = $eventFactory->create([]); - - $this->assertSame('sentry.sdk.identifier', $event->getSdkIdentifier()); - $this->assertSame('1.2.3', $event->getSdkVersion()); - $this->assertSame($options->getServerName(), $event->getServerName()); - $this->assertSame($options->getRelease(), $event->getRelease()); - $this->assertSame($options->getTags(), $event->getTags()); - $this->assertSame($options->getEnvironment(), $event->getEnvironment()); - $this->assertNull($event->getStacktrace()); - } - - /** - * @dataProvider createWithPayloadDataProvider - */ - public function testCreateWithPayload(array $payload, ?string $expectedLogger, ?string $expectedMessage, array $expectedMessageParams, ?string $expectedFormattedMessage): void - { - $eventFactory = new EventFactory( - $this->createMock(SerializerInterface::class), - $this->createMock(RepresentationSerializerInterface::class), - new Options(), - 'sentry.sdk.identifier', - '1.2.3' - ); - - $event = $eventFactory->create($payload); - - $this->assertSame($expectedLogger, $event->getLogger()); - $this->assertSame($expectedMessage, $event->getMessage()); - $this->assertSame($expectedMessageParams, $event->getMessageParams()); - $this->assertSame($expectedFormattedMessage, $event->getMessageFormatted()); - } - - public function createWithPayloadDataProvider(): iterable - { - yield [ - ['logger' => 'app.php'], - 'app.php', - null, - [], - null, - ]; - - yield [ - ['message' => 'My raw message with interpreted strings like this'], - null, - 'My raw message with interpreted strings like this', - [], - null, - ]; - - yield [ - [ - 'message' => 'My raw message with interpreted strings like that', - 'message_params' => ['this'], - 'message_formatted' => 'My raw message with interpreted strings like %s', - ], - null, - 'My raw message with interpreted strings like that', - ['this'], - 'My raw message with interpreted strings like %s', - ]; - } - - public function testCreateEventInCLIDoesntSetTransaction(): void - { - $eventFactory = new EventFactory( - $this->createMock(SerializerInterface::class), - $this->createMock(RepresentationSerializerInterface::class), - new Options(), - 'sentry.sdk.identifier', - '1.2.3' - ); - - $event = $eventFactory->create([]); - - $this->assertNull($event->getTransaction()); - } - - public function testCreateWithException(): void - { - $options = new Options(); - $previousException = new \RuntimeException('testMessage2'); - $exception = new \Exception('testMessage', 0, $previousException); - $eventFactory = new EventFactory( - new Serializer($options), - $this->createMock(RepresentationSerializerInterface::class), - $options, - 'sentry.sdk.identifier', - '1.2.3' - ); - - $event = $eventFactory->create(['exception' => $exception]); - $capturedExceptions = $event->getExceptions(); - - $this->assertCount(2, $capturedExceptions); - $this->assertNotNull($capturedExceptions[0]->getStacktrace()); - $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), $capturedExceptions[0]->getMechanism()); - $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); - $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); - - $this->assertNotNull($capturedExceptions[1]->getStacktrace()); - $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), $capturedExceptions[1]->getMechanism()); - $this->assertSame(\RuntimeException::class, $capturedExceptions[1]->getType()); - $this->assertSame('testMessage2', $capturedExceptions[1]->getValue()); - } - - public function testCreateWithErrorException(): void - { - $options = new Options(); - $exception = new \ErrorException('testMessage', 0, E_USER_ERROR); - $eventFactory = new EventFactory( - new Serializer($options), - $this->createMock(RepresentationSerializerInterface::class), - $options, - 'sentry.sdk.identifier', - '1.2.3' - ); - - $event = $eventFactory->create(['exception' => $exception]); - - $this->assertTrue(Severity::error()->isEqualTo($event->getLevel())); - } - - public function testCreateWithStacktrace(): void - { - $options = new Options(); - $options->setAttachStacktrace(true); - - $eventFactory = new EventFactory( - $this->createMock(SerializerInterface::class), - $this->createMock(RepresentationSerializerInterface::class), - $options, - 'sentry.sdk.identifier', - '1.2.3' - ); - - $event = $eventFactory->createWithStacktrace([]); - $stacktrace = $event->getStacktrace(); - - $this->assertInstanceOf(Stacktrace::class, $stacktrace); - - /** @var Frame $lastFrame */ - $lastFrame = array_reverse($stacktrace->getFrames())[0]; - - $this->assertSame( - 'src' . \DIRECTORY_SEPARATOR . 'EventFactory.php', - ltrim($lastFrame->getFile(), \DIRECTORY_SEPARATOR) - ); - } - - public function createThrowsDeprecationErrorIfLastArgumentIsNotSetToFalseDataProvider(): \Generator - { - yield [[true]]; - - yield [[1]]; - - yield [['foo']]; - - yield [[new class() { - }]]; - - yield [[]]; - } -} From 25a90d4215b86aa71ff3f71054ffe736313bcc20 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 24 Sep 2020 09:43:13 +0200 Subject: [PATCH 0608/1161] Polish the API related to APM (#1092) --- src/Event.php | 12 +- src/Options.php | 10 ++ src/ResponseStatus.php | 2 +- src/Serializer/PayloadSerializer.php | 2 +- src/State/Hub.php | 102 ++++++----- src/State/Scope.php | 17 +- src/Tracing/SamplingContext.php | 27 ++- src/Tracing/Span.php | 130 +++++++++----- src/Tracing/SpanContext.php | 52 +++--- src/Tracing/SpanId.php | 10 ++ src/Tracing/SpanRecorder.php | 17 +- src/Tracing/SpanStatus.php | 197 +++++++++++++++++++++ src/Tracing/Transaction.php | 70 +++----- src/Tracing/TransactionContext.php | 53 +++++- tests/Serializer/PayloadSerializerTest.php | 15 +- tests/State/HubTest.php | 105 +++++++++++ tests/Tracing/SamplingContextTest.php | 21 +++ tests/Tracing/SpanContextTest.php | 69 ++++++++ tests/Tracing/SpanIdTest.php | 36 ++++ tests/Tracing/SpanRecorderTest.php | 36 ++++ tests/Tracing/SpanStatusTest.php | 173 ++++++++++++++++++ tests/Tracing/SpanTest.php | 105 ++++++----- tests/Tracing/TraceIdTest.php | 36 ++++ tests/Tracing/TransactionContextTest.php | 25 +++ tests/Tracing/TransactionTest.php | 180 ++++--------------- 25 files changed, 1111 insertions(+), 391 deletions(-) create mode 100644 src/Tracing/SpanStatus.php create mode 100644 tests/Tracing/SamplingContextTest.php create mode 100644 tests/Tracing/SpanContextTest.php create mode 100644 tests/Tracing/SpanIdTest.php create mode 100644 tests/Tracing/SpanRecorderTest.php create mode 100644 tests/Tracing/SpanStatusTest.php create mode 100644 tests/Tracing/TraceIdTest.php create mode 100644 tests/Tracing/TransactionContextTest.php diff --git a/src/Event.php b/src/Event.php index b06b5c5fd..60bf4ae8e 100644 --- a/src/Event.php +++ b/src/Event.php @@ -22,7 +22,7 @@ final class Event private $id; /** - * @var string|float The date and time of when this event was generated + * @var float|null The date and time of when this event was generated */ private $timestamp; @@ -161,7 +161,7 @@ final class Event private function __construct(?EventId $eventId, EventType $eventType) { $this->id = $eventId ?? EventId::generate(); - $this->timestamp = gmdate('Y-m-d\TH:i:s\Z'); + $this->timestamp = microtime(true); $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); $this->type = $eventType; } @@ -237,19 +237,17 @@ public function setSdkVersion(string $sdkVersion): void /** * Gets the timestamp of when this event was generated. * - * @return string|float + * @return float */ - public function getTimestamp() + public function getTimestamp(): ?float { return $this->timestamp; } /** * Sets the timestamp of when the Event was created. - * - * @param float|string $timestamp */ - public function setTimestamp($timestamp): void + public function setTimestamp(?float $timestamp): void { $this->timestamp = $timestamp; } diff --git a/src/Options.php b/src/Options.php index af43551cc..bb8b7e2df 100644 --- a/src/Options.php +++ b/src/Options.php @@ -134,6 +134,16 @@ public function setTracesSampleRate(float $sampleRate): void $this->options = $this->resolver->resolve($options); } + /** + * Gets whether tracing is enabled or not. The feature is enabled when at + * least one of the `traces_sample_rate` and `traces_sampler` options is + * set. + */ + public function isTracingEnabled(): bool + { + return 0 != $this->options['traces_sample_rate'] || null !== $this->options['traces_sampler']; + } + /** * Gets whether the stacktrace will be attached on captureMessage. */ diff --git a/src/ResponseStatus.php b/src/ResponseStatus.php index bd80f96fe..de5ed364e 100644 --- a/src/ResponseStatus.php +++ b/src/ResponseStatus.php @@ -16,7 +16,7 @@ final class ResponseStatus private $value; /** - * @var array + * @var array */ private static $instances = []; diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index dcd143731..756325f6f 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -330,7 +330,7 @@ private function serializeSpan(Span $span): array } if (null !== $span->getStatus()) { - $result['status'] = $span->getStatus(); + $result['status'] = (string) $span->getStatus(); } if (null !== $span->getDescription()) { diff --git a/src/State/Hub.php b/src/State/Hub.php index a46f2b636..be9e9e5c1 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -209,63 +209,47 @@ public function getIntegration(string $className): ?IntegrationInterface return null; } - /** - * Gets the scope bound to the top of the stack. - */ - private function getScope(): Scope - { - return $this->getStackTop()->getScope(); - } - - /** - * Gets the topmost client/layer pair in the stack. - */ - private function getStackTop(): Layer - { - return $this->stack[\count($this->stack) - 1]; - } - /** * {@inheritdoc} */ public function startTransaction(TransactionContext $context): Transaction { + $transaction = new Transaction($context, $this); $client = $this->getClient(); - $sampleRate = null; + $options = null !== $client ? $client->getOptions() : null; - if (null !== $client) { - $sampler = $client->getOptions()->getTracesSampler(); + if (null === $options || !$options->isTracingEnabled()) { + $transaction->setSampled(false); - if (null !== $sampler) { - $sampleRate = $sampler(SamplingContext::getDefault($context)); - } + return $transaction; } - // Roll the dice for sampling transaction, all child spans inherit the sampling decision. - // Only if $sampleRate is `null` (which can only be because then the traces_sampler wasn't defined) - // we need to roll the dice. - if (null === $context->sampled && null === $sampleRate) { - if (null !== $client) { - $sampleRate = $client->getOptions()->getTracesSampleRate(); - } + $samplingContext = SamplingContext::getDefault($context); + $tracesSampler = $options->getTracesSampler(); + $sampleRate = null !== $tracesSampler + ? $tracesSampler($samplingContext) + : $this->getSampleRate($samplingContext->getParentSampled(), $options->getSampleRate()); + + if (!$this->isValidSampleRate($sampleRate)) { + $transaction->setSampled(false); + + return $transaction; } - if ($sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { - // if true = we want to have the transaction - // if false = we don't want to have it - $context->sampled = false; - } else { - $context->sampled = true; + if (0.0 === $sampleRate) { + $transaction->setSampled(false); + + return $transaction; } - $transaction = new Transaction($context, $this); + $transaction->setSampled(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < $sampleRate); - // We only want to create a span list if we sampled the transaction - // If sampled == false, we will discard the span anyway, so we can save memory by not storing child spans - if ($context->sampled) { - $transaction->initSpanRecorder(); + if (!$transaction->getSampled()) { + return $transaction; } + $transaction->initSpanRecorder(); + return $transaction; } @@ -297,4 +281,42 @@ public function getSpan(): ?Span { return $this->getScope()->getSpan(); } + + /** + * Gets the scope bound to the top of the stack. + */ + private function getScope(): Scope + { + return $this->getStackTop()->getScope(); + } + + /** + * Gets the topmost client/layer pair in the stack. + */ + private function getStackTop(): Layer + { + return $this->stack[\count($this->stack) - 1]; + } + + private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float + { + if (true === $hasParentBeenSampled) { + return 1; + } + + if (false === $hasParentBeenSampled) { + return 0; + } + + return $fallbackSampleRate; + } + + private function isValidSampleRate(float $sampleRate): bool + { + if ($sampleRate < 0 || $sampleRate > 1) { + return false; + } + + return true; + } } diff --git a/src/State/Scope.php b/src/State/Scope.php index da84a55b7..a7a77d252 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -363,7 +363,7 @@ public function applyToEvent(Event $event, $payload): ?Event } /** - * Returns the Span that is on the Scope. + * Returns the span that is on the scope. */ public function getSpan(): ?Span { @@ -371,9 +371,9 @@ public function getSpan(): ?Span } /** - * Sets the Span on the Scope. + * Sets the span on the scope. * - * @param Span|null $span The Span + * @param Span|null $span The span * * @return $this */ @@ -385,19 +385,16 @@ public function setSpan(?Span $span): self } /** - * Returns the Transaction that is on the Scope. - * - * @psalm-suppress MoreSpecificReturnType - * @psalm-suppress LessSpecificReturnStatement + * Returns the transaction attached to the scope (if there is one). */ public function getTransaction(): ?Transaction { $span = $this->span; - if (null !== $span && null !== $span->spanRecorder && !empty($span->spanRecorder->getSpans())) { + if (null !== $span && null !== $span->getSpanRecorder() && !empty($span->getSpanRecorder()->getSpans())) { // The first span in the recorder is considered to be a Transaction - /** @phpstan-ignore-next-line */ - return $span->spanRecorder->getSpans()[0]; + /** @var Transaction */ + return $span->getSpanRecorder()->getSpans()[0]; } return null; diff --git a/src/Tracing/SamplingContext.php b/src/Tracing/SamplingContext.php index 0210195b8..4df9bcf4c 100644 --- a/src/Tracing/SamplingContext.php +++ b/src/Tracing/SamplingContext.php @@ -12,12 +12,18 @@ final class SamplingContext private $transactionContext; /** - * Returns the default instance of for the SamplingContext. + * @var bool|null Sampling decision from the parent transaction, if any + */ + private $parentSampled; + + /** + * Returns an instance populated with the data of the transaction context. */ public static function getDefault(TransactionContext $transactionContext): self { - $context = new SamplingContext(); - $context->setTransactionContext($transactionContext); + $context = new self(); + $context->transactionContext = $transactionContext; + $context->parentSampled = $transactionContext->getParentSampled(); return $context; } @@ -27,8 +33,19 @@ public function getTransactionContext(): ?TransactionContext return $this->transactionContext; } - public function setTransactionContext(?TransactionContext $transactionContext): void + /** + * Gets the sampling decision from the parent transaction, if any. + */ + public function getParentSampled(): ?bool + { + return $this->parentSampled; + } + + /** + * Sets the sampling decision from the parent transaction, if any. + */ + public function setParentSampled(?bool $parentSampled): void { - $this->transactionContext = $transactionContext; + $this->parentSampled = $parentSampled; } } diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 61dc924d1..0f672710a 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -32,7 +32,7 @@ class Span protected $op; /** - * @var string|null Completion status of the Span + * @var SpanStatus|null Completion status of the Span */ protected $status; @@ -68,13 +68,11 @@ class Span /** * @var SpanRecorder|null Reference instance to the SpanRecorder - * - * @internal */ - public $spanRecorder; + protected $spanRecorder; /** - * Span constructor. + * Constructor. * * @param SpanContext|null $context The context to create the span with * @@ -82,24 +80,34 @@ class Span */ public function __construct(?SpanContext $context = null) { - $this->traceId = $context->traceId ?? TraceId::generate(); - $this->spanId = $context->spanId ?? SpanId::generate(); - $this->parentSpanId = $context->parentSpanId ?? null; - $this->description = $context->description ?? null; - $this->op = $context->op ?? null; - $this->status = $context->status ?? null; - $this->sampled = $context->sampled ?? null; - - if (null !== $context && $context->tags) { - $this->tags = $context->tags; + $this->traceId = TraceId::generate(); + $this->spanId = SpanId::generate(); + $this->startTimestamp = microtime(true); + + if (null === $context) { + return; + } + + if (null !== $context->getTraceId()) { + $this->traceId = $context->getTraceId(); + } + + if (null !== $context->getSpanId()) { + $this->spanId = $context->getSpanId(); } - if (null !== $context && $context->data) { - $this->data = $context->data; + if (null !== $context->getStartTimestamp()) { + $this->startTimestamp = $context->getStartTimestamp(); } - $this->startTimestamp = $context->startTimestamp ?? microtime(true); - $this->endTimestamp = $context->endTimestamp ?? null; + $this->parentSpanId = $context->getParentSpanId(); + $this->description = $context->getDescription(); + $this->op = $context->getOp(); + $this->status = $context->getStatus(); + $this->sampled = $context->getSampled(); + $this->tags = $context->getTags(); + $this->data = $context->getData(); + $this->endTimestamp = $context->getEndTimestamp(); } /** @@ -174,19 +182,6 @@ public function getEndTimestamp(): ?float return $this->endTimestamp; } - /** - * Returns `sentry-trace` header content. - */ - public function toTraceparent(): string - { - $sampled = ''; - if (null !== $this->sampled) { - $sampled = $this->sampled ? '-1' : '-0'; - } - - return $this->traceId . '-' . $this->spanId . $sampled; - } - /** * Gets a description of the span's operation, which uniquely identifies * the span but is consistent across instances of the span. @@ -228,7 +223,7 @@ public function setOp(?string $op): void /** * Gets the status of the span/transaction. */ - public function getStatus(): ?string + public function getStatus(): ?SpanStatus { return $this->status; } @@ -236,13 +231,29 @@ public function getStatus(): ?string /** * Sets the status of the span/transaction. * - * @param string|null $status The status + * @param SpanStatus|null $status The status */ - public function setStatus(?string $status): void + public function setStatus(?SpanStatus $status): void { $this->status = $status; } + /** + * Sets the HTTP status code and the status of the span/transaction. + * + * @param int $statusCode The HTTP status code + */ + public function setHttpStatus(int $statusCode): void + { + $this->tags['http.status_code'] = (string) $statusCode; + + $status = SpanStatus::createFromHttpStatusCode($statusCode); + + if ($status !== SpanStatus::unknownError()) { + $this->status = $status; + } + } + /** * Gets a map of tags for this event. * @@ -346,7 +357,7 @@ public function getTraceContext(): array } if (null !== $this->status) { - $result['status'] = $this->status; + $result['status'] = (string) $this->status; } if (!empty($this->data)) { @@ -375,26 +386,57 @@ public function finish(?float $endTimestamp = null): ?EventId } /** - * Creates a new `Span` while setting the current `Span.id` as `parentSpanId`. + * Creates a new {@see Span} while setting the current ID as `parentSpanId`. * Also the `sampled` decision will be inherited. * - * @param SpanContext $context The Context of the child span - * - * @return Span Instance of the newly created Span + * @param SpanContext $context The context of the child span */ public function startChild(SpanContext $context): self { - $context->sampled = $this->sampled; - $context->parentSpanId = $this->spanId; - $context->traceId = $this->traceId; + $context = clone $context; + $context->setSampled($this->sampled); + $context->setParentSpanId($this->spanId); + $context->setTraceId($this->traceId); $span = new self($context); - $span->spanRecorder = $this->spanRecorder; + if (null != $span->spanRecorder) { $span->spanRecorder->add($span); } return $span; } + + /** + * Gets the span recorder attached to this span. + * + * @internal + */ + public function getSpanRecorder(): ?SpanRecorder + { + return $this->spanRecorder; + } + + /** + * Detaches the span recorder from this instance. + */ + public function detachSpanRecorder(): void + { + $this->spanRecorder = null; + } + + /** + * Returns a string that can be used for the `sentry-trace` header. + */ + public function toTraceparent(): string + { + $sampled = ''; + + if (null !== $this->sampled) { + $sampled = $this->sampled ? '-1' : '-0'; + } + + return sprintf('%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled); + } } diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 2e5e2bff0..d6072323a 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -11,57 +11,57 @@ class SpanContext /** * @var string|null Description of the Span */ - public $description; + private $description; /** * @var string|null Operation of the Span */ - public $op; + private $op; /** - * @var string|null Completion status of the Span + * @var SpanStatus|null Completion status of the Span */ - public $status; + private $status; /** * @var SpanId|null ID of the parent Span */ - public $parentSpanId; + private $parentSpanId; /** * @var bool|null Has the sample decision been made? */ - public $sampled; + private $sampled; /** * @var SpanId|null Span ID */ - public $spanId; + private $spanId; /** * @var TraceId|null Trace ID */ - public $traceId; + private $traceId; /** - * @var array|null A List of tags associated to this Span + * @var array A List of tags associated to this Span */ - public $tags; + private $tags = []; /** - * @var array|null An arbitrary mapping of additional metadata + * @var array An arbitrary mapping of additional metadata */ - public $data; + private $data = []; /** * @var float|null Timestamp in seconds (epoch time) indicating when the span started */ - public $startTimestamp; + private $startTimestamp; /** * @var float|null Timestamp in seconds (epoch time) indicating when the span ended */ - public $endTimestamp; + private $endTimestamp; public function getDescription(): ?string { @@ -83,12 +83,12 @@ public function setOp(?string $op): void $this->op = $op; } - public function getStatus(): ?string + public function getStatus(): ?SpanStatus { return $this->status; } - public function setStatus(?string $status): void + public function setStatus(?SpanStatus $status): void { $this->status = $status; } @@ -136,15 +136,15 @@ public function setTraceId(?TraceId $traceId): void /** * @return array */ - public function getTags(): ?array + public function getTags(): array { return $this->tags; } /** - * @param array|null $tags + * @param array $tags */ - public function setTags(?array $tags): void + public function setTags(array $tags): void { $this->tags = $tags; } @@ -152,15 +152,15 @@ public function setTags(?array $tags): void /** * @return array */ - public function getData(): ?array + public function getData(): array { return $this->data; } /** - * @param array|null $data + * @param array $data */ - public function setData(?array $data): void + public function setData(array $data): void { $this->data = $data; } @@ -186,7 +186,7 @@ public function setEndTimestamp(?float $endTimestamp): void } /** - * Returns a context depending on the header given. Containing trace_id, parent_span_id and sampled. + * Returns a context populated with the data of the given header. * * @param string $header The sentry-trace header from the request * @@ -201,15 +201,15 @@ public static function fromTraceparent(string $header) return $context; } - if (mb_strlen($matches['trace_id']) > 0) { + if (!empty($matches['trace_id'])) { $context->traceId = new TraceId($matches['trace_id']); } - if (mb_strlen($matches['span_id']) > 0) { + if (!empty($matches['span_id'])) { $context->parentSpanId = new SpanId($matches['span_id']); } - if (\array_key_exists('sampled', $matches)) { + if (isset($matches['sampled'])) { $context->sampled = '1' === $matches['sampled']; } diff --git a/src/Tracing/SpanId.php b/src/Tracing/SpanId.php index b852b4682..a11757d46 100644 --- a/src/Tracing/SpanId.php +++ b/src/Tracing/SpanId.php @@ -36,6 +36,16 @@ public static function generate(): self return new self(substr(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM)), 0, 16)); } + /** + * Compares whether two objects are equals. + * + * @param SpanId $other The object to compare + */ + public function isEqualTo(self $other): bool + { + return $this->value === $other->value; + } + public function __toString(): string { return $this->value; diff --git a/src/Tracing/SpanRecorder.php b/src/Tracing/SpanRecorder.php index c133da352..560763044 100644 --- a/src/Tracing/SpanRecorder.php +++ b/src/Tracing/SpanRecorder.php @@ -4,9 +4,6 @@ namespace Sentry\Tracing; -/** - * Class SpanRecorder. - */ final class SpanRecorder { /** @@ -15,12 +12,15 @@ final class SpanRecorder private $maxSpans; /** - * @var Span[] Collection of Spans + * @var Span[] List of spans managed by this recorder */ private $spans = []; /** - * SpanRecorder constructor. + * Constructor. + * + * @param int $maxSpans The maximum number of spans to record before + * detaching the recorder from the span */ public function __construct(int $maxSpans = 1000) { @@ -28,18 +28,21 @@ public function __construct(int $maxSpans = 1000) } /** - * Adds a span to the array. + * Adds a span to the list of recorded spans or detaches the recorder if the + * maximum number of spans to store has been reached. */ public function add(Span $span): void { if (\count($this->spans) > $this->maxSpans) { - $span->spanRecorder = null; + $span->detachSpanRecorder(); } else { $this->spans[] = $span; } } /** + * Gets all the spans managed by this recorder. + * * @return Span[] */ public function getSpans(): array diff --git a/src/Tracing/SpanStatus.php b/src/Tracing/SpanStatus.php new file mode 100644 index 000000000..b8b4cee2c --- /dev/null +++ b/src/Tracing/SpanStatus.php @@ -0,0 +1,197 @@ + + */ + private static $instances = []; + + /** + * Constructor. + * + * @param string $value The value of the enum instance + */ + private function __construct(string $value) + { + $this->value = $value; + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 401 Unauthorized (actually does mean unauthenticated according to RFC 7235). + */ + public static function unauthenticated(): self + { + return self::getInstance('unauthenticated'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 403 Forbidden. + */ + public static function permissionDenied(): self + { + return self::getInstance('permission_denied'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 404 Not Found. + */ + public static function notFound(): self + { + return self::getInstance('not_found'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 409 Already exists. + */ + public static function alreadyExists(): self + { + return self::getInstance('already_exists'); + } + + /** + * Gets an instance of this enum representing the fact that the operation + * was rejected because the system is not in a state required for the + * operation. + */ + public static function failedPrecondition(): self + { + return self::getInstance('failed_precondition'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 429 Too Many Requests. + */ + public static function resourceExchausted(): self + { + return self::getInstance('resource_exhausted'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 501 Not Implemented. + */ + public static function unimplemented(): self + { + return self::getInstance('unimplemented'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 503 Service Unavailable. + */ + public static function unavailable(): self + { + return self::getInstance('unavailable'); + } + + /** + * Gets an instance of this enum representing the fact that the deadline + * expired before operation could complete. + */ + public static function deadlineExceeded(): self + { + return self::getInstance('deadline_exceeded'); + } + + /** + * Gets an instance of this enum representing the fact that the operation + * completed successfully. + */ + public static function ok(): self + { + return self::getInstance('ok'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 4xx as response status code. + */ + public static function invalidArgument(): self + { + return self::getInstance('invalid_argument'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * 5xx as response status code. + */ + public static function internalError(): self + { + return self::getInstance('internal_error'); + } + + /** + * Gets an instance of this enum representing the fact that the server returned + * with any non-standard HTTP status code. + */ + public static function unknownError(): self + { + return self::getInstance('unknown_error'); + } + + /** + * Returns an instance of this enum according to the given HTTP status code. + * + * @param int $statusCode The HTTP status code + */ + public static function createFromHttpStatusCode(int $statusCode): self + { + switch (true) { + case 401 === $statusCode: + return self::unauthenticated(); + case 403 === $statusCode: + return self::permissionDenied(); + case 404 === $statusCode: + return self::notFound(); + case 409 === $statusCode: + return self::alreadyExists(); + case 413 === $statusCode: + return self::failedPrecondition(); + case 429 === $statusCode: + return self::resourceExchausted(); + case 501 === $statusCode: + return self::unimplemented(); + case 503 === $statusCode: + return self::unavailable(); + case 504 === $statusCode: + return self::deadlineExceeded(); + case $statusCode < 400: + return self::ok(); + case $statusCode < 500: + return self::invalidArgument(); + case $statusCode < 600: + return self::internalError(); + default: + return self::unknownError(); + } + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index 5576a3544..ac8fd6648 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -6,7 +6,7 @@ use Sentry\Event; use Sentry\EventId; -use Sentry\Severity; +use Sentry\SentrySdk; use Sentry\State\HubInterface; /** @@ -15,7 +15,7 @@ final class Transaction extends Span { /** - * @var HubInterface|null The hub instance + * @var HubInterface The hub instance */ private $hub; @@ -27,21 +27,23 @@ final class Transaction extends Span /** * Span constructor. * - * @param TransactionContext|null $context The context to create the transaction with - * @param HubInterface|null $hub Instance of a hub to flush the transaction + * @param TransactionContext $context The context to create the transaction with + * @param HubInterface|null $hub Instance of a hub to flush the transaction * * @internal */ - public function __construct(?TransactionContext $context = null, ?HubInterface $hub = null) + public function __construct(TransactionContext $context, ?HubInterface $hub = null) { parent::__construct($context); - $this->hub = $hub; - $this->name = $context->name ?? ''; + $this->hub = $hub ?? SentrySdk::getCurrentHub(); + $this->name = $context->getName(); } /** - * @param string $name Name of the transaction + * Sets the name of this transaction. + * + * @param string $name The name */ public function setName(string $name): void { @@ -49,20 +51,21 @@ public function setName(string $name): void } /** - * Attaches SpanRecorder to the transaction itself. + * Attaches a {@see SpanRecorder} to the transaction itself. + * + * @param int $maxSpans The maximum number of spans that can be recorded */ - public function initSpanRecorder(): void + public function initSpanRecorder(int $maxSpans = 1000): void { if (null === $this->spanRecorder) { - $this->spanRecorder = new SpanRecorder(); + $this->spanRecorder = new SpanRecorder($maxSpans); } + $this->spanRecorder->add($this); } /** * {@inheritdoc} - * - * @return EventId|null Finish for a transaction returns the eventId or null in case we didn't send it */ public function finish(?float $endTimestamp = null): ?EventId { @@ -77,39 +80,24 @@ public function finish(?float $endTimestamp = null): ?EventId return null; } - if (null !== $this->hub) { - return $this->hub->captureEvent($this->toEvent()); - } + $finishedSpans = []; - return null; - } + if (null !== $this->spanRecorder) { + foreach ($this->spanRecorder->getSpans() as $span) { + if ($span->getSpanId() !== $this->getSpanId() && null !== $span->getEndTimestamp()) { + $finishedSpans[] = $span; + } + } + } - /** - * Returns an Event. - */ - public function toEvent(): Event - { $event = Event::createTransaction(); - $event->setTags(array_merge($event->getTags(), $this->tags)); - $event->setTransaction($this->name); + $event->setSpans($finishedSpans); $event->setStartTimestamp($this->startTimestamp); + $event->setTimestamp($this->endTimestamp); + $event->setTags($this->tags); + $event->setTransaction($this->name); $event->setContext('trace', $this->getTraceContext()); - if (null != $this->spanRecorder) { - $event->setSpans(array_filter($this->spanRecorder->getSpans(), function (Span $span): bool { - return $this->spanId != $span->spanId && null != $span->endTimestamp; - })); - } - - $event->setLevel(null); - if (null != $this->status && 'ok' != $this->status) { - $event->setLevel(Severity::error()); - } - - if ($this->endTimestamp) { - $event->setTimestamp($this->endTimestamp); - } - - return $event; + return $this->hub->captureEvent($event); } } diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index 889b36620..2853fa190 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -6,18 +6,63 @@ final class TransactionContext extends SpanContext { + public const DEFAULT_NAME = ''; + + /** + * @var string Name of the transaction + */ + private $name; + + /** + * @var bool|null The parent's sampling decision + */ + private $parentSampled; + /** - * @var string|null Name of the transaction + * Constructor. + * + * @param string $name The name of the transaction + * @param bool|null $parentSampled The parent's sampling decision */ - public $name; + public function __construct(string $name = self::DEFAULT_NAME, ?bool $parentSampled = null) + { + $this->name = $name; + $this->parentSampled = $parentSampled; + } - public function getName(): ?string + /** + * Gets the name of the transaction. + */ + public function getName(): string { return $this->name; } - public function setName(?string $name): void + /** + * Sets the name of the transaction. + * + * @param string $name The name + */ + public function setName(string $name): void { $this->name = $name; } + + /** + * Gets the parent's sampling decision. + */ + public function getParentSampled(): ?bool + { + return $this->parentSampled; + } + + /** + * Sets the parent's sampling decision. + * + * @param bool|null $parentSampled The decision + */ + public function setParentSampled(?bool $parentSampled): void + { + $this->parentSampled = $parentSampled; + } } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index f1e93bfdf..8cfeff07c 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -19,6 +19,7 @@ use Sentry\Stacktrace; use Sentry\Tracing\Span; use Sentry\Tracing\SpanId; +use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TraceId; use Sentry\UserDataBag; use Symfony\Bridge\PhpUnit\ClockMock; @@ -65,7 +66,7 @@ public function serializeDataProvider(): iterable <<setTraceId(new TraceId('1e57b752bc6e4544bbaa246cd1d05dee')); $span2->setOp('http'); $span2->setDescription('GET /sockjs-node/info'); - $span2->setStatus('ok'); + $span2->setStatus(SpanStatus::ok()); $span2->setStartTimestamp(1597790835); $span2->setTags(['http.status_code' => '200']); $span2->setData([ @@ -416,7 +417,7 @@ public function serializeDataProvider(): iterable <<assertSame($integration, $hub->getIntegration('Foo\\Bar')); } + + /** + * @dataProvider startTransactionDataProvider + */ + public function testStartTransactionWithTracesSampler(Options $options, TransactionContext $transactionContext, bool $expectedSampled): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn($options); + + $hub = new Hub($client); + $transaction = $hub->startTransaction($transactionContext); + + $this->assertSame($expectedSampled, $transaction->getSampled()); + } + + public function startTransactionDataProvider(): iterable + { + yield [ + new Options([ + 'traces_sampler' => static function (): float { + return 1; + }, + ]), + new TransactionContext(), + true, + ]; + + yield [ + new Options([ + 'traces_sampler' => static function (): float { + return 0; + }, + ]), + new TransactionContext(), + false, + ]; + + yield [ + new Options([ + 'traces_sampler' => static function (): int { + return 1; + }, + ]), + new TransactionContext(), + true, + ]; + + yield [ + new Options([ + 'traces_sampler' => static function (): int { + return 0; + }, + ]), + new TransactionContext(), + false, + ]; + + yield [ + new Options([ + 'traces_sample_rate' => 1.0, + ]), + new TransactionContext(), + true, + ]; + + yield [ + new Options([ + 'traces_sample_rate' => 0.0, + ]), + new TransactionContext(), + false, + ]; + + yield [ + new Options([ + 'traces_sample_rate' => 0.5, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, true), + true, + ]; + + yield [ + new Options([ + 'traces_sample_rate' => 1.0, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + } + + public function testStartTransactionDoesNothingIfTracingIsNotEnabled(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options()); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext()); + + $this->assertFalse($transaction->getSampled()); + } } diff --git a/tests/Tracing/SamplingContextTest.php b/tests/Tracing/SamplingContextTest.php new file mode 100644 index 000000000..2bea20d65 --- /dev/null +++ b/tests/Tracing/SamplingContextTest.php @@ -0,0 +1,21 @@ +assertSame($transactionContext, $samplingContext->getTransactionContext()); + $this->assertSame($transactionContext->getParentSampled(), $samplingContext->getParentSampled()); + } +} diff --git a/tests/Tracing/SpanContextTest.php b/tests/Tracing/SpanContextTest.php new file mode 100644 index 000000000..bd86f2acb --- /dev/null +++ b/tests/Tracing/SpanContextTest.php @@ -0,0 +1,69 @@ +assertEquals($expectedSpanId, $spanContext->getParentSpanId()); + } + + if (null !== $expectedTraceId) { + $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); + } + + $this->assertSame($expectedSampled, $spanContext->getSampled()); + } + + public function fromTraceparentDataProvider(): iterable + { + yield [ + '0', + null, + null, + false, + ]; + + yield [ + '1', + null, + null, + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + false, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + null, + ]; + } +} diff --git a/tests/Tracing/SpanIdTest.php b/tests/Tracing/SpanIdTest.php new file mode 100644 index 000000000..6af5f0878 --- /dev/null +++ b/tests/Tracing/SpanIdTest.php @@ -0,0 +1,36 @@ +assertSame($value, (string) new SpanId($value)); + } + + /** + * @dataProvider constructorThrowsOnInvalidValueDataProvider + */ + public function testConstructorThrowsOnInvalidValue(string $value): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $value argument must be a 16 characters long hexadecimal string.'); + + new SpanId($value); + } + + public function constructorThrowsOnInvalidValueDataProvider(): \Generator + { + yield 'Value too long' => ['566e3688a61d4bc88']; + yield 'Value too short' => ['566e3688a61d4b8']; + yield 'Value with invalid characters' => ['88951642d6f14a1g']; + } +} diff --git a/tests/Tracing/SpanRecorderTest.php b/tests/Tracing/SpanRecorderTest.php new file mode 100644 index 000000000..8a8d085be --- /dev/null +++ b/tests/Tracing/SpanRecorderTest.php @@ -0,0 +1,36 @@ +spanRecorder = new SpanRecorder(1); + $this->spanRecorder->add($this); + } + + public function getSpanRecorder(): SpanRecorder + { + return $this->spanRecorder; + } + }; + + $span2 = $span1->startChild(new SpanContext()); + $span3 = $span2->startChild(new SpanContext()); // this should not end up being recorded + + $this->assertSame([$span1, $span2], $span1->getSpanRecorder()->getSpans()); + } +} diff --git a/tests/Tracing/SpanStatusTest.php b/tests/Tracing/SpanStatusTest.php new file mode 100644 index 000000000..8856c7bde --- /dev/null +++ b/tests/Tracing/SpanStatusTest.php @@ -0,0 +1,173 @@ +assertSame($expectedStringRepresentation, (string) $spanStatus); + } + + public function toStringDataProvider(): iterable + { + yield [ + SpanStatus::unauthenticated(), + 'unauthenticated', + ]; + + yield [ + SpanStatus::permissionDenied(), + 'permission_denied', + ]; + + yield [ + SpanStatus::notFound(), + 'not_found', + ]; + + yield [ + SpanStatus::alreadyExists(), + 'already_exists', + ]; + + yield [ + SpanStatus::failedPrecondition(), + 'failed_precondition', + ]; + + yield [ + SpanStatus::resourceExchausted(), + 'resource_exhausted', + ]; + + yield [ + SpanStatus::unimplemented(), + 'unimplemented', + ]; + + yield [ + SpanStatus::unavailable(), + 'unavailable', + ]; + + yield [ + SpanStatus::deadlineExceeded(), + 'deadline_exceeded', + ]; + + yield [ + SpanStatus::ok(), + 'ok', + ]; + + yield [ + SpanStatus::invalidArgument(), + 'invalid_argument', + ]; + + yield [ + SpanStatus::internalError(), + 'internal_error', + ]; + + yield [ + SpanStatus::unknownError(), + 'unknown_error', + ]; + } + + /** + * @dataProvider createFromHttpStatusCodeDataProvider + */ + public function testCreateFromHttpStatusCode(SpanStatus $expectedSpanStatus, int $httpStatusCode): void + { + $this->assertSame($expectedSpanStatus, SpanStatus::createFromHttpStatusCode($httpStatusCode)); + } + + public function createFromHttpStatusCodeDataProvider(): iterable + { + yield [ + SpanStatus::unauthenticated(), + 401, + ]; + + yield [ + SpanStatus::permissionDenied(), + 403, + ]; + + yield [ + SpanStatus::notFound(), + 404, + ]; + + yield [ + SpanStatus::alreadyExists(), + 409, + ]; + + yield [ + SpanStatus::failedPrecondition(), + 413, + ]; + + yield [ + SpanStatus::resourceExchausted(), + 429, + ]; + + yield [ + SpanStatus::unimplemented(), + 501, + ]; + + yield [ + SpanStatus::unavailable(), + 503, + ]; + + yield [ + SpanStatus::deadlineExceeded(), + 504, + ]; + + yield [ + SpanStatus::ok(), + 200, + ]; + + yield [ + SpanStatus::invalidArgument(), + 400, + ]; + + yield [ + SpanStatus::internalError(), + 500, + ]; + + yield [ + SpanStatus::unknownError(), + 600, + ]; + } + + public function testStrictComparison(): void + { + $responseStatus1 = SpanStatus::ok(); + $responseStatus2 = SpanStatus::ok(); + $responseStatus3 = SpanStatus::unknownError(); + + $this->assertSame($responseStatus1, $responseStatus2); + $this->assertNotSame($responseStatus1, $responseStatus3); + } +} diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index af99f3965..df039f7af 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -16,50 +16,17 @@ */ final class SpanTest extends TestCase { - public function testConstructor(): void - { - $context = new SpanContext(); - $context->traceId = TraceId::generate(); - $context->spanId = SpanId::generate(); - $context->parentSpanId = SpanId::generate(); - $context->description = 'description'; - $context->op = 'op'; - $context->status = 'ok'; - $context->sampled = true; - $tags = []; - $tags['a'] = 'b'; - $context->tags = $tags; - $data = []; - $data['c'] = 'd'; - $context->data = $data; - $context->startTimestamp = microtime(true); - $context->endTimestamp = microtime(true); - - $span = new Span($context); - - $this->assertEquals($context->traceId, $span->getTraceId()); - $this->assertEquals($context->spanId, $span->getSpanId()); - $this->assertEquals($context->parentSpanId, $span->getParentSpanId()); - $this->assertSame($context->op, $span->getOp()); - $this->assertSame($context->description, $span->getDescription()); - $this->assertSame($context->status, $span->getStatus()); - $this->assertSame($context->tags, $span->getTags()); - $this->assertSame($context->data, $span->getData()); - $this->assertSame($context->startTimestamp, $span->getStartTimestamp()); - $this->assertSame($context->endTimestamp, $span->getEndTimestamp()); - } - /** * @dataProvider finishDataProvider */ - public function testFinish(?float $currentTimestamp, ?float $timestamp, float $expectedTimestamp): void + public function testFinish(?float $currentTimestamp, ?float $endTimestamp, float $expectedEndTimestamp): void { ClockMock::withClockMock($currentTimestamp); $span = new Span(); - $span->finish($timestamp); + $span->finish($endTimestamp); - $this->assertSame($expectedTimestamp, $span->getEndTimestamp()); + $this->assertSame($expectedEndTimestamp, $span->getEndTimestamp()); } public function finishDataProvider(): iterable @@ -77,33 +44,61 @@ public function finishDataProvider(): iterable ]; } - public function testTraceparentHeader(): void + public function testStartChild(): void { - $context = SpanContext::fromTraceparent('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb'); - $this->assertEquals('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', $context->traceId); - $this->assertNotEquals('bbbbbbbbbbbbbbbb', $context->spanId); - $this->assertEquals('bbbbbbbbbbbbbbbb', $context->parentSpanId); - $this->assertNull($context->sampled); - } + $spanContext2ParentSpanId = SpanId::generate(); + $spanContext2TraceId = TraceId::generate(); - public function testTraceparentHeaderSampledTrue(): void - { - $context = SpanContext::fromTraceparent('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-1'); - $this->assertTrue($context->sampled); + $spanContext1 = new SpanContext(); + $spanContext1->setSampled(false); + $spanContext1->setSpanId(SpanId::generate()); + $spanContext1->setTraceId(TraceId::generate()); + + $spanContext2 = new SpanContext(); + $spanContext2->setSampled(true); + $spanContext2->setParentSpanId($spanContext2ParentSpanId); + $spanContext2->setTraceId($spanContext2TraceId); + + $span1 = new Span($spanContext1); + $span2 = $span1->startChild($spanContext2); + + $this->assertSame($spanContext1->getSampled(), $span1->getSampled()); + $this->assertSame($spanContext1->getSpanId(), $span1->getSpanId()); + $this->assertSame($spanContext1->getTraceId(), $span1->getTraceId()); + + $this->assertSame($spanContext1->getSampled(), $span2->getSampled()); + $this->assertSame($spanContext1->getSpanId(), $span2->getParentSpanId()); + $this->assertSame($spanContext1->getTraceId(), $span2->getTraceId()); } - public function testTraceparentHeaderSampledFalse(): void + /** + * @dataProvider toTraceparentDataProvider + */ + public function testToTraceparent(?bool $sampled, string $expectedValue): void { - $context = SpanContext::fromTraceparent('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-0'); - $this->assertFalse($context->sampled); + $span = new Span(); + $span->setSpanId(new SpanId('566e3688a61d4bc8')); + $span->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $span->setSampled($sampled); + + $this->assertSame($expectedValue, $span->toTraceparent()); } - public function testTraceparentHeaderJustSampleRate(): void + public function toTraceparentDataProvider(): iterable { - $context = SpanContext::fromTraceparent('1'); - $this->assertTrue($context->sampled); + yield [ + null, + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', + ]; - $context = SpanContext::fromTraceparent('0'); - $this->assertFalse($context->sampled); + yield [ + false, + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', + ]; + + yield [ + true, + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + ]; } } diff --git a/tests/Tracing/TraceIdTest.php b/tests/Tracing/TraceIdTest.php new file mode 100644 index 000000000..badd23e03 --- /dev/null +++ b/tests/Tracing/TraceIdTest.php @@ -0,0 +1,36 @@ +assertSame($value, (string) new TraceId($value)); + } + + /** + * @dataProvider constructorThrowsOnInvalidValueDataProvider + */ + public function testConstructorThrowsOnInvalidValue(string $value): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $value argument must be a 32 characters long hexadecimal string.'); + + new TraceId($value); + } + + public function constructorThrowsOnInvalidValueDataProvider(): \Generator + { + yield 'Value too long' => ['566e3688a61d4bc888951642d6f14a199']; + yield 'Value too short' => ['566e3688a61d4bc888951642d6f14a1']; + yield 'Value with invalid characters' => ['566e3688a61d4bc888951642d6f14a1g']; + } +} diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php new file mode 100644 index 000000000..c27835f62 --- /dev/null +++ b/tests/Tracing/TransactionContextTest.php @@ -0,0 +1,25 @@ +assertSame('', $transactionContext->getName()); + $this->assertNull($transactionContext->getParentSampled()); + + $transactionContext->setName('foo'); + $transactionContext->setParentSampled(true); + + $this->assertSame('foo', $transactionContext->getName()); + $this->assertTrue($transactionContext->getParentSampled()); + } +} diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index 12afb08f1..31fb419cf 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -4,180 +4,74 @@ namespace Sentry\Tests\Tracing; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventId; use Sentry\EventType; -use Sentry\Options; -use Sentry\State\Hub; -use Sentry\Tracing\SamplingContext; +use Sentry\State\HubInterface; +use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; -use Sentry\Tracing\SpanId; -use Sentry\Tracing\SpanRecorder; -use Sentry\Tracing\TraceId; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; +use Symfony\Bridge\PhpUnit\ClockMock; /** * @group time-sensitive */ final class TransactionTest extends TestCase { - public function testConstructor(): void + public function testFinish(): void { - $context = new TransactionContext(); - $context->traceId = TraceId::generate(); - $context->spanId = SpanId::generate(); - $context->parentSpanId = SpanId::generate(); - $context->description = 'description'; - $context->name = 'name'; - $context->op = 'op'; - $context->status = 'ok'; - $context->sampled = true; - $tags = []; - $tags['a'] = 'b'; - $context->tags = $tags; - $data = []; - $data['c'] = 'd'; - $context->data = $data; - $context->startTimestamp = microtime(true); - $context->endTimestamp = microtime(true); - $transaction = new Transaction($context); - $data = $transaction->toEvent(); - $traceContext = $data->getContexts()['trace']; - - $this->assertEquals($context->op, $traceContext['op']); - $this->assertEquals($context->name, $data->getTransaction()); - $this->assertEquals($context->traceId->__toString(), $traceContext['trace_id']); - $this->assertEquals($context->spanId->__toString(), $traceContext['span_id']); - $this->assertEquals($context->parentSpanId->__toString(), $traceContext['parent_span_id']); - $this->assertEquals($context->description, $traceContext['description']); - $this->assertEquals($context->status, $traceContext['status']); - $this->assertEquals($context->tags, $data->getTags()); - $this->assertEquals($context->data, $traceContext['data']); - $this->assertEquals($context->startTimestamp, $data->getStartTimestamp()); - $this->assertEquals($context->endTimestamp, $data->getTimestamp()); - } + ClockMock::withClockMock(1600640877); - public function testShouldContainFinishSpans(): void - { - $transaction = new Transaction(new TransactionContext()); - $transaction->spanRecorder = new SpanRecorder(); - $span1 = $transaction->startChild(new SpanContext()); - $span2 = $transaction->startChild(new SpanContext()); - $span3 = $transaction->startChild(new SpanContext()); - $span1->finish(); - $span2->finish(); - // $span3 is not finished and therefore not included - $transaction->finish(); - $data = $transaction->toEvent(); - $this->assertCount(2, $data->getSpans()); - } + $expectedEventId = null; + $transactionContext = new TransactionContext(); + $transactionContext->setTags(['ios_version' => '4.0']); + $transactionContext->setSampled(true); + $transactionContext->setStartTimestamp(1600640865); - public function testStartAndSendTransactionSampleRateOne(): void - { - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->any()) - ->method('getOptions') - ->willReturn(new Options(['traces_sample_rate' => 1])); - - $hub = new Hub($client); - $transaction = $hub->startTransaction(new TransactionContext()); - $span1 = $transaction->startChild(new SpanContext()); - $span1->finish(); + $hub = $this->createMock(HubInterface::class); - $data = $transaction->toEvent(); + $transaction = new Transaction($transactionContext, $hub); + $transaction->initSpanRecorder(); - $client->expects($this->once()) + $span1 = $transaction->startChild(new SpanContext()); + $span2 = $transaction->startChild(new SpanContext()); + $span3 = $transaction->startChild(new SpanContext()); // This span isn't finished, so it should not be included in the event + + $hub->expects($this->once()) ->method('captureEvent') - ->with($this->callback(function (Event $eventArg) use ($data): bool { + ->with($this->callback(function (Event $eventArg) use ($transactionContext, $span1, $span2): bool { $this->assertSame(EventType::transaction(), $eventArg->getType()); - $this->assertSame($data->getStartTimestamp(), $eventArg->getStartTimestamp()); - $this->assertSame(microtime(true), $eventArg->getTimestamp()); - $this->assertCount(1, $data->getSpans()); + $this->assertSame($transactionContext->getName(), $eventArg->getTransaction()); + $this->assertSame($transactionContext->getStartTimestamp(), $eventArg->getStartTimestamp()); + $this->assertSame(ClockMock::microtime(true), $eventArg->getTimestamp()); + $this->assertSame($transactionContext->getTags(), $eventArg->getTags()); + $this->assertSame([$span1, $span2], $eventArg->getSpans()); return true; - })); + })) + ->willReturnCallback(static function (Event $eventArg) use (&$expectedEventId): EventId { + $expectedEventId = $eventArg->getId(); - $transaction->finish(); - } + return $expectedEventId; + }); - public function testStartAndSendTransactionSampleRateZero(): void - { - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->any()) - ->method('getOptions') - ->willReturn(new Options(['traces_sample_rate' => 0])); - - $hub = new Hub($client); - $transaction = $hub->startTransaction(new TransactionContext()); - $span1 = $transaction->startChild(new SpanContext()); $span1->finish(); + $span2->finish(); - $client->expects($this->never()) - ->method('captureEvent'); - - $transaction->finish(); - } - - public function testStartAndSendTransactionSamplerShouldBeStrongerThanRateOne(): void - { - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->any()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 0, - 'traces_sampler' => static function (SamplingContext $context): float { - return 1; - }, - ])); - - $hub = new Hub($client); - $transaction = $hub->startTransaction(new TransactionContext()); - $span1 = $transaction->startChild(new SpanContext()); - $span1->finish(); - - $data = $transaction->toEvent(); - - $client->expects($this->once()) - ->method('captureEvent') - ->with($this->callback(function (Event $eventArg) use ($data): bool { - $this->assertSame(EventType::transaction(), $eventArg->getType()); - $this->assertSame($data->getStartTimestamp(), $eventArg->getStartTimestamp()); - $this->assertSame(microtime(true), $eventArg->getTimestamp()); - $this->assertCount(1, $data->getSpans()); - - return true; - })); + $eventId = $transaction->finish(); - $transaction->finish(); + $this->assertSame($expectedEventId, $eventId); } - public function testStartAndSendTransactionSamplerShouldBeStrongerThanRateZero(): void + public function testFinishDoesNothingIfSampledFlagIsNotTrue(): void { - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->any()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1, - 'traces_sampler' => static function (SamplingContext $context): float { - return 0; - }, - ])); - - $hub = new Hub($client); - $transaction = $hub->startTransaction(new TransactionContext()); - $span1 = $transaction->startChild(new SpanContext()); - $span1->finish(); - - $client->expects($this->never()) + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->never()) ->method('captureEvent'); + $transaction = new Transaction(new TransactionContext(), $hub); $transaction->finish(); } } From 9b4a85e40d1d741d92e6d207679aeb46c9354dec Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 24 Sep 2020 21:31:23 +0200 Subject: [PATCH 0609/1161] Implement Guzzle middleware to track performance of HTTP requests (#1096) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + src/Tracing/GuzzleTracingMiddleware.php | 48 +++++++++++ tests/Tracing/GuzzleTracingMiddlewareTest.php | 85 +++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/Tracing/GuzzleTracingMiddleware.php create mode 100644 tests/Tracing/GuzzleTracingMiddlewareTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 0af7d5891..4fa0742c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add `traces_sampler` option to set custom sample rate callback (#1083) - [BC BREAK] Add named constructors to the `Event` class (#1085) - Raise the minimum version of PHP to `7.2` and the minimum version of some dependencies (#1088) +- Add Guzzle middleware to trace performance of HTTP requests (#1096) ## 3.0.0-beta1 (2020-09-03) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php new file mode 100644 index 000000000..95720574d --- /dev/null +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -0,0 +1,48 @@ +getTransaction(); + $span = null; + + if (null !== $transaction) { + $spanContext = new SpanContext(); + $spanContext->setOp('http.guzzle'); + $spanContext->setDescription($request->getMethod() . ' ' . $request->getUri()); + + $span = $transaction->startChild($spanContext); + } + + $handlerPromiseCallback = static function ($responseOrException) use ($span) { + if (null !== $span) { + $span->finish(); + } + + if ($responseOrException instanceof \Throwable) { + throw $responseOrException; + } + + return $responseOrException; + }; + + return $handler($request, $options)->then($handlerPromiseCallback, $handlerPromiseCallback); + }; + }; + } +} diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php new file mode 100644 index 000000000..cd8e21bca --- /dev/null +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -0,0 +1,85 @@ +createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 1])); + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(function (Event $eventArg): bool { + $this->assertSame(EventType::transaction(), $eventArg->getType()); + + $spans = $eventArg->getSpans(); + + $this->assertCount(1, $spans); + $this->assertSame('http.guzzle', $spans[0]->getOp()); + $this->assertSame('GET http://www.example.com', $spans[0]->getDescription()); + + return true; + })); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext()); + + $hub->setSpan($transaction); + + $middleware = GuzzleTracingMiddleware::trace($hub); + $function = $middleware(static function () use ($expectedPromiseResult): PromiseInterface { + if ($expectedPromiseResult instanceof \Throwable) { + return new RejectedPromise($expectedPromiseResult); + } + + return new FulfilledPromise($expectedPromiseResult); + }); + + /** @var PromiseInterface $promise */ + $promise = $function(new Request('GET', 'http://www.example.com'), []); + + try { + $promiseResult = $promise->wait(); + } catch (\Throwable $exception) { + $promiseResult = $exception; + } + + $this->assertSame($expectedPromiseResult, $promiseResult); + + $transaction->finish(); + } + + public function traceDataProvider(): iterable + { + yield [ + new Response(), + ]; + + yield [ + new \Exception(), + ]; + } +} From 17588a2a00d2c4f3f55d847cdb5684ee676c4e1e Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 25 Sep 2020 12:38:05 +0200 Subject: [PATCH 0610/1161] Change captureEvent to only accept an instance of the Event class rather than also a plain array (#1094) --- CHANGELOG.md | 1 + UPGRADE-3.0.md | 7 +- src/Client.php | 126 +++++++---------- src/ClientInterface.php | 7 +- src/EventHint.php | 56 ++++++++ src/Integration/TransactionIntegration.php | 9 +- src/Monolog/Handler.php | 19 +-- src/State/Hub.php | 6 +- src/State/HubAdapter.php | 6 +- src/State/HubInterface.php | 6 +- src/State/Scope.php | 17 ++- src/functions.php | 7 +- tests/ClientTest.php | 118 +++++----------- tests/EventHintTest.php | 50 +++++++ tests/FunctionsTest.php | 10 +- .../EnvironmentIntegrationTest.php | 2 +- .../FrameContextifierIntegrationTest.php | 4 +- .../IgnoreErrorsIntegrationTest.php | 2 +- tests/Integration/ModulesIntegrationTest.php | 2 +- tests/Integration/RequestIntegrationTest.php | 2 +- .../TransactionIntegrationTest.php | 51 +++++-- tests/Monolog/HandlerTest.php | 128 +++++++++++------- tests/State/HubTest.php | 24 ++-- tests/State/ScopeTest.php | 78 +++++++---- 24 files changed, 429 insertions(+), 309 deletions(-) create mode 100644 src/EventHint.php create mode 100644 tests/EventHintTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa0742c3..9c7d04265 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add `traces_sampler` option to set custom sample rate callback (#1083) - [BC BREAK] Add named constructors to the `Event` class (#1085) - Raise the minimum version of PHP to `7.2` and the minimum version of some dependencies (#1088) +- [BC BREAK] Change the `captureEvent` to only accept an instance of the `Event` class rather than also a plain array (#1094) - Add Guzzle middleware to trace performance of HTTP requests (#1096) ## 3.0.0-beta1 (2020-09-03) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 007cb76fa..be8111286 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -102,7 +102,7 @@ - Removed the `Event::getTagsContext()` method, use `Event::getTags()` instead - Removed the `Event::getUserContext()` method, use `Event::getUser()` instead - Renamed the `Event::getServerOsContext()` method to `Event::getOsContext()` -- The signature of the `Scope::setUser()` method changed ot accept a plain array +- The signature of the `Scope::setUser()` method changed to accept a plain array - Removed the `FlushableClientInterface` and `ClosableTransportInterface` interfaces. Their methods have been moved to the corresponding `ClientInterface` and `TransportInterface` interfaces - Removed the `Event::toArray()` and `Event::jsonSerialize()` methods - Removed the `Breadcrumb::toArray()` and `Breadcrumb::jsonSerialize()` methods @@ -110,3 +110,8 @@ - Removed the `Stacktrace::toArray()` and `Stacktrace::jsonSerialize()` methods - Removed the `SpoolTransport` class and the `SpoolInterface` interface with related implementation - Made the `Event::__construct()` method `private`, use the named constructors instead +- The signature of `ClientInterface::captureEvent()` changed to `ClientInterface::captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null)` +- The signature of `HubInterface::captureEvent()` changed to `HubInterface::captureEvent(Event $event, ?EventHint $hint = null)` +- The signature of `captureEvent()` changed to `captureEvent(Event $event, ?EventHint $hint = null)` +- The signature of `Scope::applyToEvent()` changed to `Scope::applyToEvent(Event $event, ?EventHint $hint = null)` +- Global and scope event processors will now receive a `EventHint` as the second parameter, callable should now have the signature `callable(Event $event, EventHint $hint)` diff --git a/src/Client.php b/src/Client.php index 3261285d3..eac510f77 100644 --- a/src/Client.php +++ b/src/Client.php @@ -8,7 +8,6 @@ use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Sentry\Exception\EventCreationException; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\IntegrationRegistry; use Sentry\Serializer\RepresentationSerializer; @@ -126,12 +125,11 @@ public function getOptions(): Options */ public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?EventId { - $payload = [ - 'message' => $message, - 'level' => $level, - ]; + $event = Event::createEvent(); + $event->setMessage($message); + $event->setLevel($level); - return $this->captureEvent($payload, $scope); + return $this->captureEvent($event, null, $scope); } /** @@ -139,15 +137,17 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope */ public function captureException(\Throwable $exception, ?Scope $scope = null): ?EventId { - return $this->captureEvent(['exception' => $exception], $scope); + return $this->captureEvent(Event::createEvent(), EventHint::fromArray([ + 'exception' => $exception, + ]), $scope); } /** * {@inheritdoc} */ - public function captureEvent($payload, ?Scope $scope = null): ?EventId + public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId { - $event = $this->prepareEvent($payload, $scope); + $event = $this->prepareEvent($event, $hint, $scope); if (null === $event) { return null; @@ -205,19 +205,33 @@ public function flush(?int $timeout = null): PromiseInterface /** * Assembles an event and prepares it to be sent of to Sentry. * - * @param array|Event $payload The payload that will be converted to an Event - * @param Scope|null $scope Optional scope which enriches the Event + * @param Event $event The payload that will be converted to an Event + * @param EventHint|null $hint May contain additional information about the event + * @param Scope|null $scope Optional scope which enriches the Event * * @return Event|null The prepared event object or null if it must be discarded */ - private function prepareEvent($payload, ?Scope $scope = null): ?Event + private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?Event { - if ($this->options->shouldAttachStacktrace() && !($payload instanceof Event) && !isset($payload['exception']) && !isset($payload['stacktrace'])) { - $event = $this->buildEventWithStacktrace($payload); - } else { - $event = $this->buildEvent($payload); + if (null !== $hint) { + if (null !== $hint->exception && empty($event->getExceptions())) { + $this->addThrowableToEvent($event, $hint->exception); + } + + if (null !== $hint->stacktrace && null === $event->getStacktrace()) { + $event->setStacktrace($hint->stacktrace); + } } + $this->addMissingStacktraceToEvent($event); + + $event->setSdkIdentifier($this->sdkIdentifier); + $event->setSdkVersion($this->sdkVersion); + $event->setServerName($this->options->getServerName()); + $event->setRelease($this->options->getRelease()); + $event->setTags($this->options->getTags()); + $event->setEnvironment($this->options->getEnvironment()); + $sampleRate = $this->options->getSampleRate(); if (EventType::transaction() !== $event->getType() && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { @@ -228,7 +242,7 @@ private function prepareEvent($payload, ?Scope $scope = null): ?Event if (null !== $scope) { $previousEvent = $event; - $event = $scope->applyToEvent($event, $payload); + $event = $scope->applyToEvent($event, $hint); if (null === $event) { $this->logger->info('The event will be discarded because one of the event processors returned "null".', ['event' => $previousEvent]); @@ -248,81 +262,33 @@ private function prepareEvent($payload, ?Scope $scope = null): ?Event } /** - * Create an {@see Event} with a stacktrace attached to it. + * Optionally adds a missing stacktrace to the Event if the client is configured to do so. * - * @param array $payload The data to be attached to the Event + * @param Event $event The Event to add the missing stacktrace to */ - private function buildEventWithStacktrace($payload): Event + private function addMissingStacktraceToEvent(Event $event): void { - if (!isset($payload['stacktrace']) || !$payload['stacktrace'] instanceof Stacktrace) { - $payload['stacktrace'] = $this->stacktraceBuilder->buildFromBacktrace( - debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), - __FILE__, - __LINE__ - 3 - ); + if (!$this->options->shouldAttachStacktrace()) { + return; } - return $this->buildEvent($payload); - } - - /** - * Create an {@see Event} from a data payload. - * - * @param array|Event $payload The data to be attached to the Event - */ - private function buildEvent($payload): Event - { - try { - if ($payload instanceof Event) { - $event = $payload; - } else { - $event = Event::createEvent(); - - if (isset($payload['logger'])) { - $event->setLogger($payload['logger']); - } - - $message = isset($payload['message']) ? mb_substr($payload['message'], 0, $this->options->getMaxValueLength()) : null; - $messageParams = $payload['message_params'] ?? []; - $messageFormatted = isset($payload['message_formatted']) ? mb_substr($payload['message_formatted'], 0, $this->options->getMaxValueLength()) : null; - - if (null !== $message) { - $event->setMessage($message, $messageParams, $messageFormatted); - } - - if (isset($payload['exception']) && $payload['exception'] instanceof \Throwable) { - $this->addThrowableToEvent($event, $payload['exception']); - } - - if (isset($payload['level']) && $payload['level'] instanceof Severity) { - $event->setLevel($payload['level']); - } - - if (isset($payload['stacktrace']) && $payload['stacktrace'] instanceof Stacktrace) { - $event->setStacktrace($payload['stacktrace']); - } - } - } catch (\Throwable $exception) { - throw new EventCreationException($exception); + // We should not add a stacktrace when the event already has one or contains exceptions + if (null !== $event->getStacktrace() || !empty($event->getExceptions())) { + return; } - $event->setSdkIdentifier($this->sdkIdentifier); - $event->setSdkVersion($this->sdkVersion); - $event->setServerName($this->options->getServerName()); - $event->setRelease($this->options->getRelease()); - $event->setTags($this->options->getTags()); - $event->setEnvironment($this->options->getEnvironment()); - - return $event; + $event->setStacktrace($this->stacktraceBuilder->buildFromBacktrace( + debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), + __FILE__, + __LINE__ - 3 + )); } /** * Stores the given exception in the passed event. * - * @param Event $event The event that will be enriched with the - * exception - * @param \Throwable $exception The exception that will be processed and - * added to the event + * @param Event $event The event that will be enriched with the exception + * @param \Throwable $exception The exception that will be processed and added to the event */ private function addThrowableToEvent(Event $event, \Throwable $exception): void { diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 82d2aedc5..80fe63956 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -47,10 +47,11 @@ public function captureLastError(?Scope $scope = null): ?EventId; /** * Captures a new event using the provided data. * - * @param array|Event $payload The data of the event being captured - * @param Scope|null $scope An optional scope keeping the state + * @param Event $event The event being captured + * @param EventHint|null $hint May contain additional information about the event + * @param Scope|null $scope An optional scope keeping the state */ - public function captureEvent($payload, ?Scope $scope = null): ?EventId; + public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId; /** * Returns the integration instance if it is installed on the client. diff --git a/src/EventHint.php b/src/EventHint.php new file mode 100644 index 000000000..18bc61a8a --- /dev/null +++ b/src/EventHint.php @@ -0,0 +1,56 @@ + + */ + public $extra = []; + + /** + * Create a EventHint instance from an array of values. + * + * @psalm-param array{ + * exception?: \Throwable, + * stacktrace?: Event, + * extra?: array + * } $hintData + */ + public static function fromArray(array $hintData): self + { + $hint = new self(); + + foreach ($hintData as $hintKey => $hintValue) { + if (!property_exists($hint, $hintKey)) { + throw new \InvalidArgumentException(sprintf('There is no EventHint attribute called "%s".', $hintKey)); + } + + $hint->{$hintKey} = $hintValue; + } + + return $hint; + } +} diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index 4471ba419..7f24be550 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -5,6 +5,7 @@ namespace Sentry\Integration; use Sentry\Event; +use Sentry\EventHint; use Sentry\SentrySdk; use Sentry\State\Scope; @@ -22,7 +23,7 @@ final class TransactionIntegration implements IntegrationInterface */ public function setupOnce(): void { - Scope::addGlobalEventProcessor(static function (Event $event, $payload): Event { + Scope::addGlobalEventProcessor(static function (Event $event, EventHint $hint): Event { $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); // The client bound to the current hub, if any, could not have this @@ -35,10 +36,8 @@ public function setupOnce(): void return $event; } - if ($payload instanceof Event && $payload->getTransaction()) { - $event->setTransaction($payload->getTransaction()); - } elseif (isset($payload['transaction'])) { - $event->setTransaction($payload['transaction']); + if (isset($hint->extra['transaction'])) { + $event->setTransaction($hint->extra['transaction']); } elseif (isset($_SERVER['PATH_INFO'])) { $event->setTransaction($_SERVER['PATH_INFO']); } diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 93dddd490..7e7382fff 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -6,6 +6,8 @@ use Monolog\Handler\AbstractProcessingHandler; use Monolog\Logger; +use Sentry\Event; +use Sentry\EventHint; use Sentry\Severity; use Sentry\State\HubInterface; use Sentry\State\Scope; @@ -44,21 +46,22 @@ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bub */ protected function write(array $record): void { - $payload = [ - 'level' => self::getSeverityFromLevel($record['level']), - 'message' => $record['message'], - 'logger' => 'monolog.' . $record['channel'], - ]; + $event = Event::createEvent(); + $event->setLevel(self::getSeverityFromLevel($record['level'])); + $event->setMessage($record['message']); + $event->setLogger(sprintf('monolog.%s', $record['channel'])); + + $hint = new EventHint(); if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { - $payload['exception'] = $record['context']['exception']; + $hint->exception = $record['context']['exception']; } - $this->hub->withScope(function (Scope $scope) use ($record, $payload): void { + $this->hub->withScope(function (Scope $scope) use ($record, $event, $hint): void { $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); - $this->hub->captureEvent($payload); + $this->hub->captureEvent($event, $hint); }); } diff --git a/src/State/Hub.php b/src/State/Hub.php index be9e9e5c1..2e5323e22 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -6,6 +6,8 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; +use Sentry\Event; +use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; @@ -142,12 +144,12 @@ public function captureException(\Throwable $exception): ?EventId /** * {@inheritdoc} */ - public function captureEvent($payload): ?EventId + public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId { $client = $this->getClient(); if (null !== $client) { - return $this->lastEventId = $client->captureEvent($payload, $this->getScope()); + return $this->lastEventId = $client->captureEvent($event, $hint, $this->getScope()); } return null; diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 76123e829..fe21539a6 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -6,6 +6,8 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; +use Sentry\Event; +use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\SentrySdk; @@ -120,9 +122,9 @@ public function captureException(\Throwable $exception): ?EventId /** * {@inheritdoc} */ - public function captureEvent($payload): ?EventId + public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId { - return SentrySdk::getCurrentHub()->captureEvent($payload); + return SentrySdk::getCurrentHub()->captureEvent($event, $hint); } /** diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 01185b0e6..9e585728a 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -7,6 +7,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; @@ -88,9 +89,10 @@ public function captureException(\Throwable $exception): ?EventId; /** * Captures a new event using the provided data. * - * @param Event|array $payload The data of the event being captured + * @param Event $event The event being captured + * @param EventHint|null $hint May contain additional information about the event */ - public function captureEvent($payload): ?EventId; + public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId; /** * Captures an event that logs the last occurred error. diff --git a/src/State/Scope.php b/src/State/Scope.php index a7a77d252..c70c77722 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -6,6 +6,7 @@ use Sentry\Breadcrumb; use Sentry\Event; +use Sentry\EventHint; use Sentry\Severity; use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; @@ -57,7 +58,7 @@ final class Scope /** * @var callable[] List of event processors * - * @psalm-var array + * @psalm-var array */ private $eventProcessors = []; @@ -69,7 +70,7 @@ final class Scope /** * @var callable[] List of event processors * - * @psalm-var array + * @psalm-var array */ private static $globalEventProcessors = []; @@ -303,10 +304,9 @@ public function clear(): self * Applies the current context and fingerprint to the event. If the event has * already some breadcrumbs on it, the ones from this scope won't get merged. * - * @param Event $event The event object that will be enriched with scope data - * @param array|Event $payload The raw payload of the event that will be propagated to the event processors + * @param Event $event The event object that will be enriched with scope data */ - public function applyToEvent(Event $event, $payload): ?Event + public function applyToEvent(Event $event, ?EventHint $hint = null): ?Event { $event->setFingerprint(array_merge($event->getFingerprint(), $this->fingerprint)); @@ -347,8 +347,13 @@ public function applyToEvent(Event $event, $payload): ?Event $event->setContext($name, $data); } + // We create a empty `EventHint` instance to allow processors to always receive a `EventHint` instance even if there wasn't one + if (null === $hint) { + $hint = new EventHint(); + } + foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) { - $event = $processor($event, $payload); + $event = $processor($event, $hint); if (null === $event) { return null; diff --git a/src/functions.php b/src/functions.php index 089cbdb40..7d385c2c6 100644 --- a/src/functions.php +++ b/src/functions.php @@ -43,11 +43,12 @@ function captureException(\Throwable $exception): ?EventId /** * Captures a new event using the provided data. * - * @param array $payload The data of the event being captured + * @param Event $event The event being captured + * @param EventHint|null $hint May contain additional information about the event */ -function captureEvent(array $payload): ?EventId +function captureEvent(Event $event, ?EventHint $hint = null): ?EventId { - return SentrySdk::getCurrentHub()->captureEvent($payload); + return SentrySdk::getCurrentHub()->captureEvent($event, $hint); } /** diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 5efac8696..137b01aee 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -13,6 +13,7 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\EventHint; use Sentry\ExceptionMechanism; use Sentry\Frame; use Sentry\Integration\IntegrationInterface; @@ -27,6 +28,7 @@ use Sentry\State\Scope; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; +use Sentry\UserDataBag; final class ClientTest extends TestCase { @@ -73,7 +75,7 @@ public function setupOnce(): void $logger ); - $client->captureEvent([], new Scope()); + $client->captureEvent(Event::createEvent(), null, new Scope()); $this->assertTrue($integrationCalled); } @@ -144,22 +146,21 @@ public function testCaptureEvent(): void ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $inputData = [ - 'transaction' => 'foo bar', - 'level' => Severity::debug(), - 'logger' => 'foo', - 'tags_context' => ['foo', 'bar'], - 'extra_context' => ['foo' => 'bar'], - 'user_context' => ['bar' => 'foo'], - ]; + $event = Event::createEvent(); + $event->setTransaction('foo bar'); + $event->setLevel(Severity::debug()); + $event->setLogger('foo'); + $event->setTags(['foo', 'bar']); + $event->setExtra(['foo' => 'bar']); + $event->setUser(UserDataBag::createFromUserIdentifier('foo')); - $this->assertNotNull($client->captureEvent($inputData)); + $this->assertNotNull($client->captureEvent($event)); } /** * @dataProvider captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider */ - public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOption(bool $attachStacktraceOption, array $payload, bool $shouldAttachStacktrace): void + public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOption(bool $attachStacktraceOption, ?EventHint $hint, bool $shouldAttachStacktrace): void { /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -184,36 +185,36 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertNotNull($client->captureEvent($payload)); + $this->assertNotNull($client->captureEvent(Event::createEvent(), $hint)); } public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider(): \Generator { yield 'Stacktrace attached when attach_stacktrace = true and both payload.exception and payload.stacktrace are unset' => [ true, - [], + null, true, ]; yield 'No stacktrace attached when attach_stacktrace = false' => [ false, - [], + null, false, ]; yield 'No stacktrace attached when attach_stacktrace = true and payload.exception is set' => [ true, - [ + EventHint::fromArray([ 'exception' => new \Exception(), - ], + ]), false, ]; yield 'No stacktrace attached when attach_stacktrace = false and payload.exception is set' => [ true, - [ + EventHint::fromArray([ 'exception' => new \Exception(), - ], + ]), false, ]; } @@ -239,7 +240,9 @@ public function testCaptureEventPrefersExplicitStacktrace(): void ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->assertNotNull($client->captureEvent(['stacktrace' => $stacktrace])); + $this->assertNotNull($client->captureEvent(Event::createEvent(), EventHint::fromArray([ + 'stacktrace' => $stacktrace, + ]))); } public function testCaptureLastError(): void @@ -309,7 +312,7 @@ public function testSendChecksBeforeSendOption(): void ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $client->captureEvent([]); + $client->captureEvent(Event::createEvent()); $this->assertTrue($beforeSendCalled); } @@ -515,68 +518,7 @@ public function testBuildEventWithDefaultValues(): void $this->createMock(RepresentationSerializerInterface::class) ); - $client->captureEvent([]); - } - - /** - * @dataProvider buildWithPayloadDataProvider - */ - public function testBuildWithPayload(array $payload, ?string $expectedLogger, ?string $expectedMessage, array $expectedMessageParams, ?string $expectedFormattedMessage): void - { - /** @var TransportInterface&MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event) use ($expectedLogger, $expectedMessage, $expectedFormattedMessage, $expectedMessageParams): bool { - $this->assertSame($expectedLogger, $event->getLogger()); - $this->assertSame($expectedMessage, $event->getMessage()); - $this->assertSame($expectedMessageParams, $event->getMessageParams()); - $this->assertSame($expectedFormattedMessage, $event->getMessageFormatted()); - - return true; - })); - - $client = new Client( - new Options(), - $transport, - 'sentry.sdk.identifier', - '1.2.3', - $this->createMock(SerializerInterface::class), - $this->createMock(RepresentationSerializerInterface::class) - ); - - $client->captureEvent($payload); - } - - public function buildWithPayloadDataProvider(): iterable - { - yield [ - ['logger' => 'app.php'], - 'app.php', - null, - [], - null, - ]; - - yield [ - ['message' => 'My raw message with interpreted strings like this'], - null, - 'My raw message with interpreted strings like this', - [], - null, - ]; - - yield [ - [ - 'message' => 'My raw message with interpreted strings like that', - 'message_params' => ['this'], - 'message_formatted' => 'My raw message with interpreted strings like %s', - ], - null, - 'My raw message with interpreted strings like that', - ['this'], - 'My raw message with interpreted strings like %s', - ]; + $client->captureEvent(Event::createEvent()); } public function testBuildEventInCLIDoesntSetTransaction(): void @@ -600,7 +542,7 @@ public function testBuildEventInCLIDoesntSetTransaction(): void $this->createMock(RepresentationSerializerInterface::class) ); - $client->captureEvent([]); + $client->captureEvent(Event::createEvent()); } public function testBuildEventWithException(): void @@ -639,7 +581,9 @@ public function testBuildEventWithException(): void $this->createMock(RepresentationSerializerInterface::class) ); - $client->captureEvent(['exception' => $exception]); + $client->captureEvent(Event::createEvent(), EventHint::fromArray([ + 'exception' => $exception, + ])); } public function testBuildWithErrorException(): void @@ -665,7 +609,9 @@ public function testBuildWithErrorException(): void $this->createMock(RepresentationSerializerInterface::class) ); - $client->captureEvent(['exception' => $exception]); + $client->captureEvent(Event::createEvent(), EventHint::fromArray([ + 'exception' => $exception, + ])); } public function testBuildWithStacktrace(): void @@ -702,6 +648,6 @@ public function testBuildWithStacktrace(): void $this->createMock(RepresentationSerializerInterface::class) ); - $client->captureEvent([]); + $client->captureEvent(Event::createEvent()); } } diff --git a/tests/EventHintTest.php b/tests/EventHintTest.php new file mode 100644 index 000000000..11c0a737c --- /dev/null +++ b/tests/EventHintTest.php @@ -0,0 +1,50 @@ + $exception, + 'stacktrace' => $stacktrace, + ]); + + $this->assertEquals($exception, $hint->exception); + $this->assertEquals($stacktrace, $hint->stacktrace); + } + + public function testThrowsExceptionOnInvalidKeyInArray(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('There is no EventHint attribute called "missing_property".'); + + EventHint::fromArray([ + 'missing_property' => 'some value', + ]); + } + + public function testThrowsExceptionOnInvalidKeyInArrayWhenValidKeyIsPresent(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('There is no EventHint attribute called "missing_property".'); + + EventHint::fromArray([ + 'exception' => new \Exception(), + 'missing_property' => 'some value', + ]); + } +} diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 3c26129e4..9fcdc434c 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -67,18 +67,18 @@ public function testCaptureException(): void public function testCaptureEvent(): void { - $eventId = EventId::generate(); + $event = Event::createEvent(); /** @var ClientInterface|MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') - ->with(['message' => 'foo']) - ->willReturn($eventId); + ->with($event) + ->willReturn($event->getId()); SentrySdk::getCurrentHub()->bindClient($client); - $this->assertSame($eventId, captureEvent(['message' => 'foo'])); + $this->assertSame($event->getId(), captureEvent($event)); } public function testCaptureLastError() @@ -112,7 +112,7 @@ public function testAddBreadcrumb(): void addBreadcrumb($breadcrumb); configureScope(function (Scope $scope) use ($breadcrumb): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); diff --git a/tests/Integration/EnvironmentIntegrationTest.php b/tests/Integration/EnvironmentIntegrationTest.php index 13d2a1c04..1aa356627 100644 --- a/tests/Integration/EnvironmentIntegrationTest.php +++ b/tests/Integration/EnvironmentIntegrationTest.php @@ -39,7 +39,7 @@ public function testInvoke(bool $isIntegrationEnabled, ?RuntimeContext $initialR $event->setRuntimeContext($initialRuntimeContext); $event->setOsContext($initialOsContext); - $event = $scope->applyToEvent($event, []); + $event = $scope->applyToEvent($event); $this->assertNotNull($event); diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index 5a65b3113..b54cda285 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -67,7 +67,7 @@ public function testInvoke(string $fixtureFilePath, int $lineNumber, int $contex $event->setStacktrace($stacktrace); withScope(static function (Scope $scope) use (&$event): void { - $event = $scope->applyToEvent($event, []); + $event = $scope->applyToEvent($event); }); $this->assertNotNull($event); @@ -161,7 +161,7 @@ public function testInvokeLogsWarningMessageIfSourceCodeExcerptCannotBeRetrieved $event->setStacktrace($stacktrace); withScope(static function (Scope $scope) use (&$event): void { - $event = $scope->applyToEvent($event, []); + $event = $scope->applyToEvent($event); }); $this->assertNotNull($event); diff --git a/tests/Integration/IgnoreErrorsIntegrationTest.php b/tests/Integration/IgnoreErrorsIntegrationTest.php index 2bc9130be..2275b47f6 100644 --- a/tests/Integration/IgnoreErrorsIntegrationTest.php +++ b/tests/Integration/IgnoreErrorsIntegrationTest.php @@ -33,7 +33,7 @@ public function testInvoke(Event $event, bool $isIntegrationEnabled, array $inte SentrySdk::getCurrentHub()->bindClient($client); withScope(function (Scope $scope) use ($event, $expectedEventToBeDropped): void { - $event = $scope->applyToEvent($event, []); + $event = $scope->applyToEvent($event); if ($expectedEventToBeDropped) { $this->assertNull($event); diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index b9457be19..b428ad46e 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -32,7 +32,7 @@ public function testInvoke(bool $isIntegrationEnabled, bool $expectedEmptyModule SentrySdk::getCurrentHub()->bindClient($client); withScope(function (Scope $scope) use ($expectedEmptyModules): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index ed5371a8b..510685367 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -47,7 +47,7 @@ public function testInvoke(array $options, ServerRequestInterface $request, arra SentrySdk::getCurrentHub()->bindClient($client); withScope(function (Scope $scope) use ($event, $expectedRequestContextData, $initialUser, $expectedUser): void { - $event = $scope->applyToEvent($event, []); + $event = $scope->applyToEvent($event); $this->assertNotNull($event); $this->assertSame($expectedRequestContextData, $event->getRequest()); diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index 85151c677..f991c11cc 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventHint; use Sentry\Integration\TransactionIntegration; use Sentry\SentrySdk; use Sentry\State\Scope; @@ -20,7 +21,7 @@ final class TransactionIntegrationTest extends TestCase * * @backupGlobals */ - public function testSetupOnce(Event $event, bool $isIntegrationEnabled, array $payload, array $serverGlobals, ?string $expectedTransaction): void + public function testSetupOnce(Event $event, bool $isIntegrationEnabled, ?EventHint $hint, array $serverGlobals, ?string $expectedTransaction): void { $_SERVER = array_merge($_SERVER, $serverGlobals); @@ -35,8 +36,8 @@ public function testSetupOnce(Event $event, bool $isIntegrationEnabled, array $p SentrySdk::getCurrentHub()->bindClient($client); - withScope(function (Scope $scope) use ($event, $payload, $expectedTransaction): void { - $event = $scope->applyToEvent($event, $payload); + withScope(function (Scope $scope) use ($event, $hint, $expectedTransaction): void { + $event = $scope->applyToEvent($event, $hint); $this->assertNotNull($event); $this->assertSame($event->getTransaction(), $expectedTransaction); @@ -48,7 +49,15 @@ public function setupOnceDataProvider(): \Generator yield [ Event::createEvent(), true, + null, [], + null, + ]; + + yield [ + Event::createEvent(), + false, + null, [], null, ]; @@ -56,16 +65,20 @@ public function setupOnceDataProvider(): \Generator yield [ Event::createEvent(), true, - ['transaction' => '/foo/bar'], - [], + null, + ['PATH_INFO' => '/foo/bar'], '/foo/bar', ]; yield [ Event::createEvent(), true, + EventHint::fromArray([ + 'extra' => [ + 'transaction' => '/foo/bar', + ], + ]), [], - ['PATH_INFO' => '/foo/bar'], '/foo/bar', ]; @@ -75,7 +88,7 @@ public function setupOnceDataProvider(): \Generator yield [ $event, true, - [], + null, [], '/foo/bar', ]; @@ -85,17 +98,33 @@ public function setupOnceDataProvider(): \Generator yield [ $event, - true, - ['/foo/bar/baz'], + false, + null, [], '/foo/bar', ]; + yield [ + Event::createEvent(), + true, + EventHint::fromArray([ + 'extra' => [ + 'transaction' => '/some/other', + ], + ]), + ['PATH_INFO' => '/foo/bar'], + '/some/other', + ]; + yield [ Event::createEvent(), false, - ['transaction' => '/foo/bar'], - [], + EventHint::fromArray([ + 'extra' => [ + 'transaction' => '/some/other', + ], + ]), + ['PATH_INFO' => '/foo/bar'], null, ]; } diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index 2d177dd3a..2a5c61adc 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventHint; use Sentry\Monolog\Handler; use Sentry\Severity; use Sentry\State\Hub; @@ -19,14 +20,20 @@ final class HandlerTest extends TestCase /** * @dataProvider handleDataProvider */ - public function testHandle(array $record, array $expectedPayload, array $expectedExtra): void + public function testHandle(array $record, Event $expectedEvent, EventHint $expectedHint, array $expectedExtra): void { /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') - ->with($expectedPayload, $this->callback(function (Scope $scopeArg) use ($expectedExtra): bool { - $event = $scopeArg->applyToEvent(Event::createEvent(), []); + ->with($this->callback(function (Event $event) use ($expectedEvent): bool { + $this->assertEquals($expectedEvent->getLevel(), $event->getLevel()); + $this->assertEquals($expectedEvent->getMessage(), $event->getMessage()); + $this->assertEquals($expectedEvent->getLogger(), $event->getLogger()); + + return true; + }), $expectedHint, $this->callback(function (Scope $scopeArg) use ($expectedExtra): bool { + $event = $scopeArg->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame($expectedExtra, $event->getExtra()); @@ -40,6 +47,11 @@ public function testHandle(array $record, array $expectedPayload, array $expecte public function handleDataProvider(): iterable { + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::debug()); + yield [ [ 'message' => 'foo bar', @@ -49,17 +61,19 @@ public function handleDataProvider(): iterable 'context' => [], 'extra' => [], ], - [ - 'level' => Severity::debug(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], + $event, + new EventHint(), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::DEBUG), ], ]; + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::info()); + yield [ [ 'message' => 'foo bar', @@ -69,17 +83,19 @@ public function handleDataProvider(): iterable 'context' => [], 'extra' => [], ], - [ - 'level' => Severity::info(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], + $event, + new EventHint(), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::INFO), ], ]; + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::info()); + yield [ [ 'message' => 'foo bar', @@ -89,17 +105,19 @@ public function handleDataProvider(): iterable 'context' => [], 'extra' => [], ], - [ - 'level' => Severity::info(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], + $event, + new EventHint(), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::NOTICE), ], ]; + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::warning()); + yield [ [ 'message' => 'foo bar', @@ -109,17 +127,19 @@ public function handleDataProvider(): iterable 'context' => [], 'extra' => [], ], - [ - 'level' => Severity::warning(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], + $event, + new EventHint(), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::WARNING), ], ]; + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::error()); + yield [ [ 'message' => 'foo bar', @@ -129,17 +149,19 @@ public function handleDataProvider(): iterable 'context' => [], 'extra' => [], ], - [ - 'level' => Severity::error(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], + $event, + new EventHint(), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::ERROR), ], ]; + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::fatal()); + yield [ [ 'message' => 'foo bar', @@ -149,17 +171,19 @@ public function handleDataProvider(): iterable 'context' => [], 'extra' => [], ], - [ - 'level' => Severity::fatal(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], + $event, + new EventHint(), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::CRITICAL), ], ]; + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::fatal()); + yield [ [ 'message' => 'foo bar', @@ -169,17 +193,19 @@ public function handleDataProvider(): iterable 'context' => [], 'extra' => [], ], - [ - 'level' => Severity::fatal(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], + $event, + new EventHint(), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::ALERT), ], ]; + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::fatal()); + yield [ [ 'message' => 'foo bar', @@ -189,34 +215,36 @@ public function handleDataProvider(): iterable 'context' => [], 'extra' => [], ], - [ - 'level' => Severity::fatal(), - 'message' => 'foo bar', - 'logger' => 'monolog.channel.foo', - ], + $event, + new EventHint(), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::EMERGENCY), ], ]; + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::warning()); + + $exampleException = new \Exception('exception message'); + yield [ [ 'message' => 'foo bar', 'level' => Logger::WARNING, 'level_name' => Logger::getLevelName(Logger::WARNING), 'context' => [ - 'exception' => new \Exception('exception message'), + 'exception' => $exampleException, ], 'channel' => 'channel.foo', 'extra' => [], ], - [ - 'level' => Severity::warning(), - 'message' => 'foo bar', - 'exception' => new \Exception('exception message'), - 'logger' => 'monolog.channel.foo', - ], + $event, + EventHint::fromArray([ + 'exception' => $exampleException, + ]), [ 'monolog.channel' => 'channel.foo', 'monolog.level' => Logger::getLevelName(Logger::WARNING), diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index c222a53c7..48826573c 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -258,22 +258,24 @@ public function testCaptureLastError(): void public function testCaptureEvent(): void { - $eventId = EventId::generate(); + $event = Event::createEvent($eventId = EventId::generate()); + + $event->setMessage('test'); /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') - ->with(['message' => 'test']) + ->with($event) ->willReturn($eventId); $hub = new Hub(); - $this->assertNull($hub->captureEvent([])); + $this->assertNull($hub->captureEvent($event)); $hub->bindClient($client); - $this->assertSame($eventId, $hub->captureEvent(['message' => 'test'])); + $this->assertSame($eventId, $hub->captureEvent($event)); } public function testAddBreadcrumb(): void @@ -290,7 +292,7 @@ public function testAddBreadcrumb(): void $hub->addBreadcrumb($breadcrumb); $hub->configureScope(function (Scope $scope): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -299,7 +301,7 @@ public function testAddBreadcrumb(): void $hub->bindClient($client); $hub->addBreadcrumb($breadcrumb); $hub->configureScope(function (Scope $scope) use (&$callbackInvoked, $breadcrumb): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); @@ -322,7 +324,7 @@ public function testAddBreadcrumbDoesNothingIfMaxBreadcrumbsLimitIsZero(): void $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); $hub->configureScope(function (Scope $scope): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -346,7 +348,7 @@ public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void $hub->addBreadcrumb($breadcrumb2); $hub->configureScope(function (Scope $scope) use ($breadcrumb1, $breadcrumb2): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb1, $breadcrumb2], $event->getBreadcrumbs()); @@ -355,7 +357,7 @@ public function testAddBreadcrumbRespectsMaxBreadcrumbsLimit(): void $hub->addBreadcrumb($breadcrumb3); $hub->configureScope(function (Scope $scope) use ($breadcrumb2, $breadcrumb3): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb2, $breadcrumb3], $event->getBreadcrumbs()); @@ -378,7 +380,7 @@ public function testAddBreadcrumbDoesNothingWhenBeforeBreadcrumbCallbackReturnsN $hub->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); $hub->configureScope(function (Scope $scope): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -405,7 +407,7 @@ public function testAddBreadcrumbStoresBreadcrumbReturnedByBeforeBreadcrumbCallb $hub->addBreadcrumb($breadcrumb1); $hub->configureScope(function (Scope $scope) use (&$callbackInvoked, $breadcrumb2): void { - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb2], $event->getBreadcrumbs()); diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 95ced8739..f723e5252 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; use Sentry\Event; +use Sentry\EventHint; use Sentry\Severity; use Sentry\State\Scope; use Sentry\UserDataBag; @@ -16,7 +17,7 @@ final class ScopeTest extends TestCase public function testSetTag(): void { $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getTags()); @@ -24,7 +25,7 @@ public function testSetTag(): void $scope->setTag('foo', 'bar'); $scope->setTag('bar', 'baz'); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); @@ -35,14 +36,14 @@ public function testSetTags(): void $scope = new Scope(); $scope->setTags(['foo' => 'bar']); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar'], $event->getTags()); $scope->setTags(['bar' => 'baz']); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); @@ -53,14 +54,14 @@ public function testSetAndRemoveContext(): void $scope = new Scope(); $scope->setContext('foo', ['foo' => 'bar']); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => ['foo' => 'bar']], $event->getContexts()); $scope->removeContext('foo'); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([], $event->getContexts()); @@ -69,7 +70,7 @@ public function testSetAndRemoveContext(): void public function testSetExtra(): void { $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getExtra()); @@ -77,7 +78,7 @@ public function testSetExtra(): void $scope->setExtra('foo', 'bar'); $scope->setExtra('bar', 'baz'); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtra()); @@ -88,14 +89,14 @@ public function testSetExtras(): void $scope = new Scope(); $scope->setExtras(['foo' => 'bar']); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar'], $event->getExtra()); $scope->setExtras(['bar' => 'baz']); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtra()); @@ -104,7 +105,7 @@ public function testSetExtras(): void public function testSetUser(): void { $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNull($event->getUser()); @@ -114,7 +115,7 @@ public function testSetUser(): void $scope->setUser($user); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame($user, $event->getUser()); @@ -125,7 +126,7 @@ public function testSetUser(): void $scope->setUser(['ip_address' => '127.0.0.1', 'subscription_expires_at' => '2020-08-26']); - $event = $scope->applyToEvent($event, []); + $event = $scope->applyToEvent($event); $this->assertNotNull($event); $this->assertEquals($user, $event->getUser()); @@ -145,14 +146,14 @@ public function testRemoveUser(): void $scope = new Scope(); $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNotNull($event->getUser()); $scope->removeUser(); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNull($event->getUser()); @@ -161,14 +162,14 @@ public function testRemoveUser(): void public function testSetFingerprint(): void { $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getFingerprint()); $scope->setFingerprint(['foo', 'bar']); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo', 'bar'], $event->getFingerprint()); @@ -177,14 +178,14 @@ public function testSetFingerprint(): void public function testSetLevel(): void { $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNull($event->getLevel()); $scope->setLevel(Severity::debug()); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEquals(Severity::debug(), $event->getLevel()); @@ -197,7 +198,7 @@ public function testAddBreadcrumb(): void $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -205,14 +206,14 @@ public function testAddBreadcrumb(): void $scope->addBreadcrumb($breadcrumb1); $scope->addBreadcrumb($breadcrumb2); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb1, $breadcrumb2], $event->getBreadcrumbs()); $scope->addBreadcrumb($breadcrumb3, 2); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb2, $breadcrumb3], $event->getBreadcrumbs()); @@ -225,14 +226,14 @@ public function testClearBreadcrumbs(): void $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNotEmpty($event->getBreadcrumbs()); $scope->clearBreadcrumbs(); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -256,7 +257,7 @@ public function testAddEventProcessor(): void return $eventArg; }); - $this->assertSame($event, $scope->applyToEvent($event, [])); + $this->assertSame($event, $scope->applyToEvent($event)); $this->assertTrue($callback1Called); $scope->addEventProcessor(function () use ($callback1Called, &$callback2Called, $callback3Called) { @@ -274,11 +275,32 @@ public function testAddEventProcessor(): void return null; }); - $this->assertNull($scope->applyToEvent($event, [])); + $this->assertNull($scope->applyToEvent($event)); $this->assertTrue($callback2Called); $this->assertFalse($callback3Called); } + public function testEventProcessorReceivesTheEventAndEventHint(): void + { + $event = Event::createEvent(); + $scope = new Scope(); + $hint = new EventHint(); + + $processorCalled = false; + $processorReceivedHint = null; + + $scope->addEventProcessor(function (Event $eventArg, EventHint $hint) use (&$processorCalled, &$processorReceivedHint): ?Event { + $processorCalled = true; + $processorReceivedHint = $hint; + + return $eventArg; + }); + + $this->assertSame($event, $scope->applyToEvent($event, $hint)); + $this->assertSame($hint, $processorReceivedHint); + $this->assertTrue($processorCalled); + } + public function testClear(): void { $scope = new Scope(); @@ -292,7 +314,7 @@ public function testClear(): void $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); $scope->clear(); - $event = $scope->applyToEvent(Event::createEvent(), []); + $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNull($event->getLevel()); @@ -321,7 +343,7 @@ public function testApplyToEvent(): void $scope->setContext('foocontext', ['foo' => 'bar']); $scope->setContext('barcontext', ['bar' => 'foo']); - $this->assertSame($event, $scope->applyToEvent($event, [])); + $this->assertSame($event, $scope->applyToEvent($event)); $this->assertTrue($event->getLevel()->isEqualTo(Severity::warning())); $this->assertSame(['foo'], $event->getFingerprint()); $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); From 8a9c7c85e2ca630fefe6df2b81ffd777c9681d06 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 25 Sep 2020 13:58:03 +0200 Subject: [PATCH 0611/1161] Bump version to 3.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 772fdb2b3..9ee2fbf78 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "3.1.x-dev" } } } From 43aa3a8b4775b0aa470d9a38cbe3a22758fbcffe Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 27 Sep 2020 23:42:47 +0200 Subject: [PATCH 0612/1161] Cleanup deprecated code (#1098) --- phpstan.neon | 3 - src/Serializer/AbstractSerializer.php | 21 +------ src/Transport/NullTransport.php | 4 +- src/Util/JSON.php | 87 +-------------------------- tests/Util/JSONTest.php | 58 +----------------- 5 files changed, 9 insertions(+), 164 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 8e883fb31..5d724909a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,9 +6,6 @@ parameters: - src ignoreErrors: - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - - - message: '/^Argument of an invalid type array\|object supplied for foreach, only iterables are supported\.$/' - path: src/Util/JSON.php - message: '/^Access to constant (?:PROXY|TIMEOUT|CONNECT_TIMEOUT) on an unknown class GuzzleHttp\\RequestOptions\.$/' path: src/HttpClient/HttpClientFactory.php diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 8562eec83..d744bdb64 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -100,7 +100,7 @@ protected function serializeRecursively($value, int $_depth = 0) } if (\is_callable($value)) { - return $this->serializeCallableWithoutTypeHint($value); + return $this->serializeCallable($value); } if (\is_array($value)) { @@ -245,7 +245,7 @@ protected function serializeValue($value) } if (\is_callable($value)) { - return $this->serializeCallableWithoutTypeHint($value); + return $this->serializeCallable($value); } if (\is_array($value)) { @@ -256,12 +256,9 @@ protected function serializeValue($value) } /** - * This method is provided as a non-BC upgrade of serializeCallable, - * since using the callable type raises a deprecation in some cases. - * * @param callable|mixed $callable */ - protected function serializeCallableWithoutTypeHint($callable): string + protected function serializeCallable($callable): string { if (\is_string($callable) && !\function_exists($callable)) { return $callable; @@ -271,18 +268,6 @@ protected function serializeCallableWithoutTypeHint($callable): string throw new InvalidArgumentException(sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); } - return $this->serializeCallable($callable); - } - - /** - * Use serializeCallableWithoutTypeHint instead (no type in argument). - * - * @see https://github.com/getsentry/sentry-php/pull/821 - * - * @param callable $callable callable type to be removed in 3.0, see #821 - */ - protected function serializeCallable(callable $callable): string - { try { if (\is_array($callable)) { $reflection = new \ReflectionMethod($callable[0], $callable[1]); diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index 7aeef1728..0c067e104 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -14,10 +14,8 @@ * This transport fakes the sending of events by just ignoring them. * * @author Stefano Arlandini - * - * @final since 2.3 */ -class NullTransport implements TransportInterface +final class NullTransport implements TransportInterface { /** * {@inheritdoc} diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 1cd4685dd..5e0b7bb6a 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -28,22 +28,9 @@ final class JSON */ public static function encode($data, int $options = 0, int $maxDepth = 512) { - $options |= JSON_UNESCAPED_UNICODE; + $options |= JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE; - if (\PHP_VERSION_ID >= 70200) { - /** @psalm-suppress UndefinedConstant */ - $options |= JSON_INVALID_UTF8_SUBSTITUTE; - } - - $encodedData = json_encode($data, $options); - - // This should never happen on PHP >= 7.2 as the substitution of invalid - // UTF-8 characters is done internally. On lower versions instead, we - // try to sanitize the data ourselves before retrying encoding. If it - // fails again we throw an exception as usual. - if (JSON_ERROR_UTF8 === json_last_error()) { - $encodedData = json_encode(self::sanitizeData($data, $maxDepth - 1), $options); - } + $encodedData = json_encode($data, $options, $maxDepth); if (JSON_ERROR_NONE !== json_last_error()) { throw new JsonException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); @@ -71,74 +58,4 @@ public static function decode(string $data) return $decodedData; } - - /** - * Performs sanity checks on data that shall be encoded to JSON. - * - * @param mixed $data The data to sanitize - * @param int $maxDepth The maximum depth to walk through `$data` - * - * @return mixed - * - * @throws JsonException If the value of $maxDepth is less than 0 - */ - private static function sanitizeData($data, int $maxDepth) - { - if ($maxDepth < 0) { - throw new JsonException('Reached the maximum depth limit while sanitizing the data.'); - } - - if (\is_string($data)) { - return self::convertStringToUtf8($data); - } elseif (\is_array($data) || \is_object($data)) { - $output = []; - - foreach ($data as $key => $value) { - if (\is_string($key)) { - $key = self::convertStringToUtf8($key); - } - - if (\is_string($value)) { - $value = self::convertStringToUtf8($value); - } elseif (\is_array($value) || \is_object($value)) { - // This check is here because the `Event::toArray()` method - // is broken and doesn't return all child items as scalars - // or objects/arrays, so the sanitification would fail (e.g. - // on breadcrumb objects which do not expose public properties - // to iterate on) - if (\is_object($value) && method_exists($value, 'toArray')) { - $value = $value->toArray(); - } - - $value = self::sanitizeData($value, $maxDepth - 1); - } - - $output[$key] = $value; - } - - return \is_array($data) ? $output : (object) $output; - } else { - return $data; - } - } - - /** - * Converts a string to UTF-8 to avoid errors during its encoding to - * the JSON format. - * - * @param string $value The text to convert to UTF-8 - */ - private static function convertStringToUtf8(string $value): string - { - $previousSubstituteCharacter = mb_substitute_character(); - $encoding = mb_detect_encoding($value, mb_detect_order(), true); - - mb_substitute_character(0xfffd); - - $value = mb_convert_encoding($value, 'UTF-8', $encoding ?: 'UTF-8'); - - mb_substitute_character($previousSubstituteCharacter); - - return $value; - } } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index f8c60c2d8..3489182dd 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -62,16 +62,14 @@ public function encodeDataProvider(): \Generator } /** - * @requires PHP >= 7.2 - * - * @dataProvider encodeSubstitutesInvalidUtf8CharactersOnPhp72OrGreaterDataProvider + * @dataProvider encodeSubstitutesInvalidUtf8CharactersDataProvider */ - public function testEncodeSubstitutesInvalidUtf8CharactersOnPhp72OrGreater($value, string $expectedResult): void + public function testEncodeSubstitutesInvalidUtf8Characters($value, string $expectedResult): void { $this->assertSame($expectedResult, JSON::encode($value)); } - public function encodeSubstitutesInvalidUtf8CharactersOnPhp72OrGreaterDataProvider(): \Generator + public function encodeSubstitutesInvalidUtf8CharactersDataProvider(): \Generator { yield [ "\x61\xb0\x62", @@ -100,45 +98,6 @@ public function encodeSubstitutesInvalidUtf8CharactersOnPhp72OrGreaterDataProvid ]; } - /** - * @requires PHP < 7.2 - * - * @dataProvider encodeSubstitutesInvalidUtf8CharactersOnPhp71OrLowerDataProvider - */ - public function testEncodeSubstitutesInvalidUtf8CharactersOnPhp71OrLower($value, string $expectedResult): void - { - $this->assertSame($expectedResult, JSON::encode($value)); - } - - public function encodeSubstitutesInvalidUtf8CharactersOnPhp71OrLowerDataProvider(): \Generator - { - yield [ - "\x61\xb0\x62", - '"a�b"', - ]; - - yield [ - "\x61\xf0\x80\x80\x41", - '"a���A"', - ]; - - yield [ - [ - 123.45, - 'foo', - "\x61\xb0\x62", - [ - 'bar' => "\x61\xf0\x80\x80\x41", - "\x61\xf0\x80\x80\x41" => (object) [ - "\x61\xb0\x62", - "\x61\xf0\x80\x80\x41" => 'baz', - ], - ], - ], - '[123.45,"foo","a�b",{"bar":"a���A","a���A":{"0":"a�b","a���A":"baz"}}]', - ]; - } - /** * @expectedException \Sentry\Exception\JsonException * @expectedExceptionMessage Could not encode value into JSON format. Error was: "Type is not supported". @@ -159,17 +118,6 @@ public function testEncodeRespectsOptionsArgument(): void $this->assertSame('{}', JSON::encode([], JSON_FORCE_OBJECT)); } - /** - * @requires PHP < 7.2 - * - * @expectedException \Sentry\Exception\JsonException - * @expectedExceptionMessage Reached the maximum depth limit while sanitizing the data. - */ - public function testEncodeThrowsOnPhp71OrLowerWhenSanitizationReachesMaxDepthLimit(): void - { - JSON::encode([[["\x61\xb0\x62"]]], 0, 2); - } - /** * @dataProvider decodeDataProvider */ From b77ff3783060ce3213011ddae369e550ec985dc8 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 28 Sep 2020 09:11:32 +0200 Subject: [PATCH 0613/1161] meta: Prepare 3.0.0 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7d04265..607d5ef47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ ## Unreleased +## 3.0.0 (2020-09-28) + +**Tracing API** + +In this version we released API for Tracing. `\Sentry\startTransaction` is your entry point for manual instrumentation. +More information can be found in our [Performance](https://docs.sentry.io/platforms/php/performance/) docs. + +**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are +using an on-premise installation it requires Sentry version `>= v20.6.0` to work. If you are using +[sentry.io](https://sentry.io) nothing will change and no action is needed. + +- [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) +- [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) +- [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) +- [BC BREAK] Remove deprecated code to return the event ID as a `string` rather than an object instance from the transport, the client and the hub (#1036) +- [BC BREAK] Remove some deprecated methods from the `Options` class. (#1047) +- [BC BREAK] Remove the deprecated code from the `ModulesIntegration` integration (#1047) +- [BC BREAK] Remove the deprecated code from the `RequestIntegration` integration (#1047) +- [BC BREAK] Remove the deprecated code from the `Breadcrumb` class (#1047) +- [BC BREAK] Remove the deprecated methods from the `ClientBuilderInterface` interface and its implementations (#1047) +- [BC BREAK] The `Scope::setUser()` method now always merges the given data with the existing one instead of replacing it as a whole (#1047) +- [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) +- [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) +- [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) +- [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) +- [BC BREAK] Remove the `FlushableClientInterface` and the `ClosableTransportInterface` interfaces (#1079) +- [BC BREAK] Remove the `SpoolTransport` transport and all its related classes (#1080) +- Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) +- Refactor how the event data gets serialized to JSON (#1077) - Add `traces_sampler` option to set custom sample rate callback (#1083) - [BC BREAK] Add named constructors to the `Event` class (#1085) - Raise the minimum version of PHP to `7.2` and the minimum version of some dependencies (#1088) From 65030a1376b900aee06a4f477bbf320bc8dc9c52 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 29 Sep 2020 14:44:52 +0200 Subject: [PATCH 0614/1161] Trace the Guzzle request on the scope rather than the current transaction (#1099) --- CHANGELOG.md | 2 ++ src/Tracing/GuzzleTracingMiddleware.php | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 607d5ef47..d0f9afdc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fix: Use Span on Scope instead of Transaction for GuzzleMiddleware (#1099) + ## 3.0.0 (2020-09-28) **Tracing API** diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 95720574d..8c54ac32f 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -18,20 +18,20 @@ public static function trace(?HubInterface $hub = null): \Closure return function (callable $handler) use ($hub): \Closure { return function (RequestInterface $request, array $options) use ($hub, $handler) { $hub = $hub ?? SentrySdk::getCurrentHub(); - $transaction = $hub->getTransaction(); - $span = null; + $span = $hub->getSpan(); + $childSpan = null; - if (null !== $transaction) { + if (null !== $span) { $spanContext = new SpanContext(); $spanContext->setOp('http.guzzle'); $spanContext->setDescription($request->getMethod() . ' ' . $request->getUri()); - $span = $transaction->startChild($spanContext); + $childSpan = $span->startChild($spanContext); } - $handlerPromiseCallback = static function ($responseOrException) use ($span) { - if (null !== $span) { - $span->finish(); + $handlerPromiseCallback = static function ($responseOrException) use ($childSpan) { + if (null !== $childSpan) { + $childSpan->finish(); } if ($responseOrException instanceof \Throwable) { From 9407fe73adfb9a8c5ed8630859843503acea0be6 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 30 Sep 2020 11:38:02 +0200 Subject: [PATCH 0615/1161] Add setter for value on ExceptionDataBag (#1100) --- CHANGELOG.md | 1 + src/ExceptionDataBag.php | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f9afdc5..93fd4c7fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - fix: Use Span on Scope instead of Transaction for GuzzleMiddleware (#1099) +- Add setter for value on the `ExceptionDataBag` (#1100) ## 3.0.0 (2020-09-28) diff --git a/src/ExceptionDataBag.php b/src/ExceptionDataBag.php index 1d096c867..0173e3a61 100644 --- a/src/ExceptionDataBag.php +++ b/src/ExceptionDataBag.php @@ -58,6 +58,14 @@ public function getValue(): string return $this->value; } + /** + * Sets the value of the exception. + */ + public function setValue(string $value): void + { + $this->value = $value; + } + /** * Gets the stack trace object corresponding to the Stack Trace Interface. */ From b39248013660036d222681a359990284904a5db3 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 1 Oct 2020 14:10:56 +0200 Subject: [PATCH 0616/1161] meta: Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f9afdc5..a445fcf86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.0.1 (2020-10-01) + - fix: Use Span on Scope instead of Transaction for GuzzleMiddleware (#1099) ## 3.0.0 (2020-09-28) From 001f2e279fdbd68bbdc0e0916927f88e56af4e1b Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 2 Oct 2020 09:25:01 +0200 Subject: [PATCH 0617/1161] Fix tracing sampling considering sample_rate rather than the traces_sample_rate option (#1106) --- CHANGELOG.md | 2 ++ src/State/Hub.php | 2 +- tests/State/HubTest.php | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a445fcf86..1dfc0358d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fix: Use the traces sample rate for traces instead of the event sample rate (#1106) + ## 3.0.1 (2020-10-01) - fix: Use Span on Scope instead of Transaction for GuzzleMiddleware (#1099) diff --git a/src/State/Hub.php b/src/State/Hub.php index 2e5323e22..6649909a1 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -230,7 +230,7 @@ public function startTransaction(TransactionContext $context): Transaction $tracesSampler = $options->getTracesSampler(); $sampleRate = null !== $tracesSampler ? $tracesSampler($samplingContext) - : $this->getSampleRate($samplingContext->getParentSampled(), $options->getSampleRate()); + : $this->getSampleRate($samplingContext->getParentSampled(), $options->getTracesSampleRate()); if (!$this->isValidSampleRate($sampleRate)) { $transaction->setSampled(false); diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 48826573c..36e4fb65f 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -513,6 +513,24 @@ public function startTransactionDataProvider(): iterable false, ]; + yield [ + new Options([ + 'sample_rate' => 1.0, + 'traces_sample_rate' => 0.0, + ]), + new TransactionContext(), + false, + ]; + + yield [ + new Options([ + 'sample_rate' => 0.0, + 'traces_sample_rate' => 1.0, + ]), + new TransactionContext(), + true, + ]; + yield [ new Options([ 'traces_sample_rate' => 0.5, From a35c6c71693a72f2fedb9b6f9644ced52268771d Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 2 Oct 2020 16:16:36 +0200 Subject: [PATCH 0618/1161] meta: Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dfc0358d..4903a1479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.0.2 (2020-10-02) + - fix: Use the traces sample rate for traces instead of the event sample rate (#1106) ## 3.0.1 (2020-10-01) From e3d9a02a6db188d9022543befa547fe20d5e49f0 Mon Sep 17 00:00:00 2001 From: Ion Bazan Date: Mon, 5 Oct 2020 23:36:57 +0800 Subject: [PATCH 0619/1161] Fix missing source code excerpts for stacktrace frames when the absolute file path is equal to the stripped file path (#1104) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 2 ++ src/FrameBuilder.php | 2 +- src/StacktraceBuilder.php | 2 +- tests/FrameBuilderTest.php | 14 +++++++------- tests/StacktraceBuilderTest.php | 2 ++ 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4903a1479..81f70974f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix missing source code excerpts for stacktrace frames whose absolute file path is equal to the file path (#1104) + ## 3.0.2 (2020-10-02) - fix: Use the traces sample rate for traces instead of the event sample rate (#1106) diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 5bbb40b80..21659f585 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -84,7 +84,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac $strippedFilePath, $line, $rawFunctionName, - Frame::INTERNAL_FRAME_FILENAME !== $file && $strippedFilePath !== $file ? $file : null, + Frame::INTERNAL_FRAME_FILENAME !== $file ? $file : null, $this->getFunctionArguments($backtraceFrame), $this->isFrameInApp($file, $functionName) ); diff --git a/src/StacktraceBuilder.php b/src/StacktraceBuilder.php index fc4b66c8d..d48b2cdf5 100644 --- a/src/StacktraceBuilder.php +++ b/src/StacktraceBuilder.php @@ -70,7 +70,7 @@ public function buildFromBacktrace(array $backtrace, string $file, int $line): S } // Add a final stackframe for the first method ever of this stacktrace - array_unshift($frames, new Frame(null, $file, $line)); + array_unshift($frames, $this->frameBuilder->buildFromBacktraceFrame($file, $line, [])); return new Stacktrace($frames); } diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php index b73dd3696..2d0e6e1ec 100644 --- a/tests/FrameBuilderTest.php +++ b/tests/FrameBuilderTest.php @@ -36,7 +36,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'line' => 20, 'function' => 'test_function', ], - new Frame('test_function', '/path/to/file', 10), + new Frame('test_function', '/path/to/file', 10, null, '/path/to/file'), ]; yield [ @@ -46,7 +46,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'line' => 20, 'function' => 'test_function', ], - new Frame('test_function', '/path/to/file', 10), + new Frame('test_function', '/path/to/file', 10, null, '/path/to/file'), ]; yield [ @@ -57,7 +57,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'function' => 'test_function', 'class' => 'TestClass', ], - new Frame('TestClass::test_function', '/path/to/file', 10, 'TestClass::test_function'), + new Frame('TestClass::test_function', '/path/to/file', 10, 'TestClass::test_function', '/path/to/file'), ]; yield [ @@ -67,7 +67,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'line' => 10, 'function' => 'test_function', ], - new Frame('test_function', '/path/to/file', 10), + new Frame('test_function', '/path/to/file', 10, null, '/path/to/file'), ]; yield [ @@ -78,7 +78,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'function' => 'test_function', 'class' => "class@anonymous\0/path/to/file", ], - new Frame("class@anonymous\0/path/to/file::test_function", '/path/to/file', 10, "class@anonymous\0/path/to/file::test_function"), + new Frame("class@anonymous\0/path/to/file::test_function", '/path/to/file', 10, "class@anonymous\0/path/to/file::test_function", '/path/to/file'), ]; yield [ @@ -133,7 +133,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'file' => 'path/not/of/app/path/to/file', 'line' => 10, ], - new Frame(null, 'path/not/of/app/path/to/file', 10, null), + new Frame(null, 'path/not/of/app/path/to/file', 10, null, 'path/not/of/app/path/to/file'), ]; yield [ @@ -147,7 +147,7 @@ public function buildFromBacktraceFrameDataProvider(): \Generator 'file' => 'path/not/of/app/to/file', 'line' => 10, ], - new Frame(null, 'path/not/of/app/to/file', 10, null), + new Frame(null, 'path/not/of/app/to/file', 10, null, 'path/not/of/app/to/file'), ]; } diff --git a/tests/StacktraceBuilderTest.php b/tests/StacktraceBuilderTest.php index 1e8f0dccf..544919063 100644 --- a/tests/StacktraceBuilderTest.php +++ b/tests/StacktraceBuilderTest.php @@ -38,10 +38,12 @@ public function testBuildFromBacktrace(): void $this->assertNull($frames[0]->getFunctionName()); $this->assertSame('/in/jXVmi', $frames[0]->getFile()); + $this->assertSame('/in/jXVmi', $frames[0]->getAbsoluteFilePath()); $this->assertSame(5, $frames[0]->getLine()); $this->assertSame('{closure}', $frames[1]->getFunctionName()); $this->assertSame('/in/jXVmi', $frames[1]->getFile()); + $this->assertSame('/in/jXVmi', $frames[1]->getAbsoluteFilePath()); $this->assertSame(9, $frames[1]->getLine()); $this->assertSame('main', $frames[2]->getFunctionName()); From f09173d6100a0fd802b666b8a00c0bd2a1b472a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Tue, 6 Oct 2020 09:40:27 +0200 Subject: [PATCH 0620/1161] Make the PayloadSerializerInterface interface part of the public API (#1110) --- UPGRADE-3.0.md | 2 +- src/Serializer/PayloadSerializerInterface.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index be8111286..028903f2c 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -104,7 +104,7 @@ - Renamed the `Event::getServerOsContext()` method to `Event::getOsContext()` - The signature of the `Scope::setUser()` method changed to accept a plain array - Removed the `FlushableClientInterface` and `ClosableTransportInterface` interfaces. Their methods have been moved to the corresponding `ClientInterface` and `TransportInterface` interfaces -- Removed the `Event::toArray()` and `Event::jsonSerialize()` methods +- Removed the `Event::toArray()` and `Event::jsonSerialize()` methods, use `PayloadSerializerInterface::serialize()` instead - Removed the `Breadcrumb::toArray()` and `Breadcrumb::jsonSerialize()` methods - Removed the `Frame::toArray()` and `Frame::jsonSerialize()` methods - Removed the `Stacktrace::toArray()` and `Stacktrace::jsonSerialize()` methods diff --git a/src/Serializer/PayloadSerializerInterface.php b/src/Serializer/PayloadSerializerInterface.php index f1cabf15a..abb2364c1 100644 --- a/src/Serializer/PayloadSerializerInterface.php +++ b/src/Serializer/PayloadSerializerInterface.php @@ -9,8 +9,6 @@ /** * This interface defines the contract for the classes willing to serialize an * event object to a format suitable for sending over the wire to Sentry. - * - * @internal */ interface PayloadSerializerInterface { From 8826b9c29fd71217d2a400290bef7fd8f4fd6644 Mon Sep 17 00:00:00 2001 From: Adrian Haurat Date: Mon, 12 Oct 2020 01:05:21 -0700 Subject: [PATCH 0621/1161] Fix requirements to construct a valid object instance of the UserDataBag class (#1108) Co-authored-by: Adrian Haurat Co-authored-by: Alex Bouma --- CHANGELOG.md | 1 + src/UserDataBag.php | 43 ++++++++------------ tests/UserDataBagTest.php | 83 +++++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f70974f..e97d7f748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix missing source code excerpts for stacktrace frames whose absolute file path is equal to the file path (#1104) +- Fix requirements to construct a valid object instance of the `UserDataBag` class (#1108) ## 3.0.2 (2020-10-02) diff --git a/src/UserDataBag.php b/src/UserDataBag.php index 5849cbcaa..2237612be 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -34,8 +34,17 @@ final class UserDataBag */ private $metadata = []; - private function __construct() + /** + * UserDataBag constructor. + * + * @param string|int|null $id + */ + public function __construct($id = null, ?string $email = null, ?string $ipAddress = null, ?string $username = null) { + $this->setId($id); + $this->setEmail($email); + $this->setIpAddress($ipAddress); + $this->setUsername($username); } /** @@ -45,10 +54,7 @@ private function __construct() */ public static function createFromUserIdentifier($id): self { - $instance = new self(); - $instance->setId($id); - - return $instance; + return new self($id); } /** @@ -58,10 +64,7 @@ public static function createFromUserIdentifier($id): self */ public static function createFromUserIpAddress(string $ipAddress): self { - $instance = new self(); - $instance->setIpAddress($ipAddress); - - return $instance; + return new self(null, null, $ipAddress); } /** @@ -71,25 +74,21 @@ public static function createFromUserIpAddress(string $ipAddress): self */ public static function createFromArray(array $data): self { - if (!isset($data['id']) && !isset($data['ip_address'])) { - throw new \InvalidArgumentException('Either the "id" or the "ip_address" field must be set.'); - } - $instance = new self(); foreach ($data as $field => $value) { switch ($field) { case 'id': - $instance->setId($data['id']); + $instance->setId($value); break; case 'ip_address': - $instance->setIpAddress($data['ip_address']); + $instance->setIpAddress($value); break; case 'email': - $instance->setEmail($data['email']); + $instance->setEmail($value); break; case 'username': - $instance->setUsername($data['username']); + $instance->setUsername($value); break; default: $instance->setMetadata($field, $value); @@ -117,11 +116,7 @@ public function getId() */ public function setId($id): void { - if (null === $id && null === $this->ipAddress) { - throw new \BadMethodCallException('Either the IP address or the ID must be set.'); - } - - if (!\is_string($id) && !\is_int($id)) { + if (null !== $id && !\is_string($id) && !\is_int($id)) { throw new \UnexpectedValueException(sprintf('Expected an integer or string value for the $id argument. Got: "%s".', get_debug_type($id))); } @@ -183,10 +178,6 @@ public function setIpAddress(?string $ipAddress): void throw new \InvalidArgumentException(sprintf('The "%s" value is not a valid IP address.', $ipAddress)); } - if (null === $ipAddress && null === $this->id) { - throw new \BadMethodCallException('Either the IP address or the ID must be set.'); - } - $this->ipAddress = $ipAddress; } diff --git a/tests/UserDataBagTest.php b/tests/UserDataBagTest.php index ba9ace823..e4ca87a1c 100644 --- a/tests/UserDataBagTest.php +++ b/tests/UserDataBagTest.php @@ -9,17 +9,10 @@ final class UserDataBagTest extends TestCase { - public function testConstructorThrowsIfArgumentIsInvalid(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The "foo" value is not a valid IP address.'); - - UserDataBag::createFromUserIpAddress('foo'); - } - public function testGettersAndSetters(): void { - $userDataBag = UserDataBag::createFromUserIdentifier('unique_id'); + $userDataBag = new UserDataBag(); + $userDataBag->setId('unique_id'); $userDataBag->setIpAddress('127.0.0.1'); $userDataBag->setEmail('foo@example.com'); $userDataBag->setUsername('my_user'); @@ -76,11 +69,8 @@ public function createFromArrayDataProvider(): iterable ]; yield [ - [ - 'id' => 'unique_id', - 'email' => 'foo@example.com', - ], - 'unique_id', + ['email' => 'foo@example.com'], + null, null, 'foo@example.com', null, @@ -88,11 +78,8 @@ public function createFromArrayDataProvider(): iterable ]; yield [ - [ - 'id' => 'unique_id', - 'username' => 'my_user', - ], - 'unique_id', + ['username' => 'my_user'], + null, null, null, 'my_user', @@ -100,11 +87,8 @@ public function createFromArrayDataProvider(): iterable ]; yield [ - [ - 'id' => 'unique_id', - 'subscription' => 'basic', - ], - 'unique_id', + ['subscription' => 'basic'], + null, null, null, null, @@ -113,17 +97,40 @@ public function createFromArrayDataProvider(): iterable } /** - * @dataProvider setIdThrowsIfValueIsUnexpectedValueDataProvider + * @dataProvider unexpectedValueForIdFieldDataProvider + */ + public function testConstructorThrowsIfIdValueIsUnexpectedValue($value, string $expectedExceptionMessage): void + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + new UserDataBag($value); + } + + /** + * @dataProvider unexpectedValueForIdFieldDataProvider */ public function testSetIdThrowsIfValueIsUnexpectedValue($value, string $expectedExceptionMessage): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessage($expectedExceptionMessage); + $userDataBag = new UserDataBag(); + $userDataBag->setId($value); + } + + /** + * @dataProvider unexpectedValueForIdFieldDataProvider + */ + public function testCreateFromUserIdentifierThrowsIfArgumentIsInvalid($value, string $expectedExceptionMessage): void + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + UserDataBag::createFromUserIdentifier($value); } - public function setIdThrowsIfValueIsUnexpectedValueDataProvider(): iterable + public function unexpectedValueForIdFieldDataProvider(): iterable { yield [ 12.34, @@ -136,31 +143,29 @@ public function setIdThrowsIfValueIsUnexpectedValueDataProvider(): iterable ]; } - public function testSetIdThrowsIfBothArgumentAndIpAddressAreNull(): void + public function testConstructorThrowsIfIpAddressArgumentIsInvalid(): void { - $this->expectException(\BadMethodCallException::class); - $this->expectExceptionMessage('Either the IP address or the ID must be set.'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "foo" value is not a valid IP address.'); - $userDataBag = UserDataBag::createFromUserIdentifier('unique_id'); - $userDataBag->setId(null); + new UserDataBag(null, null, 'foo'); } - public function testSetIpAddressThrowsIfBothArgumentAndIdAreNull(): void + public function testSetIpAddressThrowsIfArgumentIsInvalid(): void { - $this->expectException(\BadMethodCallException::class); - $this->expectExceptionMessage('Either the IP address or the ID must be set.'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "foo" value is not a valid IP address.'); - $userDataBag = UserDataBag::createFromUserIpAddress('127.0.0.1'); - $userDataBag->setIpAddress(null); + $userDataBag = new UserDataBag(); + $userDataBag->setIpAddress('foo'); } - public function testSetIpAddressThrowsIfArgumentIsInvalid(): void + public function testCreateFromIpAddressThrowsIfArgumentIsInvalid(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('The "foo" value is not a valid IP address.'); - $userDataBag = UserDataBag::createFromUserIpAddress('127.0.0.1'); - $userDataBag->setIpAddress('foo'); + UserDataBag::createFromUserIpAddress('foo'); } public function testMerge(): void From e9f9cc24150da81f54f80611c565778d957c0aa3 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 12 Oct 2020 10:11:29 +0200 Subject: [PATCH 0622/1161] Prepare 3.0.3 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e97d7f748..52e85dfba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.0.3 (2020-10-12) + - Fix missing source code excerpts for stacktrace frames whose absolute file path is equal to the file path (#1104) - Fix requirements to construct a valid object instance of the `UserDataBag` class (#1108) From abc25882351a2009c4604354a34f45a5a1965f5c Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 13 Oct 2020 10:17:56 +0200 Subject: [PATCH 0623/1161] meta: No artifacts for craft.yml --- .craft.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.craft.yml b/.craft.yml index a492a4e02..7cb921217 100644 --- a/.craft.yml +++ b/.craft.yml @@ -5,10 +5,11 @@ github: changelogPolicy: simple statusProvider: name: github +artifactProvider: + name: none preReleaseCommand: "" targets: - name: github - includeNames: /none/ - name: registry type: sdk config: From 9946159662dccc730c4659219daf948d3aa497f6 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 2 Nov 2020 10:10:31 +0100 Subject: [PATCH 0624/1161] Fix broken build due to updated PHPStan --- phpstan.neon | 3 --- 1 file changed, 3 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 5d724909a..082066750 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,9 +12,6 @@ parameters: - message: '/^Call to static method create\(\) on an unknown class Symfony\\Component\\HttpClient\\HttpClient\.$/' path: src/HttpClient/HttpClientFactory.php - - - message: '/^Parameter #1 \$c of function ctype_digit expects int\|string, string\|null given\.$/' - path: src/Dsn.php - message: "/^Offset 'scheme' does not exist on array\\(\\?'scheme' => string, \\?'host' => string, \\?'port' => int, \\?'user' => string, \\?'pass' => string, \\?'path' => string, \\?'query' => string, \\?'fragment' => string\\)\\.$/" path: src/Dsn.php From b1c8679177f0f869ae5b177e9d29fc6007bff121 Mon Sep 17 00:00:00 2001 From: mark burdett Date: Mon, 2 Nov 2020 03:07:01 -0800 Subject: [PATCH 0625/1161] Fix missing serialization of the "stacktrace" field of the event (#1123) --- CHANGELOG.md | 2 ++ src/Serializer/PayloadSerializer.php | 8 ++++++ tests/Serializer/PayloadSerializerTest.php | 29 ++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e85dfba..a9d354a51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix stacktrace missing from payload for non-exception events (#1123) + ## 3.0.3 (2020-10-12) - Fix missing source code excerpts for stacktrace frames whose absolute file path is equal to the file path (#1104) diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 756325f6f..12071a9db 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -152,6 +152,14 @@ private function serializeAsEvent(Event $event): string $result['spans'] = array_values(array_map([$this, 'serializeSpan'], $event->getSpans())); } + $stacktrace = $event->getStacktrace(); + + if (null !== $stacktrace) { + $result['stacktrace'] = [ + 'frames' => array_map([$this, 'serializeStacktraceFrame'], $stacktrace->getFrames()), + ]; + } + return JSON::encode($result); } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 8cfeff07c..0ea26b502 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -422,5 +422,34 @@ public function serializeDataProvider(): iterable , false, ]; + + $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setStacktrace(new Stacktrace([new Frame(null, '', 0)])); + + yield [ + $event, + << Date: Thu, 5 Nov 2020 21:19:00 +0200 Subject: [PATCH 0626/1161] Fix capturing of the request body in the RequestIntegration integration when the stream is empty (#1119) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 5 ++-- src/Integration/RequestIntegration.php | 2 +- tests/Integration/RequestIntegrationTest.php | 28 +++++++++++++++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9d354a51..66df0d54e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix stacktrace missing from payload for non-exception events (#1123) +- Fix capturing of the request body in the `RequestIntegration` integration when the stream is empty (#1119) ## 3.0.3 (2020-10-12) @@ -11,11 +12,11 @@ ## 3.0.2 (2020-10-02) -- fix: Use the traces sample rate for traces instead of the event sample rate (#1106) +- Fix use of the `sample_rate` option rather than `traces_sample_rate` when capturing a `Transaction` (#1106) ## 3.0.1 (2020-10-01) -- fix: Use Span on Scope instead of Transaction for GuzzleMiddleware (#1099) +- Fix use of `Transaction` instead of `Span` in the `GuzzleMiddleware` middleware (#1099) ## 3.0.0 (2020-09-28) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 3995498ed..10a075fd8 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -160,7 +160,7 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re $requestBodySize = $request->getBody()->getSize(); if ( - null === $requestBodySize || + !$requestBodySize || 'none' === $maxRequestBodySize || ('small' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) || ('medium' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 510685367..12c3e4891 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -304,7 +304,8 @@ public function invokeDataProvider(): iterable (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withUploadedFiles([ 'foo' => new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), - ]), + ]) + ->withBody($this->getStreamMock(123 + 321)), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', @@ -333,7 +334,8 @@ public function invokeDataProvider(): iterable new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), ], - ]), + ]) + ->withBody($this->getStreamMock(123 + 321)), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', @@ -371,7 +373,8 @@ public function invokeDataProvider(): iterable new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), ], ], - ]), + ]) + ->withBody($this->getStreamMock(123 + 321)), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', @@ -459,6 +462,25 @@ public function invokeDataProvider(): iterable null, null, ]; + + yield [ + [ + 'max_request_body_size' => 'always', + ], + (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + ->withHeader('Content-Type', 'application/json') + ->withBody($this->getStreamMock(0)), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + 'Content-Type' => ['application/json'], + ], + ], + null, + null, + ]; } private function getStreamMock(?int $size, string $content = ''): StreamInterface From 745b3faafdc865ea8de75e0dc61ddfdd36a31d9c Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 6 Nov 2020 12:58:00 +0100 Subject: [PATCH 0627/1161] Prepare 3.0.4 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66df0d54e..5500505f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.0.4 (2020-11-6) + - Fix stacktrace missing from payload for non-exception events (#1123) - Fix capturing of the request body in the `RequestIntegration` integration when the stream is empty (#1119) From 227c716f13f51924f215a02f85a7f8e790a93845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Dobe=C5=A1?= Date: Fri, 6 Nov 2020 16:18:30 +0100 Subject: [PATCH 0628/1161] Add Scope::removeTag() method (#1126) --- CHANGELOG.md | 1 + src/State/Scope.php | 14 ++++++++++++++ tests/State/ScopeTest.php | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de77facb6..5493544d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Add setter for value on the `ExceptionDataBag` (#1100) +- Add `Scope::removeTag` method (#1126) ## 3.0.4 (2020-11-6) diff --git a/src/State/Scope.php b/src/State/Scope.php index c70c77722..78cd452a4 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -103,6 +103,20 @@ public function setTags(array $tags): self return $this; } + /** + * Removes a given tag from the tags context. + * + * @param string $key The key that uniquely identifies the tag + * + * @return $this + */ + public function removeTag(string $key): self + { + unset($this->tags[$key]); + + return $this; + } + /** * Sets context data with the given name. * diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index f723e5252..a5c5618a1 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -49,6 +49,27 @@ public function testSetTags(): void $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); } + public function testRemoveTag(): void + { + $scope = new Scope(); + $event = $scope->applyToEvent(Event::createEvent()); + + $scope->setTag('foo', 'bar'); + $scope->setTag('bar', 'baz'); + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertNotNull($event); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); + + $scope->removeTag('foo'); + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertNotNull($event); + $this->assertSame(['bar' => 'baz'], $event->getTags()); + } + public function testSetAndRemoveContext(): void { $scope = new Scope(); From e4d49c889f514d71d2830171054e7c86d562c347 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 27 Nov 2020 10:22:27 +0100 Subject: [PATCH 0629/1161] Fix regression introduced in #1119 that prevents capturing of the request body (#1139) --- CHANGELOG.md | 4 +- composer.json | 2 +- src/Integration/RequestIntegration.php | 50 ++++-- tests/Integration/RequestIntegrationTest.php | 177 +++++-------------- 4 files changed, 83 insertions(+), 150 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5500505f0..a26102876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased -## 3.0.4 (2020-11-6) +- Fix capturing of the request body in the `RequestIntegration` integration (#1139) + +## 3.0.4 (2020-11-06) - Fix stacktrace missing from payload for non-exception events (#1123) - Fix capturing of the request body in the `RequestIntegration` integration when the stream is empty (#1119) diff --git a/composer.json b/composer.json index 772fdb2b3..7350d1a28 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "ext-json": "*", "ext-mbstring": "*", "guzzlehttp/promises": "^1.3", - "guzzlehttp/psr7": "^1.6", + "guzzlehttp/psr7": "^1.7", "jean85/pretty-package-versions": "^1.5", "ocramius/package-versions": "^1.8", "php-http/async-client-implementation": "^1.0", diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 10a075fd8..d9f2cac17 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -4,6 +4,7 @@ namespace Sentry\Integration; +use GuzzleHttp\Psr7\Utils; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; use Sentry\Event; @@ -36,6 +37,17 @@ final class RequestIntegration implements IntegrationInterface */ private const REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH = 10 ** 4; + /** + * This constant is a map of maximum allowed sizes for each value of the + * `max_request_body_size` option. + */ + private const MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP = [ + 'none' => 0, + 'small' => self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH, + 'medium' => self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH, + 'always' => -1, + ]; + /** * @var RequestFetcherInterface PSR-7 request fetcher */ @@ -155,16 +167,9 @@ static function (string $key) use ($keysToRemove): bool { private function captureRequestBody(Options $options, ServerRequestInterface $request) { $maxRequestBodySize = $options->getMaxRequestBodySize(); + $requestBodySize = (int) $request->getHeaderLine('Content-Length'); - $requestBody = $request->getBody(); - $requestBodySize = $request->getBody()->getSize(); - - if ( - !$requestBodySize || - 'none' === $maxRequestBodySize || - ('small' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) || - ('medium' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) - ) { + if (!$this->isRequestBodySizeWithinReadBounds($requestBodySize, $maxRequestBodySize)) { return null; } @@ -178,15 +183,17 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re return $requestData; } + $requestBody = Utils::copyToString($request->getBody(), self::MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP[$maxRequestBodySize]); + if ('application/json' === $request->getHeaderLine('Content-Type')) { try { - return JSON::decode($requestBody->getContents()); + return JSON::decode($requestBody); } catch (JsonException $exception) { // Fallback to returning the raw data from the request body } } - return $requestBody->getContents(); + return $requestBody; } /** @@ -217,4 +224,25 @@ private function parseUploadedFiles(array $uploadedFiles): array return $result; } + + private function isRequestBodySizeWithinReadBounds(int $requestBodySize, string $maxRequestBodySize): bool + { + if ($requestBodySize <= 0) { + return false; + } + + if ('none' === $maxRequestBodySize) { + return false; + } + + if ('small' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) { + return false; + } + + if ('medium' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) { + return false; + } + + return true; + } } diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 12c3e4891..e8d2a69f2 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -7,10 +7,10 @@ use GuzzleHttp\Psr7\ServerRequest; use GuzzleHttp\Psr7\UploadedFile; use GuzzleHttp\Psr7\Uri; +use GuzzleHttp\Psr7\Utils; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamInterface; use Sentry\ClientInterface; use Sentry\Event; use Sentry\Integration\RequestFetcherInterface; @@ -189,16 +189,14 @@ public function invokeDataProvider(): iterable 'max_request_body_size' => 'none', ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) - ->withParsedBody([ - 'foo' => 'foo value', - 'bar' => 'bar value', - ]) - ->withBody($this->getStreamMock(1)), + ->withHeader('Content-Length', '3') + ->withBody(Utils::streamFor('foo')), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', 'headers' => [ 'Host' => ['www.example.com'], + 'Content-Length' => ['3'], ], ], null, @@ -210,21 +208,16 @@ public function invokeDataProvider(): iterable 'max_request_body_size' => 'small', ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) - ->withParsedBody([ - 'foo' => 'foo value', - 'bar' => 'bar value', - ]) - ->withBody($this->getStreamMock(10 ** 3)), + ->withHeader('Content-Length', 10 ** 3) + ->withBody(Utils::streamFor('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus at placerat est. Donec maximus odio augue, vitae bibendum nisi euismod nec. Nunc vel velit ligula. Ut non ultricies magna, non condimentum turpis. Donec pellentesque id nunc at facilisis. Sed fermentum ultricies nunc, id posuere ex ullamcorper quis. Sed varius tincidunt nulla, id varius nulla interdum sit amet. Pellentesque molestie sapien at mi tristique consequat. Nullam id eleifend arcu. Vivamus sed placerat neque. Ut sapien magna, elementum in euismod pretium, rhoncus vitae augue. Nam ullamcorper dui et tortor semper, eu feugiat elit faucibus. Curabitur vel auctor odio. Phasellus vestibulum ullamcorper dictum. Suspendisse fringilla, ipsum bibendum venenatis vulputate, nunc orci facilisis leo, commodo finibus mi arcu in turpis. Mauris ut ultrices est. Nam quis purus ut nulla interdum ornare. Proin in tellus egestas, commodo magna porta, consequat justo. Vivamus in convallis odio. Pellentesque porttitor, urna non gravida.')), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', 'headers' => [ 'Host' => ['www.example.com'], + 'Content-Length' => ['1000'], ], - 'data' => [ - 'foo' => 'foo value', - 'bar' => 'bar value', - ], + 'data' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus at placerat est. Donec maximus odio augue, vitae bibendum nisi euismod nec. Nunc vel velit ligula. Ut non ultricies magna, non condimentum turpis. Donec pellentesque id nunc at facilisis. Sed fermentum ultricies nunc, id posuere ex ullamcorper quis. Sed varius tincidunt nulla, id varius nulla interdum sit amet. Pellentesque molestie sapien at mi tristique consequat. Nullam id eleifend arcu. Vivamus sed placerat neque. Ut sapien magna, elementum in euismod pretium, rhoncus vitae augue. Nam ullamcorper dui et tortor semper, eu feugiat elit faucibus. Curabitur vel auctor odio. Phasellus vestibulum ullamcorper dictum. Suspendisse fringilla, ipsum bibendum venenatis vulputate, nunc orci facilisis leo, commodo finibus mi arcu in turpis. Mauris ut ultrices est. Nam quis purus ut nulla interdum ornare. Proin in tellus egestas, commodo magna porta, consequat justo. Vivamus in convallis odio. Pellentesque porttitor, urna non gravid', ], null, null, @@ -235,16 +228,21 @@ public function invokeDataProvider(): iterable 'max_request_body_size' => 'small', ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + ->withHeader('Content-Length', (string) (10 ** 3)) ->withParsedBody([ 'foo' => 'foo value', 'bar' => 'bar value', - ]) - ->withBody($this->getStreamMock(10 ** 3 + 1)), + ]), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', 'headers' => [ 'Host' => ['www.example.com'], + 'Content-Length' => ['1000'], + ], + 'data' => [ + 'foo' => 'foo value', + 'bar' => 'bar value', ], ], null, @@ -253,23 +251,20 @@ public function invokeDataProvider(): iterable yield [ [ - 'max_request_body_size' => 'medium', + 'max_request_body_size' => 'small', ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + ->withHeader('Content-Length', (string) (10 ** 3 + 1)) ->withParsedBody([ 'foo' => 'foo value', 'bar' => 'bar value', - ]) - ->withBody($this->getStreamMock(10 ** 4)), + ]), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', 'headers' => [ 'Host' => ['www.example.com'], - ], - 'data' => [ - 'foo' => 'foo value', - 'bar' => 'bar value', + 'Content-Length' => ['1001'], ], ], null, @@ -281,16 +276,21 @@ public function invokeDataProvider(): iterable 'max_request_body_size' => 'medium', ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + ->withHeader('Content-Length', (string) (10 ** 4)) ->withParsedBody([ 'foo' => 'foo value', 'bar' => 'bar value', - ]) - ->withBody($this->getStreamMock(10 ** 4 + 1)), + ]), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', 'headers' => [ 'Host' => ['www.example.com'], + 'Content-Length' => ['10000'], + ], + 'data' => [ + 'foo' => 'foo value', + 'bar' => 'bar value', ], ], null, @@ -299,25 +299,20 @@ public function invokeDataProvider(): iterable yield [ [ - 'max_request_body_size' => 'always', + 'max_request_body_size' => 'medium', ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) - ->withUploadedFiles([ - 'foo' => new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), - ]) - ->withBody($this->getStreamMock(123 + 321)), + ->withHeader('Content-Length', (string) (10 ** 4 + 1)) + ->withParsedBody([ + 'foo' => 'foo value', + 'bar' => 'bar value', + ]), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', 'headers' => [ 'Host' => ['www.example.com'], - ], - 'data' => [ - 'foo' => [ - 'client_filename' => 'foo.ext', - 'client_media_type' => 'application/text', - 'size' => 123, - ], + 'Content-Length' => ['10001'], ], ], null, @@ -329,18 +324,19 @@ public function invokeDataProvider(): iterable 'max_request_body_size' => 'always', ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + ->withHeader('Content-Length', '444') ->withUploadedFiles([ 'foo' => [ new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), ], - ]) - ->withBody($this->getStreamMock(123 + 321)), + ]), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', 'headers' => [ 'Host' => ['www.example.com'], + 'Content-Length' => ['444'], ], 'data' => [ 'foo' => [ @@ -361,60 +357,21 @@ public function invokeDataProvider(): iterable null, ]; - yield [ - [ - 'max_request_body_size' => 'always', - ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) - ->withUploadedFiles([ - 'foo' => [ - 'bar' => [ - new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), - new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), - ], - ], - ]) - ->withBody($this->getStreamMock(123 + 321)), - [ - 'url' => 'http://www.example.com/foo', - 'method' => 'POST', - 'headers' => [ - 'Host' => ['www.example.com'], - ], - 'data' => [ - 'foo' => [ - 'bar' => [ - [ - 'client_filename' => 'foo.ext', - 'client_media_type' => 'application/text', - 'size' => 123, - ], - [ - 'client_filename' => 'bar.ext', - 'client_media_type' => 'application/octet-stream', - 'size' => 321, - ], - ], - ], - ], - ], - null, - null, - ]; - yield [ [ 'max_request_body_size' => 'always', ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withHeader('Content-Type', 'application/json') - ->withBody($this->getStreamMock(13, '{"foo":"bar"}')), + ->withHeader('Content-Length', '13') + ->withBody(Utils::streamFor('{"foo":"bar"}')), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', 'headers' => [ 'Host' => ['www.example.com'], 'Content-Type' => ['application/json'], + 'Content-Length' => ['13'], ], 'data' => [ 'foo' => 'bar', @@ -430,27 +387,7 @@ public function invokeDataProvider(): iterable ], (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) ->withHeader('Content-Type', 'application/json') - ->withBody($this->getStreamMock(1, '{')), - [ - 'url' => 'http://www.example.com/foo', - 'method' => 'POST', - 'headers' => [ - 'Host' => ['www.example.com'], - 'Content-Type' => ['application/json'], - ], - 'data' => '{', - ], - null, - null, - ]; - - yield [ - [ - 'max_request_body_size' => 'always', - ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) - ->withHeader('Content-Type', 'application/json') - ->withBody($this->getStreamMock(null)), + ->withBody(Utils::streamFor('{"foo":"bar"}')), [ 'url' => 'http://www.example.com/foo', 'method' => 'POST', @@ -462,40 +399,6 @@ public function invokeDataProvider(): iterable null, null, ]; - - yield [ - [ - 'max_request_body_size' => 'always', - ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) - ->withHeader('Content-Type', 'application/json') - ->withBody($this->getStreamMock(0)), - [ - 'url' => 'http://www.example.com/foo', - 'method' => 'POST', - 'headers' => [ - 'Host' => ['www.example.com'], - 'Content-Type' => ['application/json'], - ], - ], - null, - null, - ]; - } - - private function getStreamMock(?int $size, string $content = ''): StreamInterface - { - /** @var MockObject&StreamInterface $stream */ - $stream = $this->createMock(StreamInterface::class); - $stream->expects($this->any()) - ->method('getSize') - ->willReturn($size); - - $stream->expects(null === $size ? $this->never() : $this->any()) - ->method('getContents') - ->willReturn($content); - - return $stream; } private function createRequestFetcher(ServerRequestInterface $request): RequestFetcherInterface From 819e3ae62a845df7a10f496cd05cafd695c57dd0 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 1 Dec 2020 00:39:15 +0100 Subject: [PATCH 0630/1161] Deprecate SpanContext::fromTraceparent() and allow passing custom sampling context data to startTransaction() (#1134) --- CHANGELOG.md | 2 + composer.json | 2 +- phpstan.neon | 9 +++ src/Dsn.php | 2 +- src/State/Hub.php | 44 ++++++++---- src/State/HubAdapter.php | 5 +- src/State/HubInterface.php | 4 +- src/Tracing/SamplingContext.php | 25 +++++++ src/Tracing/SpanContext.php | 11 ++- src/Tracing/Transaction.php | 8 +++ src/Tracing/TransactionContext.php | 32 +++++++++ src/functions.php | 8 ++- tests/DsnTest.php | 10 +++ tests/FunctionsTest.php | 28 +++++++- tests/State/HubTest.php | 89 ++++++++++++++++++++---- tests/Tracing/SpanContextTest.php | 7 ++ tests/Tracing/TransactionContextTest.php | 58 +++++++++++++++ 17 files changed, 309 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5493544d0..ef3f6b64c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Deprecate `SpanContext::fromTraceparent()` in favor of `TransactionContext::fromSentryTrace()` (#1134) +- Allow setting custom data on the sampling context by passing it as 2nd argument of the `startTransaction()` function (#1134) - Add setter for value on the `ExceptionDataBag` (#1100) - Add `Scope::removeTag` method (#1126) diff --git a/composer.json b/composer.json index 9ee2fbf78..233c47ca9 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "phpstan/phpstan": "^0.12", "phpstan/phpstan-phpunit": "^0.12", "phpunit/phpunit": "^7.5.18", - "symfony/phpunit-bridge": "^4.3|^5.0", + "symfony/phpunit-bridge": "^5.1", "vimeo/psalm": "^3.4" }, "suggest": { diff --git a/phpstan.neon b/phpstan.neon index 082066750..1f0e46af2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -30,6 +30,15 @@ parameters: - message: "/Call to function in_array\\(\\) with arguments callable\\(\\): mixed&string, array\\('{closure}', '__lambda_func'\\) and true will always evaluate to false\\.$/" path: src/FrameBuilder.php + - + message: "/^PHPDoc tag @param references unknown parameter: \\$customSamplingContext$/" + path: src/State/HubInterface.php + - + message: '/^Method Sentry\\State\\HubInterface::startTransaction\(\) invoked with 2 parameters, 1 required\.$/' + path: src/State/HubAdapter.php + - + message: '/^Method Sentry\\State\\HubInterface::startTransaction\(\) invoked with 2 parameters, 1 required\.$/' + path: src/functions.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/Dsn.php b/src/Dsn.php index f99754dc3..795b4801d 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -101,7 +101,7 @@ public static function createFromString(string $value): self $segmentPaths = explode('/', $parsedDsn['path']); $projectId = array_pop($segmentPaths); - if (!ctype_digit($projectId)) { + if ((int) $projectId <= 0) { throw new \InvalidArgumentException('"%s" DSN must contain a valid project ID.'); } diff --git a/src/State/Hub.php b/src/State/Hub.php index 6649909a1..c24d59ef4 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -216,6 +216,12 @@ public function getIntegration(string $className): ?IntegrationInterface */ public function startTransaction(TransactionContext $context): Transaction { + $customSamplingContext = null; + + if (\func_num_args() > 1) { + $customSamplingContext = func_get_arg(1); + } + $transaction = new Transaction($context, $this); $client = $this->getClient(); $options = null !== $client ? $client->getOptions() : null; @@ -227,24 +233,29 @@ public function startTransaction(TransactionContext $context): Transaction } $samplingContext = SamplingContext::getDefault($context); + $samplingContext->setAdditionalContext($customSamplingContext); + $tracesSampler = $options->getTracesSampler(); - $sampleRate = null !== $tracesSampler - ? $tracesSampler($samplingContext) - : $this->getSampleRate($samplingContext->getParentSampled(), $options->getTracesSampleRate()); - if (!$this->isValidSampleRate($sampleRate)) { - $transaction->setSampled(false); + if (null === $transaction->getSampled()) { + $sampleRate = null !== $tracesSampler + ? $tracesSampler($samplingContext) + : $this->getSampleRate($samplingContext->getParentSampled(), $options->getTracesSampleRate()); - return $transaction; - } + if (!$this->isValidSampleRate($sampleRate)) { + $transaction->setSampled(false); - if (0.0 === $sampleRate) { - $transaction->setSampled(false); + return $transaction; + } - return $transaction; - } + if (0.0 === $sampleRate) { + $transaction->setSampled(false); + + return $transaction; + } - $transaction->setSampled(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < $sampleRate); + $transaction->setSampled(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < $sampleRate); + } if (!$transaction->getSampled()) { return $transaction; @@ -313,8 +324,15 @@ private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampl return $fallbackSampleRate; } - private function isValidSampleRate(float $sampleRate): bool + /** + * @param mixed $sampleRate + */ + private function isValidSampleRate($sampleRate): bool { + if (!\is_float($sampleRate) && !\is_int($sampleRate)) { + return false; + } + if ($sampleRate < 0 || $sampleRate > 1) { return false; } diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index fe21539a6..7df115f9c 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -156,7 +156,10 @@ public function getIntegration(string $className): ?IntegrationInterface */ public function startTransaction(TransactionContext $context): Transaction { - return SentrySdk::getCurrentHub()->startTransaction($context); + $customSamplingContext = \func_num_args() > 1 ? func_get_arg(1) : []; + + /** @psalm-suppress TooManyArguments */ + return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } /** diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 9e585728a..15223d0a8 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -11,6 +11,7 @@ use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Severity; +use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -138,7 +139,8 @@ public function getIntegration(string $className): ?IntegrationInterface; * which point the transaction with all its finished child spans will be sent to * Sentry. * - * @param TransactionContext $context Properties of the new transaction + * @param TransactionContext $context Properties of the new transaction + * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ public function startTransaction(TransactionContext $context): Transaction; diff --git a/src/Tracing/SamplingContext.php b/src/Tracing/SamplingContext.php index 4df9bcf4c..f698286a7 100644 --- a/src/Tracing/SamplingContext.php +++ b/src/Tracing/SamplingContext.php @@ -16,6 +16,11 @@ final class SamplingContext */ private $parentSampled; + /** + * @var array|null Additional context, depending on where the SDK runs + */ + private $additionalContext; + /** * Returns an instance populated with the data of the transaction context. */ @@ -48,4 +53,24 @@ public function setParentSampled(?bool $parentSampled): void { $this->parentSampled = $parentSampled; } + + /** + * Sets additional data that will be provided as a second argument to {@link \Sentry\startTransaction()}. + * + * @param array|null $additionalContext + */ + public function setAdditionalContext(?array $additionalContext): void + { + $this->additionalContext = $additionalContext; + } + + /** + * Gets the additional data that will be provided as a second argument to {@link \Sentry\startTransaction()}. + * + * @return array|null + */ + public function getAdditionalContext(): ?array + { + return $this->additionalContext; + } } diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index d6072323a..6bed921bf 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -6,6 +6,9 @@ class SpanContext { + /** + * @deprecated since version 3.1, to be removed in 4.0 + */ private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; /** @@ -26,7 +29,7 @@ class SpanContext /** * @var SpanId|null ID of the parent Span */ - private $parentSpanId; + protected $parentSpanId; /** * @var bool|null Has the sample decision been made? @@ -41,7 +44,7 @@ class SpanContext /** * @var TraceId|null Trace ID */ - private $traceId; + protected $traceId; /** * @var array A List of tags associated to this Span @@ -191,9 +194,13 @@ public function setEndTimestamp(?float $endTimestamp): void * @param string $header The sentry-trace header from the request * * @return static + * + * @deprecated since version 3.1, to be removed in 4.0 */ public static function fromTraceparent(string $header) { + @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.', __METHOD__), E_USER_DEPRECATED); + /** @phpstan-ignore-next-line */ /** @psalm-suppress UnsafeInstantiation */ $context = new static(); diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index ac8fd6648..222361f19 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -40,6 +40,14 @@ public function __construct(TransactionContext $context, ?HubInterface $hub = nu $this->name = $context->getName(); } + /** + * Gets the name of this transaction. + */ + public function getName(): string + { + return $this->name; + } + /** * Sets the name of this transaction. * diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index 2853fa190..765fdb24b 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -6,6 +6,8 @@ final class TransactionContext extends SpanContext { + private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + public const DEFAULT_NAME = ''; /** @@ -65,4 +67,34 @@ public function setParentSampled(?bool $parentSampled): void { $this->parentSampled = $parentSampled; } + + /** + * Returns a context populated with the data of the given header. + * + * @param string $header The sentry-trace header from the request + * + * @return self + */ + public static function fromSentryTrace(string $header) + { + $context = new self(); + + if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { + return $context; + } + + if (!empty($matches['trace_id'])) { + $context->traceId = new TraceId($matches['trace_id']); + } + + if (!empty($matches['span_id'])) { + $context->parentSpanId = new SpanId($matches['span_id']); + } + + if (isset($matches['sampled'])) { + $context->parentSampled = '1' === $matches['sampled']; + } + + return $context; + } } diff --git a/src/functions.php b/src/functions.php index 7d385c2c6..e6ce3d855 100644 --- a/src/functions.php +++ b/src/functions.php @@ -108,9 +108,11 @@ function withScope(callable $callback): void * which point the transaction with all its finished child spans will be sent to * Sentry. * - * @param TransactionContext $context Properties of the new transaction + * @param TransactionContext $context Properties of the new transaction + * @param array $customSamplingContext Additional context that will be passed to the {@see \Sentry\Tracing\SamplingContext} */ -function startTransaction(TransactionContext $context): Transaction +function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - return SentrySdk::getCurrentHub()->startTransaction($context); + /** @psalm-suppress TooManyArguments */ + return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } diff --git a/tests/DsnTest.php b/tests/DsnTest.php index a551d540c..1681367ce 100644 --- a/tests/DsnTest.php +++ b/tests/DsnTest.php @@ -171,6 +171,16 @@ public function createFromStringThrowsExceptionIfValueIsInvalidDataProvider(): \ 'tcp://public:secret@example.com/1', 'The scheme of the "tcp://public:secret@example.com/1" DSN must be either "http" or "https".', ]; + + yield 'invalid project ID (char instead of number)' => [ + 'http://public:secret@example.com/j', + 'DSN must contain a valid project ID.', + ]; + + yield 'invalid project ID (negative number)' => [ + 'http://public:secret@example.com/-2', + 'DSN must contain a valid project ID.', + ]; } /** diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 9fcdc434c..ba36a7807 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -14,6 +14,8 @@ use Sentry\SentrySdk; use Sentry\Severity; use Sentry\State\Scope; +use Sentry\Tracing\SamplingContext; +use Sentry\Tracing\TransactionContext; use function Sentry\addBreadcrumb; use function Sentry\captureEvent; use function Sentry\captureException; @@ -21,6 +23,7 @@ use function Sentry\captureMessage; use function Sentry\configureScope; use function Sentry\init; +use function Sentry\startTransaction; use function Sentry\withScope; final class FunctionsTest extends TestCase @@ -130,7 +133,7 @@ public function testWithScope(): void $this->assertTrue($callbackInvoked); } - public function configureScope(): void + public function testConfigureScope(): void { $callbackInvoked = false; @@ -140,4 +143,27 @@ public function configureScope(): void $this->assertTrue($callbackInvoked); } + + public function testStartTransaction(): void + { + $transactionContext = new TransactionContext('foo'); + $customSamplingContext = ['foo' => 'bar']; + + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { + $this->assertSame($customSamplingContext, $samplingContext->getAdditionalContext()); + + return 0.0; + }, + ])); + + SentrySdk::getCurrentHub()->bindClient($client); + + $transaction = startTransaction($transactionContext, $customSamplingContext); + + $this->assertSame($transactionContext->getName(), $transaction->getName()); + } } diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 36e4fb65f..d0fd6f64a 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -15,6 +15,7 @@ use Sentry\Severity; use Sentry\State\Hub; use Sentry\State\Scope; +use Sentry\Tracing\SamplingContext; use Sentry\Tracing\TransactionContext; final class HubTest extends TestCase @@ -457,27 +458,27 @@ public function testStartTransactionWithTracesSampler(Options $options, Transact public function startTransactionDataProvider(): iterable { - yield [ + yield 'Acceptable float value returned from traces_sampler' => [ new Options([ 'traces_sampler' => static function (): float { - return 1; + return 1.0; }, ]), new TransactionContext(), true, ]; - yield [ + yield 'Acceptable but too low float value returned from traces_sampler' => [ new Options([ 'traces_sampler' => static function (): float { - return 0; + return 0.0; }, ]), new TransactionContext(), false, ]; - yield [ + yield 'Acceptable integer value returned from traces_sampler' => [ new Options([ 'traces_sampler' => static function (): int { return 1; @@ -487,7 +488,7 @@ public function startTransactionDataProvider(): iterable true, ]; - yield [ + yield 'Acceptable but too low integer value returned from traces_sampler' => [ new Options([ 'traces_sampler' => static function (): int { return 0; @@ -497,7 +498,7 @@ public function startTransactionDataProvider(): iterable false, ]; - yield [ + yield 'Acceptable float value returned from traces_sample_rate' => [ new Options([ 'traces_sample_rate' => 1.0, ]), @@ -505,7 +506,7 @@ public function startTransactionDataProvider(): iterable true, ]; - yield [ + yield 'Acceptable but too low float value returned from traces_sample_rate' => [ new Options([ 'traces_sample_rate' => 0.0, ]), @@ -513,7 +514,23 @@ public function startTransactionDataProvider(): iterable false, ]; - yield [ + yield 'Acceptable integer value returned from traces_sample_rate' => [ + new Options([ + 'traces_sample_rate' => 1, + ]), + new TransactionContext(), + true, + ]; + + yield 'Acceptable but too low integer value returned from traces_sample_rate' => [ + new Options([ + 'traces_sample_rate' => 0, + ]), + new TransactionContext(), + false, + ]; + + yield 'Acceptable but too low value returned from traces_sample_rate which is preferred over sample_rate' => [ new Options([ 'sample_rate' => 1.0, 'traces_sample_rate' => 0.0, @@ -522,7 +539,7 @@ public function startTransactionDataProvider(): iterable false, ]; - yield [ + yield 'Acceptable value returned from traces_sample_rate which is preferred over sample_rate' => [ new Options([ 'sample_rate' => 0.0, 'traces_sample_rate' => 1.0, @@ -531,7 +548,7 @@ public function startTransactionDataProvider(): iterable true, ]; - yield [ + yield 'Acceptable value returned from SamplingContext::getParentSampled() which is preferred over traces_sample_rate (x1)' => [ new Options([ 'traces_sample_rate' => 0.5, ]), @@ -539,13 +556,43 @@ public function startTransactionDataProvider(): iterable true, ]; - yield [ + yield 'Acceptable value returned from SamplingContext::getParentSampled() which is preferred over traces_sample_rate (x2)' => [ new Options([ 'traces_sample_rate' => 1.0, ]), new TransactionContext(TransactionContext::DEFAULT_NAME, false), false, ]; + + yield 'Out of range sample rate returned from traces_sampler (lower than minimum)' => [ + new Options([ + 'traces_sampler' => static function (): float { + return -1.0; + }, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + + yield 'Out of range sample rate returned from traces_sampler (greater than maximum)' => [ + new Options([ + 'traces_sampler' => static function (): float { + return 1.1; + }, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + + yield 'Invalid type returned from traces_sampler' => [ + new Options([ + 'traces_sampler' => static function (): string { + return 'foo'; + }, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; } public function testStartTransactionDoesNothingIfTracingIsNotEnabled(): void @@ -560,4 +607,22 @@ public function testStartTransactionDoesNothingIfTracingIsNotEnabled(): void $this->assertFalse($transaction->getSampled()); } + + public function testStartTransactionWithCustomSamplingContext(): void + { + $customSamplingContext = ['a' => 'b']; + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { + $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); + + return 1.0; + }, + ])); + + $hub = new Hub($client); + $hub->startTransaction(new TransactionContext(), $customSamplingContext); + } } diff --git a/tests/Tracing/SpanContextTest.php b/tests/Tracing/SpanContextTest.php index bd86f2acb..d5ee39046 100644 --- a/tests/Tracing/SpanContextTest.php +++ b/tests/Tracing/SpanContextTest.php @@ -8,14 +8,21 @@ use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; final class SpanContextTest extends TestCase { + use ExpectDeprecationTrait; + /** * @dataProvider fromTraceparentDataProvider + * + * @group legacy */ public function testFromTraceparent(string $header, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedSampled): void { + $this->expectDeprecation('The Sentry\\Tracing\\SpanContext::fromTraceparent() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.'); + $spanContext = SpanContext::fromTraceparent($header); if (null !== $expectedSpanId) { diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index c27835f62..1a1387cea 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -5,6 +5,8 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; +use Sentry\Tracing\SpanId; +use Sentry\Tracing\TraceId; use Sentry\Tracing\TransactionContext; final class TransactionContextTest extends TestCase @@ -22,4 +24,60 @@ public function testGettersAndSetters(): void $this->assertSame('foo', $transactionContext->getName()); $this->assertTrue($transactionContext->getParentSampled()); } + + /** + * @dataProvider fromSentryTraceDataProvider + */ + public function testFromTraceparent(string $header, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedParentSampled): void + { + $spanContext = TransactionContext::fromSentryTrace($header); + + if (null !== $expectedSpanId) { + $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); + } + + if (null !== $expectedTraceId) { + $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); + } + + $this->assertSame($expectedParentSampled, $spanContext->getParentSampled()); + } + + public function fromSentryTraceDataProvider(): iterable + { + yield [ + '0', + null, + null, + false, + ]; + + yield [ + '1', + null, + null, + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + false, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + null, + ]; + } } From 4e82aa1f34642285cc93eadaca769b3cfaa02e97 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 1 Dec 2020 10:50:02 +0100 Subject: [PATCH 0631/1161] Fix broken CI due to PHPStan update --- phpstan.neon | 3 --- 1 file changed, 3 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 1f0e46af2..00319c976 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -27,9 +27,6 @@ parameters: - message: '/^Method Sentry\\Client::getIntegration\(\) should return T of Sentry\\Integration\\IntegrationInterface\|null but returns Sentry\\Integration\\IntegrationInterface\|null\.$/' path: src/Client.php - - - message: "/Call to function in_array\\(\\) with arguments callable\\(\\): mixed&string, array\\('{closure}', '__lambda_func'\\) and true will always evaluate to false\\.$/" - path: src/FrameBuilder.php - message: "/^PHPDoc tag @param references unknown parameter: \\$customSamplingContext$/" path: src/State/HubInterface.php From 55f5714e6eff6bc9e3dc4b47ac4a0e6d72088e1e Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 1 Dec 2020 10:59:47 +0100 Subject: [PATCH 0632/1161] Prepare release of version 3.1.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e82819203..c6d67670d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## Unreleased +## 3.1.0 (2020-12-01) - Fix capturing of the request body in the `RequestIntegration` integration (#1139) - Deprecate `SpanContext::fromTraceparent()` in favor of `TransactionContext::fromSentryTrace()` (#1134) From 5c21154eb145817cbc06b29e2e5947af465d5e3a Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 1 Dec 2020 11:23:28 +0100 Subject: [PATCH 0633/1161] meta: Prepare changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef3f6b64c..714bf1acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.1.0 (2020-12-1) + - Deprecate `SpanContext::fromTraceparent()` in favor of `TransactionContext::fromSentryTrace()` (#1134) - Allow setting custom data on the sampling context by passing it as 2nd argument of the `startTransaction()` function (#1134) - Add setter for value on the `ExceptionDataBag` (#1100) From 47cd1ae46a8a2c5779ae14ecebebbd2078418f44 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 1 Dec 2020 11:33:06 +0100 Subject: [PATCH 0634/1161] Start development of version 3.2.x --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 09d992ceb..5e84b3cd5 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "3.2.x-dev" } } } From cd27630daa2491f18f826118a7a8860d0cc6e9f4 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Sat, 5 Dec 2020 22:51:56 +0100 Subject: [PATCH 0635/1161] Add support for PHP 8 (#1087) --- .appveyor.yml | 33 ++++++++++--------- .gitattributes | 4 +-- .gitignore | 1 + .travis.yml | 4 ++- CHANGELOG.md | 4 +++ composer.json | 13 ++++---- phpunit.xml.dist | 9 ++++- psalm.xml.dist | 1 - src/ErrorHandler.php | 2 +- src/Exception/ExceptionInterface.php | 2 ++ src/Exception/InvalidArgumentException.php | 2 ++ tests/BreadcrumbTest.php | 16 ++++----- tests/ClientBuilderTest.php | 16 +++++++-- tests/ClientTest.php | 28 ++++------------ tests/Serializer/AbstractSerializerTest.php | 14 ++++---- .../RepresentationSerializerTest.php | 14 ++++---- tests/Serializer/SerializerTest.php | 6 ++-- tests/SeverityTest.php | 7 ++-- tests/StacktraceTest.php | 2 +- tests/Util/JSONTest.php | 15 ++++----- ...er_captures_out_of_memory_fatal_error.phpt | 4 +-- 21 files changed, 105 insertions(+), 92 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index abb6aefe0..c84dc3372 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,6 +1,6 @@ version: 2.x-{build} build: false -clone_depth: 2 +clone_depth: 50 clone_folder: c:\projects\sentry-php skip_branch_with_pr: true image: Visual Studio 2019 @@ -8,22 +8,28 @@ image: Visual Studio 2019 environment: matrix: - PHP_VERSION: 7.2 - XDEBUG_VERSION: 2.9.2-7.2-vc15-nts + XDEBUG_VERSION: 3.0.1-7.2-vc15-nts DEPENDENCIES: lowest - PHP_VERSION: 7.2 - XDEBUG_VERSION: 2.9.2-7.2-vc15-nts + XDEBUG_VERSION: 3.0.1-7.2-vc15-nts DEPENDENCIES: highest - PHP_VERSION: 7.3 - XDEBUG_VERSION: 2.9.2-7.3-vc15-nts + XDEBUG_VERSION: 3.0.1-7.3-vc15-nts DEPENDENCIES: lowest - PHP_VERSION: 7.3 - XDEBUG_VERSION: 2.9.2-7.3-vc15-nts + XDEBUG_VERSION: 3.0.1-7.3-vc15-nts DEPENDENCIES: highest - PHP_VERSION: 7.4 - XDEBUG_VERSION: 2.9.2-7.4-vc15-nts + XDEBUG_VERSION: 3.0.1-7.4-vc15-nts DEPENDENCIES: lowest - PHP_VERSION: 7.4 - XDEBUG_VERSION: 2.9.2-7.4-vc15-nts + XDEBUG_VERSION: 3.0.1-7.4-vc15-nts + DEPENDENCIES: highest + - PHP_VERSION: 8.0 + XDEBUG_VERSION: 3.0.1-8.0-vs16-nts + DEPENDENCIES: lowest + - PHP_VERSION: 8.0 + XDEBUG_VERSION: 3.0.1-8.0-vs16-nts DEPENDENCIES: highest matrix: @@ -52,16 +58,13 @@ install: - IF %INSTALL_PHP%==1 echo extension=php_mbstring.dll >> php.ini - IF %INSTALL_PHP%==1 echo extension=php_openssl.dll >> php.ini - IF %INSTALL_PHP%==1 echo zend_extension=C:\php\ext\php_xdebug.dll >> php.ini - - IF %INSTALL_PHP%==1 echo xdebug.overload_var_dump=0 >> php.ini - - IF %INSTALL_PHP%==1 echo xdebug.collect_includes=0 >> php.ini - - IF %INSTALL_PHP%==1 echo xdebug.dump_globals=0 >> php.ini - - IF %INSTALL_PHP%==1 echo xdebug.collect_vars=0 >> php.ini - - IF %INSTALL_PHP%==1 echo xdebug.extended_info=0 >> php.ini + - IF %INSTALL_PHP%==1 echo xdebug.mode=coverage >> php.ini - cd C:\projects\sentry-php - - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/1.9.3/composer.phar + - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/2.0.8/composer.phar - php composer.phar self-update - - IF %DEPENDENCIES%==lowest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-lowest --prefer-dist - - IF %DEPENDENCIES%==highest php composer.phar update --no-progress --no-interaction --no-suggest --ansi --prefer-dist + - IF %PHP_VERSION%==8.0 php composer.phar remove --dev friendsofphp/php-cs-fixer --no-update --no-interaction + - IF %DEPENDENCIES%==lowest php composer.phar update --no-progress --no-interaction --ansi --prefer-lowest --prefer-dist + - IF %DEPENDENCIES%==highest php composer.phar update --no-progress --no-interaction --ansi --prefer-dist test_script: - cd C:\projects\sentry-php diff --git a/.gitattributes b/.gitattributes index ec85a1b1c..c067dc5ae 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,9 +16,9 @@ /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.php_cs export-ignore -/.scrutinizer.yml export-ignore +/.php_cs.dist export-ignore /.travis.yml export-ignore /Makefile export-ignore /phpstan.neon export-ignore /phpunit.xml.dist export-ignore +/psalm.xml.dist export-ignore diff --git a/.gitignore b/.gitignore index 75ef0bad0..25a58fe9c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ package.xml /vendor .idea .php_cs.cache +.phpunit.result.cache docs/_build tests/clover.xml diff --git a/.travis.yml b/.travis.yml index ebe4ca7e9..98aadecf0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - 7.2 - 7.3 - 7.4 + - 8.0 env: - dependencies=highest @@ -34,11 +35,12 @@ jobs: script: composer psalm install: + - if [ "$TRAVIS_PHP_VERSION" = "8.0" ]; then composer remove --dev friendsofphp/php-cs-fixer --no-update --no-interaction; fi; - if [ "$dependencies" = "lowest" ]; then composer update --no-interaction --no-suggest --prefer-lowest --prefer-dist; fi; - if [ "$dependencies" = "highest" ]; then composer update --no-interaction --no-suggest --prefer-dist; fi; script: >- - vendor/bin/phpunit --coverage-clover=build/coverage-report.xml && + XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover=build/coverage-report.xml && bash <(curl -s https://codecov.io/bash) -f build/coverage-report.xml notifications: diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d67670d..530b4ff61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 3.1.0 (2020-12-01) +- Add support for PHP 8.0 (#1087) +- Change the error handling for silenced fatal errors using `@` to use a mask check in order to be php 8 compatible (#1141) +- Update the `guzzlehttp/promises` package to the minimum required version compatible with PHP 8 (#1144) +- Update the `symfony/options-resolver` package to the minimum required version compatible with PHP 8 (#1144) - Fix capturing of the request body in the `RequestIntegration` integration (#1139) - Deprecate `SpanContext::fromTraceparent()` in favor of `TransactionContext::fromSentryTrace()` (#1134) - Allow setting custom data on the sampling context by passing it as 2nd argument of the `startTransaction()` function (#1134) diff --git a/composer.json b/composer.json index 09d992ceb..751f71577 100644 --- a/composer.json +++ b/composer.json @@ -20,10 +20,10 @@ } ], "require": { - "php": "^7.2", + "php": "^7.2|^8.0", "ext-json": "*", "ext-mbstring": "*", - "guzzlehttp/promises": "^1.3", + "guzzlehttp/promises": "^1.4", "guzzlehttp/psr7": "^1.7", "jean85/pretty-package-versions": "^1.5", "ocramius/package-versions": "^1.8", @@ -35,7 +35,7 @@ "psr/http-factory": "^1.0", "psr/http-message-implementation": "^1.0", "psr/log": "^1.0", - "symfony/options-resolver": "^3.4.4|^4.0|^5.0", + "symfony/options-resolver": "^3.4.43|^4.4.11|^5.0.11", "symfony/polyfill-php80": "^1.17", "symfony/polyfill-uuid": "^1.13.1" }, @@ -43,13 +43,14 @@ "friendsofphp/php-cs-fixer": "^2.16", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.3|^2.0", + "nikic/php-parser": "^4.10.3", "php-http/mock-client": "^1.3", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12", "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.5.18", - "symfony/phpunit-bridge": "^5.1", - "vimeo/psalm": "^3.4" + "phpunit/phpunit": "^8.5.13|^9.4", + "symfony/phpunit-bridge": "^5.2", + "vimeo/psalm": "^3.4|^4.2" }, "suggest": { "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8c633d455..0f5d14742 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,11 @@ - @@ -17,6 +18,12 @@ + + + src + + + src diff --git a/psalm.xml.dist b/psalm.xml.dist index 34d01006d..e1cbbfde2 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -34,7 +34,6 @@ - diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 95493f447..af00bf53d 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -265,7 +265,7 @@ public function addExceptionHandlerListener(callable $listener): void */ private function handleError(int $level, string $message, string $file, int $line, ?array $errcontext = []): bool { - if (0 === error_reporting()) { + if (0 === (error_reporting() & $level)) { $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); } else { $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index b957bd2e1..d4be5c56d 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -8,6 +8,8 @@ * This interface must be implemented by all exception classes of this library. * * @author Stefano Arlandini + * + * @deprecated since version 3.1, to be removed in 4.0 */ interface ExceptionInterface { diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 711a78d68..745f0e82b 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -9,6 +9,8 @@ * the expected value. * * @author Stefano Arlandini + * + * @deprecated since version 3.1, to be removed in 4.0 */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { diff --git a/tests/BreadcrumbTest.php b/tests/BreadcrumbTest.php index ec0684d12..7499acb42 100644 --- a/tests/BreadcrumbTest.php +++ b/tests/BreadcrumbTest.php @@ -12,21 +12,19 @@ */ final class BreadcrumbTest extends TestCase { - /** - * @expectedException \Sentry\Exception\InvalidArgumentException - * @expectedExceptionMessage The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants. - */ public function testConstructorThrowsOnInvalidLevel(): void { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); + new Breadcrumb('foo', 'bar', 'baz'); } - /** - * @expectedException \Sentry\Exception\InvalidArgumentException - * @expectedExceptionMessage The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants. - */ - public function testSetLevelThrowsOnInvalidLevel(): void + public function testWithLevelThrowsOnInvalidLevel(): void { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo'); $breadcrumb->withLevel('bar'); } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 7e95f8caa..2f123c9b2 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -13,6 +13,7 @@ use Sentry\Options; use Sentry\Transport\HttpTransport; use Sentry\Transport\NullTransport; +use Sentry\Transport\TransportInterface; final class ClientBuilderTest extends TestCase { @@ -20,7 +21,7 @@ public function testHttpTransportIsUsedWhenServerIsConfigured(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); - $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); + $transport = $this->getTransport($clientBuilder->getClient()); $this->assertInstanceOf(HttpTransport::class, $transport); } @@ -29,7 +30,7 @@ public function testNullTransportIsUsedWhenNoServerIsConfigured(): void { $clientBuilder = new ClientBuilder(); - $transport = $this->getObjectAttribute($clientBuilder->getClient(), 'transport'); + $transport = $this->getTransport($clientBuilder->getClient()); $this->assertInstanceOf(NullTransport::class, $transport); } @@ -84,6 +85,17 @@ public function testCreateWithNoOptionsIsTheSameAsDefaultOptions(): void ClientBuilder::create([]) ); } + + private function getTransport(Client $client): TransportInterface + { + $property = new \ReflectionProperty(Client::class, 'transport'); + + $property->setAccessible(true); + $value = $property->getValue($client); + $property->setAccessible(false); + + return $value; + } } final class StubIntegration implements IntegrationInterface diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 137b01aee..0e5aa45af 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -6,8 +6,8 @@ use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; -use PHPUnit\Framework\MockObject\Matcher\Invocation; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Sentry\Client; @@ -271,7 +271,7 @@ public function testCaptureLastError(): void $this->assertNotNull($client->captureLastError()); - $this->clearLastError(); + error_clear_last(); } public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void @@ -280,14 +280,13 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) ->method('send') - ->with($this->anything()) - ->willReturn(null); + ->with($this->anything()); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $this->clearLastError(); + error_clear_last(); $this->assertNull($client->captureLastError()); } @@ -320,14 +319,13 @@ public function testSendChecksBeforeSendOption(): void /** * @dataProvider processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDataProvider */ - public function testProcessEventDiscardsEventWhenItIsSampledDueToSampleRateOption(float $sampleRate, Invocation $transportCallInvocationMatcher, Invocation $loggerCallInvocationMatcher): void + public function testProcessEventDiscardsEventWhenItIsSampledDueToSampleRateOption(float $sampleRate, InvocationOrder $transportCallInvocationMatcher, InvocationOrder $loggerCallInvocationMatcher): void { /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($transportCallInvocationMatcher) ->method('send') - ->with($this->anything()) - ->willReturn(null); + ->with($this->anything()); /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); @@ -448,20 +446,6 @@ public function testFlush(): void $this->assertTrue($promise->wait()); } - /** - * @see https://github.com/symfony/polyfill/blob/52332f49d18c413699d2dccf465234356f8e0b2c/src/Php70/Php70.php#L52-L61 - */ - private function clearLastError(): void - { - set_error_handler(static function (): bool { - return false; - }); - - @trigger_error(''); - - restore_error_handler(); - } - private function createTransportFactory(TransportInterface $transport): TransportFactoryInterface { return new class($transport) implements TransportFactoryInterface { diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 0ec7c17c8..7d421f17e 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -16,7 +16,7 @@ abstract protected function createSerializer(): AbstractSerializer; /** * This method is only existed because of testSerializeCallable. */ - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { } @@ -331,7 +331,7 @@ public function testBrokenEncoding(bool $serializeAllObjects): void $input = pack('H*', $key); $result = $this->invokeSerialization($serializer, $input); - $this->assertInternalType('string', $result); + $this->assertIsString($result); if (\function_exists('mb_detect_encoding')) { $this->assertContains(mb_detect_encoding($result), ['ASCII', 'UTF-8']); @@ -354,7 +354,7 @@ public function testLongString(bool $serializeAllObjects): void $input = str_repeat('x', $length); $result = $this->invokeSerialization($serializer, $input); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertLessThanOrEqual(1024, \strlen($result)); } } @@ -378,7 +378,7 @@ public function testSerializeValueResource(bool $serializeAllObjects): void $result = $this->invokeSerialization($serializer, $resource); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertSame('Resource stream', $result); } @@ -449,15 +449,15 @@ public function serializableCallableProvider(): array ], [ 'callable' => [TestCase::class, 'setUpBeforeClass'], - 'expected' => 'Callable PHPUnit\\Framework\\TestCase::setUpBeforeClass []', + 'expected' => 'Callable void PHPUnit\\Framework\\TestCase::setUpBeforeClass []', ], [ 'callable' => [$this, 'setUpBeforeClass'], - 'expected' => 'Callable ' . __CLASS__ . '::setUpBeforeClass []', + 'expected' => 'Callable void ' . __CLASS__ . '::setUpBeforeClass []', ], [ 'callable' => [self::class, 'setUpBeforeClass'], - 'expected' => 'Callable ' . __CLASS__ . '::setUpBeforeClass []', + 'expected' => 'Callable void ' . __CLASS__ . '::setUpBeforeClass []', ], [ 'callable' => [SerializerTestObject::class, 'testy'], diff --git a/tests/Serializer/RepresentationSerializerTest.php b/tests/Serializer/RepresentationSerializerTest.php index e2510759c..8d512caec 100644 --- a/tests/Serializer/RepresentationSerializerTest.php +++ b/tests/Serializer/RepresentationSerializerTest.php @@ -39,7 +39,7 @@ public function testIntsBecomeStrings(bool $serializeAllObjects): void $result = $serializer->representationSerialize(1); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertSame('1', $result); } @@ -56,7 +56,7 @@ public function testFloatsBecomeStrings(bool $serializeAllObjects): void $result = $serializer->representationSerialize(1.5); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertSame('1.5', $result); } @@ -77,7 +77,7 @@ public function testBooleansBecomeStrings(bool $serializeAllObjects): void $result = $serializer->representationSerialize(false); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertSame('false', $result); } @@ -94,7 +94,7 @@ public function testNullsBecomeString(bool $serializeAllObjects): void $result = $serializer->representationSerialize(null); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertSame('null', $result); } @@ -111,17 +111,17 @@ public function testSerializeRoundedFloat(bool $serializeAllObjects): void $result = $serializer->representationSerialize((float) 1); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertSame('1.0', $result); $result = $serializer->representationSerialize(floor(5 / 2)); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertSame('2.0', $result); $result = $serializer->representationSerialize(floor(12345.678901234)); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertSame('12345.0', $result); } diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index f04e55e00..d8d7e2fd0 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -40,7 +40,7 @@ public function testIntsAreInts(bool $serializeAllObjects): void $result = $this->invokeSerialization($serializer, 1); - $this->assertInternalType('integer', $result); + $this->assertIsInt($result); $this->assertSame(1, $result); } @@ -57,7 +57,7 @@ public function testFloats(bool $serializeAllObjects): void $result = $this->invokeSerialization($serializer, 1.5); - $this->assertInternalType('double', $result); + $this->assertIsFloat($result); $this->assertSame(1.5, $result); } @@ -174,7 +174,7 @@ public function testLongStringWithOverwrittenMessageLength(): void $input = str_repeat('x', $length); $result = $this->invokeSerialization($serializer, $input); - $this->assertInternalType('string', $result); + $this->assertIsString($result); $this->assertLessThanOrEqual(500, \strlen($result)); } } diff --git a/tests/SeverityTest.php b/tests/SeverityTest.php index c6c000a4d..661088cab 100644 --- a/tests/SeverityTest.php +++ b/tests/SeverityTest.php @@ -9,12 +9,11 @@ final class SeverityTest extends TestCase { - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage The "foo" is not a valid enum value. - */ public function testConstructorThrowsOnInvalidValue(): void { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "foo" is not a valid enum value.'); + new Severity('foo'); } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index b05014276..e3de8fa7e 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -27,7 +27,7 @@ public function testConstructorThrowsIfFramesListIsEmpty(): void public function testConstructorThrowsIfFramesListContainsUnexpectedValue(array $values, string $expectedExceptionMessage): void { $this->expectException(\UnexpectedValueException::class); - $this->expectExceptionMessageRegExp($expectedExceptionMessage); + $this->expectExceptionMessageMatches($expectedExceptionMessage); new Stacktrace($values); } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index 3489182dd..78b4f6aa2 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -5,6 +5,7 @@ namespace Sentry\Tests\Util; use PHPUnit\Framework\TestCase; +use Sentry\Exception\JsonException; use Sentry\Tests\Util\Fixtures\JsonSerializableClass; use Sentry\Tests\Util\Fixtures\SimpleClass; use Sentry\Util\JSON; @@ -98,10 +99,6 @@ public function encodeSubstitutesInvalidUtf8CharactersDataProvider(): \Generator ]; } - /** - * @expectedException \Sentry\Exception\JsonException - * @expectedExceptionMessage Could not encode value into JSON format. Error was: "Type is not supported". - */ public function testEncodeThrowsIfValueIsResource(): void { $resource = fopen('php://memory', 'r'); @@ -110,6 +107,9 @@ public function testEncodeThrowsIfValueIsResource(): void fclose($resource); + $this->expectException(JsonException::class); + $this->expectExceptionMessage('Could not encode value into JSON format. Error was: "Type is not supported".'); + JSON::encode($resource); } @@ -151,12 +151,11 @@ public function decodeDataProvider(): \Generator ]; } - /** - * @expectedException \Sentry\Exception\JsonException - * @expectedExceptionMessage Could not decode value from JSON format. Error was: "Syntax error". - */ public function testDecodeThrowsIfValueIsNotValidJson(): void { + $this->expectException(JsonException::class); + $this->expectExceptionMessage('Could not decode value from JSON format. Error was: "Syntax error".'); + JSON::decode('foo'); } } diff --git a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt index 68e207879..20cf5ff13 100644 --- a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt @@ -1,7 +1,7 @@ --TEST-- Test catching out of memory fatal error --INI-- -memory_limit=20M +memory_limit=64M --FILE-- addExceptionHandlerListener(static function (): void { echo 'Exception listener called (it should not have been)' . PHP_EOL; }); -$foo = str_repeat('x', 1024 * 1024 * 30); +$foo = str_repeat('x', 1024 * 1024 * 70); ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d From 2a0ecb127dbccf93fb5a37df907ce08822a62e6c Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 5 Dec 2020 22:59:39 +0100 Subject: [PATCH 0636/1161] Prepare release 3.1.1 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 530b4ff61..80645d4ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ # CHANGELOG -## 3.1.0 (2020-12-01) +## 3.1.1 (2020-12-07) - Add support for PHP 8.0 (#1087) - Change the error handling for silenced fatal errors using `@` to use a mask check in order to be php 8 compatible (#1141) - Update the `guzzlehttp/promises` package to the minimum required version compatible with PHP 8 (#1144) - Update the `symfony/options-resolver` package to the minimum required version compatible with PHP 8 (#1144) + +## 3.1.0 (2020-12-01) + - Fix capturing of the request body in the `RequestIntegration` integration (#1139) - Deprecate `SpanContext::fromTraceparent()` in favor of `TransactionContext::fromSentryTrace()` (#1134) - Allow setting custom data on the sampling context by passing it as 2nd argument of the `startTransaction()` function (#1134) From 9f734af4ce8fb8ee45723cdfe9a5f633774a6373 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Tue, 15 Dec 2020 18:54:53 +0100 Subject: [PATCH 0637/1161] Switch from Travis CI and AppVeyor to GitHub Actions (#1150) Co-authored-by: Stefano Arlandini --- .appveyor.yml | 72 ---- .github/workflows/ci.yaml | 67 +++ .github/workflows/static-analysis.yaml | 59 +++ .travis.yml | 54 --- CHANGELOG.md | 408 ------------------- README.md | 22 +- composer.json | 4 +- src/Options.php | 2 +- tests/Integration/RequestIntegrationTest.php | 2 +- 9 files changed, 140 insertions(+), 550 deletions(-) delete mode 100644 .appveyor.yml create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/static-analysis.yaml delete mode 100644 .travis.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index c84dc3372..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,72 +0,0 @@ -version: 2.x-{build} -build: false -clone_depth: 50 -clone_folder: c:\projects\sentry-php -skip_branch_with_pr: true -image: Visual Studio 2019 - -environment: - matrix: - - PHP_VERSION: 7.2 - XDEBUG_VERSION: 3.0.1-7.2-vc15-nts - DEPENDENCIES: lowest - - PHP_VERSION: 7.2 - XDEBUG_VERSION: 3.0.1-7.2-vc15-nts - DEPENDENCIES: highest - - PHP_VERSION: 7.3 - XDEBUG_VERSION: 3.0.1-7.3-vc15-nts - DEPENDENCIES: lowest - - PHP_VERSION: 7.3 - XDEBUG_VERSION: 3.0.1-7.3-vc15-nts - DEPENDENCIES: highest - - PHP_VERSION: 7.4 - XDEBUG_VERSION: 3.0.1-7.4-vc15-nts - DEPENDENCIES: lowest - - PHP_VERSION: 7.4 - XDEBUG_VERSION: 3.0.1-7.4-vc15-nts - DEPENDENCIES: highest - - PHP_VERSION: 8.0 - XDEBUG_VERSION: 3.0.1-8.0-vs16-nts - DEPENDENCIES: lowest - - PHP_VERSION: 8.0 - XDEBUG_VERSION: 3.0.1-8.0-vs16-nts - DEPENDENCIES: highest - -matrix: - fast_finish: true - -cache: - - composer.phar - - '%LOCALAPPDATA%\Composer\files' - - C:\php -> .appveyor.yml - -init: - - SET PATH=C:\php;%PATH% - - SET ANSICON=121x90 (121x90) - - SET INSTALL_PHP=1 - -install: - - IF EXIST C:\php SET INSTALL_PHP=0 - - ps: choco upgrade chocolatey --confirm --no-progress --allow-downgrade --version 0.10.13 - - ps: choco install codecov --confirm --no-progress - - ps: choco install php --confirm --no-progress --package-parameters '""/InstallDir:C:\php""' --version (choco search php --exact --all-versions --limit-output | Select-String -Pattern $env:PHP_VERSION | ForEach-Object {$_ -Replace "php\|", ""} | Sort {[version] $_} -Descending | Select-Object -First 1) - - cd C:\php - - ps: if ($env:INSTALL_PHP -imatch 1) { appveyor-retry appveyor DownloadFile "https://xdebug.org/files/php_xdebug-$env:XDEBUG_VERSION-x86_64.dll" -FileName C:\php\ext\php_xdebug.dll } - - IF %INSTALL_PHP%==1 copy /Y php.ini-production php.ini - - IF %INSTALL_PHP%==1 echo extension_dir=C:\php\ext >> php.ini - - IF %INSTALL_PHP%==1 echo extension=php_curl.dll >> php.ini - - IF %INSTALL_PHP%==1 echo extension=php_mbstring.dll >> php.ini - - IF %INSTALL_PHP%==1 echo extension=php_openssl.dll >> php.ini - - IF %INSTALL_PHP%==1 echo zend_extension=C:\php\ext\php_xdebug.dll >> php.ini - - IF %INSTALL_PHP%==1 echo xdebug.mode=coverage >> php.ini - - cd C:\projects\sentry-php - - IF NOT EXIST composer.phar appveyor-retry appveyor DownloadFile https://github.com/composer/composer/releases/download/2.0.8/composer.phar - - php composer.phar self-update - - IF %PHP_VERSION%==8.0 php composer.phar remove --dev friendsofphp/php-cs-fixer --no-update --no-interaction - - IF %DEPENDENCIES%==lowest php composer.phar update --no-progress --no-interaction --ansi --prefer-lowest --prefer-dist - - IF %DEPENDENCIES%==highest php composer.phar update --no-progress --no-interaction --ansi --prefer-dist - -test_script: - - cd C:\projects\sentry-php - - vendor\bin\phpunit.bat --coverage-clover=build/coverage-report.xml - - codecov -f build/coverage-report.xml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..b59deb5b9 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,67 @@ +name: CI + +on: + pull_request: + push: + branches: + - master + - develop + +jobs: + tests: + name: Tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + php: + - '7.2' + - '7.3' + - '7.4' + - '8.0' + dependencies: + - lowest + - highest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + + - name: Setup Problem Matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Determine Composer cache directory + id: composer-cache + run: echo "::set-output name=directory::$(composer config cache-dir)" + + - name: Cache Composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.directory }} + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.dependencies }}-composer- + + - name: Install highest dependencies + run: composer update --no-progress --no-interaction --prefer-dist + if: ${{ matrix.dependencies == 'highest' }} + + - name: Install lowest dependencies + run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest + if: ${{ matrix.dependencies == 'lowest' }} + + - name: Run tests + run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml + + - name: Upload code coverage + uses: codecov/codecov-action@v1 + with: + file: build/coverage-report.xml diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml new file mode 100644 index 000000000..12ae66997 --- /dev/null +++ b/.github/workflows/static-analysis.yaml @@ -0,0 +1,59 @@ +name: Code style and static analysis + +on: + pull_request: + push: + branches: + - master + - develop + +jobs: + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist + + - name: Run script + run: composer phpcs + + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist + + - name: Run script + run: composer phpstan + + psalm: + name: Psalm + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist + + - name: Run script + run: composer psalm diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 98aadecf0..000000000 --- a/.travis.yml +++ /dev/null @@ -1,54 +0,0 @@ -language: php - -php: - - 7.2 - - 7.3 - - 7.4 - - 8.0 - -env: - - dependencies=highest - - dependencies=lowest - -cache: - directories: - - $HOME/.composer/cache - -stages: - - Code style & static analysis - - Test - -jobs: - fast_finish: true - include: - - &code-style-static-analysis - stage: Code style & static analysis - name: PHP-CS-Fixer - php: 7.4 - env: dependencies=highest - script: composer phpcs - - <<: *code-style-static-analysis - name: PHPStan - script: composer phpstan - - <<: *code-style-static-analysis - name: Psalm - script: composer psalm - -install: - - if [ "$TRAVIS_PHP_VERSION" = "8.0" ]; then composer remove --dev friendsofphp/php-cs-fixer --no-update --no-interaction; fi; - - if [ "$dependencies" = "lowest" ]; then composer update --no-interaction --no-suggest --prefer-lowest --prefer-dist; fi; - - if [ "$dependencies" = "highest" ]; then composer update --no-interaction --no-suggest --prefer-dist; fi; - -script: >- - XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover=build/coverage-report.xml && - bash <(curl -s https://codecov.io/bash) -f build/coverage-report.xml - -notifications: - webhooks: - urls: - - https://zeus.ci/hooks/cf8597c4-ffba-11e7-89c9-0a580a281308/public/provider/travis/webhook - on_success: always - on_failure: always - on_start: always - on_cancel: always - on_error: always diff --git a/CHANGELOG.md b/CHANGELOG.md index 80645d4ea..26abeaae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,411 +98,3 @@ using an on-premise installation it requires Sentry version `>= v20.6.0` to work - [BC BREAK] Remove the `SpoolTransport` transport and all its related classes (#1080) - Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) - Refactor how the event data gets serialized to JSON (#1077) - -### 2.5.0 (2020-09-14) - -- Support the `timeout` and `proxy` options for the Symfony HTTP Client (#1084) - -### 2.4.3 (2020-08-13) - -- Fix `Options::setEnvironment` method not accepting `null` values (#1057) -- Fix the capture of the request body in the `RequestIntegration` integration when the stream size is unknown (#1064) - -### 2.4.2 (2020-07-24) - -- Fix typehint errors while instantiating the Httplug cURL client by forcing the usage of PSR-17 complaint factories (#1052) - -### 2.4.1 (2020-07-03) - -- Fix HTTP client connection timeouts not being applied if an HTTP proxy is specified (#1033) -- [BC CHANGE] Revert "Add support for iterables in the serializer (#991)" (#1030) - -### 2.4.0 (2020-05-21) - -- Enforce a timeout for connecting to the server and for the requests instead of waiting indefinitely (#979) -- Add `RequestFetcherInterface` to allow customizing the request data attached to the logged event (#984) -- Log internal debug and error messages to a PSR-3 compatible logger (#989) -- Make `AbstractSerializer` to accept `Traversable` values using `is_iterable` instead of `is_array` (#991) -- Refactor the `ModulesIntegration` integration to improve its code and its tests (#990) -- Extract the parsing and validation logic of the DSN into its own value object (#995) -- Support passing either a Httplug or PSR-17 stream factory to the `GzipEncoderPlugin` class (#1012) -- Add the `FrameContextifierIntegration` integration (#1011) -- Add missing validation for the `context_lines` option and fix its behavior when passing `null` to make it working as described in the documentation (#1003) -- Trim the file path from the anonymous class name in the stacktrace according to the `prefixes` option (#1016) - -## 2.3.2 (2020-03-06) - -- Hard-limit concurrent requests in `HttpTransport` and removed pre-init of promises (fixes "too many open files" errors) (#981) -- Fix `http_proxy` option not being applied (#978) -- Fix the error handler rethrowing the captured exception when previous handler didn't (#974) - -## 2.3.1 (2020-01-23) - -- Allow unsetting the stack trace on an `Event` by calling `Event::setStacktrace(null)` (#961) -- Fix sending of both `event.stacktrace` and `event.exceptions` when `attach_stacktrace = true` (#960) -- Fix regression that set all frames of a stacktrace as not in app by default (#958) -- Fix issues with memory addresses in anonymous class stack traces (#956) -- Fix exception thrown regardless of whether the HTTP client was instantiated when using the `http_proxy option` (#951) - -## 2.3.0 (2020-01-08) - -- Add `in_app_include` option to whitelist paths that should be marked as part of the app (#909) -- Fix `Client::captureEvent` not considering the `attach_stacktrace` option (#940) -- Replace `ramsey/uuid` dependency with `uuid_create` from the PECL [`uuid`](https://pecl.php.net/package/uuid) extension or [`symfony/polyfill-uuid`](https://github.com/symfony/polyfill-uuid) (#937) -- Deprecate `Scope::setUser` behaviour of replacing user data. (#929) -- Add the `$merge` parameter on `Scope::setUser` to allow merging user context. (#929) -- Make the `integrations` option accept a `callable` that will receive the list of default integrations and returns a customized list (#919) -- Add the `IgnoreErrorsIntegration` integration to deprecate and replace the `exclude_exceptions` option (#928) -- Allow setting custom contexts on the scope and on the event (#839) -- Replace dependency to `zendframework/zend-diactoros` with `guzzlehttp/psr7` (#945) - -## 2.2.6 (2019-12-18) - -- Fix remaining PHP 7.4 deprecations (#930) -- Fix error thrown during JSON encoding if a string contains invalid UTF-8 characters (#934) - -## 2.2.5 (2019-11-27) - -- Add compatibility with Symfony 5 (#925) -- Ensure compatibility with PHP 7.4 (#894, #926) - -## 2.2.4 (2019-11-04) - -- Suggest installing Monolog to send log messages directly to Sentry (#908) -- Make the `$errcontext` argument of the `ErrorHandler::handleError()` method `nullable` (#917) - -## 2.2.3 (2019-10-31) - -- Fix deprecation raised when serializing callable in certain circumstances (#821) -- Fix incorrect `critical` breadcrumb level by replacing it with the `fatal` level (#901) -- Fix regression on default sending behavior of the `HttpTransport` transport (#905) -- Fix stacktrace frame inApp detection: all paths outside the project_root are now considered as not in app (#911) - -## 2.2.2 (2019-10-10) - -- Fix handling of fifth argument in the error handler (#892) -- Catch exception from vendors in `Sentry\Transport\HttpTransport` (#899) - -## 2.2.1 (2019-09-23) - -- Disable default deprecation warning `Sentry\Transport\HttpTransport` (#884) - -## 2.2.0 (2019-09-23) - -- Change type hint for both parameter and return value of `HubInterface::getCurrentHub` and `HubInterface::setCurrentHub()` methods (#849) -- Add the `setTags`, `setExtras` and `clearBreadcrumbs` methods to the `Scope` class (#852) -- Silently cast numeric values to strings when trying to set the tags instead of throwing (#858) -- Support force sending events on-demand and fix sending of events in long-running processes (#813) -- Update PHPStan and introduce Psalm (#846) -- Add an integration to set the transaction attribute of the event (#865) -- Deprecate `Hub::getCurrent` and `Hub::setCurrent` methods to set the current hub instance (#847) - -## 2.1.3 (2019-09-06) - -- Fix GZIP-compressed requests failing when `exit($code)` was used to terminate the application (#877) - -## 2.1.2 (2019-08-22) - -- Fix `TypeError` in `Sentry\Monolog\Handler` when the extra data array has numeric keys (#833). -- Fix sending of GZIP-compressed requests when the `enable_compression` option is `true` (#857) -- Fix error thrown when trying to set the `transaction` attribute of the event in a CLI environment (#862) -- Fix integrations that were not skipped if the client bound to the current hub was not using them (#861) -- Fix undefined index generated by missing function in class (#823) - -## 2.1.1 (2019-06-13) - -- Fix the behavior of the `excluded_exceptions` option: now it's used to skip capture of exceptions, not to purge the - `exception` data of the event, which resulted in broken or empty chains of exceptions in reported events (#822) -- Fix handling of uploaded files in the `RequestIntegration`, to respect the PSR-7 spec fully (#827) -- Fix use of `REMOTE_ADDR` server variable rather than HTTP header -- Fix exception, open_basedir restriction in effect (#824) - -## 2.1.0 (2019-05-22) - -- Mark Sentry internal frames when using `attach_stacktrace` as `in_app` `false` (#786) -- Increase default severity of `E_RECOVERABLE_ERROR` to `Severity::ERROR`, instead of warning (#792) -- Make it possible to register fatal error listeners separately from the error listeners - and change the type of the reported exception to `\Sentry\Exception\FatalErrorException` (#788) -- Add a static factory method to create a breadcrumb from an array of data (#798) -- Add support for `SENTRY_ENVRIONMENT` and `SENTRY_RELEASE` environment variables (#810) -- Add the `class_serializers` option to make it possible to customize how objects are serialized in the event payload (#809) -- Fix the default value of the `$exceptions` property of the Event class (#806) -- Add a Monolog handler (#808) -- Allow capturing the body of an HTTP request (#807) -- Capture exceptions during serialization, to avoid hard failures (#818) - -## 2.0.1 (2019-03-01) - -- Do no longer report silenced errors by default (#785) -- New option `capture_silenced_error` to enable reporting of silenced errors, disabled by default (#785) - -## 2.0.0 (2019-02-25) - -**Version 2.0.0 is a complete rewrite of the existing SDK. Code Changes are needed. Please see [UPGRADE 2.0](https://github.com/getsentry/sentry-php/blob/master/UPGRADE-2.0.md) for more details.** - -- Updated .gitattributes to reduce package footprint (#770) -- Use multibyte functions to handle unicode paths (#774) -- Remove `Hub::getScope()` to deny direct access to `Scope` instances (#776) -- Reintroduce `http_proxy` option (#775) -- Added support for HTTPlug 2 / PSR-18 (#777) - -## 2.0.0-beta2 (2019-02-11) -- Rename `SentryAuth` class to `SentryAuthentication` (#742) -- `Client` class is now final -- Fix issue with `ClientBuilder`: factories are not instantiated if transport is set manually (#747) -- Rename `excluded_paths` to `in_app_exclude` option to follow Unified API spec (#755) -- Add `max_value_length` option to trim long values during serialization (#754) -- Lower the default `send_attempts` to 3 (#760) -- Fix method argument name handling when Xdebug is enabled (#763) -- Add CI build under Windows with AppVeyor (#758) and fix some bugs -- Change the `ErrorHandler` and default integrations behavior: the handler is now a singleton, - and it's possible to attach a number of callables as listeners for errors and exceptions (#762) -- The `context_lines` options changed the default to `5` and is properly applied (#743) -- Add support for "formatted messages" in `captureEvent` as payload (#752) -- Fix issue when capturing exceptions to remove warning when converting array args (#761) - -## 2.0.0-beta1 (2018-12-19) - -- Require PHP >= 7.1 -- Refactor the whole codebase to support the Unified API SDK specs -- See the UPGRADE.md document for more information. - -## 1.10.0 (2018-11-09) - -- Added passing data from context in monolog breadcrumb handler (#683) -- Do not return error id if we know we did not send the error (#667) -- Do not force IPv4 protocol by default (#654) - -## 1.9.2 (2018-08-18) - -- Remove secret_key from required keys for CLI test command. (#645) -- Proper case in Raven_Util class name usage. (#642) -- Support longer credit card numbers. (#635) -- Use configured message limit when creating serializers. (#634) -- Do not truncate strings if message limit is set to zero. (#630) -- Add option to ignore SERVER_PORT getting added to url. (#629) -- Cleanup the PHP version reported. (#604) - -## 1.9.1 (2018-06-19) - -- Allow the use of a public DSN (private part of the DSN was deprecated in Sentry 9) (#615) -- Send transaction as transaction not as culprit (#601) - -## 1.9.0 (2018-05-03) - -- Fixed undefined variable (#588) -- Fix for exceptions throwing exceptions when setting event id (#587) -- Fix monolog handler not accepting Throwable (#586) -- Add `excluded_exceptions` option to exclude exceptions and their extending exceptions (#583) -- Fix `HTTP_X_FORWARDED_PROTO` header detection (#578) -- Fix sending events async in PHP 5 (#576) -- Avoid double reporting due to `ErrorException`s (#574) -- Make it possible to overwrite serializer message limit of 1024 (#559) -- Allow request data to be nested up to 5 levels deep (#554) -- Update serializer to handle UTF-8 characters correctly (#553) - -## 1.8.4 (2018-03-20) - -- Revert ignoring fatal errors on PHP 7+ (#571) -- Add PHP runtime information (#564) -- Cleanup the `site` value if it's empty (#555) -- Add `application/json` input handling (#546) - -## 1.8.3 (2018-02-07) - -- Serialize breadcrumbs to prevent issues with binary data (#538) -- Fix notice array_key_exists() expects parameter 2 to be array, null given (#527) - -## 1.8.2 (2017-12-21) - -- Improve handling DSN with "null" like values (#522) -- Prevent warning in Raven_Stacktrace (#493) - -## 1.8.1 (2017-11-09) - -- Add setters for the serializers on the `Raven_Client` (#515) -- Avoid to capture `E_ERROR` in PHP 7+, because it's also a `Throwable` that gets captured and duplicates the error (#514) - -## 1.8.0 (2017-10-29) - -- Use namespaced classes in test for PHPUnit (#506) -- Prevent segmentation fault on PHP `<5.6` (#504) -- Remove `ini_set` call for unneeded functionality (#501) -- Exclude single `.php` files from the app path (#500) -- Start testing PHP 7.2 (#489) -- Exclude anonymous frames from app path (#482) - -## 1.7.1 (2017-08-02) - -- Fix of filtering sensitive data when there is an exception with multiple 'values' (#483) - -## 1.7.0 (2017-06-07) - -- Corrected some issues with argument serialization in stacktraces (#399). -- The default exception handler will now re-raise exceptions when `call_existing` is true and no exception handler is registered (#421). -- Collect `User.ip_address` automatically (#419). -- Added a processor to remove web cookies. It will be enabled by default in `2.0` (#405). -- Added a processor to remove HTTP body data for POST, PUT, PATCH and DELETE requests. It will be enabled by default in `2.0` (#405). -- Added a processor to sanitize HTTP headers (e.g. the Authorization header) (#428). -- Added a processor to remove `pre_context`, `context_line` and `post_context` informations from reported exceptions (#429). - -## 1.6.2 (2017-02-03) - -- Fixed behavior where fatal errors weren't correctly being reported in most situations. - -## 1.6.1 (2016-12-14) - -- Correct handling of null in `user_context`. - -## 1.6.0 (2016-12-09) - -- Improved serialization of certain types to be more restrictive. -- `error_types` can now be configured via `RavenClient`. -- Class serialization has been expanded to include attributes. -- The session extension is no longer required. -- Monolog is no longer a required dependency. -- `user_context` now merges by default. - -## 1.5.0 (2016-09-29) - -- Added named transaction support. - -## 1.4.0 (2016-09-20) - -This version primarily overhauls the exception/stacktrace generation to fix -a few bugs and improve the quality of data (#359). - -- Added `excluded_app_paths` config. -- Removed `shift_vars` config. -- Correct fatal error handling to only operate on expected types. This also fixes some behavior with the error suppression operator. -- Expose anonymous and similar frames in the stacktrace. -- Default `prefixes` to PHP's include paths. -- Remove `module` usage. -- Better handle empty argument context. -- Correct alignment of filename (current frame) and function (caller frame) - -## 1.3.0 (2016-12-19) - -- Fixed an issue causing the error suppression operator to not be respected (#335) -- Fixed some serialization behavior (#352) -- Fixed an issue with app paths and trailing slashes (#350) -- Handle non-latin encoding with source code context line (#345) - -## 1.2.0 (2016-12-08) - -- Handle non-latin encoding in source code and exception values (#342) -- Ensure pending events are sent on shutdown by default (#338) -- Add `captureLastError` helper (#334) -- Dont report duplicate errors with fatal error handler (#334) -- Enforce maximum length for string serialization (#329) - -## 1.1.0 (2016-07-30) - -- Uncoercable values should no longer prevent exceptions from sending - to the Sentry server. -- `install()` can no longer be called multiple times. - -## 1.0.0 (2016-07-28) - -- Removed deprecated error codes configuration from ErrorHandler. -- Removed env data from HTTP interface. -- Removed `message` attribute from exceptions. -- appPath and prefixes are now resolved fully. -- Fixed various getter methods requiring invalid args. -- Fixed data mutation with `send_callback`. - -## 0.22.0 (2016-06-23) - -- Improve handling of encodings. -- Improve resiliency of variable serialization. -- Add 'formatted' attribute to Message interface. - -## 0.21.0 (2016-06-10) - -- Added `transport` option. -- Added `install()` shortcut. - -## 0.20.0 (2016-06-02) - -- Handle missing function names on frames. -- Remove suppression operator usage in breadcrumbs buffer. -- Force serialization of context values. - -## 0.19.0 (2016-05-27) - -- Add `error_reporting` breadcrumb handler. - -## 0.18.0 (2016-05-17) - -- Remove session from serialized data. -- `send_callback` return value must now be false to prevent capture. -- Add various getter/setter methods for configuration. - -## 0.17.0 (2016-05-11) - -- Don't attempt to serialize fixed SDK inputs. -- Improvements to breadcrumbs support in Monolog. - -## 0.16.0 (2016-05-03) - -- Initial breadcrumbs support with Monolog handler. - -## 0.15.0 (2016-04-29) - -- Fixed some cases where serialization wouldn't happen. -- Added sdk attribute. - -## 0.14.0 (2016-04-27) - -- Added `prefixes` option for stripping absolute paths. -- Removed `abs_path` from stacktraces. -- Added `app_path` to specify application root for resolving `in_app` on frames. -- Moved Laravel support to `sentry-laravel` project. -- Fixed duplicate stack computation. -- Added `dsn` option to ease configuration. -- Fixed an issue with the curl async transport. -- Improved serialization of values. - -## 0.13.0 (2015-09-09) - -- Updated API to use new style interfaces. -- Remove session cookie in default processor. -- Expand docs for Laravel, Symfony2, and Monolog. -- Default error types can now be set as part of ErrorHandler configuration. - -## 0.12.1 (2015-07-26) - -- Dont send empty values for various context. - -## 0.12.0 (2015-05-19) - -- Bumped protocol version to 6. -- Fixed an issue with the async curl handler (GH-216). -- Removed UDP transport. - -## 0.11.0 (2015-03-25) - -- New configuration parameter: `release` -- New configuration parameter: `message_limit` -- New configuration parameter: `curl_ssl_version` -- New configuration parameter: `curl_ipv4` -- New configuration parameter: `verify_ssl` -- Updated remote endpoint to use modern project-based path. -- Expanded default sanitizer support to include `auth_pw` attribute. - -## 0.10.0 (2014-09-03) - -- Added a default certificate bundle which includes common root CA's as well as getsentry.com's CA. - -## 0.9.1 (2014-08-26) - -- Change default curl connection to `sync` -- Improve CLI reporting - -## 0.9.0 (2014-06-04) - -- Protocol version 5 -- Default to asynchronous HTTP handler using curl_multi. - - -(For previous versions see the commit history) diff --git a/README.md b/README.md index 5f4f8c442..ac6503ff9 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ | Version | Build Status | Code Coverage | |:---------:|:-------------:|:-----:| -| `master`| [![Build Status][Travis Master Build Status Image]][Travis Build Status] [![Build Status][AppVeyor Master Build Status Image]][AppVeyor Build Status] | [![Coverage Status][Master Code Coverage Image]][Master Code Coverage] | -| `develop` | [![Build Status][Travis Develop Build Status Image]][Travis Build Status] [![Build Status][AppVeyor Develop Build Status Image]][AppVeyor Build Status] | [![Coverage Status][Develop Code Coverage Image]][Develop Code Coverage] | +| `master`| [![CI][master Build Status Image]][master Build Status] | [![Coverage Status][master Code Coverage Image]][master Code Coverage] | +| `develop`| [![CI][develop Build Status Image]][develop Build Status] | [![Coverage Status][develop Code Coverage Image]][develop Code Coverage] | The Sentry PHP error reporter tracks errors and exceptions that happen during the execution of your application and provides instant notification with detailed @@ -110,13 +110,11 @@ Tests can then be run via phpunit: $ vendor/bin/phpunit ``` -[Travis Build Status]: http://travis-ci.org/getsentry/sentry-php -[Travis Master Build Status Image]: https://img.shields.io/travis/getsentry/sentry-php/master?logo=travis -[Travis Develop Build Status Image]: https://img.shields.io/travis/getsentry/sentry-php/develop?logo=travis -[AppVeyor Build Status]: https://ci.appveyor.com/project/sentry/sentry-php -[AppVeyor Master Build Status Image]: https://img.shields.io/appveyor/ci/sentry/sentry-php/master?logo=appveyor -[AppVeyor Develop Build Status Image]: https://img.shields.io/appveyor/ci/sentry/sentry-php/develop?logo=appveyor -[Master Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/master -[Master Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/master?logo=codecov -[Develop Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/develop -[Develop Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/develop?logo=codecov +[master Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Amaster +[master Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=master +[develop Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Adevelop +[develop Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=develop +[master Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/master +[master Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/master?logo=codecov +[develop Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/develop +[develop Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/develop?logo=codecov diff --git a/composer.json b/composer.json index 751f71577..18afdc4da 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "symfony/polyfill-uuid": "^1.13.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", + "friendsofphp/php-cs-fixer": "^2.17", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.3|^2.0", "nikic/php-parser": "^4.10.3", @@ -50,7 +50,7 @@ "phpstan/phpstan-phpunit": "^0.12", "phpunit/phpunit": "^8.5.13|^9.4", "symfony/phpunit-bridge": "^5.2", - "vimeo/psalm": "^3.4|^4.2" + "vimeo/psalm": "^4.2" }, "suggest": { "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." diff --git a/src/Options.php b/src/Options.php index bb8b7e2df..9d1c7e1eb 100644 --- a/src/Options.php +++ b/src/Options.php @@ -680,7 +680,7 @@ private function configureOptions(OptionsResolver $resolver): void 'integrations' => [], 'default_integrations' => true, 'send_attempts' => 3, - 'prefixes' => explode(PATH_SEPARATOR, get_include_path()), + 'prefixes' => array_filter(explode(PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, 'traces_sample_rate' => 0, 'traces_sampler' => null, diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index e8d2a69f2..b92097261 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -46,7 +46,7 @@ public function testInvoke(array $options, ServerRequestInterface $request, arra SentrySdk::getCurrentHub()->bindClient($client); - withScope(function (Scope $scope) use ($event, $expectedRequestContextData, $initialUser, $expectedUser): void { + withScope(function (Scope $scope) use ($event, $expectedRequestContextData, $expectedUser): void { $event = $scope->applyToEvent($event); $this->assertNotNull($event); From 490952f8b1c752fa8297af6d7dcdc789b7042320 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 27 Dec 2020 13:57:09 +0100 Subject: [PATCH 0638/1161] Fix unwanted call to the `before_send` callback with transaction events (#1158) --- CHANGELOG.md | 4 ++++ src/Client.php | 13 ++++++++----- src/EventHint.php | 2 +- tests/ClientTest.php | 40 ++++++++++++++++++++++++---------------- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26abeaae3..cf9983074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Unreleased + +- Fix unwanted call to the `before_send` callback with transaction events, use `traces_sampler` instead to filter transactions (#1158) + ## 3.1.1 (2020-12-07) - Add support for PHP 8.0 (#1087) diff --git a/src/Client.php b/src/Client.php index eac510f77..51606745b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -232,9 +232,10 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setTags($this->options->getTags()); $event->setEnvironment($this->options->getEnvironment()); + $isTransaction = EventType::transaction() === $event->getType(); $sampleRate = $this->options->getSampleRate(); - if (EventType::transaction() !== $event->getType() && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { + if (!$isTransaction && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]); return null; @@ -251,11 +252,13 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco } } - $previousEvent = $event; - $event = ($this->options->getBeforeSendCallback())($event); + if (!$isTransaction) { + $previousEvent = $event; + $event = ($this->options->getBeforeSendCallback())($event); - if (null === $event) { - $this->logger->info('The event will be discarded because the "before_send" callback returned "null".', ['event' => $previousEvent]); + if (null === $event) { + $this->logger->info('The event will be discarded because the "before_send" callback returned "null".', ['event' => $previousEvent]); + } } return $event; diff --git a/src/EventHint.php b/src/EventHint.php index 18bc61a8a..c5b8abe8a 100644 --- a/src/EventHint.php +++ b/src/EventHint.php @@ -35,7 +35,7 @@ final class EventHint * * @psalm-param array{ * exception?: \Throwable, - * stacktrace?: Event, + * stacktrace?: Stacktrace|null, * extra?: array * } $hintData */ diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 0e5aa45af..3c2b219b8 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -291,29 +291,37 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void $this->assertNull($client->captureLastError()); } - public function testSendChecksBeforeSendOption(): void + /** + * @dataProvider processEventChecksBeforeSendOptionDataProvider + */ + public function testProcessEventChecksBeforeSendOption(Event $event, bool $expectedBeforeSendCall): void { $beforeSendCalled = false; + $options = [ + 'before_send' => static function () use (&$beforeSendCalled) { + $beforeSendCalled = true; - /** @var TransportInterface&MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->never()) - ->method('send'); - - $options = new Options(['dsn' => 'http://public:secret@example.com/1']); - $options->setBeforeSendCallback(function () use (&$beforeSendCalled) { - $beforeSendCalled = true; + return null; + }, + ]; - return null; - }); + $client = ClientBuilder::create($options)->getClient(); + $client->captureEvent($event); - $client = (new ClientBuilder($options)) - ->setTransportFactory($this->createTransportFactory($transport)) - ->getClient(); + $this->assertSame($expectedBeforeSendCall, $beforeSendCalled); + } - $client->captureEvent(Event::createEvent()); + public function processEventChecksBeforeSendOptionDataProvider(): \Generator + { + yield [ + Event::createEvent(), + true, + ]; - $this->assertTrue($beforeSendCalled); + yield [ + Event::createTransaction(), + false, + ]; } /** From dbade6ae845181afc6fe1232d2f21dd5d4d51f2c Mon Sep 17 00:00:00 2001 From: Rustam Mamadaminov Date: Wed, 6 Jan 2021 22:40:21 +0500 Subject: [PATCH 0639/1161] Add Yii2 third-party integration to the README (#1164) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ac6503ff9..3b36d4689 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ The following integrations are available and maintained by members of the Sentry - [ZendFramework](https://github.com/facile-it/sentry-module) - [SilverStripe](https://github.com/phptek/silverstripe-sentry) - [TYPO3](https://github.com/networkteam/sentry_client) +- [Yii2](https://github.com/notamedia/yii2-sentry) - ... feel free to be famous, create a port to your favourite platform! ### 3rd party integrations using old SDK 1.x From 7ec2a976bdc6ed057fb78ba8901854620405a2a1 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 7 Jan 2021 12:36:24 +0100 Subject: [PATCH 0640/1161] Fix the logger option not being applied to the event object (#1165) --- CHANGELOG.md | 1 + src/Client.php | 4 ++++ tests/ClientTest.php | 45 ++++++++++++++++++++++---------------------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf9983074..90136affc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Fix unwanted call to the `before_send` callback with transaction events, use `traces_sampler` instead to filter transactions (#1158) +- Fix the `logger` option not being applied to the event object (#1165) ## 3.1.1 (2020-12-07) diff --git a/src/Client.php b/src/Client.php index 51606745b..35081c4af 100644 --- a/src/Client.php +++ b/src/Client.php @@ -232,6 +232,10 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setTags($this->options->getTags()); $event->setEnvironment($this->options->getEnvironment()); + if (null === $event->getLogger()) { + $event->setLogger($this->options->getLogger()); + } + $isTransaction = EventType::transaction() === $event->getType(); $sampleRate = $this->options->getSampleRate(); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 3c2b219b8..112c4cb26 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -454,29 +454,6 @@ public function testFlush(): void $this->assertTrue($promise->wait()); } - private function createTransportFactory(TransportInterface $transport): TransportFactoryInterface - { - return new class($transport) implements TransportFactoryInterface { - /** - * @var TransportInterface - */ - private $transport; - - public function __construct(TransportInterface $transport) - { - $this->transport = $transport; - } - - public function create(Options $options): TransportInterface - { - return $this->transport; - } - }; - } - - /** - * @backupGlobals - */ public function testBuildEventWithDefaultValues(): void { $options = new Options(); @@ -484,6 +461,7 @@ public function testBuildEventWithDefaultValues(): void $options->setRelease('testRelease'); $options->setTags(['test' => 'tag']); $options->setEnvironment('testEnvironment'); + $options->setLogger('app.logger'); /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -496,6 +474,7 @@ public function testBuildEventWithDefaultValues(): void $this->assertSame($options->getRelease(), $event->getRelease()); $this->assertSame($options->getTags(), $event->getTags()); $this->assertSame($options->getEnvironment(), $event->getEnvironment()); + $this->assertSame($options->getLogger(), $event->getLogger()); $this->assertNull($event->getStacktrace()); return true; @@ -642,4 +621,24 @@ public function testBuildWithStacktrace(): void $client->captureEvent(Event::createEvent()); } + + private function createTransportFactory(TransportInterface $transport): TransportFactoryInterface + { + return new class($transport) implements TransportFactoryInterface { + /** + * @var TransportInterface + */ + private $transport; + + public function __construct(TransportInterface $transport) + { + $this->transport = $transport; + } + + public function create(Options $options): TransportInterface + { + return $this->transport; + } + }; + } } From 7b07a7ea755109b46d46d75237f256b150d55f3d Mon Sep 17 00:00:00 2001 From: Matthijs Date: Thu, 7 Jan 2021 19:06:13 +0100 Subject: [PATCH 0641/1161] Avoid overwrite of event attributes by option config values when calling `captureEvent()` (#1148) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + src/Client.php | 17 ++++++-- tests/ClientTest.php | 94 ++++++++++++++++++++------------------------ 3 files changed, 57 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90136affc..ac4e201cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix unwanted call to the `before_send` callback with transaction events, use `traces_sampler` instead to filter transactions (#1158) - Fix the `logger` option not being applied to the event object (#1165) +- Fix a bug that made some event attributes being overwritten by option config values when calling `captureEvent()` (#1148) ## 3.1.1 (2020-12-07) diff --git a/src/Client.php b/src/Client.php index 35081c4af..8618b9a91 100644 --- a/src/Client.php +++ b/src/Client.php @@ -227,10 +227,19 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setSdkIdentifier($this->sdkIdentifier); $event->setSdkVersion($this->sdkVersion); - $event->setServerName($this->options->getServerName()); - $event->setRelease($this->options->getRelease()); - $event->setTags($this->options->getTags()); - $event->setEnvironment($this->options->getEnvironment()); + $event->setTags(array_merge($this->options->getTags(), $event->getTags())); + + if (null === $event->getServerName()) { + $event->setServerName($this->options->getServerName()); + } + + if (null === $event->getRelease()) { + $event->setRelease($this->options->getRelease()); + } + + if (null === $event->getEnvironment()) { + $event->setEnvironment($this->options->getEnvironment()); + } if (null === $event->getLogger()) { $event->setLogger($this->options->getLogger()); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 112c4cb26..5c446896f 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -14,6 +14,7 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\EventHint; +use Sentry\EventId; use Sentry\ExceptionMechanism; use Sentry\Frame; use Sentry\Integration\IntegrationInterface; @@ -28,7 +29,6 @@ use Sentry\State\Scope; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; -use Sentry\UserDataBag; final class ClientTest extends TestCase { @@ -132,29 +132,59 @@ public function testCaptureException(): void $this->assertNotNull($client->captureException($exception)); } - public function testCaptureEvent(): void + /** + * @dataProvider captureEventDataProvider + */ + public function testCaptureEvent(array $options, Event $event, Event $expectedEvent): void { - /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') - ->willReturnCallback(static function (Event $event): FulfilledPromise { + ->willReturnCallback(function (Event $event) use ($expectedEvent): FulfilledPromise { + $this->assertEquals($expectedEvent, $event); + return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); }); - $client = ClientBuilder::create() + $client = ClientBuilder::create($options) ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - $event = Event::createEvent(); - $event->setTransaction('foo bar'); - $event->setLevel(Severity::debug()); - $event->setLogger('foo'); - $event->setTags(['foo', 'bar']); - $event->setExtra(['foo' => 'bar']); - $event->setUser(UserDataBag::createFromUserIdentifier('foo')); + $this->assertSame($event->getId(), $client->captureEvent($event)); + } + + public function captureEventDataProvider(): \Generator + { + $eventId = EventId::generate(); + $event = Event::createEvent($eventId); + + yield 'Options set && no event properties set => use options' => [ + [ + 'server_name' => 'example.com', + 'release' => '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33', + 'environment' => 'development', + 'tags' => ['context' => 'development'], + ], + $event, + $event, + ]; - $this->assertNotNull($client->captureEvent($event)); + $event = Event::createEvent($eventId); + $event->setServerName('foo.example.com'); + $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); + $event->setEnvironment('production'); + $event->setTags(['context' => 'production']); + + yield 'Options set && event properties set => event properties override options' => [ + [ + 'server_name' => 'example.com', + 'release' => '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33', + 'environment' => 'development', + 'tags' => ['context' => 'development', 'ios_version' => '14.0'], + ], + $event, + $event, + ]; } /** @@ -454,44 +484,6 @@ public function testFlush(): void $this->assertTrue($promise->wait()); } - public function testBuildEventWithDefaultValues(): void - { - $options = new Options(); - $options->setServerName('testServerName'); - $options->setRelease('testRelease'); - $options->setTags(['test' => 'tag']); - $options->setEnvironment('testEnvironment'); - $options->setLogger('app.logger'); - - /** @var TransportInterface&MockObject $transport */ - $transport = $this->createMock(TransportInterface::class); - $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event) use ($options): bool { - $this->assertSame('sentry.sdk.identifier', $event->getSdkIdentifier()); - $this->assertSame('1.2.3', $event->getSdkVersion()); - $this->assertSame($options->getServerName(), $event->getServerName()); - $this->assertSame($options->getRelease(), $event->getRelease()); - $this->assertSame($options->getTags(), $event->getTags()); - $this->assertSame($options->getEnvironment(), $event->getEnvironment()); - $this->assertSame($options->getLogger(), $event->getLogger()); - $this->assertNull($event->getStacktrace()); - - return true; - })); - - $client = new Client( - $options, - $transport, - 'sentry.sdk.identifier', - '1.2.3', - $this->createMock(SerializerInterface::class), - $this->createMock(RepresentationSerializerInterface::class) - ); - - $client->captureEvent(Event::createEvent()); - } - public function testBuildEventInCLIDoesntSetTransaction(): void { /** @var TransportInterface&MockObject $transport */ From e9b2d45b248d75f4c79a9d166b13b947b72f01fa Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 7 Jan 2021 19:16:44 +0100 Subject: [PATCH 0642/1161] Prepare release 3.1.2 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4e201cf..9dd9fa5cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.1.2 (2021-01-08) + - Fix unwanted call to the `before_send` callback with transaction events, use `traces_sampler` instead to filter transactions (#1158) - Fix the `logger` option not being applied to the event object (#1165) - Fix a bug that made some event attributes being overwritten by option config values when calling `captureEvent()` (#1148) From 8b89414443cfdd1bb9b90836da1c4c6e9d816c1f Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 12 Jan 2021 11:55:41 +0100 Subject: [PATCH 0643/1161] Deprecate the logger option (#1167) --- CHANGELOG.md | 2 + src/Client.php | 2 +- src/Options.php | 22 +++- tests/ClientTest.php | 3 + tests/OptionsTest.php | 299 +++++++++++++++++++++++++++++++++++++----- 5 files changed, 289 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dd9fa5cc..ad49a5927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Deprecate the `logger` option (#1167) + ## 3.1.2 (2021-01-08) - Fix unwanted call to the `before_send` callback with transaction events, use `traces_sampler` instead to filter transactions (#1158) diff --git a/src/Client.php b/src/Client.php index 8618b9a91..4613e6b66 100644 --- a/src/Client.php +++ b/src/Client.php @@ -242,7 +242,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco } if (null === $event->getLogger()) { - $event->setLogger($this->options->getLogger()); + $event->setLogger($this->options->getLogger(false)); } $isTransaction = EventType::transaction() === $event->getType(); diff --git a/src/Options.php b/src/Options.php index 9d1c7e1eb..685c1da3b 100644 --- a/src/Options.php +++ b/src/Options.php @@ -270,9 +270,15 @@ public function setInAppIncludedPaths(array $paths): void /** * Gets the logger used by Sentry. + * + * @deprecated since version 3.2, to be removed in 4.0 */ - public function getLogger(): string + public function getLogger(/*bool $triggerDeprecation = true*/): string { + if (0 === \func_num_args() || false !== func_get_arg(0)) { + @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + } + return $this->options['logger']; } @@ -280,9 +286,13 @@ public function getLogger(): string * Sets the logger used by Sentry. * * @param string $logger The logger + * + * @deprecated since version 3.2, to be removed in 4.0 */ public function setLogger(string $logger): void { + @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + $options = array_merge($this->options, ['logger' => $logger]); $this->options = $this->resolver->resolve($options); @@ -722,7 +732,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('in_app_exclude', 'string[]'); $resolver->setAllowedTypes('in_app_include', 'string[]'); - $resolver->setAllowedTypes('logger', 'string'); + $resolver->setAllowedTypes('logger', ['null', 'string']); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); @@ -759,6 +769,14 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setNormalizer('in_app_include', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); + + $resolver->setNormalizer('logger', function (SymfonyOptions $options, ?string $value): ?string { + if ('php' !== $value) { + @trigger_error('The option "logger" is deprecated.', E_USER_DEPRECATED); + } + + return $value; + }); } /** diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 5c446896f..de8f9eb36 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -29,9 +29,12 @@ use Sentry\State\Scope; use Sentry\Transport\TransportFactoryInterface; use Sentry\Transport\TransportInterface; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; final class ClientTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructorSetupsIntegrations(): void { $integrationCalled = false; diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 418856073..24b7b30db 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -7,62 +7,289 @@ use PHPUnit\Framework\TestCase; use Sentry\Dsn; use Sentry\Options; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; final class OptionsTest extends TestCase { + use ExpectDeprecationTrait; + /** + * @group legacy + * * @dataProvider optionsDataProvider */ - public function testConstructor($option, $value, $getterMethod): void - { - $configuration = new Options([$option => $value]); + public function testConstructor( + string $option, + $value, + string $getterMethod, + ?string $setterMethod, + ?string $expectedGetterDeprecationMessage + ): void { + if (null !== $expectedGetterDeprecationMessage) { + $this->expectDeprecation($expectedGetterDeprecationMessage); + } - $this->assertEquals($value, $configuration->$getterMethod()); + $options = new Options([$option => $value]); + + $this->assertSame($value, $options->$getterMethod()); } /** + * @group legacy + * * @dataProvider optionsDataProvider */ - public function testGettersAndSetters(string $option, $value, string $getterMethod, ?string $setterMethod = null): void - { - $configuration = new Options(); + public function testGettersAndSetters( + string $option, + $value, + string $getterMethod, + ?string $setterMethod, + ?string $expectedGetterDeprecationMessage, + ?string $expectedSetterDeprecationMessage + ): void { + if (null !== $expectedSetterDeprecationMessage) { + $this->expectDeprecation($expectedSetterDeprecationMessage); + } + + if (null !== $expectedGetterDeprecationMessage) { + $this->expectDeprecation($expectedGetterDeprecationMessage); + } + + $options = new Options(); if (null !== $setterMethod) { - $configuration->$setterMethod($value); + $options->$setterMethod($value); } - $this->assertEquals($value, $configuration->$getterMethod()); + $this->assertSame($value, $options->$getterMethod()); } - public function optionsDataProvider(): array + public function optionsDataProvider(): \Generator { - return [ - ['send_attempts', 1, 'getSendAttempts', 'setSendAttempts'], - ['prefixes', ['foo', 'bar'], 'getPrefixes', 'setPrefixes'], - ['sample_rate', 0.5, 'getSampleRate', 'setSampleRate'], - ['traces_sample_rate', 0.5, 'getTracesSampleRate', 'setTracesSampleRate'], - ['traces_sampler', static function (): void {}, 'getTracesSampler', 'setTracesSampler'], - ['attach_stacktrace', false, 'shouldAttachStacktrace', 'setAttachStacktrace'], - ['context_lines', 3, 'getContextLines', 'setContextLines'], - ['enable_compression', false, 'isCompressionEnabled', 'setEnableCompression'], - ['environment', 'foo', 'getEnvironment', 'setEnvironment'], - ['in_app_exclude', ['foo', 'bar'], 'getInAppExcludedPaths', 'setInAppExcludedPaths'], - ['in_app_include', ['foo', 'bar'], 'getInAppIncludedPaths', 'setInAppIncludedPaths'], - ['logger', 'foo', 'getLogger', 'setLogger'], - ['release', 'dev', 'getRelease', 'setRelease'], - ['server_name', 'foo', 'getServerName', 'setServerName'], - ['tags', ['foo', 'bar'], 'getTags', 'setTags'], - ['error_types', 0, 'getErrorTypes', 'setErrorTypes'], - ['max_breadcrumbs', 50, 'getMaxBreadcrumbs', 'setMaxBreadcrumbs'], - ['before_send', static function (): void {}, 'getBeforeSendCallback', 'setBeforeSendCallback'], - ['before_breadcrumb', static function (): void {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback'], - ['send_default_pii', true, 'shouldSendDefaultPii', 'setSendDefaultPii'], - ['default_integrations', false, 'hasDefaultIntegrations', 'setDefaultIntegrations'], - ['max_value_length', 50, 'getMaxValueLength', 'setMaxValueLength'], - ['http_proxy', '127.0.0.1', 'getHttpProxy', 'setHttpProxy'], - ['capture_silenced_errors', true, 'shouldCaptureSilencedErrors', 'setCaptureSilencedErrors'], - ['max_request_body_size', 'small', 'getMaxRequestBodySize', 'setMaxRequestBodySize'], + yield [ + 'send_attempts', + 1, + 'getSendAttempts', + 'setSendAttempts', + null, + null, + ]; + + yield [ + 'prefixes', + ['foo', 'bar'], + 'getPrefixes', + 'setPrefixes', + null, + null, + ]; + + yield [ + 'sample_rate', + 0.5, + 'getSampleRate', + 'setSampleRate', + null, + null, + ]; + + yield [ + 'traces_sample_rate', + 0.5, + 'getTracesSampleRate', + 'setTracesSampleRate', + null, + null, + ]; + + yield [ + 'traces_sampler', + static function (): void {}, + 'getTracesSampler', + 'setTracesSampler', + null, + null, + ]; + + yield [ + 'attach_stacktrace', + false, + 'shouldAttachStacktrace', + 'setAttachStacktrace', + null, + null, + ]; + + yield [ + 'context_lines', + 3, + 'getContextLines', + 'setContextLines', + null, + null, + ]; + + yield [ + 'enable_compression', + false, + 'isCompressionEnabled', + 'setEnableCompression', + null, + null, + ]; + + yield [ + 'environment', + 'foo', + 'getEnvironment', + 'setEnvironment', + null, + null, + ]; + + yield [ + 'in_app_exclude', + ['foo', 'bar'], + 'getInAppExcludedPaths', + 'setInAppExcludedPaths', + null, + null, + ]; + + yield [ + 'in_app_include', + ['foo', 'bar'], + 'getInAppIncludedPaths', + 'setInAppIncludedPaths', + null, + null, + ]; + + yield [ + 'logger', + 'foo', + 'getLogger', + 'setLogger', + 'Method Sentry\\Options::getLogger() is deprecated since version 3.2 and will be removed in 4.0.', + 'Method Sentry\\Options::setLogger() is deprecated since version 3.2 and will be removed in 4.0.', + ]; + + yield [ + 'release', + 'dev', + 'getRelease', + 'setRelease', + null, + null, + ]; + + yield [ + 'server_name', + 'foo', + 'getServerName', + 'setServerName', + null, + null, + ]; + + yield [ + 'tags', + ['foo', 'bar'], + 'getTags', + 'setTags', + null, + null, + ]; + + yield [ + 'error_types', + 0, + 'getErrorTypes', + 'setErrorTypes', + null, + null, + ]; + + yield [ + 'max_breadcrumbs', + 50, + 'getMaxBreadcrumbs', + 'setMaxBreadcrumbs', + null, + null, + ]; + + yield [ + 'before_send', + static function (): void {}, + 'getBeforeSendCallback', + 'setBeforeSendCallback', + null, + null, + ]; + + yield [ + 'before_breadcrumb', + static function (): void {}, + 'getBeforeBreadcrumbCallback', + 'setBeforeBreadcrumbCallback', + null, + null, + ]; + + yield [ + 'send_default_pii', + true, + 'shouldSendDefaultPii', + 'setSendDefaultPii', + null, + null, + ]; + + yield [ + 'default_integrations', + false, + 'hasDefaultIntegrations', + 'setDefaultIntegrations', + null, + null, + ]; + + yield [ + 'max_value_length', + 50, + 'getMaxValueLength', + 'setMaxValueLength', + null, + null, + ]; + + yield [ + 'http_proxy', + '127.0.0.1', + 'getHttpProxy', + 'setHttpProxy', + null, + null, + ]; + + yield [ + 'capture_silenced_errors', + true, + 'shouldCaptureSilencedErrors', + 'setCaptureSilencedErrors', + null, + null, + ]; + + yield [ + 'max_request_body_size', + 'small', + 'getMaxRequestBodySize', + 'setMaxRequestBodySize', + null, + null, ]; } From 52e5712a032032564ce3d7069dd1b0c99e4586a7 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 16 Jan 2021 21:51:20 +0100 Subject: [PATCH 0644/1161] Fix broken build due to newly reported PHPStan errors (#1173) --- phpstan-baseline.neon | 77 +++++++++++++++++++++++++++++++++++++++++ phpstan.neon | 35 ++----------------- src/EventHint.php | 23 ++++++++---- tests/EventHintTest.php | 40 +++++++++++++-------- 4 files changed, 122 insertions(+), 53 deletions(-) create mode 100644 phpstan-baseline.neon diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 000000000..5009178a1 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,77 @@ +parameters: + ignoreErrors: + - + message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" + count: 1 + path: src/Client.php + + - + message: "#^Offset 'scheme' does not exist on array\\(\\?'scheme' \\=\\> string, \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + count: 1 + path: src/Dsn.php + + - + message: "#^Offset 'path' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + count: 4 + path: src/Dsn.php + + - + message: "#^Offset 'host' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + count: 1 + path: src/Dsn.php + + - + message: "#^Offset 'user' does not exist on array\\('scheme' \\=\\> 'http'\\|'https', \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + count: 1 + path: src/Dsn.php + + - + message: "#^Result of && is always false\\.$#" + count: 2 + path: src/EventHint.php + + - + message: "#^Call to static method create\\(\\) on an unknown class Symfony\\\\Component\\\\HttpClient\\\\HttpClient\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Access to constant TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Access to constant CONNECT_TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Access to constant PROXY on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Method Sentry\\\\Monolog\\\\Handler\\:\\:write\\(\\) has parameter \\$record with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Monolog/Handler.php + + - + message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/Serializer/AbstractSerializer.php + + - + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/State/HubAdapter.php + + - + message: "#^PHPDoc tag @param references unknown parameter\\: \\$customSamplingContext$#" + count: 1 + path: src/State/HubInterface.php + + - + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/functions.php + diff --git a/phpstan.neon b/phpstan.neon index 00319c976..f180d1007 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,41 +1,12 @@ +includes: + - phpstan-baseline.neon + parameters: tipsOfTheDay: false treatPhpDocTypesAsCertain: false level: 8 paths: - src - ignoreErrors: - - '/Argument of an invalid type object supplied for foreach, only iterables are supported/' - - - message: '/^Access to constant (?:PROXY|TIMEOUT|CONNECT_TIMEOUT) on an unknown class GuzzleHttp\\RequestOptions\.$/' - path: src/HttpClient/HttpClientFactory.php - - - message: '/^Call to static method create\(\) on an unknown class Symfony\\Component\\HttpClient\\HttpClient\.$/' - path: src/HttpClient/HttpClientFactory.php - - - message: "/^Offset 'scheme' does not exist on array\\(\\?'scheme' => string, \\?'host' => string, \\?'port' => int, \\?'user' => string, \\?'pass' => string, \\?'path' => string, \\?'query' => string, \\?'fragment' => string\\)\\.$/" - path: src/Dsn.php - - - message: "/^Offset 'path' does not exist on array\\(\\?'host' => string, \\?'port' => int, \\?'user' => string, \\?'pass' => string, \\?'path' => string, \\?'query' => string, \\?'fragment' => string, 'scheme' => 'http'|'https'\\)\\.$/" - path: src/Dsn.php - - - message: "/^Offset 'user' does not exist on array\\('scheme' => 'http'\\|'https', \\?'host' => string, \\?'port' => int, \\?'user' => string, \\?'pass' => string, \\?'path' => string, \\?'query' => string, \\?'fragment' => string\\)\\.$/" - path: src/Dsn.php - - - message: '/^Method Sentry\\Monolog\\Handler::write\(\) has parameter \$record with no value type specified in iterable type array\.$/' - path: src/Monolog/Handler.php - - - message: '/^Method Sentry\\Client::getIntegration\(\) should return T of Sentry\\Integration\\IntegrationInterface\|null but returns Sentry\\Integration\\IntegrationInterface\|null\.$/' - path: src/Client.php - - - message: "/^PHPDoc tag @param references unknown parameter: \\$customSamplingContext$/" - path: src/State/HubInterface.php - - - message: '/^Method Sentry\\State\\HubInterface::startTransaction\(\) invoked with 2 parameters, 1 required\.$/' - path: src/State/HubAdapter.php - - - message: '/^Method Sentry\\State\\HubInterface::startTransaction\(\) invoked with 2 parameters, 1 required\.$/' - path: src/functions.php excludes_analyse: - tests/resources - tests/Fixtures diff --git a/src/EventHint.php b/src/EventHint.php index c5b8abe8a..145156e34 100644 --- a/src/EventHint.php +++ b/src/EventHint.php @@ -34,7 +34,7 @@ final class EventHint * Create a EventHint instance from an array of values. * * @psalm-param array{ - * exception?: \Throwable, + * exception?: \Throwable|null, * stacktrace?: Stacktrace|null, * extra?: array * } $hintData @@ -42,15 +42,26 @@ final class EventHint public static function fromArray(array $hintData): self { $hint = new self(); + $exception = $hintData['exception'] ?? null; + $stacktrace = $hintData['stacktrace'] ?? null; + $extra = $hintData['extra'] ?? []; - foreach ($hintData as $hintKey => $hintValue) { - if (!property_exists($hint, $hintKey)) { - throw new \InvalidArgumentException(sprintf('There is no EventHint attribute called "%s".', $hintKey)); - } + if (null !== $exception && !$exception instanceof \Throwable) { + throw new \InvalidArgumentException(sprintf('The value of the "exception" field must be an instance of a class implementing the "%s" interface. Got: "%s".', \Throwable::class, get_debug_type($exception))); + } + + if (null !== $stacktrace && !$stacktrace instanceof Stacktrace) { + throw new \InvalidArgumentException(sprintf('The value of the "stacktrace" field must be an instance of the "%s" class. Got: "%s".', Stacktrace::class, get_debug_type($stacktrace))); + } - $hint->{$hintKey} = $hintValue; + if (!\is_array($extra)) { + throw new \InvalidArgumentException(sprintf('The value of the "extra" field must be an array. Got: "%s".', get_debug_type($extra))); } + $hint->exception = $exception; + $hint->stacktrace = $stacktrace; + $hint->extra = $extra; + return $hint; } } diff --git a/tests/EventHintTest.php b/tests/EventHintTest.php index 11c0a737c..4a5b01c83 100644 --- a/tests/EventHintTest.php +++ b/tests/EventHintTest.php @@ -21,30 +21,40 @@ public function testCreateFromArray(): void $hint = EventHint::fromArray([ 'exception' => $exception, 'stacktrace' => $stacktrace, + 'extra' => ['foo' => 'bar'], ]); - $this->assertEquals($exception, $hint->exception); - $this->assertEquals($stacktrace, $hint->stacktrace); + $this->assertSame($exception, $hint->exception); + $this->assertSame($stacktrace, $hint->stacktrace); + $this->assertSame(['foo' => 'bar'], $hint->extra); } - public function testThrowsExceptionOnInvalidKeyInArray(): void + /** + * @dataProvider createFromArrayWithInvalidValuesDataProvider + */ + public function testCreateFromArrayWithInvalidValues(array $hintData, string $expectedExceptionMessage): void { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('There is no EventHint attribute called "missing_property".'); + $this->expectExceptionMessage($expectedExceptionMessage); - EventHint::fromArray([ - 'missing_property' => 'some value', - ]); + EventHint::fromArray($hintData); } - public function testThrowsExceptionOnInvalidKeyInArrayWhenValidKeyIsPresent(): void + public function createFromArrayWithInvalidValuesDataProvider(): \Generator { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('There is no EventHint attribute called "missing_property".'); - - EventHint::fromArray([ - 'exception' => new \Exception(), - 'missing_property' => 'some value', - ]); + yield [ + ['exception' => 'foo'], + 'The value of the "exception" field must be an instance of a class implementing the "Throwable" interface. Got: "string".', + ]; + + yield [ + ['stacktrace' => 'foo'], + 'The value of the "stacktrace" field must be an instance of the "Sentry\\Stacktrace" class. Got: "string".', + ]; + + yield [ + ['extra' => 'foo'], + 'The value of the "extra" field must be an array. Got: "string".', + ]; } } From b67f57bd7ed2d6a1b223c295f6f80b52ee5e2aa3 Mon Sep 17 00:00:00 2001 From: jarstelfox Date: Sat, 16 Jan 2021 13:13:49 -0800 Subject: [PATCH 0645/1161] Pass the event hint from the capture*() methods down to the before_send callback (#1138) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + phpstan-baseline.neon | 40 ++++++++++ src/Client.php | 22 +++--- src/ClientInterface.php | 21 +++--- src/Options.php | 4 +- src/State/Hub.php | 25 +++--- src/State/HubInterface.php | 18 +++-- src/functions.php | 27 ++++--- tests/ClientTest.php | 111 +++++++++++++++++++++++++++ tests/FunctionsTest.php | 151 ++++++++++++++++++++++++++----------- tests/State/HubTest.php | 128 +++++++++++++++++++++++++------ 11 files changed, 432 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad49a5927..1dd5ec9c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Deprecate the `logger` option (#1167) +- Pass the event hint from the `capture*()` methods down to the `before_send` callback (#1138) ## 3.1.2 (2021-01-08) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5009178a1..d8bf8af3e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5,6 +5,11 @@ parameters: count: 1 path: src/Client.php + - + message: "#^PHPDoc tag @param references unknown parameter\\: \\$hint$#" + count: 3 + path: src/ClientInterface.php + - message: "#^Offset 'scheme' does not exist on array\\(\\?'scheme' \\=\\> string, \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" count: 1 @@ -60,16 +65,51 @@ parameters: count: 1 path: src/Serializer/AbstractSerializer.php + - + message: "#^Method Sentry\\\\ClientInterface\\:\\:captureMessage\\(\\) invoked with 4 parameters, 1\\-3 required\\.$#" + count: 1 + path: src/State/Hub.php + + - + message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" + count: 1 + path: src/State/Hub.php + + - + message: "#^Method Sentry\\\\ClientInterface\\:\\:captureLastError\\(\\) invoked with 2 parameters, 0\\-1 required\\.$#" + count: 1 + path: src/State/Hub.php + - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 path: src/State/HubAdapter.php + - + message: "#^PHPDoc tag @param references unknown parameter\\: \\$hint$#" + count: 3 + path: src/State/HubInterface.php + - message: "#^PHPDoc tag @param references unknown parameter\\: \\$customSamplingContext$#" count: 1 path: src/State/HubInterface.php + - + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureMessage\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" + count: 1 + path: src/functions.php + + - + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/functions.php + + - + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureLastError\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/functions.php + - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 diff --git a/src/Client.php b/src/Client.php index 4613e6b66..cf23548aa 100644 --- a/src/Client.php +++ b/src/Client.php @@ -123,23 +123,27 @@ public function getOptions(): Options /** * {@inheritdoc} */ - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?EventId + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId { $event = Event::createEvent(); $event->setMessage($message); $event->setLevel($level); - return $this->captureEvent($event, null, $scope); + return $this->captureEvent($event, $hint, $scope); } /** * {@inheritdoc} */ - public function captureException(\Throwable $exception, ?Scope $scope = null): ?EventId + public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId { - return $this->captureEvent(Event::createEvent(), EventHint::fromArray([ - 'exception' => $exception, - ]), $scope); + $hint = $hint ?? new EventHint(); + + if (null === $hint->exception) { + $hint->exception = $exception; + } + + return $this->captureEvent(Event::createEvent(), $hint, $scope); } /** @@ -170,7 +174,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scop /** * {@inheritdoc} */ - public function captureLastError(?Scope $scope = null): ?EventId + public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId { $error = error_get_last(); @@ -180,7 +184,7 @@ public function captureLastError(?Scope $scope = null): ?EventId $exception = new \ErrorException(@$error['message'], 0, @$error['type'], @$error['file'], @$error['line']); - return $this->captureException($exception, $scope); + return $this->captureException($exception, $scope, $hint); } /** @@ -267,7 +271,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco if (!$isTransaction) { $previousEvent = $event; - $event = ($this->options->getBeforeSendCallback())($event); + $event = ($this->options->getBeforeSendCallback())($event, $hint); if (null === $event) { $this->logger->info('The event will be discarded because the "before_send" callback returned "null".', ['event' => $previousEvent]); diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 80fe63956..53e1977bc 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -23,26 +23,29 @@ public function getOptions(): Options; /** * Logs a message. * - * @param string $message The message (primary description) for the event - * @param Severity $level The level of the message to be sent - * @param Scope|null $scope An optional scope keeping the state + * @param string $message The message (primary description) for the event + * @param Severity|null $level The level of the message to be sent + * @param Scope|null $scope An optional scope keeping the state + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null): ?EventId; + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; /** * Logs an exception. * - * @param \Throwable $exception The exception object - * @param Scope|null $scope An optional scope keeping the state + * @param \Throwable $exception The exception object + * @param Scope|null $scope An optional scope keeping the state + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureException(\Throwable $exception, ?Scope $scope = null): ?EventId; + public function captureException(\Throwable $exception, ?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; /** * Logs the most recent error (obtained with {@link error_get_last}). * - * @param Scope|null $scope An optional scope keeping the state + * @param Scope|null $scope An optional scope keeping the state + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureLastError(?Scope $scope = null): ?EventId; + public function captureLastError(?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; /** * Captures a new event using the provided data. diff --git a/src/Options.php b/src/Options.php index 685c1da3b..47d2c7d99 100644 --- a/src/Options.php +++ b/src/Options.php @@ -352,7 +352,7 @@ public function setServerName(string $serverName): void * Gets a callback that will be invoked before an event is sent to the server. * If `null` is returned it won't be sent. * - * @psalm-return callable(Event): ?Event + * @psalm-return callable(Event, ?EventHint): ?Event */ public function getBeforeSendCallback(): callable { @@ -365,7 +365,7 @@ public function getBeforeSendCallback(): callable * * @param callable $callback The callable * - * @psalm-param callable(Event): ?Event $callback + * @psalm-param callable(Event, ?EventHint): ?Event $callback */ public function setBeforeSendCallback(callable $callback): void { diff --git a/src/State/Hub.php b/src/State/Hub.php index c24d59ef4..beb2b97be 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -116,12 +116,13 @@ public function bindClient(ClientInterface $client): void /** * {@inheritdoc} */ - public function captureMessage(string $message, ?Severity $level = null): ?EventId + public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { $client = $this->getClient(); if (null !== $client) { - return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope()); + /** @psalm-suppress TooManyArguments */ + return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope(), $hint); } return null; @@ -130,12 +131,13 @@ public function captureMessage(string $message, ?Severity $level = null): ?Event /** * {@inheritdoc} */ - public function captureException(\Throwable $exception): ?EventId + public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { $client = $this->getClient(); if (null !== $client) { - return $this->lastEventId = $client->captureException($exception, $this->getScope()); + /** @psalm-suppress TooManyArguments */ + return $this->lastEventId = $client->captureException($exception, $this->getScope(), $hint); } return null; @@ -158,12 +160,13 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId /** * {@inheritdoc} */ - public function captureLastError(): ?EventId + public function captureLastError(?EventHint $hint = null): ?EventId { $client = $this->getClient(); if (null !== $client) { - return $this->lastEventId = $client->captureLastError($this->getScope()); + /** @psalm-suppress TooManyArguments */ + return $this->lastEventId = $client->captureLastError($this->getScope(), $hint); } return null; @@ -213,15 +216,11 @@ public function getIntegration(string $className): ?IntegrationInterface /** * {@inheritdoc} + * + * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ - public function startTransaction(TransactionContext $context): Transaction + public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - $customSamplingContext = null; - - if (\func_num_args() > 1) { - $customSamplingContext = func_get_arg(1); - } - $transaction = new Transaction($context, $this); $client = $this->getClient(); $options = null !== $client ? $client->getOptions() : null; diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 15223d0a8..5ca1a92e7 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -75,17 +75,19 @@ public function bindClient(ClientInterface $client): void; /** * Captures a message event and sends it to Sentry. * - * @param string $message The message - * @param Severity $level The severity level of the message + * @param string $message The message + * @param Severity|null $level The severity level of the message + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureMessage(string $message, ?Severity $level = null): ?EventId; + public function captureMessage(string $message, ?Severity $level = null/*, ?EventHint $hint = null*/): ?EventId; /** * Captures an exception event and sends it to Sentry. * - * @param \Throwable $exception The exception + * @param \Throwable $exception The exception + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureException(\Throwable $exception): ?EventId; + public function captureException(\Throwable $exception/*, ?EventHint $hint = null*/): ?EventId; /** * Captures a new event using the provided data. @@ -97,8 +99,10 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId; /** * Captures an event that logs the last occurred error. + * + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureLastError(): ?EventId; + public function captureLastError(/*?EventHint $hint = null*/): ?EventId; /** * Records a new breadcrumb which will be attached to future events. They @@ -142,7 +146,7 @@ public function getIntegration(string $className): ?IntegrationInterface; * @param TransactionContext $context Properties of the new transaction * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ - public function startTransaction(TransactionContext $context): Transaction; + public function startTransaction(TransactionContext $context/*, array $customSamplingContext = []*/): Transaction; /** * Returns the transaction that is on the Hub. diff --git a/src/functions.php b/src/functions.php index e6ce3d855..7e88a5434 100644 --- a/src/functions.php +++ b/src/functions.php @@ -22,22 +22,26 @@ function init(array $options = []): void /** * Captures a message event and sends it to Sentry. * - * @param string $message The message - * @param Severity|null $level The severity level of the message + * @param string $message The message + * @param Severity|null $level The severity level of the message + * @param EventHint|null $hint Object that can contain additional information about the event */ -function captureMessage(string $message, ?Severity $level = null): ?EventId +function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - return SentrySdk::getCurrentHub()->captureMessage($message, $level); + /** @psalm-suppress TooManyArguments */ + return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint); } /** * Captures an exception event and sends it to Sentry. * - * @param \Throwable $exception The exception + * @param \Throwable $exception The exception + * @param EventHint|null $hint Object that can contain additional information about the event */ -function captureException(\Throwable $exception): ?EventId +function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - return SentrySdk::getCurrentHub()->captureException($exception); + /** @psalm-suppress TooManyArguments */ + return SentrySdk::getCurrentHub()->captureException($exception, $hint); } /** @@ -52,11 +56,14 @@ function captureEvent(Event $event, ?EventHint $hint = null): ?EventId } /** - * Logs the most recent error (obtained with {@link error_get_last}). + * Logs the most recent error (obtained with {@see error_get_last()}). + * + * @param EventHint|null $hint Object that can contain additional information about the event */ -function captureLastError(): ?EventId +function captureLastError(?EventHint $hint = null): ?EventId { - return SentrySdk::getCurrentHub()->captureLastError(); + /** @psalm-suppress TooManyArguments */ + return SentrySdk::getCurrentHub()->captureLastError($hint); } /** diff --git a/tests/ClientTest.php b/tests/ClientTest.php index de8f9eb36..a10fbb980 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -106,6 +106,28 @@ public function testCaptureMessage(): void $this->assertNotNull($client->captureMessage('foo', Severity::fatal())); } + public function testCaptureMessageWithEventHint(): void + { + $hint = new EventHint(); + $hint->extra = ['foo' => 'bar']; + + $beforeSendCallbackCalled = false; + $options = new Options([ + 'before_send' => function (Event $event, ?EventHint $hintArg) use ($hint, &$beforeSendCallbackCalled) { + $this->assertSame($hint, $hintArg); + + $beforeSendCallbackCalled = true; + + return null; + }, + ]); + + $client = new Client($options, $this->createMock(TransportInterface::class)); + $client->captureMessage('foo', null, null, $hint); + + $this->assertTrue($beforeSendCallbackCalled); + } + public function testCaptureException(): void { $exception = new \Exception('Some foo error'); @@ -135,6 +157,46 @@ public function testCaptureException(): void $this->assertNotNull($client->captureException($exception)); } + /** + * @dataProvider captureExceptionWithEventHintDataProvider + */ + public function testCaptureExceptionWithEventHint(EventHint $hint): void + { + $beforeSendCallbackCalled = false; + $exception = $hint->exception ?? new \Exception(); + + $options = new Options([ + 'before_send' => function (Event $event, ?EventHint $hintArg) use ($exception, $hint, &$beforeSendCallbackCalled) { + $this->assertSame($hint, $hintArg); + $this->assertSame($exception, $hintArg->exception); + + $beforeSendCallbackCalled = true; + + return null; + }, + ]); + + $client = new Client($options, $this->createMock(TransportInterface::class)); + $client->captureException($exception, null, $hint); + + $this->assertTrue($beforeSendCallbackCalled); + } + + public function captureExceptionWithEventHintDataProvider(): \Generator + { + yield [ + EventHint::fromArray([ + 'extra' => ['foo' => 'bar'], + ]), + ]; + + yield [ + EventHint::fromArray([ + 'exception' => new \Exception('foo'), + ]), + ]; + } + /** * @dataProvider captureEventDataProvider */ @@ -190,6 +252,28 @@ public function captureEventDataProvider(): \Generator ]; } + public function testCaptureEventWithEventHint(): void + { + $hint = new EventHint(); + $hint->extra = ['foo' => 'bar']; + + $beforeSendCallbackCalled = false; + $options = new Options([ + 'before_send' => function (Event $event, ?EventHint $hintArg) use ($hint, &$beforeSendCallbackCalled) { + $this->assertSame($hint, $hintArg); + + $beforeSendCallbackCalled = true; + + return null; + }, + ]); + + $client = new Client($options, $this->createMock(TransportInterface::class)); + $client->captureEvent(Event::createEvent(), $hint); + + $this->assertTrue($beforeSendCallbackCalled); + } + /** * @dataProvider captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider */ @@ -307,6 +391,33 @@ public function testCaptureLastError(): void error_clear_last(); } + public function testCaptureLastErrorWithEventHint(): void + { + $hint = new EventHint(); + $hint->extra = ['foo' => 'bar']; + + $beforeSendCallbackCalled = false; + $options = new Options([ + 'before_send' => function (Event $event, ?EventHint $hintArg) use ($hint, &$beforeSendCallbackCalled) { + $this->assertSame($hint, $hintArg); + + $beforeSendCallbackCalled = true; + + return null; + }, + ]); + + $client = new Client($options, $this->createMock(TransportInterface::class)); + + @trigger_error('foo', E_USER_NOTICE); + + $client->captureLastError(null, $hint); + + error_clear_last(); + + $this->assertTrue($beforeSendCallbackCalled); + } + public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void { /** @var TransportInterface&MockObject $transport */ diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index ba36a7807..6a834af78 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -9,12 +9,14 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventHint; use Sentry\EventId; use Sentry\Options; use Sentry\SentrySdk; use Sentry\Severity; +use Sentry\State\HubInterface; use Sentry\State\Scope; -use Sentry\Tracing\SamplingContext; +use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use function Sentry\addBreadcrumb; use function Sentry\captureEvent; @@ -35,70 +37,141 @@ public function testInit(): void $this->assertNotNull(SentrySdk::getCurrentHub()->getClient()); } - public function testCaptureMessage(): void + /** + * @dataProvider captureMessageDataProvider + */ + public function testCaptureMessage(array $functionCallArgs, array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) ->method('captureMessage') - ->with('foo', Severity::debug()) + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); - SentrySdk::getCurrentHub()->bindClient($client); + SentrySdk::setCurrentHub($hub); - $this->assertSame($eventId, captureMessage('foo', Severity::debug())); + $this->assertSame($eventId, captureMessage(...$functionCallArgs)); } - public function testCaptureException(): void + public function captureMessageDataProvider(): \Generator + { + yield [ + [ + 'foo', + Severity::debug(), + ], + [ + 'foo', + Severity::debug(), + null, + ], + ]; + + yield [ + [ + 'foo', + Severity::debug(), + new EventHint(), + ], + [ + 'foo', + Severity::debug(), + new EventHint(), + ], + ]; + } + + /** + * @dataProvider captureExceptionDataProvider + */ + public function testCaptureException(array $functionCallArgs, array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); - $exception = new \RuntimeException('foo'); - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) ->method('captureException') - ->with($exception) + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); - SentrySdk::getCurrentHub()->bindClient($client); + SentrySdk::setCurrentHub($hub); - $this->assertSame($eventId, captureException($exception)); + $this->assertSame($eventId, captureException(...$functionCallArgs)); + } + + public function captureExceptionDataProvider(): \Generator + { + yield [ + [ + new \Exception('foo'), + ], + [ + new \Exception('foo'), + null, + ], + ]; + + yield [ + [ + new \Exception('foo'), + new EventHint(), + ], + [ + new \Exception('foo'), + new EventHint(), + ], + ]; } public function testCaptureEvent(): void { $event = Event::createEvent(); + $hint = new EventHint(); - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) ->method('captureEvent') - ->with($event) + ->with($event, $hint) ->willReturn($event->getId()); - SentrySdk::getCurrentHub()->bindClient($client); + SentrySdk::setCurrentHub($hub); - $this->assertSame($event->getId(), captureEvent($event)); + $this->assertSame($event->getId(), captureEvent($event, $hint)); } - public function testCaptureLastError() + /** + * @dataProvider captureLastErrorDataProvider + */ + public function testCaptureLastError(array $functionCallArgs, array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); - /** @var ClientInterface|MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) ->method('captureLastError') + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); - SentrySdk::getCurrentHub()->bindClient($client); + SentrySdk::setCurrentHub($hub); @trigger_error('foo', E_USER_NOTICE); - $this->assertSame($eventId, captureLastError()); + $this->assertSame($eventId, captureLastError(...$functionCallArgs)); + } + + public function captureLastErrorDataProvider(): \Generator + { + yield [ + [], + [null], + ]; + + yield [ + [new EventHint()], + [new EventHint()], + ]; } public function testAddBreadcrumb(): void @@ -147,23 +220,17 @@ public function testConfigureScope(): void public function testStartTransaction(): void { $transactionContext = new TransactionContext('foo'); + $transaction = new Transaction($transactionContext); $customSamplingContext = ['foo' => 'bar']; - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { - $this->assertSame($customSamplingContext, $samplingContext->getAdditionalContext()); - - return 0.0; - }, - ])); - - SentrySdk::getCurrentHub()->bindClient($client); + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('startTransaction') + ->with($transactionContext, $customSamplingContext) + ->willReturn($transaction); - $transaction = startTransaction($transactionContext, $customSamplingContext); + SentrySdk::setCurrentHub($hub); - $this->assertSame($transactionContext->getName(), $transaction->getName()); + $this->assertSame($transaction, startTransaction($transactionContext, $customSamplingContext)); } } diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index d0fd6f64a..85c39ec38 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -9,6 +9,7 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; use Sentry\Options; @@ -197,86 +198,164 @@ public function testBindClient(): void $this->assertSame($client2, $hub->getClient()); } - public function testCaptureMessage(): void + /** + * @dataProvider captureMessageDataProvider + */ + public function testCaptureMessage(array $functionCallArgs, array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); + $hub = new Hub(); /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureMessage') - ->with('foo', Severity::debug()) + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); - $hub = new Hub(); - $this->assertNull($hub->captureMessage('foo')); $hub->bindClient($client); - $this->assertSame($eventId, $hub->captureMessage('foo', Severity::debug())); + $this->assertSame($eventId, $hub->captureMessage(...$functionCallArgs)); + } + + public function captureMessageDataProvider(): \Generator + { + yield [ + [ + 'foo', + Severity::debug(), + ], + [ + 'foo', + Severity::debug(), + new Scope(), + ], + ]; + + yield [ + [ + 'foo', + Severity::debug(), + new EventHint(), + ], + [ + 'foo', + Severity::debug(), + new Scope(), + new EventHint(), + ], + ]; } - public function testCaptureException(): void + /** + * @dataProvider captureExceptionDataProvider + */ + public function testCaptureException(array $functionCallArgs, array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); - $exception = new \RuntimeException('foo'); + $hub = new Hub(); - /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureException') - ->with($exception) + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); - $hub = new Hub(); - $this->assertNull($hub->captureException(new \RuntimeException())); $hub->bindClient($client); - $this->assertSame($eventId, $hub->captureException($exception)); + $this->assertSame($eventId, $hub->captureException(...$functionCallArgs)); + } + + public function captureExceptionDataProvider(): \Generator + { + yield [ + [ + new \Exception('foo'), + ], + [ + new \Exception('foo'), + new Scope(), + null, + ], + ]; + + yield [ + [ + new \Exception('foo'), + new EventHint(), + ], + [ + new \Exception('foo'), + new Scope(), + new EventHint(), + ], + ]; } - public function testCaptureLastError(): void + /** + * @dataProvider captureLastErrorDataProvider + */ + public function testCaptureLastError(array $functionCallArgs, array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); + $hub = new Hub(); /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureLastError') + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); - $hub = new Hub(); - - $this->assertNull($hub->captureLastError()); + $this->assertNull($hub->captureLastError(...$functionCallArgs)); $hub->bindClient($client); - $this->assertSame($eventId, $hub->captureLastError()); + $this->assertSame($eventId, $hub->captureLastError(...$functionCallArgs)); } - public function testCaptureEvent(): void + public function captureLastErrorDataProvider(): \Generator { - $event = Event::createEvent($eventId = EventId::generate()); + yield [ + [], + [ + new Scope(), + null, + ], + ]; - $event->setMessage('test'); + yield [ + [ + new EventHint(), + ], + [ + new Scope(), + new EventHint(), + ], + ]; + } + + public function testCaptureEvent(): void + { + $hub = new Hub(); + $event = Event::createEvent(); /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') ->with($event) - ->willReturn($eventId); - - $hub = new Hub(); + ->willReturn($event->getId()); $this->assertNull($hub->captureEvent($event)); $hub->bindClient($client); - $this->assertSame($eventId, $hub->captureEvent($event)); + $this->assertSame($event->getId(), $hub->captureEvent($event)); } public function testAddBreadcrumb(): void @@ -611,6 +690,7 @@ public function testStartTransactionDoesNothingIfTracingIsNotEnabled(): void public function testStartTransactionWithCustomSamplingContext(): void { $customSamplingContext = ['a' => 'b']; + $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('getOptions') From d727bd1a35523b96dfe450ec4e8406338cec816c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 18 Jan 2021 09:52:33 +0100 Subject: [PATCH 0646/1161] Fix default value for client version (#1169) --- src/Client.php | 4 +-- src/ErrorHandler.php | 34 +++++++++---------- src/HttpClient/HttpClientFactory.php | 6 ++-- src/HttpClient/Plugin/GzipEncoderPlugin.php | 2 +- src/Integration/RequestIntegration.php | 2 +- src/Options.php | 2 +- src/Severity.php | 30 ++++++++-------- src/Tracing/SpanContext.php | 2 +- src/UserDataBag.php | 2 +- src/Util/JSON.php | 6 ++-- src/Util/PHPVersion.php | 2 +- tests/ClientTest.php | 4 +-- tests/FunctionsTest.php | 2 +- tests/HttpClient/HttpClientFactoryTest.php | 2 +- .../Plugin/GzipEncoderPluginTest.php | 2 +- tests/Integration/RequestIntegrationTest.php | 4 +-- tests/Serializer/SerializerTest.php | 2 +- tests/SeverityTest.php | 30 ++++++++-------- tests/Util/JSONTest.php | 2 +- 19 files changed, 70 insertions(+), 70 deletions(-) diff --git a/src/Client.php b/src/Client.php index 8618b9a91..82bda93e0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -109,7 +109,7 @@ public function __construct( $this->representationSerializer = $representationSerializer ?? new RepresentationSerializer($this->options); $this->stacktraceBuilder = new StacktraceBuilder($options, $this->representationSerializer); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; - $this->sdkVersion = $sdkVersion ?? PrettyVersions::getVersion(PrettyVersions::getRootPackageName())->getPrettyVersion(); + $this->sdkVersion = $sdkVersion ?? PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); } /** @@ -294,7 +294,7 @@ private function addMissingStacktraceToEvent(Event $event): void } $event->setStacktrace($this->stacktraceBuilder->buildFromBacktrace( - debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), + debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS), __FILE__, __LINE__ - 3 )); diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index af00bf53d..2e4ed0608 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -91,21 +91,21 @@ final class ErrorHandler * @var array List of error levels and their description */ private const ERROR_LEVELS_DESCRIPTION = [ - E_DEPRECATED => 'Deprecated', - E_USER_DEPRECATED => 'User Deprecated', - E_NOTICE => 'Notice', - E_USER_NOTICE => 'User Notice', - E_STRICT => 'Runtime Notice', - E_WARNING => 'Warning', - E_USER_WARNING => 'User Warning', - E_COMPILE_WARNING => 'Compile Warning', - E_CORE_WARNING => 'Core Warning', - E_USER_ERROR => 'User Error', - E_RECOVERABLE_ERROR => 'Catchable Fatal Error', - E_COMPILE_ERROR => 'Compile Error', - E_PARSE => 'Parse Error', - E_ERROR => 'Error', - E_CORE_ERROR => 'Core Error', + \E_DEPRECATED => 'Deprecated', + \E_USER_DEPRECATED => 'User Deprecated', + \E_NOTICE => 'Notice', + \E_USER_NOTICE => 'User Notice', + \E_STRICT => 'Runtime Notice', + \E_WARNING => 'Warning', + \E_USER_WARNING => 'User Warning', + \E_COMPILE_WARNING => 'Compile Warning', + \E_CORE_WARNING => 'Core Warning', + \E_USER_ERROR => 'User Error', + \E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + \E_COMPILE_ERROR => 'Compile Error', + \E_PARSE => 'Parse Error', + \E_ERROR => 'Error', + \E_CORE_ERROR => 'Core Error', ]; /** @@ -145,7 +145,7 @@ public static function registerOnceErrorHandler(): self // first call to the set_error_handler method would cause the PHP // bug https://bugs.php.net/63206 if the handler is not the first // one in the chain of handlers - set_error_handler($errorHandlerCallback, E_ALL); + set_error_handler($errorHandlerCallback, \E_ALL); } return self::$handlerInstance; @@ -300,7 +300,7 @@ private function handleFatalError(): void self::$reservedMemory = null; $error = error_get_last(); - if (!empty($error) && $error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)) { + if (!empty($error) && $error['type'] & (\E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_CORE_WARNING | \E_COMPILE_ERROR | \E_COMPILE_WARNING)) { $errorAsException = new FatalErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); $this->exceptionReflection->setValue($errorAsException, []); diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index c0eb9eb32..977d75850 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -142,12 +142,12 @@ public function create(Options $options): HttpAsyncClientInterface $httpClient = GuzzleHttpClient::createWithConfig($guzzleConfig); } elseif (class_exists(CurlHttpClient::class)) { $curlConfig = [ - CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, - CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + \CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, + \CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, ]; if (null !== $options->getHttpProxy()) { - $curlConfig[CURLOPT_PROXY] = $options->getHttpProxy(); + $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); } /** @psalm-suppress InvalidPropertyAssignmentValue */ diff --git a/src/HttpClient/Plugin/GzipEncoderPlugin.php b/src/HttpClient/Plugin/GzipEncoderPlugin.php index b5b9a84a8..d74a69fd9 100644 --- a/src/HttpClient/Plugin/GzipEncoderPlugin.php +++ b/src/HttpClient/Plugin/GzipEncoderPlugin.php @@ -50,7 +50,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl // Instead of using a stream filter we have to compress the whole request // body in one go to work around a PHP bug. See https://github.com/getsentry/sentry-php/pull/877 - $encodedBody = gzcompress($requestBody->getContents(), -1, ZLIB_ENCODING_GZIP); + $encodedBody = gzcompress($requestBody->getContents(), -1, \ZLIB_ENCODING_GZIP); if (false === $encodedBody) { throw new \RuntimeException('Failed to GZIP-encode the request body.'); diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index d9f2cac17..da5321c96 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -149,7 +149,7 @@ private function removePiiFromHeaders(array $headers): array static function (string $key) use ($keysToRemove): bool { return !\in_array(strtolower($key), $keysToRemove, true); }, - ARRAY_FILTER_USE_KEY + \ARRAY_FILTER_USE_KEY ); } diff --git a/src/Options.php b/src/Options.php index 9d1c7e1eb..3a96a68c0 100644 --- a/src/Options.php +++ b/src/Options.php @@ -680,7 +680,7 @@ private function configureOptions(OptionsResolver $resolver): void 'integrations' => [], 'default_integrations' => true, 'send_attempts' => 3, - 'prefixes' => array_filter(explode(PATH_SEPARATOR, get_include_path() ?: '')), + 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, 'traces_sample_rate' => 0, 'traces_sampler' => null, diff --git a/src/Severity.php b/src/Severity.php index b0bc7206d..97b2856d8 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -89,24 +89,24 @@ public function __construct(string $value = self::INFO) public static function fromError(int $severity): self { switch ($severity) { - case E_DEPRECATED: - case E_USER_DEPRECATED: - case E_WARNING: - case E_USER_WARNING: + case \E_DEPRECATED: + case \E_USER_DEPRECATED: + case \E_WARNING: + case \E_USER_WARNING: return self::warning(); - case E_ERROR: - case E_PARSE: - case E_CORE_ERROR: - case E_CORE_WARNING: - case E_COMPILE_ERROR: - case E_COMPILE_WARNING: + case \E_ERROR: + case \E_PARSE: + case \E_CORE_ERROR: + case \E_CORE_WARNING: + case \E_COMPILE_ERROR: + case \E_COMPILE_WARNING: return self::fatal(); - case E_RECOVERABLE_ERROR: - case E_USER_ERROR: + case \E_RECOVERABLE_ERROR: + case \E_USER_ERROR: return self::error(); - case E_NOTICE: - case E_USER_NOTICE: - case E_STRICT: + case \E_NOTICE: + case \E_USER_NOTICE: + case \E_STRICT: return self::info(); default: return self::error(); diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 6bed921bf..6b98416ce 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -199,7 +199,7 @@ public function setEndTimestamp(?float $endTimestamp): void */ public static function fromTraceparent(string $header) { - @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.', __METHOD__), \E_USER_DEPRECATED); /** @phpstan-ignore-next-line */ /** @psalm-suppress UnsafeInstantiation */ $context = new static(); diff --git a/src/UserDataBag.php b/src/UserDataBag.php index 2237612be..ac1a3973c 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -174,7 +174,7 @@ public function getIpAddress(): ?string */ public function setIpAddress(?string $ipAddress): void { - if (null !== $ipAddress && false === filter_var($ipAddress, FILTER_VALIDATE_IP)) { + if (null !== $ipAddress && false === filter_var($ipAddress, \FILTER_VALIDATE_IP)) { throw new \InvalidArgumentException(sprintf('The "%s" value is not a valid IP address.', $ipAddress)); } diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 5e0b7bb6a..c56025246 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -28,11 +28,11 @@ final class JSON */ public static function encode($data, int $options = 0, int $maxDepth = 512) { - $options |= JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE; + $options |= \JSON_UNESCAPED_UNICODE | \JSON_INVALID_UTF8_SUBSTITUTE; $encodedData = json_encode($data, $options, $maxDepth); - if (JSON_ERROR_NONE !== json_last_error()) { + if (\JSON_ERROR_NONE !== json_last_error()) { throw new JsonException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); } @@ -52,7 +52,7 @@ public static function decode(string $data) { $decodedData = json_decode($data, true); - if (JSON_ERROR_NONE !== json_last_error()) { + if (\JSON_ERROR_NONE !== json_last_error()) { throw new JsonException(sprintf('Could not decode value from JSON format. Error was: "%s".', json_last_error_msg())); } diff --git a/src/Util/PHPVersion.php b/src/Util/PHPVersion.php index c4dfecb07..3f0394b6a 100644 --- a/src/Util/PHPVersion.php +++ b/src/Util/PHPVersion.php @@ -20,7 +20,7 @@ final class PHPVersion * * @param string $version The string to parse */ - public static function parseVersion(string $version = PHP_VERSION): string + public static function parseVersion(string $version = \PHP_VERSION): string { if (!preg_match(self::VERSION_PARSING_REGEX, $version, $matches)) { return $version; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 5c446896f..a1323a0ed 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -297,7 +297,7 @@ public function testCaptureLastError(): void ->setTransportFactory($this->createTransportFactory($transport)) ->getClient(); - @trigger_error('foo', E_USER_NOTICE); + @trigger_error('foo', \E_USER_NOTICE); $this->assertNotNull($client->captureLastError()); @@ -552,7 +552,7 @@ public function testBuildEventWithException(): void public function testBuildWithErrorException(): void { $options = new Options(); - $exception = new \ErrorException('testMessage', 0, E_USER_ERROR); + $exception = new \ErrorException('testMessage', 0, \E_USER_ERROR); /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index ba36a7807..244e62479 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -96,7 +96,7 @@ public function testCaptureLastError() SentrySdk::getCurrentHub()->bindClient($client); - @trigger_error('foo', E_USER_NOTICE); + @trigger_error('foo', \E_USER_NOTICE); $this->assertSame($eventId, captureLastError()); } diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php index 625a36554..852dc430d 100644 --- a/tests/HttpClient/HttpClientFactoryTest.php +++ b/tests/HttpClient/HttpClientFactoryTest.php @@ -60,7 +60,7 @@ public function createDataProvider(): \Generator yield [ true, - gzcompress('foo bar', -1, ZLIB_ENCODING_GZIP), + gzcompress('foo bar', -1, \ZLIB_ENCODING_GZIP), ]; } diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php index f365adfb8..638406393 100644 --- a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php +++ b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php @@ -28,7 +28,7 @@ public function testHandleRequest(): void $request, function (RequestInterface $requestArg) use ($expectedPromise): PromiseInterface { $this->assertSame('gzip', $requestArg->getHeaderLine('Content-Encoding')); - $this->assertSame(gzcompress('foo', -1, ZLIB_ENCODING_GZIP), (string) $requestArg->getBody()); + $this->assertSame(gzcompress('foo', -1, \ZLIB_ENCODING_GZIP), (string) $requestArg->getBody()); return $expectedPromise; }, diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index b92097261..fb58ee752 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -327,8 +327,8 @@ public function invokeDataProvider(): iterable ->withHeader('Content-Length', '444') ->withUploadedFiles([ 'foo' => [ - new UploadedFile('foo content', 123, UPLOAD_ERR_OK, 'foo.ext', 'application/text'), - new UploadedFile('bar content', 321, UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), + new UploadedFile('foo content', 123, \UPLOAD_ERR_OK, 'foo.ext', 'application/text'), + new UploadedFile('bar content', 321, \UPLOAD_ERR_OK, 'bar.ext', 'application/octet-stream'), ], ]), [ diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index d8d7e2fd0..43dccf209 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -187,7 +187,7 @@ public function testClippingUTF8Characters(): void $this->assertSame('Прекратит {clipped}', $clipped); $this->assertNotNull(json_encode($clipped)); - $this->assertSame(JSON_ERROR_NONE, json_last_error()); + $this->assertSame(\JSON_ERROR_NONE, json_last_error()); } /** diff --git a/tests/SeverityTest.php b/tests/SeverityTest.php index 661088cab..5e8397794 100644 --- a/tests/SeverityTest.php +++ b/tests/SeverityTest.php @@ -66,24 +66,24 @@ public function levelsDataProvider(): array { return [ // Warning - [E_DEPRECATED, 'warning'], - [E_USER_DEPRECATED, 'warning'], - [E_WARNING, 'warning'], - [E_USER_WARNING, 'warning'], + [\E_DEPRECATED, 'warning'], + [\E_USER_DEPRECATED, 'warning'], + [\E_WARNING, 'warning'], + [\E_USER_WARNING, 'warning'], // Fatal - [E_ERROR, 'fatal'], - [E_PARSE, 'fatal'], - [E_CORE_ERROR, 'fatal'], - [E_CORE_WARNING, 'fatal'], - [E_COMPILE_ERROR, 'fatal'], - [E_COMPILE_WARNING, 'fatal'], + [\E_ERROR, 'fatal'], + [\E_PARSE, 'fatal'], + [\E_CORE_ERROR, 'fatal'], + [\E_CORE_WARNING, 'fatal'], + [\E_COMPILE_ERROR, 'fatal'], + [\E_COMPILE_WARNING, 'fatal'], // Error - [E_RECOVERABLE_ERROR, 'error'], - [E_USER_ERROR, 'error'], + [\E_RECOVERABLE_ERROR, 'error'], + [\E_USER_ERROR, 'error'], // Info - [E_NOTICE, 'info'], - [E_USER_NOTICE, 'info'], - [E_STRICT, 'info'], + [\E_NOTICE, 'info'], + [\E_USER_NOTICE, 'info'], + [\E_STRICT, 'info'], ]; } } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index 78b4f6aa2..d2a4ebf1e 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -115,7 +115,7 @@ public function testEncodeThrowsIfValueIsResource(): void public function testEncodeRespectsOptionsArgument(): void { - $this->assertSame('{}', JSON::encode([], JSON_FORCE_OBJECT)); + $this->assertSame('{}', JSON::encode([], \JSON_FORCE_OBJECT)); } /** From ab3122beeb77da66aa153b8da53a90cf3b87bb94 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 21 Jan 2021 18:27:46 +0100 Subject: [PATCH 0647/1161] Fix broken build after release of new PHP-CS-Fixer version --- src/Options.php | 6 +++--- tests/ClientTest.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Options.php b/src/Options.php index 1d6991f24..f6597e008 100644 --- a/src/Options.php +++ b/src/Options.php @@ -276,7 +276,7 @@ public function setInAppIncludedPaths(array $paths): void public function getLogger(/*bool $triggerDeprecation = true*/): string { if (0 === \func_num_args() || false !== func_get_arg(0)) { - @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); } return $this->options['logger']; @@ -291,7 +291,7 @@ public function getLogger(/*bool $triggerDeprecation = true*/): string */ public function setLogger(string $logger): void { - @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); $options = array_merge($this->options, ['logger' => $logger]); @@ -772,7 +772,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setNormalizer('logger', function (SymfonyOptions $options, ?string $value): ?string { if ('php' !== $value) { - @trigger_error('The option "logger" is deprecated.', E_USER_DEPRECATED); + @trigger_error('The option "logger" is deprecated.', \E_USER_DEPRECATED); } return $value; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 141414e18..090afa40c 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -409,7 +409,7 @@ public function testCaptureLastErrorWithEventHint(): void $client = new Client($options, $this->createMock(TransportInterface::class)); - @trigger_error('foo', E_USER_NOTICE); + @trigger_error('foo', \E_USER_NOTICE); $client->captureLastError(null, $hint); From 1ff7a35f2efc83870a48fde6eb5e5e48f2b84137 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 21 Jan 2021 20:25:58 +0100 Subject: [PATCH 0648/1161] Deprecate the tags option (#1174) --- CHANGELOG.md | 1 + src/Client.php | 2 +- src/Options.php | 19 ++++++++++++++++++- tests/ClientTest.php | 4 ++++ tests/OptionsTest.php | 4 ++-- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dd5ec9c3..845a967ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Deprecate the `logger` option (#1167) - Pass the event hint from the `capture*()` methods down to the `before_send` callback (#1138) +- Deprecate the `tags` option, see the [docs](https://docs.sentry.io/platforms/php/guides/laravel/enriching-events/tags/) for other ways to set tags (#1174) ## 3.1.2 (2021-01-08) diff --git a/src/Client.php b/src/Client.php index cc2126b1e..f40e68fec 100644 --- a/src/Client.php +++ b/src/Client.php @@ -231,7 +231,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setSdkIdentifier($this->sdkIdentifier); $event->setSdkVersion($this->sdkVersion); - $event->setTags(array_merge($this->options->getTags(), $event->getTags())); + $event->setTags(array_merge($this->options->getTags(false), $event->getTags())); if (null === $event->getServerName()) { $event->setServerName($this->options->getServerName()); diff --git a/src/Options.php b/src/Options.php index f6597e008..43c1963fe 100644 --- a/src/Options.php +++ b/src/Options.php @@ -378,9 +378,15 @@ public function setBeforeSendCallback(callable $callback): void * Gets a list of default tags for events. * * @return array + * + * @deprecated since version 3.2, to be removed in 4.0 */ - public function getTags(): array + public function getTags(/*bool $triggerDeprecation = true*/): array { + if (0 === \func_num_args() || false !== func_get_arg(0)) { + @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); + } + return $this->options['tags']; } @@ -388,9 +394,13 @@ public function getTags(): array * Sets a list of default tags for events. * * @param array $tags A list of tags + * + * @deprecated since version 3.2, to be removed in 4.0 */ public function setTags(array $tags): void { + @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0. Use Sentry\\Scope::setTags() instead.', __METHOD__), \E_USER_DEPRECATED); + $options = array_merge($this->options, ['tags' => $tags]); $this->options = $this->resolver->resolve($options); @@ -757,6 +767,13 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedValues('context_lines', \Closure::fromCallable([$this, 'validateContextLinesOption'])); $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); + $resolver->setNormalizer('tags', static function (SymfonyOptions $options, array $value): array { + if (!empty($value)) { + @trigger_error('The option "tags" is deprecated since version 3.2 and will be removed in 4.0. Either set the tags on the scope or on the event.', \E_USER_DEPRECATED); + } + + return $value; + }); $resolver->setNormalizer('prefixes', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 090afa40c..461f2aad9 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -198,10 +198,14 @@ public function captureExceptionWithEventHintDataProvider(): \Generator } /** + * @group legacy + * * @dataProvider captureEventDataProvider */ public function testCaptureEvent(array $options, Event $event, Event $expectedEvent): void { + $this->expectDeprecation('The option "tags" is deprecated since version 3.2 and will be removed in 4.0. Either set the tags on the scope or on the event.'); + $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 24b7b30db..54cd4ed6d 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -198,8 +198,8 @@ static function (): void {}, ['foo', 'bar'], 'getTags', 'setTags', - null, - null, + 'Method Sentry\\Options::getTags() is deprecated since version 3.2 and will be removed in 4.0.', + 'Method Sentry\\Options::setTags() is deprecated since version 3.2 and will be removed in 4.0. Use Sentry\\Scope::setTags() instead.', ]; yield [ From 540fd64e20381feeb269e151eb7227fa3aecdb75 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 21 Jan 2021 21:45:28 +0100 Subject: [PATCH 0649/1161] Add the $customSamplingContext argument to Hub::startTransaction() and HubAdapter::startTransaction() (#1176) * Add the $customSamplingContext argument to Hub::startTransaction() and HubAdapter::startTransaction() * Add missing tests for the HubAdapter class --- CHANGELOG.md | 2 + src/State/Hub.php | 10 +- src/State/HubAdapter.php | 6 +- tests/State/HubAdapterTest.php | 282 +++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 10 deletions(-) create mode 100644 tests/State/HubAdapterTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dd9fa5cc..78651654b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add the `$customSamplingContext` argument to `Hub::startTransaction()` and `HubAdapter::startTransaction()` to fix deprecations thrown in Symfony (#1176) + ## 3.1.2 (2021-01-08) - Fix unwanted call to the `before_send` callback with transaction events, use `traces_sampler` instead to filter transactions (#1158) diff --git a/src/State/Hub.php b/src/State/Hub.php index c24d59ef4..611c72ed8 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -213,15 +213,11 @@ public function getIntegration(string $className): ?IntegrationInterface /** * {@inheritdoc} + * + * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ - public function startTransaction(TransactionContext $context): Transaction + public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - $customSamplingContext = null; - - if (\func_num_args() > 1) { - $customSamplingContext = func_get_arg(1); - } - $transaction = new Transaction($context, $this); $client = $this->getClient(); $options = null !== $client ? $client->getOptions() : null; diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 7df115f9c..65110fda3 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -153,11 +153,11 @@ public function getIntegration(string $className): ?IntegrationInterface /** * {@inheritdoc} + * + * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ - public function startTransaction(TransactionContext $context): Transaction + public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - $customSamplingContext = \func_num_args() > 1 ? func_get_arg(1) : []; - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } diff --git a/tests/State/HubAdapterTest.php b/tests/State/HubAdapterTest.php new file mode 100644 index 000000000..8a83b411d --- /dev/null +++ b/tests/State/HubAdapterTest.php @@ -0,0 +1,282 @@ +assertSame(HubAdapter::getInstance(), HubAdapter::getInstance()); + } + + public function testGetInstanceReturnsUncloneableInstance(): void + { + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage('Cloning is forbidden.'); + + clone HubAdapter::getInstance(); + } + + public function testGetInstanceReturnsUnserializableInstance(): void + { + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage('Unserializing instances of this class is forbidden.'); + + unserialize(serialize(HubAdapter::getInstance())); + } + + public function testGetClient(): void + { + $client = $this->createMock(ClientInterface::class); + + SentrySdk::getCurrentHub()->bindClient($client); + + $this->assertSame($client, HubAdapter::getInstance()->getClient()); + } + + public function testGetLastEventId(): void + { + $eventId = EventId::generate(); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('getLastEventId') + ->willReturn($eventId); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($eventId, HubAdapter::getInstance()->getLastEventId()); + } + + public function testPushScope(): void + { + $scope = new Scope(); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('pushScope') + ->willReturn($scope); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($scope, HubAdapter::getInstance()->pushScope()); + } + + public function testPopScope(): void + { + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('popScope') + ->willReturn(true); + + SentrySdk::setCurrentHub($hub); + + $this->assertTrue(HubAdapter::getInstance()->popScope()); + } + + public function testWithScope(): void + { + $callback = static function () {}; + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('withScope') + ->with($callback); + + SentrySdk::setCurrentHub($hub); + HubAdapter::getInstance()->withScope($callback); + } + + public function testConfigureScope(): void + { + $callback = static function () {}; + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('configureScope') + ->with($callback); + + SentrySdk::setCurrentHub($hub); + HubAdapter::getInstance()->configureScope($callback); + } + + public function testBindClient(): void + { + $client = $this->createMock(ClientInterface::class); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('bindClient') + ->with($client); + + SentrySdk::setCurrentHub($hub); + HubAdapter::getInstance()->bindClient($client); + } + + public function testCaptureMessage(): void + { + $eventId = EventId::generate(); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('captureMessage') + ->with('foo', Severity::debug()) + ->willReturn($eventId); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($eventId, HubAdapter::getInstance()->captureMessage('foo', Severity::debug())); + } + + public function testCaptureException(): void + { + $eventId = EventId::generate(); + $exception = new \Exception(); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('captureException') + ->with($exception) + ->willReturn($eventId); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($eventId, HubAdapter::getInstance()->captureException($exception)); + } + + public function testCaptureEvent(): void + { + $event = Event::createEvent(); + $hint = EventHint::fromArray([]); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('captureEvent') + ->with($event, $hint) + ->willReturn($event->getId()); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($event->getId(), HubAdapter::getInstance()->captureEvent($event, $hint)); + } + + public function testCaptureLastError(): void + { + $eventId = EventId::generate(); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('captureLastError') + ->willReturn($eventId); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($eventId, HubAdapter::getInstance()->captureLastError()); + } + + public function testAddBreadcrumb(): void + { + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_DEBUG, Breadcrumb::TYPE_ERROR, 'user'); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('addBreadcrumb') + ->willReturn(true); + + SentrySdk::setCurrentHub($hub); + + $this->assertTrue(HubAdapter::getInstance()->addBreadcrumb($breadcrumb)); + } + + public function testGetIntegration(): void + { + $integration = $this->createMock(IntegrationInterface::class); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('getIntegration') + ->with(\get_class($integration)) + ->willReturn($integration); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($integration, HubAdapter::getInstance()->getIntegration(\get_class($integration))); + } + + public function testStartTransaction(): void + { + $transactionContext = new TransactionContext(); + $transaction = new Transaction($transactionContext); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('startTransaction') + ->with($transactionContext) + ->willReturn($transaction); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($transaction, HubAdapter::getInstance()->startTransaction($transactionContext)); + } + + public function testGetTransaction(): void + { + $transaction = new Transaction(new TransactionContext()); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('getTransaction') + ->willReturn($transaction); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($transaction, HubAdapter::getInstance()->getTransaction()); + } + + public function testGetSpan(): void + { + $span = new Span(); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('getSpan') + ->willReturn($span); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($span, HubAdapter::getInstance()->getSpan()); + } + + public function testSetSpan(): void + { + $span = new Span(); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('setSpan') + ->with($span) + ->willReturn($hub); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($hub, HubAdapter::getInstance()->setSpan($span)); + } +} From db8a322f87983bb4f3cd8db01f9a9a593efe72a3 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 25 Jan 2021 09:47:34 +0100 Subject: [PATCH 0650/1161] Prepare release 3.1.3 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78651654b..c227f1658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +## 3.1.3 (2021-01-25) + +- Fix the fetching of the version of the SDK (#1169) - Add the `$customSamplingContext` argument to `Hub::startTransaction()` and `HubAdapter::startTransaction()` to fix deprecations thrown in Symfony (#1176) ## 3.1.2 (2021-01-08) From 928cda21049c4e8c7372726004c088a36395c88d Mon Sep 17 00:00:00 2001 From: soundsgoodsofar Date: Mon, 1 Feb 2021 15:02:39 -0600 Subject: [PATCH 0651/1161] Filter PII from headers rather than removing them entirely from the request context (#1161) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + src/Integration/RequestIntegration.php | 73 ++++++++++++++++---- tests/Integration/RequestIntegrationTest.php | 55 ++++++++++----- 3 files changed, 99 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 845a967ee..aa342ceb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Make the HTTP headers sanitizable in the `RequestIntegration` integration instead of removing them entirely (#1161) - Deprecate the `logger` option (#1167) - Pass the event hint from the `capture*()` methods down to the `before_send` callback (#1138) - Deprecate the `tags` option, see the [docs](https://docs.sentry.io/platforms/php/guides/laravel/enriching-events/tags/) for other ways to set tags (#1174) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index da5321c96..b67c39a06 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -14,6 +14,8 @@ use Sentry\State\Scope; use Sentry\UserDataBag; use Sentry\Util\JSON; +use Symfony\Component\OptionsResolver\Options as SymfonyOptions; +use Symfony\Component\OptionsResolver\OptionsResolver; /** * This integration collects information from the request and attaches them to @@ -48,19 +50,46 @@ final class RequestIntegration implements IntegrationInterface 'always' => -1, ]; + /** + * This constant defines the default list of headers that may contain + * sensitive data and that will be sanitized if sending PII is disabled. + */ + private const DEFAULT_SENSITIVE_HEADERS = [ + 'Authorization', + 'Cookie', + 'Set-Cookie', + 'X-Forwarded-For', + 'X-Real-IP', + ]; + /** * @var RequestFetcherInterface PSR-7 request fetcher */ private $requestFetcher; + /** + * @var array The options + */ + private $options; + /** * Constructor. * * @param RequestFetcherInterface|null $requestFetcher PSR-7 request fetcher + * @param array $options The options + * + * @psalm-param array{ + * pii_sanitize_headers?: string[] + * } $options */ - public function __construct(?RequestFetcherInterface $requestFetcher = null) + public function __construct(?RequestFetcherInterface $requestFetcher = null, array $options = []) { + $resolver = new OptionsResolver(); + + $this->configureOptions($resolver); + $this->requestFetcher = $requestFetcher ?? new RequestFetcher(); + $this->options = $resolver->resolve($options); } /** @@ -121,7 +150,7 @@ private function processEvent(Event $event, Options $options): void $requestData['cookies'] = $request->getCookieParams(); $requestData['headers'] = $request->getHeaders(); } else { - $requestData['headers'] = $this->removePiiFromHeaders($request->getHeaders()); + $requestData['headers'] = $this->sanitizeHeaders($request->getHeaders()); } $requestBody = $this->captureRequestBody($options, $request); @@ -136,21 +165,23 @@ private function processEvent(Event $event, Options $options): void /** * Removes headers containing potential PII. * - * @param array> $headers Array containing request headers + * @param array $headers Array containing request headers * - * @return array> + * @return array */ - private function removePiiFromHeaders(array $headers): array + private function sanitizeHeaders(array $headers): array { - $keysToRemove = ['authorization', 'cookie', 'set-cookie', 'remote_addr']; - - return array_filter( - $headers, - static function (string $key) use ($keysToRemove): bool { - return !\in_array(strtolower($key), $keysToRemove, true); - }, - \ARRAY_FILTER_USE_KEY - ); + foreach ($headers as $name => $values) { + if (!\in_array(strtolower($name), $this->options['pii_sanitize_headers'], true)) { + continue; + } + + foreach ($values as $headerLine => $headerValue) { + $headers[$name][$headerLine] = '[Filtered]'; + } + } + + return $headers; } /** @@ -245,4 +276,18 @@ private function isRequestBodySizeWithinReadBounds(int $requestBodySize, string return true; } + + /** + * Configures the options of the client. + * + * @param OptionsResolver $resolver The resolver for the options + */ + private function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefault('pii_sanitize_headers', self::DEFAULT_SENSITIVE_HEADERS); + $resolver->setAllowedTypes('pii_sanitize_headers', 'string[]'); + $resolver->setNormalizer('pii_sanitize_headers', static function (SymfonyOptions $options, array $value): array { + return array_map('strtolower', $value); + }); + } } diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index fb58ee752..f57a9b1f9 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -6,7 +6,6 @@ use GuzzleHttp\Psr7\ServerRequest; use GuzzleHttp\Psr7\UploadedFile; -use GuzzleHttp\Psr7\Uri; use GuzzleHttp\Psr7\Utils; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -69,7 +68,7 @@ public function invokeDataProvider(): iterable [ 'send_default_pii' => true, ], - (new ServerRequest('GET', new Uri('http://www.example.com/foo'))) + (new ServerRequest('GET', 'http://www.example.com/foo')) ->withCookieParams(['foo' => 'bar']), [ 'url' => 'http://www.example.com/foo', @@ -89,7 +88,7 @@ public function invokeDataProvider(): iterable [ 'send_default_pii' => false, ], - (new ServerRequest('GET', new Uri('http://www.example.com/foo'))) + (new ServerRequest('GET', 'http://www.example.com/foo')) ->withCookieParams(['foo' => 'bar']), [ 'url' => 'http://www.example.com/foo', @@ -106,7 +105,7 @@ public function invokeDataProvider(): iterable [ 'send_default_pii' => true, ], - (new ServerRequest('GET', new Uri('http://www.example.com:1234/foo'))), + (new ServerRequest('GET', 'http://www.example.com:1234/foo')), [ 'url' => 'http://www.example.com:1234/foo', 'method' => 'GET', @@ -123,7 +122,7 @@ public function invokeDataProvider(): iterable [ 'send_default_pii' => false, ], - (new ServerRequest('GET', new Uri('http://www.example.com:1234/foo'))), + (new ServerRequest('GET', 'http://www.example.com:1234/foo')), [ 'url' => 'http://www.example.com:1234/foo', 'method' => 'GET', @@ -139,7 +138,7 @@ public function invokeDataProvider(): iterable [ 'send_default_pii' => true, ], - (new ServerRequest('GET', new Uri('http://www.example.com/foo?foo=bar&bar=baz'), [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1'])) + (new ServerRequest('GET', 'http://www.example.com/foo?foo=bar&bar=baz', [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1'])) ->withHeader('Host', 'www.example.com') ->withHeader('Authorization', 'foo') ->withHeader('Cookie', 'bar') @@ -167,7 +166,7 @@ public function invokeDataProvider(): iterable [ 'send_default_pii' => false, ], - (new ServerRequest('GET', new Uri('http://www.example.com/foo?foo=bar&bar=baz'), ['REMOTE_ADDR' => '127.0.0.1'])) + (new ServerRequest('GET', 'http://www.example.com/foo?foo=bar&bar=baz', [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1'])) ->withHeader('Host', 'www.example.com') ->withHeader('Authorization', 'foo') ->withHeader('Cookie', 'bar') @@ -178,6 +177,30 @@ public function invokeDataProvider(): iterable 'query_string' => 'foo=bar&bar=baz', 'headers' => [ 'Host' => ['www.example.com'], + 'Authorization' => ['[Filtered]'], + 'Cookie' => ['[Filtered]'], + 'Set-Cookie' => ['[Filtered]'], + ], + ], + null, + null, + ]; + + yield [ + [ + 'send_default_pii' => false, + 'integrations' => [ + new RequestIntegration(null, ['pii_sanitize_headers' => ['aUthOrIzAtIoN']]), + ], + ], + (new ServerRequest('GET', 'http://www.example.com', [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1'])) + ->withHeader('Authorization', 'foo'), + [ + 'url' => 'http://www.example.com', + 'method' => 'GET', + 'headers' => [ + 'Host' => ['www.example.com'], + 'Authorization' => ['[Filtered]'], ], ], null, @@ -188,7 +211,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'none', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Length', '3') ->withBody(Utils::streamFor('foo')), [ @@ -207,7 +230,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'small', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Length', 10 ** 3) ->withBody(Utils::streamFor('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus at placerat est. Donec maximus odio augue, vitae bibendum nisi euismod nec. Nunc vel velit ligula. Ut non ultricies magna, non condimentum turpis. Donec pellentesque id nunc at facilisis. Sed fermentum ultricies nunc, id posuere ex ullamcorper quis. Sed varius tincidunt nulla, id varius nulla interdum sit amet. Pellentesque molestie sapien at mi tristique consequat. Nullam id eleifend arcu. Vivamus sed placerat neque. Ut sapien magna, elementum in euismod pretium, rhoncus vitae augue. Nam ullamcorper dui et tortor semper, eu feugiat elit faucibus. Curabitur vel auctor odio. Phasellus vestibulum ullamcorper dictum. Suspendisse fringilla, ipsum bibendum venenatis vulputate, nunc orci facilisis leo, commodo finibus mi arcu in turpis. Mauris ut ultrices est. Nam quis purus ut nulla interdum ornare. Proin in tellus egestas, commodo magna porta, consequat justo. Vivamus in convallis odio. Pellentesque porttitor, urna non gravida.')), [ @@ -227,7 +250,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'small', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Length', (string) (10 ** 3)) ->withParsedBody([ 'foo' => 'foo value', @@ -253,7 +276,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'small', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Length', (string) (10 ** 3 + 1)) ->withParsedBody([ 'foo' => 'foo value', @@ -275,7 +298,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'medium', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Length', (string) (10 ** 4)) ->withParsedBody([ 'foo' => 'foo value', @@ -301,7 +324,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'medium', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Length', (string) (10 ** 4 + 1)) ->withParsedBody([ 'foo' => 'foo value', @@ -323,7 +346,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'always', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Length', '444') ->withUploadedFiles([ 'foo' => [ @@ -361,7 +384,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'always', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Type', 'application/json') ->withHeader('Content-Length', '13') ->withBody(Utils::streamFor('{"foo":"bar"}')), @@ -385,7 +408,7 @@ public function invokeDataProvider(): iterable [ 'max_request_body_size' => 'always', ], - (new ServerRequest('POST', new Uri('http://www.example.com/foo'))) + (new ServerRequest('POST', 'http://www.example.com/foo')) ->withHeader('Content-Type', 'application/json') ->withBody(Utils::streamFor('{"foo":"bar"}')), [ From 7320f2b5cdaae84b8c3ee66145c9c0fbbc18a665 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 1 Feb 2021 22:37:22 +0100 Subject: [PATCH 0652/1161] Allow installing the jean85/pretty-package-versions package at version 2.x (#1170) --- CHANGELOG.md | 2 ++ composer.json | 3 +-- src/Integration/ModulesIntegration.php | 27 ++++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c227f1658..42e432f16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Allow jean85/pretty-package-versions 2.0 (#1170) + ## 3.1.3 (2021-01-25) - Fix the fetching of the version of the SDK (#1169) diff --git a/composer.json b/composer.json index 18afdc4da..68e84a415 100644 --- a/composer.json +++ b/composer.json @@ -25,8 +25,7 @@ "ext-mbstring": "*", "guzzlehttp/promises": "^1.4", "guzzlehttp/psr7": "^1.7", - "jean85/pretty-package-versions": "^1.5", - "ocramius/package-versions": "^1.8", + "jean85/pretty-package-versions": "^1.5|^2.0.1", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", "php-http/discovery": "^1.6.1", diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index 5ae22bfa9..dc47bd89a 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -4,6 +4,7 @@ namespace Sentry\Integration; +use Composer\InstalledVersions; use Jean85\PrettyVersions; use PackageVersions\Versions; use Sentry\Event; @@ -45,11 +46,33 @@ public function setupOnce(): void private static function getComposerPackages(): array { if (empty(self::$packages)) { - foreach (Versions::VERSIONS as $package => $rawVersion) { - self::$packages[$package] = PrettyVersions::getVersion($package)->getPrettyVersion(); + foreach (self::getInstalledPackages() as $package) { + try { + self::$packages[$package] = PrettyVersions::getVersion($package)->getPrettyVersion(); + } catch (\Throwable $exception) { + continue; + } } } return self::$packages; } + + /** + * @return string[] + */ + private static function getInstalledPackages(): array + { + if (class_exists(InstalledVersions::class)) { + return InstalledVersions::getInstalledPackages(); + } + + if (class_exists(Versions::class)) { + // BC layer for Composer 1, using a transient dependency + return array_keys(Versions::VERSIONS); + } + + // this should not happen + return ['sentry/sentry']; + } } From 5ff718099ac0050385c562b7c1da88dd215c48f9 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 2 Feb 2021 09:56:03 +0100 Subject: [PATCH 0653/1161] Prepare release of version 3.1.4 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e432f16..30407ae5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.1.4 (2021-02-02) + - Allow jean85/pretty-package-versions 2.0 (#1170) ## 3.1.3 (2021-01-25) From acbf541fa2aa7b18b25cf99327dba94ddf9ccf18 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 17 Feb 2021 00:39:52 +0100 Subject: [PATCH 0654/1161] Fix incorrect detection of silenced errors (#1183) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + phpstan-baseline.neon | 2 +- src/ErrorHandler.php | 26 ++++++- ...errors_not_silencable_on_php_8_and_up.phpt | 70 ++++++++++++++++++ ...pects_capture_silenced_errors_option.phpt} | 0 ..._option_regardless_of_error_reporting.phpt | 73 +++++++++++++++++++ 6 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt rename tests/phpt/{error_handler_respects_error_reporting.phpt => error_handler_respects_capture_silenced_errors_option.phpt} (100%) create mode 100644 tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index aa342ceb9..a5d42cd58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Deprecate the `logger` option (#1167) - Pass the event hint from the `capture*()` methods down to the `before_send` callback (#1138) - Deprecate the `tags` option, see the [docs](https://docs.sentry.io/platforms/php/guides/laravel/enriching-events/tags/) for other ways to set tags (#1174) +- Fix incorrect detection of silenced errors (by the `@` operator) (#1183) ## 3.1.2 (2021-01-08) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d8bf8af3e..48e67abfc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,7 +1,7 @@ parameters: ignoreErrors: - - message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" + message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns T of Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" count: 1 path: src/Client.php diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 2e4ed0608..ac2b63804 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -22,6 +22,11 @@ final class ErrorHandler */ public const DEFAULT_RESERVED_MEMORY_SIZE = 10240; + /** + * The fatal error types that cannot be silenced using the @ operator in PHP 8+. + */ + private const PHP8_UNSILENCEABLE_FATAL_ERRORS = \E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR | \E_RECOVERABLE_ERROR; + /** * @var self|null The current registered handler (this class is a singleton) */ @@ -88,7 +93,7 @@ final class ErrorHandler private static $reservedMemory; /** - * @var array List of error levels and their description + * @var array List of error levels and their description */ private const ERROR_LEVELS_DESCRIPTION = [ \E_DEPRECATED => 'Deprecated', @@ -265,7 +270,24 @@ public function addExceptionHandlerListener(callable $listener): void */ private function handleError(int $level, string $message, string $file, int $line, ?array $errcontext = []): bool { - if (0 === (error_reporting() & $level)) { + $isSilencedError = 0 === error_reporting(); + + if (\PHP_MAJOR_VERSION >= 8) { + // Starting from PHP8, when a silenced error occurs the `error_reporting()` + // function will return a bitmask of fatal errors that are unsilenceable. + // If by subtracting from this value those errors the result is 0, we can + // conclude that the error was silenced. + $isSilencedError = 0 === (error_reporting() & ~self::PHP8_UNSILENCEABLE_FATAL_ERRORS); + + // However, starting from PHP8 some fatal errors are unsilenceable, + // so we have to check for them to avoid reporting any of them as + // silenced instead + if ($level === (self::PHP8_UNSILENCEABLE_FATAL_ERRORS & $level)) { + $isSilencedError = false; + } + } + + if ($isSilencedError) { $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); } else { $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); diff --git a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt new file mode 100644 index 000000000..f26fba50e --- /dev/null +++ b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt @@ -0,0 +1,70 @@ +--TEST-- +Test that the error handler ignores silenced errors by default, but it reports them with the appropriate option enabled. +--SKIPIF-- + +--INI-- +error_reporting=E_ALL +--FILE-- + E_ALL, 'capture_silenced_errors' => false]) + ->setTransportFactory($transportFactory) + ->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +echo 'Triggering "silenced" E_USER_ERROR error' . PHP_EOL; + +@trigger_error('This E_USER_ERROR cannot be silenced', E_USER_ERROR); +?> +--EXPECT-- +Triggering "silenced" E_USER_ERROR error +Transport called diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt similarity index 100% rename from tests/phpt/error_handler_respects_error_reporting.phpt rename to tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt diff --git a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt new file mode 100644 index 000000000..bd73e1208 --- /dev/null +++ b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt @@ -0,0 +1,73 @@ +--TEST-- +Test that the error handler reports errors set by the `error_types` option and not the `error_reporting` level. +--FILE-- + E_ALL & ~E_USER_WARNING]) + ->setTransportFactory($transportFactory) + ->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +echo 'Triggering E_USER_NOTICE error' . PHP_EOL; + +trigger_error('Error thrown', E_USER_NOTICE); + +echo 'Triggering E_USER_WARNING error (it should not be reported to Sentry due to error_types option)' . PHP_EOL; + +trigger_error('Error thrown', E_USER_WARNING); + +echo 'Triggering E_USER_ERROR error (unsilenceable on PHP8)' . PHP_EOL; + +trigger_error('Error thrown', E_USER_ERROR); +?> +--EXPECT-- +Triggering E_USER_NOTICE error +Transport called +Triggering E_USER_WARNING error (it should not be reported to Sentry due to error_types option) +Triggering E_USER_ERROR error (unsilenceable on PHP8) +Transport called From 2c59c6c28f4c475f62938c5d5dae17530b228f5f Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 17 Feb 2021 00:39:52 +0100 Subject: [PATCH 0655/1161] Fix incorrect detection of silenced errors (#1183) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 2 + phpstan-baseline.neon | 2 +- src/ErrorHandler.php | 26 ++++++- ...errors_not_silencable_on_php_8_and_up.phpt | 70 ++++++++++++++++++ ...pects_capture_silenced_errors_option.phpt} | 0 ..._option_regardless_of_error_reporting.phpt | 73 +++++++++++++++++++ 6 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt rename tests/phpt/{error_handler_respects_error_reporting.phpt => error_handler_respects_capture_silenced_errors_option.phpt} (100%) create mode 100644 tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index 30407ae5f..068c7ab85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix incorrect detection of silenced errors (by the `@` operator) (#1183) + ## 3.1.4 (2021-02-02) - Allow jean85/pretty-package-versions 2.0 (#1170) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5009178a1..bd0c0168b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,7 +1,7 @@ parameters: ignoreErrors: - - message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" + message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns T of Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" count: 1 path: src/Client.php diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 2e4ed0608..ac2b63804 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -22,6 +22,11 @@ final class ErrorHandler */ public const DEFAULT_RESERVED_MEMORY_SIZE = 10240; + /** + * The fatal error types that cannot be silenced using the @ operator in PHP 8+. + */ + private const PHP8_UNSILENCEABLE_FATAL_ERRORS = \E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR | \E_RECOVERABLE_ERROR; + /** * @var self|null The current registered handler (this class is a singleton) */ @@ -88,7 +93,7 @@ final class ErrorHandler private static $reservedMemory; /** - * @var array List of error levels and their description + * @var array List of error levels and their description */ private const ERROR_LEVELS_DESCRIPTION = [ \E_DEPRECATED => 'Deprecated', @@ -265,7 +270,24 @@ public function addExceptionHandlerListener(callable $listener): void */ private function handleError(int $level, string $message, string $file, int $line, ?array $errcontext = []): bool { - if (0 === (error_reporting() & $level)) { + $isSilencedError = 0 === error_reporting(); + + if (\PHP_MAJOR_VERSION >= 8) { + // Starting from PHP8, when a silenced error occurs the `error_reporting()` + // function will return a bitmask of fatal errors that are unsilenceable. + // If by subtracting from this value those errors the result is 0, we can + // conclude that the error was silenced. + $isSilencedError = 0 === (error_reporting() & ~self::PHP8_UNSILENCEABLE_FATAL_ERRORS); + + // However, starting from PHP8 some fatal errors are unsilenceable, + // so we have to check for them to avoid reporting any of them as + // silenced instead + if ($level === (self::PHP8_UNSILENCEABLE_FATAL_ERRORS & $level)) { + $isSilencedError = false; + } + } + + if ($isSilencedError) { $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); } else { $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); diff --git a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt new file mode 100644 index 000000000..f26fba50e --- /dev/null +++ b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt @@ -0,0 +1,70 @@ +--TEST-- +Test that the error handler ignores silenced errors by default, but it reports them with the appropriate option enabled. +--SKIPIF-- + +--INI-- +error_reporting=E_ALL +--FILE-- + E_ALL, 'capture_silenced_errors' => false]) + ->setTransportFactory($transportFactory) + ->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +echo 'Triggering "silenced" E_USER_ERROR error' . PHP_EOL; + +@trigger_error('This E_USER_ERROR cannot be silenced', E_USER_ERROR); +?> +--EXPECT-- +Triggering "silenced" E_USER_ERROR error +Transport called diff --git a/tests/phpt/error_handler_respects_error_reporting.phpt b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt similarity index 100% rename from tests/phpt/error_handler_respects_error_reporting.phpt rename to tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt diff --git a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt new file mode 100644 index 000000000..bd73e1208 --- /dev/null +++ b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt @@ -0,0 +1,73 @@ +--TEST-- +Test that the error handler reports errors set by the `error_types` option and not the `error_reporting` level. +--FILE-- + E_ALL & ~E_USER_WARNING]) + ->setTransportFactory($transportFactory) + ->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +echo 'Triggering E_USER_NOTICE error' . PHP_EOL; + +trigger_error('Error thrown', E_USER_NOTICE); + +echo 'Triggering E_USER_WARNING error (it should not be reported to Sentry due to error_types option)' . PHP_EOL; + +trigger_error('Error thrown', E_USER_WARNING); + +echo 'Triggering E_USER_ERROR error (unsilenceable on PHP8)' . PHP_EOL; + +trigger_error('Error thrown', E_USER_ERROR); +?> +--EXPECT-- +Triggering E_USER_NOTICE error +Transport called +Triggering E_USER_WARNING error (it should not be reported to Sentry due to error_types option) +Triggering E_USER_ERROR error (unsilenceable on PHP8) +Transport called From 4b683cd061ef55e55c6cde884e03deb0049f98fc Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 17 Feb 2021 19:11:35 +0100 Subject: [PATCH 0656/1161] Add a GitHub Action workflow to release a new version (#1181) --- .github/workflows/publish-release.yaml | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/publish-release.yaml diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml new file mode 100644 index 000000000..6371a4ace --- /dev/null +++ b/.github/workflows/publish-release.yaml @@ -0,0 +1,29 @@ +name: Prepare Release + +on: + workflow_dispatch: + inputs: + version: + description: Version to release + required: true + force: + description: Force a release even when there are release-blockers (optional) + required: false + +jobs: + release: + runs-on: ubuntu-latest + name: Release version + steps: + - uses: actions/checkout@v2 + with: + token: ${{ secrets.GH_RELEASE_PAT }} + fetch-depth: 0 + + - name: Prepare release + uses: getsentry/action-prepare-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }} + with: + version: ${{ github.event.inputs.version }} + force: ${{ github.event.inputs.force }} From 62f6897e1e577de39b366b5c84e19a453da36016 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 18 Feb 2021 13:56:40 +0100 Subject: [PATCH 0657/1161] Prepare release 3.1.5 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 068c7ab85..c624888a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.1.5 (2021-02-18) + - Fix incorrect detection of silenced errors (by the `@` operator) (#1183) ## 3.1.4 (2021-02-02) From 51af981636bbe97fe9271dab76819d02593c3191 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 22 Feb 2021 18:26:12 +0100 Subject: [PATCH 0658/1161] Fix Codecov GitHub Action warning due to too low fetch depth (#1188) --- .craft.yml | 20 ++--- .editorconfig | 3 + .github/workflows/ci.yaml | 108 +++++++++++++------------ .github/workflows/publish-release.yaml | 46 +++++------ .github/workflows/static-analysis.yaml | 108 ++++++++++++------------- codecov.yml | 6 +- 6 files changed, 148 insertions(+), 143 deletions(-) diff --git a/.craft.yml b/.craft.yml index 7cb921217..2b133fc7a 100644 --- a/.craft.yml +++ b/.craft.yml @@ -1,16 +1,16 @@ -minVersion: '0.9.0' +minVersion: 0.9.0 github: - owner: getsentry - repo: sentry-php + owner: getsentry + repo: sentry-php changelogPolicy: simple -statusProvider: - name: github +statusProvider: + name: github artifactProvider: name: none preReleaseCommand: "" targets: - - name: github - - name: registry - type: sdk - config: - canonical: 'composer:sentry/sentry' + - name: github + - name: registry + type: sdk + config: + canonical: 'composer:sentry/sentry' diff --git a/.editorconfig b/.editorconfig index f2a39827d..5862f92a8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,5 +13,8 @@ indent_size = 4 [*.md] max_line_length = 80 +[*.{yml, yaml}] +indent_size = 2 + [COMMIT_EDITMSG] max_line_length = 0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b59deb5b9..14c0dcc4d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,67 +1,69 @@ name: CI on: - pull_request: - push: - branches: - - master - - develop + pull_request: + push: + branches: + - master + - develop jobs: - tests: - name: Tests - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - windows-latest - php: - - '7.2' - - '7.3' - - '7.4' - - '8.0' - dependencies: - - lowest - - highest + tests: + name: Tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + php: + - '7.2' + - '7.3' + - '7.4' + - '8.0' + dependencies: + - lowest + - highest - steps: - - name: Checkout - uses: actions/checkout@v2 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - coverage: xdebug + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug - - name: Setup Problem Matchers for PHPUnit - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Setup Problem Matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Determine Composer cache directory - id: composer-cache - run: echo "::set-output name=directory::$(composer config cache-dir)" + - name: Determine Composer cache directory + id: composer-cache + run: echo "::set-output name=directory::$(composer config cache-dir)" - - name: Cache Composer dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composer-cache.outputs.directory }} - key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.dependencies }}-composer- + - name: Cache Composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.directory }} + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.dependencies }}-composer- - - name: Install highest dependencies - run: composer update --no-progress --no-interaction --prefer-dist - if: ${{ matrix.dependencies == 'highest' }} + - name: Install highest dependencies + run: composer update --no-progress --no-interaction --prefer-dist + if: ${{ matrix.dependencies == 'highest' }} - - name: Install lowest dependencies - run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest - if: ${{ matrix.dependencies == 'lowest' }} + - name: Install lowest dependencies + run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest + if: ${{ matrix.dependencies == 'lowest' }} - - name: Run tests - run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml + - name: Run tests + run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml - - name: Upload code coverage - uses: codecov/codecov-action@v1 - with: - file: build/coverage-report.xml + - name: Upload code coverage + uses: codecov/codecov-action@v1 + with: + file: build/coverage-report.xml diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 6371a4ace..ea19fe8d9 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -1,29 +1,29 @@ name: Prepare Release on: - workflow_dispatch: - inputs: - version: - description: Version to release - required: true - force: - description: Force a release even when there are release-blockers (optional) - required: false + workflow_dispatch: + inputs: + version: + description: Version to release + required: true + force: + description: Force a release even when there are release-blockers (optional) + required: false jobs: - release: - runs-on: ubuntu-latest - name: Release version - steps: - - uses: actions/checkout@v2 - with: - token: ${{ secrets.GH_RELEASE_PAT }} - fetch-depth: 0 + release: + runs-on: ubuntu-latest + name: Release version + steps: + - uses: actions/checkout@v2 + with: + token: ${{ secrets.GH_RELEASE_PAT }} + fetch-depth: 0 - - name: Prepare release - uses: getsentry/action-prepare-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }} - with: - version: ${{ github.event.inputs.version }} - force: ${{ github.event.inputs.force }} + - name: Prepare release + uses: getsentry/action-prepare-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }} + with: + version: ${{ github.event.inputs.version }} + force: ${{ github.event.inputs.force }} diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 12ae66997..60a9fcd1a 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -1,59 +1,59 @@ name: Code style and static analysis on: - pull_request: - push: - branches: - - master - - develop + pull_request: + push: + branches: + - master + - develop jobs: - php-cs-fixer: - name: PHP-CS-Fixer - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - - - name: Install dependencies - run: composer update --no-progress --no-interaction --prefer-dist - - - name: Run script - run: composer phpcs - - phpstan: - name: PHPStan - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - - - name: Install dependencies - run: composer update --no-progress --no-interaction --prefer-dist - - - name: Run script - run: composer phpstan - - psalm: - name: Psalm - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - - - name: Install dependencies - run: composer update --no-progress --no-interaction --prefer-dist - - - name: Run script - run: composer psalm + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist + + - name: Run script + run: composer phpcs + + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist + + - name: Run script + run: composer phpstan + + psalm: + name: Psalm + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist + + - name: Run script + run: composer psalm diff --git a/codecov.yml b/codecov.yml index 0d992425c..b8989a5a7 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,5 +1,5 @@ comment: false ignore: - - tests/data - - tests/resources - - tests/Fixtures + - tests/data + - tests/resources + - tests/Fixtures From f25102315e4243007111089172315b3b639e080a Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 22 Feb 2021 20:59:51 +0100 Subject: [PATCH 0659/1161] Set the environment of the event to production if it has not been overridden explicitly (#1116) --- CHANGELOG.md | 1 + src/Client.php | 2 +- src/Event.php | 2 ++ src/Options.php | 2 +- tests/ClientTest.php | 18 +++++++++++++----- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f844b051..30e4c6090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Deprecate the `logger` option (#1167) - Pass the event hint from the `capture*()` methods down to the `before_send` callback (#1138) - Deprecate the `tags` option, see the [docs](https://docs.sentry.io/platforms/php/guides/laravel/enriching-events/tags/) for other ways to set tags (#1174) +- Make sure the `environment` field is set to `production` if it has not been overridden explicitly (#1116) ## 3.1.5 (2021-02-18) diff --git a/src/Client.php b/src/Client.php index f40e68fec..87ceca46b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -242,7 +242,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco } if (null === $event->getEnvironment()) { - $event->setEnvironment($this->options->getEnvironment()); + $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT); } if (null === $event->getLogger()) { diff --git a/src/Event.php b/src/Event.php index 60bf4ae8e..6f0e582d4 100644 --- a/src/Event.php +++ b/src/Event.php @@ -16,6 +16,8 @@ */ final class Event { + public const DEFAULT_ENVIRONMENT = 'production'; + /** * @var EventId The ID */ diff --git a/src/Options.php b/src/Options.php index 43c1963fe..8b5c5bd17 100644 --- a/src/Options.php +++ b/src/Options.php @@ -311,7 +311,7 @@ public function getRelease(): ?string /** * Sets the release tag to be passed with every event sent to Sentry. * - * @param string $release The release + * @param string|null $release The release */ public function setRelease(?string $release): void { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 461f2aad9..304167725 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -14,7 +14,6 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\EventHint; -use Sentry\EventId; use Sentry\ExceptionMechanism; use Sentry\Frame; use Sentry\Integration\IntegrationInterface; @@ -204,7 +203,9 @@ public function captureExceptionWithEventHintDataProvider(): \Generator */ public function testCaptureEvent(array $options, Event $event, Event $expectedEvent): void { - $this->expectDeprecation('The option "tags" is deprecated since version 3.2 and will be removed in 4.0. Either set the tags on the scope or on the event.'); + if (isset($options['tags'])) { + $this->expectDeprecation('The option "tags" is deprecated since version 3.2 and will be removed in 4.0. Either set the tags on the scope or on the event.'); + } $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) @@ -224,8 +225,7 @@ public function testCaptureEvent(array $options, Event $event, Event $expectedEv public function captureEventDataProvider(): \Generator { - $eventId = EventId::generate(); - $event = Event::createEvent($eventId); + $event = Event::createEvent(); yield 'Options set && no event properties set => use options' => [ [ @@ -238,7 +238,7 @@ public function captureEventDataProvider(): \Generator $event, ]; - $event = Event::createEvent($eventId); + $event = Event::createEvent(); $event->setServerName('foo.example.com'); $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); $event->setEnvironment('production'); @@ -254,6 +254,14 @@ public function captureEventDataProvider(): \Generator $event, $event, ]; + + $event = Event::createEvent(); + + yield 'Environment option set to null && no event property set => fallback to default value' => [ + ['environment' => null], + $event, + $event, + ]; } public function testCaptureEventWithEventHint(): void From b9b0d90c0fe74ace6e713fb7fca3478389db2377 Mon Sep 17 00:00:00 2001 From: Robert Lemke Date: Wed, 3 Mar 2021 10:20:54 +0100 Subject: [PATCH 0660/1161] Update list of 3rd party integrations (#1191) Added a new section for integrations using the 2.x SDK and included an alternative package for Neos Flow (previously "Flow Framework") which supports Flow 7.x and Sentry SDK 3.x. --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3b36d4689..21cb8ca82 100644 --- a/README.md +++ b/README.md @@ -76,15 +76,19 @@ The following integrations are fully supported and maintained by the Sentry team The following integrations are available and maintained by members of the Sentry community. - [Drupal](https://www.drupal.org/project/raven) -- [Flow Framework](https://github.com/networkteam/Networkteam.SentryClient) -- [OXID eShop](https://github.com/OXIDprojects/sentry) +- [Neos Flow](https://github.com/flownative/flow-sentry) - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [ZendFramework](https://github.com/facile-it/sentry-module) -- [SilverStripe](https://github.com/phptek/silverstripe-sentry) -- [TYPO3](https://github.com/networkteam/sentry_client) - [Yii2](https://github.com/notamedia/yii2-sentry) - ... feel free to be famous, create a port to your favourite platform! +### 3rd party integrations using old SDK 2.x + +- [Neos Flow](https://github.com/networkteam/Networkteam.SentryClient) +- [OXID eShop](https://github.com/OXIDprojects/sentry) +- [SilverStripe](https://github.com/phptek/silverstripe-sentry) +- [TYPO3](https://github.com/networkteam/sentry_client) + ### 3rd party integrations using old SDK 1.x - [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient) From 899b0de58c1e01feb54829b3094af74252aff385 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 3 Mar 2021 12:54:34 +0100 Subject: [PATCH 0661/1161] Prepare release 3.2.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e4c6090..120f7765c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.2.0 (2021-03-03) + - Make the HTTP headers sanitizable in the `RequestIntegration` integration instead of removing them entirely (#1161) - Deprecate the `logger` option (#1167) - Pass the event hint from the `capture*()` methods down to the `before_send` callback (#1138) From 29caf123be7a0547a5b6621c4adb3c05dc59bfb9 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 3 Mar 2021 15:00:22 +0100 Subject: [PATCH 0662/1161] Start development of version 3.3.x --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cbdb64a97..38f3fdd40 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "3.3.x-dev" } } } From d9fdc40728dc58936f59e9b2e1104163fdcafe24 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 4 Mar 2021 16:34:32 +0100 Subject: [PATCH 0663/1161] Do fetch-depth 2 on CI to fix Codecov --- .github/workflows/static-analysis.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 60a9fcd1a..b470925b1 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -48,6 +48,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + with: + fetch-depth: 2 # needed by codecov sometimes - name: Setup PHP uses: shivammathur/setup-php@v2 From f35ffc85a383c22f82098a82ba283a73c0267fa0 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 5 Mar 2021 10:29:35 -0500 Subject: [PATCH 0664/1161] We're hiring --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 21cb8ca82..1d65b6e22 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@

- - - + + + +

+_Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_ + # Sentry SDK for PHP [![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) From 61eed66abbac9802c096a7793f3870224daca14c Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Mon, 15 Mar 2021 11:52:18 +0100 Subject: [PATCH 0665/1161] Allow setting a custom timestmap for the breadcrumbs (#1193) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 2 + src/Breadcrumb.php | 42 ++++++++++++---- tests/BreadcrumbTest.php | 102 ++++++++++++++++++++++++++++++++++----- 3 files changed, 123 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 120f7765c..f2a2b0e02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Allow setting a custom timestamp on the breadcrumbs (#1193) + ## 3.2.0 (2021-03-03) - Make the HTTP headers sanitizable in the `RequestIntegration` integration instead of removing them entirely (#1161) diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index 3b65be03f..7229fddc4 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -108,13 +108,14 @@ final class Breadcrumb /** * Constructor. * - * @param string $level The error level of the breadcrumb - * @param string $type The type of the breadcrumb - * @param string $category The category of the breadcrumb - * @param string|null $message Optional text message - * @param array $metadata Additional information about the breadcrumb + * @param string $level The error level of the breadcrumb + * @param string $type The type of the breadcrumb + * @param string $category The category of the breadcrumb + * @param string|null $message Optional text message + * @param array $metadata Additional information about the breadcrumb + * @param float|null $timestamp Optional timestamp of the breadcrumb */ - public function __construct(string $level, string $type, string $category, ?string $message = null, array $metadata = []) + public function __construct(string $level, string $type, string $category, ?string $message = null, array $metadata = [], ?float $timestamp = null) { if (!\in_array($level, self::ALLOWED_LEVELS, true)) { throw new InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); @@ -125,7 +126,7 @@ public function __construct(string $level, string $type, string $category, ?stri $this->category = $category; $this->message = $message; $this->metadata = $metadata; - $this->timestamp = microtime(true); + $this->timestamp = $timestamp ?? microtime(true); } /** @@ -300,6 +301,25 @@ public function getTimestamp(): float return $this->timestamp; } + /** + * Sets the breadcrumb timestamp. + * + * @param float $timestamp The timestamp + * + * @return static + */ + public function withTimestamp(float $timestamp): self + { + if ($timestamp === $this->timestamp) { + return $this; + } + + $new = clone $this; + $new->timestamp = $timestamp; + + return $new; + } + /** * Helper method to create an instance of this class from an array of data. * @@ -309,8 +329,9 @@ public function getTimestamp(): float * level: string, * type?: string, * category: string, - * message?: string, - * data?: array + * message?: string|null, + * data?: array, + * timestamp?: float|null * } $data */ public static function fromArray(array $data): self @@ -320,7 +341,8 @@ public static function fromArray(array $data): self $data['type'] ?? self::TYPE_DEFAULT, $data['category'], $data['message'] ?? null, - $data['data'] ?? [] + $data['data'] ?? [], + $data['timestamp'] ?? null ); } } diff --git a/tests/BreadcrumbTest.php b/tests/BreadcrumbTest.php index 7499acb42..a27a8f52b 100644 --- a/tests/BreadcrumbTest.php +++ b/tests/BreadcrumbTest.php @@ -6,10 +6,8 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Symfony\Bridge\PhpUnit\ClockMock; -/** - * @group time-sensitive - */ final class BreadcrumbTest extends TestCase { public function testConstructorThrowsOnInvalidLevel(): void @@ -29,23 +27,81 @@ public function testWithLevelThrowsOnInvalidLevel(): void $breadcrumb->withLevel('bar'); } - public function testConstructor(): void + /** + * @dataProvider constructorDataProvider + */ + public function testConstructor(array $constructorArgs, string $expectedLevel, string $expectedType, string $expectedCategory, ?string $expectedMessage, array $expectedMetadata, float $expectedTimestamp): void + { + ClockMock::withClockMock(1615588578.6652); + + $breadcrumb = new Breadcrumb(...$constructorArgs); + + $this->assertSame($expectedCategory, $breadcrumb->getCategory()); + $this->assertSame($expectedLevel, $breadcrumb->getLevel()); + $this->assertSame($expectedMessage, $breadcrumb->getMessage()); + $this->assertSame($expectedType, $breadcrumb->getType()); + $this->assertSame($expectedMetadata, $breadcrumb->getMetadata()); + $this->assertSame($expectedTimestamp, $breadcrumb->getTimestamp()); + } + + public function constructorDataProvider(): \Generator { - $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz']); - - $this->assertSame('foo', $breadcrumb->getCategory()); - $this->assertSame(Breadcrumb::LEVEL_INFO, $breadcrumb->getLevel()); - $this->assertSame('foo bar', $breadcrumb->getMessage()); - $this->assertSame(Breadcrumb::TYPE_USER, $breadcrumb->getType()); - $this->assertSame(['baz'], $breadcrumb->getMetadata()); - $this->assertSame(microtime(true), $breadcrumb->getTimestamp()); + yield [ + [ + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_USER, + 'log', + ], + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_USER, + 'log', + null, + [], + 1615588578.6652, + ]; + + yield [ + [ + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_USER, + 'log', + 'something happened', + ['foo' => 'bar'], + null, + ], + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_USER, + 'log', + 'something happened', + ['foo' => 'bar'], + 1615588578.6652, + ]; + + yield [ + [ + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_USER, + 'log', + null, + [], + 1615590096.3244, + ], + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_USER, + 'log', + null, + [], + 1615590096.3244, + ]; } /** * @dataProvider fromArrayDataProvider */ - public function testFromArray(array $requestData, string $expectedLevel, string $expectedType, string $expectedCategory, ?string $expectedMessage, array $expectedMetadata): void + public function testFromArray(array $requestData, string $expectedLevel, string $expectedType, string $expectedCategory, ?string $expectedMessage, array $expectedMetadata, float $expectedTimestamp): void { + ClockMock::withClockMock(1615588578.6652); + $breadcrumb = Breadcrumb::fromArray($requestData); $this->assertSame($expectedLevel, $breadcrumb->getLevel()); @@ -53,6 +109,7 @@ public function testFromArray(array $requestData, string $expectedLevel, string $this->assertSame($expectedCategory, $breadcrumb->getCategory()); $this->assertSame($expectedMessage, $breadcrumb->getMessage()); $this->assertSame($expectedMetadata, $breadcrumb->getMetadata()); + $this->assertSame($expectedTimestamp, $breadcrumb->getTimestamp()); } public function fromArrayDataProvider(): iterable @@ -64,12 +121,14 @@ public function fromArrayDataProvider(): iterable 'category' => 'foo', 'message' => 'foo bar', 'data' => ['baz'], + 'timestamp' => 1615590096.3244, ], Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', 'foo bar', ['baz'], + 1615590096.3244, ]; yield [ @@ -82,6 +141,7 @@ public function fromArrayDataProvider(): iterable 'foo', null, [], + 1615588578.6652, ]; } @@ -144,4 +204,20 @@ public function testWithoutMetadata(): void $this->assertSame(['foo' => 'bar'], $breadcrumb->getMetadata()); $this->assertArrayNotHasKey('foo', $newBreadcrumb->getMetadata()); } + + public function testWithTimestamp(): void + { + $timestamp = 12345.678; + $newTimestamp = 987.654; + $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'foo', null, ['foo' => 'bar'], $timestamp); + $newBreadcrumb = $breadcrumb->withTimestamp($timestamp); + + $this->assertSame($breadcrumb, $newBreadcrumb); + + $newBreadcrumb = $breadcrumb->withTimestamp($newTimestamp); + + $this->assertNotSame($breadcrumb, $newBreadcrumb); + $this->assertSame($timestamp, $breadcrumb->getTimestamp()); + $this->assertSame($newTimestamp, $newBreadcrumb->getTimestamp()); + } } From 4f6f8fa701e5db53c04f471b139e7d4f85831f17 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 18 Mar 2021 09:43:44 +0100 Subject: [PATCH 0666/1161] Fix random failing CI on Windows due to OOM error (#1200) --- .../error_handler_captures_out_of_memory_fatal_error.phpt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt index 20cf5ff13..0c788d88b 100644 --- a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt @@ -1,7 +1,7 @@ --TEST-- Test catching out of memory fatal error --INI-- -memory_limit=64M +memory_limit=128M --FILE-- addErrorHandlerListener(static function (): void { echo 'Error listener called (it should not have been)' . PHP_EOL; }); -$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(1024 * 1024); $errorHandler->addFatalErrorHandlerListener(static function (): void { echo 'Fatal error listener called' . PHP_EOL; }); @@ -34,7 +34,7 @@ $errorHandler->addExceptionHandlerListener(static function (): void { echo 'Exception listener called (it should not have been)' . PHP_EOL; }); -$foo = str_repeat('x', 1024 * 1024 * 70); +$foo = str_repeat('x', 1024 * 1024 * 1024); ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d From 69da9c417850b32b31666bf18bf02530f987414c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 18 Mar 2021 09:48:48 +0100 Subject: [PATCH 0667/1161] Add the ignore_tags option to the IgnoreErrorsIntegration integration (#1201) --- CHANGELOG.md | 1 + src/Integration/IgnoreErrorsIntegration.php | 31 +++++++++++++++++++ .../IgnoreErrorsIntegrationTest.php | 28 +++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a2b0e02..d9bf81878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Allow setting a custom timestamp on the breadcrumbs (#1193) +- Add option `ignore_tags` to `IgnoreErrorsIntegration` in order to ignore exceptions by tags values. (#1201) ## 3.2.0 (2021-03-03) diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index b211316fe..4cc5961a0 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -30,6 +30,7 @@ final class IgnoreErrorsIntegration implements IntegrationInterface * * @psalm-param array{ * ignore_exceptions?: list> + * ignore_tags?: array * } $options */ public function __construct(array $options = []) @@ -37,9 +38,11 @@ public function __construct(array $options = []) $resolver = new OptionsResolver(); $resolver->setDefaults([ 'ignore_exceptions' => [], + 'ignore_tags' => [], ]); $resolver->setAllowedTypes('ignore_exceptions', ['array']); + $resolver->setAllowedTypes('ignore_tags', ['array']); $this->options = $resolver->resolve($options); } @@ -73,6 +76,10 @@ private function shouldDropEvent(Event $event, array $options): bool return true; } + if ($this->isIgnoredTag($event, $options)) { + return true; + } + return false; } @@ -99,4 +106,28 @@ private function isIgnoredException(Event $event, array $options): bool return false; } + + /** + * Checks whether the given event should be dropped or not according to the + * criteria defined in the integration's options. + * + * @param Event $event The event instance + * @param array $options The options of the integration + */ + private function isIgnoredTag(Event $event, array $options): bool + { + $tags = $event->getTags(); + + if (empty($tags)) { + return false; + } + + foreach ($options['ignore_tags'] as $key => $value) { + if (isset($tags[$key]) && $tags[$key] === $value) { + return true; + } + } + + return false; + } } diff --git a/tests/Integration/IgnoreErrorsIntegrationTest.php b/tests/Integration/IgnoreErrorsIntegrationTest.php index 2275b47f6..0ead916aa 100644 --- a/tests/Integration/IgnoreErrorsIntegrationTest.php +++ b/tests/Integration/IgnoreErrorsIntegrationTest.php @@ -96,5 +96,33 @@ public function invokeDataProvider(): \Generator ], true, ]; + + $event = Event::createEvent(); + $event->setTags(['route' => 'foo']); + + yield 'The tag is matching the "ignore_tags" option' => [ + $event, + true, + [ + 'ignore_tags' => [ + 'route' => 'foo', + ], + ], + true, + ]; + + $event = Event::createEvent(); + $event->setTags(['route' => 'bar']); + + yield 'The tag is not matching the "ignore_tags" option' => [ + $event, + true, + [ + 'ignore_tags' => [ + 'route' => 'foo', + ], + ], + false, + ]; } } From a886e0a150bb6d0abce96821a402c46b66f4b2cd Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 18 Mar 2021 09:57:15 +0100 Subject: [PATCH 0668/1161] Fix random failing CI on Windows due to OOM error (#1200) (#1202) (cherry picked from commit 4f6f8fa701e5db53c04f471b139e7d4f85831f17) --- .../error_handler_captures_out_of_memory_fatal_error.phpt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt index 20cf5ff13..0c788d88b 100644 --- a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt @@ -1,7 +1,7 @@ --TEST-- Test catching out of memory fatal error --INI-- -memory_limit=64M +memory_limit=128M --FILE-- addErrorHandlerListener(static function (): void { echo 'Error listener called (it should not have been)' . PHP_EOL; }); -$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(1024 * 1024); $errorHandler->addFatalErrorHandlerListener(static function (): void { echo 'Fatal error listener called' . PHP_EOL; }); @@ -34,7 +34,7 @@ $errorHandler->addExceptionHandlerListener(static function (): void { echo 'Exception listener called (it should not have been)' . PHP_EOL; }); -$foo = str_repeat('x', 1024 * 1024 * 70); +$foo = str_repeat('x', 1024 * 1024 * 1024); ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d From 95fd4c2d723b7e84a2ced610989ae59a467f1a73 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 24 Mar 2021 20:30:48 +0100 Subject: [PATCH 0669/1161] Fix `error_reporting` value not evaluated on the moment of the error being reported by default (#1196) --- CHANGELOG.md | 2 + src/Integration/ErrorListenerIntegration.php | 2 +- src/Options.php | 6 +- tests/OptionsTest.php | 33 +++++++++++ ...espects_current_error_reporting_level.phpt | 57 +++++++++++++++++++ 5 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 tests/phpt/error_handler_respects_current_error_reporting_level.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index 120f7765c..8dcebd6cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Changes behaviour of `error_types` option when not set: before it defaulted to `error_reporting()` statically at SDK initialization; now it will be evaluated each time during error handling to allow silencing errors temporarily (#1196) + ## 3.2.0 (2021-03-03) - Make the HTTP headers sanitizable in the `RequestIntegration` integration instead of removing them entirely (#1161) diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index 3dc2bfb0b..1d7539e27 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -35,7 +35,7 @@ public function setupOnce(): void return; } - if (!($client->getOptions()->getErrorTypes() & $exception->getSeverity())) { + if (!$exception instanceof SilencedErrorException && !($client->getOptions()->getErrorTypes() & $exception->getSeverity())) { return; } diff --git a/src/Options.php b/src/Options.php index 8b5c5bd17..486b4fcaf 100644 --- a/src/Options.php +++ b/src/Options.php @@ -411,7 +411,7 @@ public function setTags(array $tags): void */ public function getErrorTypes(): int { - return $this->options['error_types']; + return $this->options['error_types'] ?? error_reporting(); } /** @@ -716,7 +716,7 @@ private function configureOptions(OptionsResolver $resolver): void return $event; }, 'tags' => [], - 'error_types' => error_reporting(), + 'error_types' => null, 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, 'before_breadcrumb' => static function (Breadcrumb $breadcrumb): Breadcrumb { return $breadcrumb; @@ -748,7 +748,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('tags', 'string[]'); - $resolver->setAllowedTypes('error_types', ['int']); + $resolver->setAllowedTypes('error_types', ['null', 'int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); $resolver->setAllowedTypes('before_breadcrumb', ['callable']); $resolver->setAllowedTypes('integrations', ['Sentry\\Integration\\IntegrationInterface[]', 'callable']); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 54cd4ed6d..7adf45485 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -14,6 +14,25 @@ final class OptionsTest extends TestCase { use ExpectDeprecationTrait; + /** + * @var int + */ + private $errorReportingOnSetUp; + + protected function setUp(): void + { + parent::setUp(); + + $this->errorReportingOnSetUp = error_reporting(); + } + + protected function tearDown(): void + { + error_reporting($this->errorReportingOnSetUp); + + parent::tearDown(); + } + /** * @group legacy * @@ -519,4 +538,18 @@ public function testReleaseOptionDefaultValueIsGotFromEnvironmentVariable(): voi $this->assertSame('0.0.1', (new Options())->getRelease()); } + + public function testErrorTypesOptionIsNotDynamiclyReadFromErrorReportingLevelWhenSet(): void + { + $errorReportingBeforeTest = error_reporting(\E_ERROR); + $errorTypesOptionValue = \E_NOTICE; + + $options = new Options(['error_types' => $errorTypesOptionValue]); + + $this->assertSame($errorTypesOptionValue, $options->getErrorTypes()); + + error_reporting($errorReportingBeforeTest); + + $this->assertSame($errorTypesOptionValue, $options->getErrorTypes()); + } } diff --git a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt new file mode 100644 index 000000000..44db31512 --- /dev/null +++ b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt @@ -0,0 +1,57 @@ +--TEST-- +Test that the error handler uses the current error_reporting() level. +--INI-- +error_reporting=E_ALL +display_errors=Off +--FILE-- + null, + 'before_send' => static function () { + echo 'Before send callback called' . PHP_EOL; + + return null; + }, +])->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +echo 'Triggering E_USER_NOTICE with error reporting on E_ALL' . PHP_EOL; + +trigger_error('A notice that should be captured', E_USER_NOTICE); + +$old_error_reporting = error_reporting(E_ALL & ~E_USER_NOTICE); + +echo 'Triggering E_USER_NOTICE with error reporting on E_ALL & ~E_USER_NOTICE' . PHP_EOL; + +trigger_error('A notice that should not be captured', E_USER_NOTICE); + +error_reporting($old_error_reporting); + +echo 'Triggering E_USER_NOTICE with error reporting on E_ALL again' . PHP_EOL; + +trigger_error('A notice that should be captured', E_USER_NOTICE); + +?> +--EXPECT-- +Triggering E_USER_NOTICE with error reporting on E_ALL +Before send callback called +Triggering E_USER_NOTICE with error reporting on E_ALL & ~E_USER_NOTICE +Triggering E_USER_NOTICE with error reporting on E_ALL again +Before send callback called From b87288c05c66abbc2f06c3babc652191e09bd35f Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 6 Apr 2021 09:41:29 +0200 Subject: [PATCH 0670/1161] Prepare release 3.2.1 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dcebd6cd..ee75d620b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +### 3.2.1 + - Changes behaviour of `error_types` option when not set: before it defaulted to `error_reporting()` statically at SDK initialization; now it will be evaluated each time during error handling to allow silencing errors temporarily (#1196) ## 3.2.0 (2021-03-03) From fb4f83e6e2d718d1e5fbfe3a20cced83f47f040f Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 6 Apr 2021 09:41:29 +0200 Subject: [PATCH 0671/1161] Fix CHANGELOG markdown --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee75d620b..16921bf21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -### 3.2.1 +## 3.2.1 - Changes behaviour of `error_types` option when not set: before it defaulted to `error_reporting()` statically at SDK initialization; now it will be evaluated each time during error handling to allow silencing errors temporarily (#1196) From 1b4acb089c63246d1a6f074e4b5ebcfe80be2057 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 8 Apr 2021 22:37:53 +0200 Subject: [PATCH 0672/1161] Fix missing handling of the event hint in the HubAdapter::capture() methods (#1206) --- CHANGELOG.md | 2 + phpstan-baseline.neon | 47 ++++++++++++++-------- src/State/HubAdapter.php | 15 ++++--- tests/State/HubAdapterTest.php | 73 ++++++++++++++++++++++++++++++---- 4 files changed, 107 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16921bf21..251231fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix missing handling of `EventHint` in the `HubAdapter::capture*()` methods (#1206) + ## 3.2.1 - Changes behaviour of `error_types` option when not set: before it defaulted to `error_reporting()` statically at SDK initialization; now it will be evaluated each time during error handling to allow silencing errors temporarily (#1196) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 48e67abfc..c374d308e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -11,7 +11,7 @@ parameters: path: src/ClientInterface.php - - message: "#^Offset 'scheme' does not exist on array\\(\\?'scheme' \\=\\> string, \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + message: "#^Offset 'host' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" count: 1 path: src/Dsn.php @@ -21,7 +21,7 @@ parameters: path: src/Dsn.php - - message: "#^Offset 'host' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + message: "#^Offset 'scheme' does not exist on array\\(\\?'scheme' \\=\\> string, \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" count: 1 path: src/Dsn.php @@ -36,22 +36,22 @@ parameters: path: src/EventHint.php - - message: "#^Call to static method create\\(\\) on an unknown class Symfony\\\\Component\\\\HttpClient\\\\HttpClient\\.$#" + message: "#^Access to constant CONNECT_TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" count: 1 path: src/HttpClient/HttpClientFactory.php - - message: "#^Access to constant TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" + message: "#^Access to constant PROXY on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" count: 1 path: src/HttpClient/HttpClientFactory.php - - message: "#^Access to constant CONNECT_TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" + message: "#^Access to constant TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" count: 1 path: src/HttpClient/HttpClientFactory.php - - message: "#^Access to constant PROXY on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" + message: "#^Call to static method create\\(\\) on an unknown class Symfony\\\\Component\\\\HttpClient\\\\HttpClient\\.$#" count: 1 path: src/HttpClient/HttpClientFactory.php @@ -66,29 +66,39 @@ parameters: path: src/Serializer/AbstractSerializer.php - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureMessage\\(\\) invoked with 4 parameters, 1\\-3 required\\.$#" + message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" count: 1 path: src/State/Hub.php - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" + message: "#^Method Sentry\\\\ClientInterface\\:\\:captureLastError\\(\\) invoked with 2 parameters, 0\\-1 required\\.$#" count: 1 path: src/State/Hub.php - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureLastError\\(\\) invoked with 2 parameters, 0\\-1 required\\.$#" + message: "#^Method Sentry\\\\ClientInterface\\:\\:captureMessage\\(\\) invoked with 4 parameters, 1\\-3 required\\.$#" count: 1 path: src/State/Hub.php - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 path: src/State/HubAdapter.php - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$hint$#" - count: 3 - path: src/State/HubInterface.php + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureLastError\\(\\) invoked with 1 parameter, 0 required\\.$#" + count: 1 + path: src/State/HubAdapter.php + + - + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureMessage\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" + count: 1 + path: src/State/HubAdapter.php + + - + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: src/State/HubAdapter.php - message: "#^PHPDoc tag @param references unknown parameter\\: \\$customSamplingContext$#" @@ -96,9 +106,9 @@ parameters: path: src/State/HubInterface.php - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureMessage\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" - count: 1 - path: src/functions.php + message: "#^PHPDoc tag @param references unknown parameter\\: \\$hint$#" + count: 3 + path: src/State/HubInterface.php - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" @@ -110,6 +120,11 @@ parameters: count: 1 path: src/functions.php + - + message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureMessage\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" + count: 1 + path: src/functions.php + - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 65110fda3..a047a9dac 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -106,17 +106,19 @@ public function bindClient(ClientInterface $client): void /** * {@inheritdoc} */ - public function captureMessage(string $message, ?Severity $level = null): ?EventId + public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - return SentrySdk::getCurrentHub()->captureMessage($message, $level); + /** @psalm-suppress TooManyArguments */ + return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint); } /** * {@inheritdoc} */ - public function captureException(\Throwable $exception): ?EventId + public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - return SentrySdk::getCurrentHub()->captureException($exception); + /** @psalm-suppress TooManyArguments */ + return SentrySdk::getCurrentHub()->captureException($exception, $hint); } /** @@ -130,9 +132,10 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId /** * {@inheritdoc} */ - public function captureLastError(): ?EventId + public function captureLastError(?EventHint $hint = null): ?EventId { - return SentrySdk::getCurrentHub()->captureLastError(); + /** @psalm-suppress TooManyArguments */ + return SentrySdk::getCurrentHub()->captureLastError($hint); } /** diff --git a/tests/State/HubAdapterTest.php b/tests/State/HubAdapterTest.php index 8a83b411d..1b09cb90d 100644 --- a/tests/State/HubAdapterTest.php +++ b/tests/State/HubAdapterTest.php @@ -131,22 +131,46 @@ public function testBindClient(): void HubAdapter::getInstance()->bindClient($client); } - public function testCaptureMessage(): void + /** + * @dataProvider captureMessageDataProvider + */ + public function testCaptureMessage(array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); $hub = $this->createMock(HubInterface::class); $hub->expects($this->once()) ->method('captureMessage') - ->with('foo', Severity::debug()) + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); SentrySdk::setCurrentHub($hub); - $this->assertSame($eventId, HubAdapter::getInstance()->captureMessage('foo', Severity::debug())); + $this->assertSame($eventId, HubAdapter::getInstance()->captureMessage(...$expectedFunctionCallArgs)); } - public function testCaptureException(): void + public function captureMessageDataProvider(): \Generator + { + yield [ + [ + 'foo', + Severity::debug(), + ], + ]; + + yield [ + [ + 'foo', + Severity::debug(), + new EventHint(), + ], + ]; + } + + /** + * @dataProvider captureExceptionDataProvider + */ + public function testCaptureException(array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); $exception = new \Exception(); @@ -154,12 +178,28 @@ public function testCaptureException(): void $hub = $this->createMock(HubInterface::class); $hub->expects($this->once()) ->method('captureException') - ->with($exception) + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); SentrySdk::setCurrentHub($hub); - $this->assertSame($eventId, HubAdapter::getInstance()->captureException($exception)); + $this->assertSame($eventId, HubAdapter::getInstance()->captureException(...$expectedFunctionCallArgs)); + } + + public function captureExceptionDataProvider(): \Generator + { + yield [ + [ + new \Exception('foo'), + ], + ]; + + yield [ + [ + new \Exception('foo'), + new EventHint(), + ], + ]; } public function testCaptureEvent(): void @@ -178,18 +218,35 @@ public function testCaptureEvent(): void $this->assertSame($event->getId(), HubAdapter::getInstance()->captureEvent($event, $hint)); } - public function testCaptureLastError(): void + /** + * @dataProvider captureLastErrorDataProvider + */ + public function testCaptureLastError(array $expectedFunctionCallArgs): void { $eventId = EventId::generate(); $hub = $this->createMock(HubInterface::class); $hub->expects($this->once()) ->method('captureLastError') + ->with(...$expectedFunctionCallArgs) ->willReturn($eventId); SentrySdk::setCurrentHub($hub); - $this->assertSame($eventId, HubAdapter::getInstance()->captureLastError()); + $this->assertSame($eventId, HubAdapter::getInstance()->captureLastError(...$expectedFunctionCallArgs)); + } + + public function captureLastErrorDataProvider(): \Generator + { + yield [ + [], + ]; + + yield [ + [ + new EventHint(), + ], + ]; } public function testAddBreadcrumb(): void From 02237728bdc5b82b0a14c37417644e3f3606db9b Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 6 May 2021 12:15:01 +0200 Subject: [PATCH 0673/1161] Prepare release 3.2.2 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 251231fc5..39148f1ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,11 @@ ## Unreleased +## 3.2.2 (2020-05-06) + - Fix missing handling of `EventHint` in the `HubAdapter::capture*()` methods (#1206) -## 3.2.1 +## 3.2.1 (2020-04-06) - Changes behaviour of `error_types` option when not set: before it defaulted to `error_reporting()` statically at SDK initialization; now it will be evaluated each time during error handling to allow silencing errors temporarily (#1196) From 7d5db440a33db63f1c75b109e9eb975eb2a4e45f Mon Sep 17 00:00:00 2001 From: Scott Dutton Date: Tue, 18 May 2021 20:24:42 +0100 Subject: [PATCH 0674/1161] Normalise licence file (#1218) * Normalise licence file Github doesn't pick this up as BSD-3 due to the change of wording, this puts it back to the original wording so github will mark this as BSD-3 * Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 9c9f585d4..bef63ca82 100644 --- a/LICENSE +++ b/LICENSE @@ -7,6 +7,6 @@ Redistribution and use in source and binary forms, with or without modification, 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of the Raven, Sentry, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + 3. Neither the name of the copyright holder, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 3bb122f9fc2bc43a4646e37db79eaf115b35b047 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 25 May 2021 20:32:24 +0200 Subject: [PATCH 0675/1161] Prepare release 3.3.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4874c054..95fecec25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.3.0 (2021-05-26) + - Allow setting a custom timestamp on the breadcrumbs (#1193) - Add option `ignore_tags` to `IgnoreErrorsIntegration` in order to ignore exceptions by tags values. (#1201) From e17fd4722f79c623377b05754dd207952d002939 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 26 May 2021 10:57:13 +0200 Subject: [PATCH 0676/1161] Start development of version 3.4.x --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 38f3fdd40..2df566b92 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.3.x-dev" + "dev-master": "3.4.x-dev" } } } From 5279f0ae581a14c12dba01e325ec3029cc29ea6c Mon Sep 17 00:00:00 2001 From: Ion Bazan Date: Fri, 4 Jun 2021 17:23:40 +0800 Subject: [PATCH 0677/1161] Refactor HttpClientFactory (#1133) * Refactor HttpClientFactory * simplify HttpClientFactory * test http client factory * fix test * fix reflection * remove added test * fix return type --- phpstan-baseline.neon | 10 +++ src/HttpClient/HttpClientFactory.php | 109 ++++++++++++++------------- 2 files changed, 68 insertions(+), 51 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c374d308e..45eaea9f4 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -55,6 +55,16 @@ parameters: count: 1 path: src/HttpClient/HttpClientFactory.php + - + message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Http\\\\Client\\\\Curl\\\\Client\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Symfony\\\\Component\\\\HttpClient\\\\HttplugClient\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + - message: "#^Method Sentry\\\\Monolog\\\\Handler\\:\\:write\\(\\) has parameter \\$record with no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 977d75850..05c1d3a8a 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -15,6 +15,7 @@ use Http\Client\Curl\Client as CurlHttpClient; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Discovery\HttpAsyncClientDiscovery; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UriFactoryInterface; @@ -106,60 +107,11 @@ public function create(Options $options): HttpAsyncClientInterface throw new \RuntimeException('Cannot create an HTTP client without the Sentry DSN set in the options.'); } - $httpClient = $this->httpClient; - - if (null !== $httpClient && null !== $options->getHttpProxy()) { + if (null !== $this->httpClient && null !== $options->getHttpProxy()) { throw new \RuntimeException('The "http_proxy" option does not work together with a custom HTTP client.'); } - if (null === $httpClient) { - if (class_exists(SymfonyHttplugClient::class)) { - $symfonyConfig = [ - 'max_duration' => self::DEFAULT_HTTP_TIMEOUT, - ]; - - if (null !== $options->getHttpProxy()) { - $symfonyConfig['proxy'] = $options->getHttpProxy(); - } - - /** @psalm-suppress UndefinedClass */ - $httpClient = new SymfonyHttplugClient( - SymfonyHttpClient::create($symfonyConfig) - ); - } elseif (class_exists(GuzzleHttpClient::class)) { - /** @psalm-suppress UndefinedClass */ - $guzzleConfig = [ - GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, - GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, - ]; - - if (null !== $options->getHttpProxy()) { - /** @psalm-suppress UndefinedClass */ - $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); - } - - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $httpClient = GuzzleHttpClient::createWithConfig($guzzleConfig); - } elseif (class_exists(CurlHttpClient::class)) { - $curlConfig = [ - \CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, - \CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, - ]; - - if (null !== $options->getHttpProxy()) { - $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); - } - - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $httpClient = new CurlHttpClient(null, null, $curlConfig); - } elseif (null !== $options->getHttpProxy()) { - throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); - } - } - - if (null === $httpClient) { - $httpClient = HttpAsyncClientDiscovery::find(); - } + $httpClient = $this->httpClient ?? $this->resolveClient($options); $httpClientPlugins = [ new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion]), @@ -175,4 +127,59 @@ public function create(Options $options): HttpAsyncClientInterface return new PluginClient($httpClient, $httpClientPlugins); } + + /** + * @return ClientInterface|HttpAsyncClientInterface + */ + private function resolveClient(Options $options) + { + if (class_exists(SymfonyHttplugClient::class)) { + $symfonyConfig = [ + 'max_duration' => self::DEFAULT_HTTP_TIMEOUT, + ]; + + if (null !== $options->getHttpProxy()) { + $symfonyConfig['proxy'] = $options->getHttpProxy(); + } + + /** @psalm-suppress UndefinedClass */ + return new SymfonyHttplugClient(SymfonyHttpClient::create($symfonyConfig)); + } + + if (class_exists(GuzzleHttpClient::class)) { + /** @psalm-suppress UndefinedClass */ + $guzzleConfig = [ + GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, + GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + ]; + + if (null !== $options->getHttpProxy()) { + /** @psalm-suppress UndefinedClass */ + $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); + } + + /** @psalm-suppress InvalidPropertyAssignmentValue */ + return GuzzleHttpClient::createWithConfig($guzzleConfig); + } + + if (class_exists(CurlHttpClient::class)) { + $curlConfig = [ + \CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, + \CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + ]; + + if (null !== $options->getHttpProxy()) { + $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); + } + + /** @psalm-suppress InvalidPropertyAssignmentValue */ + return new CurlHttpClient(null, null, $curlConfig); + } + + if (null !== $options->getHttpProxy()) { + throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client", the "symfony/http-client" or the "php-http/guzzle6-adapter" package to be installed.'); + } + + return HttpAsyncClientDiscovery::find(); + } } From a6e3d6e5487af5476ffe45a7c48c64c0177e7fbb Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 18 Jun 2021 13:03:15 +0200 Subject: [PATCH 0678/1161] Fix missing frames arguments when calling `captureEvent()` without explicit stacktrace or exception (#1223) --- CHANGELOG.md | 2 ++ src/Client.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95fecec25..3ec23e625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix missing collecting of frames's arguments when using `captureEvent()` without expliciting a stacktrace or an exception (#1223) + ## 3.3.0 (2021-05-26) - Allow setting a custom timestamp on the breadcrumbs (#1193) diff --git a/src/Client.php b/src/Client.php index 87ceca46b..53c2a4956 100644 --- a/src/Client.php +++ b/src/Client.php @@ -298,7 +298,7 @@ private function addMissingStacktraceToEvent(Event $event): void } $event->setStacktrace($this->stacktraceBuilder->buildFromBacktrace( - debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS), + debug_backtrace(0), __FILE__, __LINE__ - 3 )); From fafef24c1bc242e614a1afd9533d5dcb7e9c42fe Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 19 Jun 2021 13:37:25 +0200 Subject: [PATCH 0679/1161] Prepare release 3.3.1 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec23e625..82a1a8918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.3.1 (2021-06-21) + - Fix missing collecting of frames's arguments when using `captureEvent()` without expliciting a stacktrace or an exception (#1223) ## 3.3.0 (2021-05-26) From 7b938ca3f21e4bb437883af3f7552db6415112e6 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Sat, 3 Jul 2021 18:49:43 +0200 Subject: [PATCH 0680/1161] Allow guzzlehttp/psr7 ^2.0 (#1225) --- CHANGELOG.md | 2 ++ composer.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82a1a8918..f7bcd7ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Allow installation of `guzzlehttp/psr7:^2.0` (#1225) + ## 3.3.1 (2021-06-21) - Fix missing collecting of frames's arguments when using `captureEvent()` without expliciting a stacktrace or an exception (#1223) diff --git a/composer.json b/composer.json index 38f3fdd40..7d3b14a38 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "ext-json": "*", "ext-mbstring": "*", "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.7", + "guzzlehttp/psr7": "^1.7|^2.0", "jean85/pretty-package-versions": "^1.5|^2.0.1", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", From 4dc9222eb863126819005bd64470ec502991f6fa Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 18 Jul 2021 18:09:49 +0200 Subject: [PATCH 0681/1161] Fix PHPStan and Psalm builds --- phpstan-baseline.neon | 5 ----- src/Monolog/Handler.php | 8 ++------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c374d308e..24aa34355 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -55,11 +55,6 @@ parameters: count: 1 path: src/HttpClient/HttpClientFactory.php - - - message: "#^Method Sentry\\\\Monolog\\\\Handler\\:\\:write\\(\\) has parameter \\$record with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Monolog/Handler.php - - message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" count: 1 diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 7e7382fff..1075ed3a3 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -26,13 +26,9 @@ final class Handler extends AbstractProcessingHandler private $hub; /** - * Constructor. + * {@inheritdoc} * - * @param HubInterface $hub The hub to which errors are reported - * @param int|string $level The minimum logging level at which this - * handler will be triggered - * @param bool $bubble Whether the messages that are handled can - * bubble up the stack or not + * @param HubInterface $hub The hub to which errors are reported */ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true) { From 1242ea2df2c2d5b0dcdc52df107d66512fe8a657 Mon Sep 17 00:00:00 2001 From: Russ Michell Date: Mon, 19 Jul 2021 07:10:11 +1200 Subject: [PATCH 0682/1161] Move the Silverstripe adaptor to the list of v3-compatible community integrations in the README (#1228) Co-authored-by: dcentrica.com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d65b6e22..6a9270cb6 100644 --- a/README.md +++ b/README.md @@ -83,13 +83,13 @@ The following integrations are available and maintained by members of the Sentry - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [ZendFramework](https://github.com/facile-it/sentry-module) - [Yii2](https://github.com/notamedia/yii2-sentry) +- [Silverstripe](https://github.com/phptek/silverstripe-sentry) - ... feel free to be famous, create a port to your favourite platform! ### 3rd party integrations using old SDK 2.x - [Neos Flow](https://github.com/networkteam/Networkteam.SentryClient) - [OXID eShop](https://github.com/OXIDprojects/sentry) -- [SilverStripe](https://github.com/phptek/silverstripe-sentry) - [TYPO3](https://github.com/networkteam/sentry_client) ### 3rd party integrations using old SDK 1.x From 1f1adbaaee57b9e2e696da2e8f3fc9160ec14736 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 18 Jul 2021 23:18:40 +0200 Subject: [PATCH 0683/1161] Relax `psr/log` Composer package constraints to allow newer versions (#1229) Signed-off-by: Alexander M. Turek --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7d3b14a38..c5e9312f9 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "php-http/message": "^1.5", "psr/http-factory": "^1.0", "psr/http-message-implementation": "^1.0", - "psr/log": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", "symfony/options-resolver": "^3.4.43|^4.4.11|^5.0.11", "symfony/polyfill-php80": "^1.17", "symfony/polyfill-uuid": "^1.13.1" From 3d733139a3ba2d1d3a8011580e3acf0ba1d0dc9b Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 19 Jul 2021 10:08:35 +0200 Subject: [PATCH 0684/1161] Prepare release 3.3.2 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7bcd7ae3..22bb65753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ## Unreleased +## 3.3.2 (2021-07-19) + - Allow installation of `guzzlehttp/psr7:^2.0` (#1225) +- Allow installation of `psr/log:^1.0|^2.0|^3.0` (#1229) ## 3.3.1 (2021-06-21) From bfab90c7ef921221b52b75b5108201e87f0f4661 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 2 Aug 2021 14:08:59 +0200 Subject: [PATCH 0685/1161] Fix HttpTransportTest using removed function --- tests/Transport/HttpTransportTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index b54fa8e6e..8e9796fc5 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -8,6 +8,7 @@ use GuzzleHttp\Promise\RejectionException; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Utils; use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; use Http\Promise\FulfilledPromise as HttpFullfilledPromise; use Http\Promise\RejectedPromise as HttpRejectedPromise; @@ -23,7 +24,6 @@ use Sentry\ResponseStatus; use Sentry\Serializer\PayloadSerializerInterface; use Sentry\Transport\HttpTransport; -use function GuzzleHttp\Psr7\stream_for; final class HttpTransportTest extends TestCase { @@ -90,7 +90,7 @@ public function testSendTransactionAsEnvelope(): void ->method('createStream') ->with('{"foo":"bar"}') ->willReturnCallback(static function (string $content): StreamInterface { - return stream_for($content); + return Utils::streamFor($content); }); $this->httpClient->expects($this->once()) @@ -136,7 +136,7 @@ public function testSend(int $httpStatusCode, string $expectedPromiseStatus, Res ->method('createStream') ->with('{"foo":"bar"}') ->willReturnCallback(static function (string $content): StreamInterface { - return stream_for($content); + return Utils::streamFor($content); }); $this->requestFactory->expects($this->once()) @@ -221,7 +221,7 @@ public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientExce ->method('createStream') ->with('{"foo":"bar"}') ->willReturnCallback(static function (string $content): StreamInterface { - return stream_for($content); + return Utils::streamFor($content); }); $this->httpClient->expects($this->once()) From 8da074993494bec3304b866da3ba6201fa453810 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 4 Aug 2021 12:29:33 +0200 Subject: [PATCH 0686/1161] Update the Guzzle tracing middleware to meet the expected standard according to SDK specs (#1234) --- CHANGELOG.md | 2 + phpstan-baseline.neon | 10 ++ src/Tracing/GuzzleTracingMiddleware.php | 65 ++++++++++--- tests/Tracing/GuzzleTracingMiddlewareTest.php | 94 +++++++++++++++++-- 4 files changed, 151 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22bb65753..ba9a5c69d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Update the Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) + ## 3.3.2 (2021-07-19) - Allow installation of `guzzlehttp/psr7:^2.0` (#1225) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 035f5ee0c..fac401684 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -115,6 +115,16 @@ parameters: count: 3 path: src/State/HubInterface.php + - + message: "#^Call to method getResponse\\(\\) on an unknown class GuzzleHttp\\\\Exception\\\\RequestException\\.$#" + count: 1 + path: src/Tracing/GuzzleTracingMiddleware.php + + - + message: "#^Class GuzzleHttp\\\\Exception\\\\RequestException not found\\.$#" + count: 1 + path: src/Tracing/GuzzleTracingMiddleware.php + - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 8c54ac32f..c68924fef 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -4,7 +4,11 @@ namespace Sentry\Tracing; +use Closure; +use GuzzleHttp\Exception\RequestException as GuzzleRequestException; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Sentry\Breadcrumb; use Sentry\SentrySdk; use Sentry\State\HubInterface; @@ -13,27 +17,62 @@ */ final class GuzzleTracingMiddleware { - public static function trace(?HubInterface $hub = null): \Closure + public static function trace(?HubInterface $hub = null): Closure { - return function (callable $handler) use ($hub): \Closure { - return function (RequestInterface $request, array $options) use ($hub, $handler) { + return static function (callable $handler) use ($hub): Closure { + return static function (RequestInterface $request, array $options) use ($hub, $handler) { $hub = $hub ?? SentrySdk::getCurrentHub(); $span = $hub->getSpan(); - $childSpan = null; - if (null !== $span) { - $spanContext = new SpanContext(); - $spanContext->setOp('http.guzzle'); - $spanContext->setDescription($request->getMethod() . ' ' . $request->getUri()); - - $childSpan = $span->startChild($spanContext); + if (null === $span) { + return $handler($request, $options); } - $handlerPromiseCallback = static function ($responseOrException) use ($childSpan) { - if (null !== $childSpan) { - $childSpan->finish(); + $spanContext = new SpanContext(); + $spanContext->setOp('http.client'); + $spanContext->setDescription($request->getMethod() . ' ' . $request->getUri()); + + $childSpan = $span->startChild($spanContext); + + $request->withHeader('sentry-trace', $childSpan->toTraceparent()); + + $handlerPromiseCallback = static function ($responseOrException) use ($hub, $request, $childSpan) { + // We finish the span (which means setting the span end timestamp) first to ensure the measured time + // the span spans is as close to only the HTTP request time and do the data collection afterwards + $childSpan->finish(); + + $response = null; + + /** @psalm-suppress UndefinedClass */ + if ($responseOrException instanceof ResponseInterface) { + $response = $responseOrException; + } elseif ($responseOrException instanceof GuzzleRequestException) { + $response = $responseOrException->getResponse(); } + $breadcrumbData = [ + 'url' => (string) $request->getUri(), + 'method' => $request->getMethod(), + 'request_body_size' => $request->getBody()->getSize(), + ]; + + if (null !== $response) { + $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); + + $breadcrumbData['status_code'] = $response->getStatusCode(); + $breadcrumbData['response_body_size'] = $response->getBody()->getSize(); + } else { + $childSpan->setStatus(SpanStatus::internalError()); + } + + $hub->addBreadcrumb(new Breadcrumb( + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_HTTP, + 'http', + null, + $breadcrumbData + )); + if ($responseOrException instanceof \Throwable) { throw $responseOrException; } diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index cd8e21bca..df22de49a 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -15,36 +15,90 @@ use Sentry\EventType; use Sentry\Options; use Sentry\State\Hub; +use Sentry\State\Scope; use Sentry\Tracing\GuzzleTracingMiddleware; +use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TransactionContext; final class GuzzleTracingMiddlewareTest extends TestCase { + public function testTraceDoesNothingIfSpanIsNotSet(): void + { + $client = $this->createMock(ClientInterface::class); + + $hub = new Hub($client); + + $expectedPromiseResult = new Response(); + + $middleware = GuzzleTracingMiddleware::trace($hub); + $function = $middleware(static function () use ($expectedPromiseResult): PromiseInterface { + return new FulfilledPromise($expectedPromiseResult); + }); + + /** @var PromiseInterface $promise */ + $promise = $function(new Request('GET', 'https://www.example.com'), []); + + try { + $promiseResult = $promise->wait(); + } catch (\Throwable $exception) { + $promiseResult = $exception; + } + + $this->assertSame($expectedPromiseResult, $promiseResult); + + $hub->configureScope(function (Scope $scope): void { + $event = Event::createEvent(); + + $scope->applyToEvent($event); + + $this->assertCount(0, $event->getBreadcrumbs()); + }); + } + /** * @dataProvider traceDataProvider */ - public function testTrace($expectedPromiseResult): void + public function testTrace(Request $request, $expectedPromiseResult, array $expectedBreadcrumbData): void { $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) + $client->expects($this->exactly(2)) ->method('getOptions') ->willReturn(new Options(['traces_sample_rate' => 1])); + $hub = new Hub($client); + $client->expects($this->once()) ->method('captureEvent') - ->with($this->callback(function (Event $eventArg): bool { + ->with($this->callback(function (Event $eventArg) use ($hub, $request, $expectedPromiseResult, $expectedBreadcrumbData): bool { $this->assertSame(EventType::transaction(), $eventArg->getType()); + $hub->configureScope(static function (Scope $scope) use ($eventArg): void { + $scope->applyToEvent($eventArg); + }); + $spans = $eventArg->getSpans(); + $breadcrumbs = $eventArg->getBreadcrumbs(); $this->assertCount(1, $spans); - $this->assertSame('http.guzzle', $spans[0]->getOp()); - $this->assertSame('GET http://www.example.com', $spans[0]->getDescription()); + $this->assertCount(1, $breadcrumbs); + + $guzzleSpan = $spans[0]; + $guzzleBreadcrumb = $breadcrumbs[0]; + + $this->assertSame('http.client', $guzzleSpan->getOp()); + $this->assertSame("{$request->getMethod()} {$request->getUri()}", $guzzleSpan->getDescription()); + + if ($expectedPromiseResult instanceof Response) { + $this->assertSame(SpanStatus::createFromHttpStatusCode($expectedPromiseResult->getStatusCode()), $guzzleSpan->getStatus()); + } else { + $this->assertSame(SpanStatus::internalError(), $guzzleSpan->getStatus()); + } + + $this->assertSame($expectedBreadcrumbData, $guzzleBreadcrumb->getMetadata()); return true; })); - $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $hub->setSpan($transaction); @@ -59,7 +113,7 @@ public function testTrace($expectedPromiseResult): void }); /** @var PromiseInterface $promise */ - $promise = $function(new Request('GET', 'http://www.example.com'), []); + $promise = $function($request, []); try { $promiseResult = $promise->wait(); @@ -75,11 +129,37 @@ public function testTrace($expectedPromiseResult): void public function traceDataProvider(): iterable { yield [ + new Request('GET', 'https://www.example.com'), new Response(), + [ + 'url' => 'https://www.example.com', + 'method' => 'GET', + 'request_body_size' => 0, + 'status_code' => 200, + 'response_body_size' => 0, + ], + ]; + + yield [ + new Request('POST', 'https://www.example.com', [], 'not-sentry'), + new Response(403, [], 'sentry'), + [ + 'url' => 'https://www.example.com', + 'method' => 'POST', + 'request_body_size' => 10, + 'status_code' => 403, + 'response_body_size' => 6, + ], ]; yield [ + new Request('GET', 'https://www.example.com'), new \Exception(), + [ + 'url' => 'https://www.example.com', + 'method' => 'GET', + 'request_body_size' => 0, + ], ]; } } From 9abad1098c85884c15e3276491e77a4e832c8a3e Mon Sep 17 00:00:00 2001 From: gclove Date: Tue, 7 Sep 2021 15:50:16 +0800 Subject: [PATCH 0687/1161] Fix deprecation warning raised by `Psr17FactoryDiscovery:findUrlFactory:()` method (#1239) --- composer.json | 2 +- src/ClientBuilder.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index b899da819..d954755de 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "jean85/pretty-package-versions": "^1.5|^2.0.1", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", - "php-http/discovery": "^1.6.1", + "php-http/discovery": "^1.11", "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", "psr/http-factory": "^1.0", diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 0108ad5b3..06f6593a6 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -186,7 +186,7 @@ private function createDefaultTransportFactory(): DefaultTransportFactory { $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); $httpClientFactory = new HttpClientFactory( - Psr17FactoryDiscovery::findUrlFactory(), + Psr17FactoryDiscovery::findUriFactory(), Psr17FactoryDiscovery::findResponseFactory(), $streamFactory, $this->httpClient, From d74e49f792ef77a3b99ca97d932672465fb7a5b4 Mon Sep 17 00:00:00 2001 From: Allen Gilbert Date: Wed, 29 Sep 2021 09:55:07 -0500 Subject: [PATCH 0688/1161] Avoid fatal error in the `EnvironmentIntegration` integration if the `php_uname` function is disabled (#1246) --- CHANGELOG.md | 2 ++ src/Integration/EnvironmentIntegration.php | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22bb65753..937a6cf13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Verify availability of `php_uname` in `EnvironmentIntegration` (#1243) + ## 3.3.2 (2021-07-19) - Allow installation of `guzzlehttp/psr7:^2.0` (#1225) diff --git a/src/Integration/EnvironmentIntegration.php b/src/Integration/EnvironmentIntegration.php index 9bbc236c4..2098ced49 100644 --- a/src/Integration/EnvironmentIntegration.php +++ b/src/Integration/EnvironmentIntegration.php @@ -48,8 +48,12 @@ private function updateRuntimeContext(?RuntimeContext $runtimeContext): RuntimeC return $runtimeContext; } - private function updateServerOsContext(?OsContext $osContext): OsContext + private function updateServerOsContext(?OsContext $osContext): ?OsContext { + if (!\function_exists('php_uname')) { + return $osContext; + } + if (null === $osContext) { $osContext = new OsContext(php_uname('s')); } From 9a7b6d84ac8fa370397336028e760c71accac1dc Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 4 Oct 2021 13:20:34 +0200 Subject: [PATCH 0689/1161] Prepare release `3.3.3` --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 937a6cf13..0293c8953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased -- Verify availability of `php_uname` in `EnvironmentIntegration` (#1243) +## 3.3.3 (2021-10-04) + +- Fix fatal error in the `EnvironmentIntegration` integration if the `php_uname` function is disabled (#1243) ## 3.3.2 (2021-07-19) From da1d7563163838863a67adcf60a84b1a0b41f6be Mon Sep 17 00:00:00 2001 From: Fabien Salathe Date: Mon, 11 Oct 2021 13:50:27 +0100 Subject: [PATCH 0690/1161] Add the ablitity to transform the event to array (#1249) * Add the ablitity to transform the event to array * Add changelog entry * Add assertion to test `PayloadSerializer::toArray` * Fix code quality (phpdoc) --- CHANGELOG.md | 1 + src/Serializer/PayloadSerializer.php | 12 +++++++++++- tests/Serializer/PayloadSerializerTest.php | 6 ++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41f373092..7b25871b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Update the Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) +- Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization ## 3.3.3 (2021-10-04) diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 12071a9db..241db37c8 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -33,6 +33,16 @@ public function serialize(Event $event): string } private function serializeAsEvent(Event $event): string + { + $result = $this->toArray($event); + + return JSON::encode($result); + } + + /** + * @return array + */ + public function toArray(Event $event): array { $result = [ 'event_id' => (string) $event->getId(), @@ -160,7 +170,7 @@ private function serializeAsEvent(Event $event): string ]; } - return JSON::encode($result); + return $result; } private function serializeAsEnvelope(Event $event): string diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 0ea26b502..1d2c047bd 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -11,6 +11,7 @@ use Sentry\Context\RuntimeContext; use Sentry\Event; use Sentry\EventId; +use Sentry\EventType; use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; @@ -48,6 +49,11 @@ public function testSerialize(Event $event, string $expectedResult, bool $isOutp $result = $this->serializer->serialize($event); + if (EventType::transaction() !== $event->getType()) { + $resultArray = $this->serializer->toArray($event); + $this->assertJsonStringEqualsJsonString($result, json_encode($resultArray)); + } + if ($isOutputJson) { $this->assertJsonStringEqualsJsonString($expectedResult, $result); } else { From 2d9c139f3b283390f7a3c7cd124f9827277a0b66 Mon Sep 17 00:00:00 2001 From: Hideki Kinjyo Date: Fri, 22 Oct 2021 17:23:16 +0900 Subject: [PATCH 0691/1161] Add 3rd party integration for `CakePHP` to the README (#1256) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6a9270cb6..8b36bc4a7 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ The following integrations are available and maintained by members of the Sentry - [ZendFramework](https://github.com/facile-it/sentry-module) - [Yii2](https://github.com/notamedia/yii2-sentry) - [Silverstripe](https://github.com/phptek/silverstripe-sentry) +- [CakePHP](https://github.com/Connehito/cake-sentry) - ... feel free to be famous, create a port to your favourite platform! ### 3rd party integrations using old SDK 2.x @@ -91,6 +92,7 @@ The following integrations are available and maintained by members of the Sentry - [Neos Flow](https://github.com/networkteam/Networkteam.SentryClient) - [OXID eShop](https://github.com/OXIDprojects/sentry) - [TYPO3](https://github.com/networkteam/sentry_client) +- [CakePHP](https://github.com/Connehito/cake-sentry/tree/3.x) ### 3rd party integrations using old SDK 1.x From a8f3b97557bad9532396f3df5649fd41d8bd4929 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 22 Oct 2021 10:25:14 +0200 Subject: [PATCH 0692/1161] Fix overwriting of the event's error level set by the user when capturing an `ErrorException` exception (#1251) * Fix overwriting of the event's error level set by the user when capturing an ErrorException exception * Update the CHANGELOG --- CHANGELOG.md | 2 ++ src/Client.php | 2 +- tests/ClientTest.php | 40 +++++++++++++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0293c8953..437ba19aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Avoid overwriting the error level set by the user on the event when capturing an `ErrorException` exception (#1251) + ## 3.3.3 (2021-10-04) - Fix fatal error in the `EnvironmentIntegration` integration if the `php_uname` function is disabled (#1243) diff --git a/src/Client.php b/src/Client.php index 53c2a4956..d159a5793 100644 --- a/src/Client.php +++ b/src/Client.php @@ -312,7 +312,7 @@ private function addMissingStacktraceToEvent(Event $event): void */ private function addThrowableToEvent(Event $event, \Throwable $exception): void { - if ($exception instanceof \ErrorException) { + if ($exception instanceof \ErrorException && null === $event->getLevel()) { $event->setLevel(Severity::fromError($exception->getSeverity())); } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 304167725..1aaef8eed 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -14,6 +14,7 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\EventHint; +use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; use Sentry\Integration\IntegrationInterface; @@ -210,9 +211,8 @@ public function testCaptureEvent(array $options, Event $event, Event $expectedEv $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') - ->willReturnCallback(function (Event $event) use ($expectedEvent): FulfilledPromise { - $this->assertEquals($expectedEvent, $event); - + ->with($expectedEvent) + ->willReturnCallback(static function (Event $event): FulfilledPromise { return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); }); @@ -226,6 +226,12 @@ public function testCaptureEvent(array $options, Event $event, Event $expectedEv public function captureEventDataProvider(): \Generator { $event = Event::createEvent(); + $expectedEvent = clone $event; + $expectedEvent->setLogger('php'); + $expectedEvent->setServerName('example.com'); + $expectedEvent->setRelease('0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'); + $expectedEvent->setEnvironment('development'); + $expectedEvent->setTags(['context' => 'development']); yield 'Options set && no event properties set => use options' => [ [ @@ -235,7 +241,7 @@ public function captureEventDataProvider(): \Generator 'tags' => ['context' => 'development'], ], $event, - $event, + $expectedEvent, ]; $event = Event::createEvent(); @@ -244,6 +250,10 @@ public function captureEventDataProvider(): \Generator $event->setEnvironment('production'); $event->setTags(['context' => 'production']); + $expectedEvent = clone $event; + $expectedEvent->setLogger('php'); + $expectedEvent->setTags(['context' => 'production', 'ios_version' => '14.0']); + yield 'Options set && event properties set => event properties override options' => [ [ 'server_name' => 'example.com', @@ -252,15 +262,35 @@ public function captureEventDataProvider(): \Generator 'tags' => ['context' => 'development', 'ios_version' => '14.0'], ], $event, - $event, + $expectedEvent, ]; $event = Event::createEvent(); + $event->setServerName('example.com'); + + $expectedEvent = clone $event; + $expectedEvent->setLogger('php'); + $expectedEvent->setEnvironment('production'); yield 'Environment option set to null && no event property set => fallback to default value' => [ ['environment' => null], $event, + $expectedEvent, + ]; + + $event = Event::createEvent(); + $event->setServerName('example.com'); + $event->setLevel(Severity::warning()); + $event->setExceptions([new ExceptionDataBag(new \ErrorException())]); + + $expectedEvent = clone $event; + $expectedEvent->setLogger('php'); + $expectedEvent->setEnvironment('production'); + + yield 'Error level is set && exception is instance of ErrorException => preserve the error level set by the user' => [ + [], $event, + $expectedEvent, ]; } From af3ab2807c61d0bf675cbea37b7cc5764f2d3f19 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 22 Oct 2021 22:01:56 +0200 Subject: [PATCH 0693/1161] Support installing the project alongside Symfony `6.x` components (#1257) * Support installing the project alongside Symfony `6.x` components * Update the CHANGELOG --- CHANGELOG.md | 1 + composer.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 437ba19aa..496370cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Avoid overwriting the error level set by the user on the event when capturing an `ErrorException` exception (#1251) +- Allow installing the project alongside Symfony `6.x` components (#1257) ## 3.3.3 (2021-10-04) diff --git a/composer.json b/composer.json index c5e9312f9..89ade93be 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "psr/http-factory": "^1.0", "psr/http-message-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.11|^5.0.11", + "symfony/options-resolver": "^3.4.43|^4.4.11|^5.0.11|^6.0", "symfony/polyfill-php80": "^1.17", "symfony/polyfill-uuid": "^1.13.1" }, @@ -48,7 +48,7 @@ "phpstan/phpstan": "^0.12", "phpstan/phpstan-phpunit": "^0.12", "phpunit/phpunit": "^8.5.13|^9.4", - "symfony/phpunit-bridge": "^5.2", + "symfony/phpunit-bridge": "^5.2|^6.0", "vimeo/psalm": "^4.2" }, "suggest": { From 9c75d51b67bcd5303f671154f6d8f5d7d23e84cb Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 29 Oct 2021 17:31:27 +0200 Subject: [PATCH 0694/1161] Run the test suite against PHP `8.1` (#1245) --- .github/workflows/ci.yaml | 8 ++++++++ composer.json | 4 ++-- src/Dsn.php | 2 +- src/EventId.php | 2 +- src/EventType.php | 2 +- src/ResponseStatus.php | 2 +- src/Severity.php | 2 +- src/Tracing/SpanId.php | 2 +- src/Tracing/SpanStatus.php | 2 +- src/Tracing/TraceId.php | 2 +- tests/Util/Fixtures/JsonSerializableClass.php | 2 +- tests/phpt/error_handler_captures_fatal_error.phpt | 4 ++-- .../fatal_error_integration_captures_fatal_error.phpt | 4 ++-- ...tal_error_integration_respects_error_types_option.phpt | 4 ++-- 14 files changed, 25 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 14c0dcc4d..baee46665 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,9 +22,13 @@ jobs: - '7.3' - '7.4' - '8.0' + - '8.1' dependencies: - lowest - highest + include: + - php: '8.1' + dependencies: lowest steps: - name: Checkout @@ -56,6 +60,10 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist if: ${{ matrix.dependencies == 'highest' }} + - name: Restrict lowest Symfony version on PHP 8.1 + run: composer require symfony/options-resolver:^4.4.30 --no-update + if: ${{ matrix.dependencies == 'lowest' && matrix.php == '8.1' }} + - name: Install lowest dependencies run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest if: ${{ matrix.dependencies == 'lowest' }} diff --git a/composer.json b/composer.json index 89ade93be..a654412e1 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "psr/http-factory": "^1.0", "psr/http-message-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.11|^5.0.11|^6.0", + "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", "symfony/polyfill-php80": "^1.17", "symfony/polyfill-uuid": "^1.13.1" }, @@ -47,7 +47,7 @@ "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12", "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5.13|^9.4", + "phpunit/phpunit": "^8.5.14|^9.4", "symfony/phpunit-bridge": "^5.2|^6.0", "vimeo/psalm": "^4.2" }, diff --git a/src/Dsn.php b/src/Dsn.php index 795b4801d..04109e16d 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -10,7 +10,7 @@ * * @author Stefano Arlandini */ -final class Dsn +final class Dsn implements \Stringable { /** * @var string The protocol to be used to access the resource diff --git a/src/EventId.php b/src/EventId.php index d345e9a5e..76c36b272 100644 --- a/src/EventId.php +++ b/src/EventId.php @@ -9,7 +9,7 @@ * * @author Stefano Arlandini */ -final class EventId +final class EventId implements \Stringable { /** * @var string The ID diff --git a/src/EventType.php b/src/EventType.php index 99e9053cb..09a14b1a9 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -10,7 +10,7 @@ * * @author Stefano Arlandini */ -final class EventType +final class EventType implements \Stringable { /** * @var string The value of the enum instance diff --git a/src/ResponseStatus.php b/src/ResponseStatus.php index de5ed364e..2a2292a4f 100644 --- a/src/ResponseStatus.php +++ b/src/ResponseStatus.php @@ -8,7 +8,7 @@ * This enum represents all possible reasons an event sending operation succeeded * or failed. */ -final class ResponseStatus +final class ResponseStatus implements \Stringable { /** * @var string The value of the enum instance diff --git a/src/Severity.php b/src/Severity.php index 97b2856d8..4e065d62f 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -10,7 +10,7 @@ * * @author Stefano Arlandini */ -final class Severity +final class Severity implements \Stringable { /** * This constant represents the "debug" severity level. diff --git a/src/Tracing/SpanId.php b/src/Tracing/SpanId.php index a11757d46..d36224383 100644 --- a/src/Tracing/SpanId.php +++ b/src/Tracing/SpanId.php @@ -7,7 +7,7 @@ /** * This class represents an span ID. */ -final class SpanId +final class SpanId implements \Stringable { /** * @var string The ID diff --git a/src/Tracing/SpanStatus.php b/src/Tracing/SpanStatus.php index b8b4cee2c..cd0e878fc 100644 --- a/src/Tracing/SpanStatus.php +++ b/src/Tracing/SpanStatus.php @@ -4,7 +4,7 @@ namespace Sentry\Tracing; -final class SpanStatus +final class SpanStatus implements \Stringable { /** * @var string The value of the enum instance diff --git a/src/Tracing/TraceId.php b/src/Tracing/TraceId.php index 86cdf31e6..2e9194757 100644 --- a/src/Tracing/TraceId.php +++ b/src/Tracing/TraceId.php @@ -7,7 +7,7 @@ /** * This class represents an trace ID. */ -final class TraceId +final class TraceId implements \Stringable { /** * @var string The ID diff --git a/tests/Util/Fixtures/JsonSerializableClass.php b/tests/Util/Fixtures/JsonSerializableClass.php index 602d9c9b9..977bab53f 100644 --- a/tests/Util/Fixtures/JsonSerializableClass.php +++ b/tests/Util/Fixtures/JsonSerializableClass.php @@ -8,7 +8,7 @@ class JsonSerializableClass implements \JsonSerializable { public $keyPublic = 'public'; - public function jsonSerialize() + public function jsonSerialize(): array { return [ 'key' => 'value', diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index 48f079d3a..1e970c329 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -67,11 +67,11 @@ $errorHandler->addExceptionHandlerListener(static function (): void { echo 'Exception listener called (it should not have been)' . PHP_EOL; }); -class TestClass implements \Serializable +final class TestClass implements \JsonSerializable { } ?> --EXPECTF-- -Fatal error: Class Sentry\Tests\TestClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d +Fatal error: Class Sentry\Tests\TestClass contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize) in %s on line %d Transport called Fatal error listener called diff --git a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt index b5705cd74..d264e9810 100644 --- a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt @@ -59,10 +59,10 @@ $client = (new ClientBuilder($options)) SentrySdk::getCurrentHub()->bindClient($client); -class FooClass implements \Serializable +final class TestClass implements \JsonSerializable { } ?> --EXPECTF-- -Fatal error: Class Sentry\Tests\FooClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d +Fatal error: Class Sentry\Tests\TestClass contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize) in %s on line %d Transport called diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index b57487945..00db34a07 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -60,9 +60,9 @@ $client = (new ClientBuilder($options)) SentrySdk::getCurrentHub()->bindClient($client); -class FooClass implements \Serializable +final class TestClass implements \JsonSerializable { } ?> --EXPECTF-- -Fatal error: Class Sentry\Tests\FooClass contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize) in %s on line %d +Fatal error: Class Sentry\Tests\TestClass contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize) in %s on line %d From ecbd09ea5d053a202cf773cb24ab28af820831bd Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 8 Nov 2021 09:44:00 +0100 Subject: [PATCH 0695/1161] Prepare release `3.3.4` --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 496370cdb..4acd8ff79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ ## Unreleased +## 3.3.4 (2021-11-08) + - Avoid overwriting the error level set by the user on the event when capturing an `ErrorException` exception (#1251) - Allow installing the project alongside Symfony `6.x` components (#1257) +- Run the test suite against PHP `8.1` (#1245) ## 3.3.3 (2021-10-04) From ea3dca1fbd182cd0fc5fbcb61aa669a46f83d154 Mon Sep 17 00:00:00 2001 From: "J. Schreuders" <3071062+99linesofcode@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:38:14 -0600 Subject: [PATCH 0696/1161] Bump the minimum required version of `jean85/pretty-package-versions` (#1267) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a654412e1..afc45d39b 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ext-mbstring": "*", "guzzlehttp/promises": "^1.4", "guzzlehttp/psr7": "^1.7|^2.0", - "jean85/pretty-package-versions": "^1.5|^2.0.1", + "jean85/pretty-package-versions": "^1.5|^2.0.4", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", "php-http/discovery": "^1.6.1", From c186c44c32899ad0cf5b4e942d71035f01b87b64 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 27 Dec 2021 13:31:24 +0100 Subject: [PATCH 0697/1161] Prepare release `3.3.5` --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4acd8ff79..241073e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 3.3.5 (2021-12-27) + +- Bump the minimum required version of the `jean85/pretty-package-versions` package (#1267) + ## 3.3.4 (2021-11-08) - Avoid overwriting the error level set by the user on the event when capturing an `ErrorException` exception (#1251) From 8f0869cd84d4562bcd423844a7e8095807d2a96d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 12 Jan 2022 08:44:46 +0100 Subject: [PATCH 0698/1161] Update PHPStan, Psalm and PHP-CS-Fixer to the latest versions (#1272) * Update PHP-CS-Fixer * Update PHPStan * Update Psalm * Raise PHPStan level to 9 * Run PHP-CS-Fixer on the latest stable PHP version * Fix code style * Fix Psalm type definition and update PHPStan baseline * Relax Composer constraint about PHP-CS-Fixer and make it run on PHP `8.1` * Update PHPStan baseline --- .github/workflows/static-analysis.yaml | 2 +- .gitignore | 2 +- .php_cs.dist => .php-cs-fixer.dist.php | 8 +- composer.json | 8 +- phpstan-baseline.neon | 218 +++++++++++++++++++- phpstan.neon | 4 +- psalm-baseline.xml | 73 +++++++ psalm.xml.dist | 1 + src/Client.php | 7 - src/ClientBuilder.php | 11 +- src/Context/OsContext.php | 4 +- src/Context/RuntimeContext.php | 4 +- src/Dsn.php | 3 - src/ErrorHandler.php | 12 +- src/Event.php | 4 +- src/Frame.php | 2 +- src/FrameBuilder.php | 29 ++- src/HttpClient/HttpClientFactory.php | 17 -- src/Integration/IgnoreErrorsIntegration.php | 15 +- src/Integration/IntegrationRegistry.php | 2 - src/Integration/RequestIntegration.php | 4 + src/Integration/TransactionIntegration.php | 2 +- src/Serializer/RepresentationSerializer.php | 3 - src/StacktraceBuilder.php | 13 +- src/State/Hub.php | 6 - src/State/HubAdapter.php | 4 - src/Tracing/SpanContext.php | 1 - src/Util/JSON.php | 8 +- src/functions.php | 4 - tests/Util/JSONTest.php | 17 ++ 30 files changed, 371 insertions(+), 117 deletions(-) rename .php_cs.dist => .php-cs-fixer.dist.php (82%) create mode 100644 psalm-baseline.xml diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index b470925b1..b1b7c445a 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -18,7 +18,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.1' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist diff --git a/.gitignore b/.gitignore index 25a58fe9c..83cb61992 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ package.xml /vendor .idea -.php_cs.cache +.php-cs-fixer.cache .phpunit.result.cache docs/_build tests/clover.xml diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 82% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index 0a411e3e2..8f4027a98 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -1,19 +1,15 @@ setRules([ - '@PSR2' => true, + '@PHP71Migration' => true, '@Symfony' => true, '@Symfony:risky' => true, - 'array_syntax' => ['syntax' => 'short'], 'concat_space' => ['spacing' => 'one'], 'ordered_imports' => [ 'imports_order' => ['class', 'function', 'const'], ], 'declare_strict_types' => true, - 'psr0' => true, - 'psr4' => true, - 'random_api_migration' => true, 'yoda_style' => true, 'self_accessor' => false, 'phpdoc_no_useless_inheritdoc' => false, diff --git a/composer.json b/composer.json index afc45d39b..efc50b676 100644 --- a/composer.json +++ b/composer.json @@ -39,17 +39,17 @@ "symfony/polyfill-uuid": "^1.13.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", + "friendsofphp/php-cs-fixer": "^2.19|^3.4", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.3|^2.0", "nikic/php-parser": "^4.10.3", "php-http/mock-client": "^1.3", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^8.5.14|^9.4", "symfony/phpunit-bridge": "^5.2|^6.0", - "vimeo/psalm": "^4.2" + "vimeo/psalm": "^4.17" }, "suggest": { "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 24aa34355..e9c9d7eab 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,10 @@ parameters: ignoreErrors: + - + message: "#^Constructor of class Sentry\\\\Client has an unused parameter \\$serializer\\.$#" + count: 1 + path: src/Client.php + - message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns T of Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" count: 1 @@ -11,25 +16,30 @@ parameters: path: src/ClientInterface.php - - message: "#^Offset 'host' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + message: "#^Offset 'host' does not exist on array\\{host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string, scheme\\: 'http'\\|'https'\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'path' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + message: "#^Offset 'path' does not exist on array\\{host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string, scheme\\: 'http'\\|'https'\\}\\.$#" count: 4 path: src/Dsn.php - - message: "#^Offset 'scheme' does not exist on array\\(\\?'scheme' \\=\\> string, \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + message: "#^Offset 'scheme' does not exist on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'user' does not exist on array\\('scheme' \\=\\> 'http'\\|'https', \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + message: "#^Offset 'user' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php + - + message: "#^Parameter \\#1 \\$backtrace of method Sentry\\\\ErrorHandler\\:\\:cleanBacktraceFromErrorHandlerFrames\\(\\) expects array\\, array\\\\> given\\.$#" + count: 1 + path: src/ErrorHandler.php + - message: "#^Result of && is always false\\.$#" count: 2 @@ -55,11 +65,181 @@ parameters: count: 1 path: src/HttpClient/HttpClientFactory.php + - + message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$responseFactory\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$uriFactory\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Property Sentry\\\\Integration\\\\IgnoreErrorsIntegration\\:\\:\\$options \\(array\\{ignore_exceptions\\: array\\\\>, ignore_tags\\: array\\\\}\\) does not accept array\\.$#" + count: 1 + path: src/Integration/IgnoreErrorsIntegration.php + + - + message: "#^Property Sentry\\\\Integration\\\\RequestIntegration\\:\\:\\$options \\(array\\{pii_sanitize_headers\\: array\\\\}\\) does not accept array\\.$#" + count: 1 + path: src/Integration/RequestIntegration.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeBreadcrumbCallback\\(\\) should return callable\\(Sentry\\\\Breadcrumb\\)\\: Sentry\\\\Breadcrumb\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: Sentry\\\\Event\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getClassSerializers\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getContextLines\\(\\) should return int\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getDsn\\(\\) should return Sentry\\\\Dsn\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getEnvironment\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getErrorTypes\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpProxy\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getInAppExcludedPaths\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getInAppIncludedPaths\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getIntegrations\\(\\) should return array\\\\|\\(callable\\(array\\\\)\\: array\\\\) but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getLogger\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxBreadcrumbs\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxRequestBodySize\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxValueLength\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getPrefixes\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getRelease\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getSampleRate\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getSendAttempts\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getServerName\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTags\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTracesSampleRate\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTracesSampler\\(\\) should return \\(callable\\(\\)\\: mixed\\)\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:hasDefaultIntegrations\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:isCompressionEnabled\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldAttachStacktrace\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldCaptureSilencedErrors\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldSendDefaultPii\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" count: 1 path: src/Serializer/AbstractSerializer.php + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/Serializer/AbstractSerializer.php + + - + message: "#^Parameter \\#1 \\$backtrace of method Sentry\\\\StacktraceBuilder\\:\\:buildFromBacktrace\\(\\) expects array\\, array\\\\> given\\.$#" + count: 1 + path: src/StacktraceBuilder.php + - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" count: 1 @@ -105,6 +285,36 @@ parameters: count: 3 path: src/State/HubInterface.php + - + message: "#^Unsafe usage of new static\\(\\)\\.$#" + count: 1 + path: src/Tracing/SpanContext.php + + - + message: "#^Parameter \\#1 \\$email of method Sentry\\\\UserDataBag\\:\\:setEmail\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$id of method Sentry\\\\UserDataBag\\:\\:setId\\(\\) expects int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$ipAddress of method Sentry\\\\UserDataBag\\:\\:setIpAddress\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$username of method Sentry\\\\UserDataBag\\:\\:setUsername\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Method Sentry\\\\Util\\\\JSON\\:\\:encode\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/Util/JSON.php + - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index f180d1007..43c4ca673 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,9 +4,9 @@ includes: parameters: tipsOfTheDay: false treatPhpDocTypesAsCertain: false - level: 8 + level: 9 paths: - src - excludes_analyse: + excludePaths: - tests/resources - tests/Fixtures diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 000000000..fe746dad3 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,73 @@ + + + + + $parsedDsn['host'] + $parsedDsn['path'] + $parsedDsn['scheme'] + $parsedDsn['user'] + + + + + GuzzleHttpClientOptions + GuzzleHttpClientOptions + GuzzleHttpClientOptions + SymfonyHttpClient + + + + + $userIntegration + $userIntegrations + + + + + JSON::encode($result) + + + string + + + $envelopeHeader + $itemHeader + + + + + $value + + + representationSerialize + + + + + captureException + captureLastError + captureMessage + + + + + captureException + captureLastError + captureMessage + startTransaction + + + + + new static() + + + + + captureException + captureLastError + captureMessage + startTransaction + + + diff --git a/psalm.xml.dist b/psalm.xml.dist index e1cbbfde2..c60ac2108 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -5,6 +5,7 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" memoizeMethodCallResults="true" + errorBaseline="psalm-baseline.xml" > diff --git a/src/Client.php b/src/Client.php index d159a5793..37a6838ac 100644 --- a/src/Client.php +++ b/src/Client.php @@ -12,7 +12,6 @@ use Sentry\Integration\IntegrationRegistry; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; use Sentry\Transport\TransportInterface; @@ -56,11 +55,6 @@ final class Client implements ClientInterface */ private $integrations; - /** - * @var SerializerInterface The serializer of the client - */ - private $serializer; - /** * @var RepresentationSerializerInterface The representation serializer of the client */ @@ -105,7 +99,6 @@ public function __construct( $this->transport = $transport; $this->logger = $logger ?? new NullLogger(); $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); - $this->serializer = $serializer ?? new Serializer($this->options); $this->representationSerializer = $representationSerializer ?? new RepresentationSerializer($this->options); $this->stacktraceBuilder = new StacktraceBuilder($options, $this->representationSerializer); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 0108ad5b3..13c887760 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,13 +4,11 @@ namespace Sentry; -use Http\Client\HttpAsyncClient; use Http\Discovery\Psr17FactoryDiscovery; use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; use Sentry\Transport\DefaultTransportFactory; use Sentry\Transport\TransportFactoryInterface; @@ -38,11 +36,6 @@ final class ClientBuilder implements ClientBuilderInterface */ private $transport; - /** - * @var HttpAsyncClient|null The HTTP client - */ - private $httpClient; - /** * @var SerializerInterface|null The serializer to be injected in the client */ @@ -84,7 +77,7 @@ public function __construct(Options $options = null) */ public static function create(array $options = []): ClientBuilderInterface { - return new static(new Options($options)); + return new self(new Options($options)); } /** @@ -189,7 +182,7 @@ private function createDefaultTransportFactory(): DefaultTransportFactory Psr17FactoryDiscovery::findUrlFactory(), Psr17FactoryDiscovery::findResponseFactory(), $streamFactory, - $this->httpClient, + null, $this->sdkIdentifier, $this->sdkVersion ); diff --git a/src/Context/OsContext.php b/src/Context/OsContext.php index 7e6928f77..04e9387bd 100644 --- a/src/Context/OsContext.php +++ b/src/Context/OsContext.php @@ -41,7 +41,7 @@ final class OsContext */ public function __construct(string $name, ?string $version = null, ?string $build = null, ?string $kernelVersion = null) { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -66,7 +66,7 @@ public function getName(): string */ public function setName(string $name): void { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 9801e9ee3..d0a114e48 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -29,7 +29,7 @@ final class RuntimeContext */ public function __construct(string $name, ?string $version = null) { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -52,7 +52,7 @@ public function getName(): string */ public function setName(string $name): void { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Dsn.php b/src/Dsn.php index 04109e16d..e96d4484a 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -92,12 +92,10 @@ public static function createFromString(string $value): self throw new \InvalidArgumentException(sprintf('The "%s" DSN must contain a valid secret key.', $value)); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ if (!\in_array($parsedDsn['scheme'], ['http', 'https'], true)) { throw new \InvalidArgumentException(sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value)); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ $segmentPaths = explode('/', $parsedDsn['path']); $projectId = array_pop($segmentPaths); @@ -112,7 +110,6 @@ public static function createFromString(string $value): self $path = substr($parsedDsn['path'], 0, $lastSlashPosition); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ return new self( $parsedDsn['scheme'], $parsedDsn['host'], diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index ac2b63804..1fee7d01c 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -12,6 +12,8 @@ * error types and relays them to all configured listeners. Registering this * error handler more than once is not supported and will lead to nasty * problems. The code is based on the Symfony ErrorHandler component. + * + * @psalm-import-type StacktraceFrame from FrameBuilder */ final class ErrorHandler { @@ -93,7 +95,7 @@ final class ErrorHandler private static $reservedMemory; /** - * @var array List of error levels and their description + * @var string[] List of error levels and their description */ private const ERROR_LEVELS_DESCRIPTION = [ \E_DEPRECATED => 'Deprecated', @@ -379,11 +381,13 @@ private function handleException(\Throwable $exception): void * Cleans and returns the backtrace without the first frames that belong to * this error handler. * - * @param array $backtrace The backtrace to clear - * @param string $file The filename the backtrace was raised in - * @param int $line The line number the backtrace was raised at + * @param array> $backtrace The backtrace to clear + * @param string $file The filename the backtrace was raised in + * @param int $line The line number the backtrace was raised at * * @return array + * + * @psalm-param list $backtrace */ private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array { diff --git a/src/Event.php b/src/Event.php index 6f0e582d4..4c77aae91 100644 --- a/src/Event.php +++ b/src/Event.php @@ -71,7 +71,7 @@ final class Event private $messageFormatted; /** - * @var mixed[] The parameters to use to format the message + * @var string[] The parameters to use to format the message */ private $messageParams = []; @@ -376,7 +376,7 @@ public function getMessageParams(): array * Sets the error message. * * @param string $message The message - * @param mixed[] $params The parameters to use to format the message + * @param string[] $params The parameters to use to format the message * @param string|null $formatted The formatted message */ public function setMessage(string $message, array $params = [], ?string $formatted = null): void diff --git a/src/Frame.php b/src/Frame.php index ade6ac811..4d6a067af 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -67,7 +67,7 @@ final class Frame /** * @var array A mapping of variables which were available within - * this frame (usually context-locals) + * this frame (usually context-locals) */ private $vars = []; diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 21659f585..393c1b946 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -10,6 +10,15 @@ * This class builds a {@see Frame} object out of a backtrace's raw frame. * * @internal + * + * @psalm-type StacktraceFrame array{ + * function?: string, + * line?: int, + * file?: string, + * class?: class-string, + * type?: string, + * args?: mixed[] + * } */ final class FrameBuilder { @@ -42,14 +51,7 @@ public function __construct(Options $options, RepresentationSerializerInterface * @param int $line The line at which the frame originated * @param array $backtraceFrame The raw frame * - * @psalm-param array{ - * function?: callable-string, - * line?: integer, - * file?: string, - * class?: class-string, - * type?: string, - * args?: array - * } $backtraceFrame + * @psalm-param StacktraceFrame $backtraceFrame */ public function buildFromBacktraceFrame(string $file, int $line, array $backtraceFrame): Frame { @@ -153,14 +155,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool * * @return array * - * @throws \ReflectionException - * - * @psalm-param array{ - * function?: callable-string, - * class?: class-string, - * type?: string, - * args?: array - * } $backtraceFrame + * @psalm-param StacktraceFrame $backtraceFrame */ private function getFunctionArguments(array $backtraceFrame): array { @@ -179,7 +174,7 @@ private function getFunctionArguments(array $backtraceFrame): array } else { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); } - } elseif (isset($backtraceFrame['function']) && !\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { + } elseif (!\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { $reflectionFunction = new \ReflectionFunction($backtraceFrame['function']); } } catch (\ReflectionException $e) { diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 977d75850..0eb231acd 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -41,16 +41,6 @@ final class HttpClientFactory implements HttpClientFactoryInterface */ private const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; - /** - * @var UriFactoryInterface The PSR-7 URI factory - */ - private $uriFactory; - - /** - * @var ResponseFactoryInterface The PSR-7 response factory - */ - private $responseFactory; - /** * @var StreamFactoryInterface The PSR-17 stream factory */ @@ -89,8 +79,6 @@ public function __construct( string $sdkIdentifier, string $sdkVersion ) { - $this->uriFactory = $uriFactory; - $this->responseFactory = $responseFactory; $this->streamFactory = $streamFactory; $this->httpClient = $httpClient; $this->sdkIdentifier = $sdkIdentifier; @@ -122,23 +110,19 @@ public function create(Options $options): HttpAsyncClientInterface $symfonyConfig['proxy'] = $options->getHttpProxy(); } - /** @psalm-suppress UndefinedClass */ $httpClient = new SymfonyHttplugClient( SymfonyHttpClient::create($symfonyConfig) ); } elseif (class_exists(GuzzleHttpClient::class)) { - /** @psalm-suppress UndefinedClass */ $guzzleConfig = [ GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, ]; if (null !== $options->getHttpProxy()) { - /** @psalm-suppress UndefinedClass */ $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ $httpClient = GuzzleHttpClient::createWithConfig($guzzleConfig); } elseif (class_exists(CurlHttpClient::class)) { $curlConfig = [ @@ -150,7 +134,6 @@ public function create(Options $options): HttpAsyncClientInterface $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ $httpClient = new CurlHttpClient(null, null, $curlConfig); } elseif (null !== $options->getHttpProxy()) { throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index 4cc5961a0..aae22e10b 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -14,11 +14,18 @@ * to a series of options that must match with its data. * * @author Stefano Arlandini + * + * @psalm-type IntegrationOptions array{ + * ignore_exceptions: list>, + * ignore_tags: array + * } */ final class IgnoreErrorsIntegration implements IntegrationInterface { /** * @var array The options + * + * @psalm-var IntegrationOptions */ private $options; @@ -29,7 +36,7 @@ final class IgnoreErrorsIntegration implements IntegrationInterface * @param array $options The options * * @psalm-param array{ - * ignore_exceptions?: list> + * ignore_exceptions?: list>, * ignore_tags?: array * } $options */ @@ -69,6 +76,8 @@ public function setupOnce(): void * * @param Event $event The event to check * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function shouldDropEvent(Event $event, array $options): bool { @@ -89,6 +98,8 @@ private function shouldDropEvent(Event $event, array $options): bool * * @param Event $event The event instance * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function isIgnoredException(Event $event, array $options): bool { @@ -113,6 +124,8 @@ private function isIgnoredException(Event $event, array $options): bool * * @param Event $event The event instance * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function isIgnoredTag(Event $event, array $options): bool { diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index 731c4469c..ff15b2b60 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -86,7 +86,6 @@ private function getIntegrationsToSetup(Options $options): array $userIntegrations = $options->getIntegrations(); if (\is_array($userIntegrations)) { - /** @psalm-suppress PossiblyInvalidArgument */ $userIntegrationsClasses = array_map('get_class', $userIntegrations); $pickedIntegrationsClasses = []; @@ -100,7 +99,6 @@ private function getIntegrationsToSetup(Options $options): array } foreach ($userIntegrations as $userIntegration) { - /** @psalm-suppress PossiblyInvalidArgument */ $integrationClassName = \get_class($userIntegration); if (!isset($pickedIntegrationsClasses[$integrationClassName])) { diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index b67c39a06..b3b515387 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -69,6 +69,10 @@ final class RequestIntegration implements IntegrationInterface /** * @var array The options + * + * @psalm-var array{ + * pii_sanitize_headers: string[] + * } */ private $options; diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index 7f24be550..d7c775fce 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -36,7 +36,7 @@ public function setupOnce(): void return $event; } - if (isset($hint->extra['transaction'])) { + if (isset($hint->extra['transaction']) && \is_string($hint->extra['transaction'])) { $event->setTransaction($hint->extra['transaction']); } elseif (isset($_SERVER['PATH_INFO'])) { $event->setTransaction($_SERVER['PATH_INFO']); diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index 2d91446f6..f8b9e07e2 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -12,9 +12,6 @@ class RepresentationSerializer extends AbstractSerializer implements Representat { /** * {@inheritdoc} - * - * @psalm-suppress InvalidReturnType - * @psalm-suppress InvalidReturnStatement */ public function representationSerialize($value) { diff --git a/src/StacktraceBuilder.php b/src/StacktraceBuilder.php index d48b2cdf5..bf998cb61 100644 --- a/src/StacktraceBuilder.php +++ b/src/StacktraceBuilder.php @@ -11,14 +11,11 @@ * or from a backtrace. * * @internal + * + * @psalm-import-type StacktraceFrame from FrameBuilder */ final class StacktraceBuilder { - /** - * @var Options The SDK client options - */ - private $options; - /** * @var FrameBuilder An instance of the builder of {@see Frame} objects */ @@ -32,7 +29,6 @@ final class StacktraceBuilder */ public function __construct(Options $options, RepresentationSerializerInterface $representationSerializer) { - $this->options = $options; $this->frameBuilder = new FrameBuilder($options, $representationSerializer); } @@ -53,10 +49,7 @@ public function buildFromException(\Throwable $exception): Stacktrace * @param string $file The file where the backtrace originated from * @param int $line The line from which the backtrace originated from * - * @phpstan-param list $backtrace + * @psalm-param list $backtrace */ public function buildFromBacktrace(array $backtrace, string $file, int $line): Stacktrace { diff --git a/src/State/Hub.php b/src/State/Hub.php index beb2b97be..4d8bf36a3 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -121,7 +121,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?EventH $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope(), $hint); } @@ -136,7 +135,6 @@ public function captureException(\Throwable $exception, ?EventHint $hint = null) $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureException($exception, $this->getScope(), $hint); } @@ -165,7 +163,6 @@ public function captureLastError(?EventHint $hint = null): ?EventId $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureLastError($this->getScope(), $hint); } @@ -267,9 +264,6 @@ public function startTransaction(TransactionContext $context, array $customSampl /** * {@inheritdoc} - * - * @psalm-suppress MoreSpecificReturnType - * @psalm-suppress LessSpecificReturnStatement */ public function getTransaction(): ?Transaction { diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index a047a9dac..4782c8b81 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -108,7 +108,6 @@ public function bindClient(ClientInterface $client): void */ public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint); } @@ -117,7 +116,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?EventH */ public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureException($exception, $hint); } @@ -134,7 +132,6 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId */ public function captureLastError(?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureLastError($hint); } @@ -161,7 +158,6 @@ public function getIntegration(string $className): ?IntegrationInterface */ public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 6b98416ce..1fcaaaf51 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -201,7 +201,6 @@ public static function fromTraceparent(string $header) { @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.', __METHOD__), \E_USER_DEPRECATED); - /** @phpstan-ignore-next-line */ /** @psalm-suppress UnsafeInstantiation */ $context = new static(); if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { diff --git a/src/Util/JSON.php b/src/Util/JSON.php index c56025246..3cd092980 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -22,12 +22,14 @@ final class JSON * @param int $options Bitmask consisting of JSON_* constants * @param int $maxDepth The maximum depth allowed for serializing $data * - * @return mixed - * * @throws JsonException If the encoding failed */ - public static function encode($data, int $options = 0, int $maxDepth = 512) + public static function encode($data, int $options = 0, int $maxDepth = 512): string { + if ($maxDepth < 1) { + throw new \InvalidArgumentException('The $maxDepth argument must be an integer greater than 0.'); + } + $options |= \JSON_UNESCAPED_UNICODE | \JSON_INVALID_UTF8_SUBSTITUTE; $encodedData = json_encode($data, $options, $maxDepth); diff --git a/src/functions.php b/src/functions.php index 7e88a5434..24ef3265b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -28,7 +28,6 @@ function init(array $options = []): void */ function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint); } @@ -40,7 +39,6 @@ function captureMessage(string $message, ?Severity $level = null, ?EventHint $hi */ function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureException($exception, $hint); } @@ -62,7 +60,6 @@ function captureEvent(Event $event, ?EventHint $hint = null): ?EventId */ function captureLastError(?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureLastError($hint); } @@ -120,6 +117,5 @@ function withScope(callable $callback): void */ function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index d2a4ebf1e..ecc086a52 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -113,6 +113,23 @@ public function testEncodeThrowsIfValueIsResource(): void JSON::encode($resource); } + /** + * @dataProvider encodeThrowsExceptionIfMaxDepthArgumentIsInvalidDataProvider + */ + public function testEncodeThrowsExceptionIfMaxDepthArgumentIsInvalid(int $maxDepth): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $maxDepth argument must be an integer greater than 0.'); + + JSON::encode('foo', 0, $maxDepth); + } + + public function encodeThrowsExceptionIfMaxDepthArgumentIsInvalidDataProvider(): \Generator + { + yield [0]; + yield [-1]; + } + public function testEncodeRespectsOptionsArgument(): void { $this->assertSame('{}', JSON::encode([], \JSON_FORCE_OBJECT)); From 6cfedbed157fae4c6d2248925d811e362f17a5b9 Mon Sep 17 00:00:00 2001 From: Vladan Paunovic Date: Wed, 12 Jan 2022 22:35:42 +0100 Subject: [PATCH 0699/1161] meta(gha): Deploy action stale.yml (#1259) --- .github/workflows/stale.yml | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..5054c94db --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,47 @@ +name: 'close stale issues/PRs' +on: + schedule: + - cron: '* */3 * * *' + workflow_dispatch: +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@87c2b794b9b47a9bec68ae03c01aeb572ffebdb1 + with: + repo-token: ${{ github.token }} + days-before-stale: 21 + days-before-close: 7 + only-labels: "" + operations-per-run: 100 + remove-stale-when-updated: true + debug-only: false + ascending: false + + exempt-issue-labels: "Status: Backlog,Status: In Progress" + stale-issue-label: "Status: Stale" + stale-issue-message: |- + This issue has gone three weeks without activity. In another week, I will close it. + + But! If you comment or otherwise update it, I will reset the clock, and if you label it `Status: Backlog` or `Status: In Progress`, I will leave it alone ... forever! + + ---- + + "A weed is but an unloved flower." ― _Ella Wheeler Wilcox_ 🥀 + skip-stale-issue-message: false + close-issue-label: "" + close-issue-message: "" + + exempt-pr-labels: "Status: Backlog,Status: In Progress" + stale-pr-label: "Status: Stale" + stale-pr-message: |- + This pull request has gone three weeks without activity. In another week, I will close it. + + But! If you comment or otherwise update it, I will reset the clock, and if you label it `Status: Backlog` or `Status: In Progress`, I will leave it alone ... forever! + + ---- + + "A weed is but an unloved flower." ― _Ella Wheeler Wilcox_ 🥀 + skip-stale-pr-message: false + close-pr-label: + close-pr-message: "" From 0b53e10e299468708ee580430a9d081d7e7c8730 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 12 Jan 2022 23:00:09 +0100 Subject: [PATCH 0700/1161] Reduce PSalm baseline --- psalm-baseline.xml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index fe746dad3..3df2be9a5 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $parsedDsn['host'] @@ -22,18 +22,6 @@ $userIntegrations - - - JSON::encode($result) - - - string - - - $envelopeHeader - $itemHeader - - $value From 8fea3e3ec6b27bfef7899efa9ecf26b1396a1b31 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 12 Jan 2022 23:05:34 +0100 Subject: [PATCH 0701/1161] Allow plugins for Composer 2.2 --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index efc50b676..dfa87fe8c 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,11 @@ ] }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true, + "phpstan/extension-installer": true + } }, "prefer-stable": true, "extra": { From 7ee1505ae4e8480aef77e141e8bde0fd9cd9eb74 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 13 Jan 2022 22:29:58 +0100 Subject: [PATCH 0702/1161] Optimize the constructor of the `Span` class and add some benchmarks (#1274) --- .github/workflows/ci.yaml | 7 +++-- .gitignore | 1 + CHANGELOG.md | 2 ++ composer.json | 1 + phpbench.json | 6 ++++ src/Tracing/Span.php | 23 +++++--------- tests/Benchmark/SpanBench.php | 57 +++++++++++++++++++++++++++++++++++ 7 files changed, 78 insertions(+), 19 deletions(-) create mode 100644 phpbench.json create mode 100644 tests/Benchmark/SpanBench.php diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index baee46665..3f4c257a5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,9 +26,6 @@ jobs: dependencies: - lowest - highest - include: - - php: '8.1' - dependencies: lowest steps: - name: Checkout @@ -75,3 +72,7 @@ jobs: uses: codecov/codecov-action@v1 with: file: build/coverage-report.xml + + - name: Check benchmarks + run: vendor/bin/phpbench run --revs=1 --iterations=1 + if: ${{ matrix.dependencies == 'highest' && matrix.php == '8.1' }} diff --git a/.gitignore b/.gitignore index 83cb61992..cd5863099 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ package.xml /vendor .idea .php-cs-fixer.cache +.phpbench .phpunit.result.cache docs/_build tests/clover.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 241073e2a..8148b82fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Optimize `Span` constructor and add benchmarks (#1274) + ## 3.3.5 (2021-12-27) - Bump the minimum required version of the `jean85/pretty-package-versions` package (#1267) diff --git a/composer.json b/composer.json index dfa87fe8c..baedd8d10 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "monolog/monolog": "^1.3|^2.0", "nikic/php-parser": "^4.10.3", "php-http/mock-client": "^1.3", + "phpbench/phpbench": "^1.0", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.3", "phpstan/phpstan-phpunit": "^1.0", diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 000000000..3ae277865 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,6 @@ +{ + "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.retry_threshold": 2, + "runner.path": "tests/Benchmark" +} diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 0f672710a..a198361c2 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -80,26 +80,17 @@ class Span */ public function __construct(?SpanContext $context = null) { - $this->traceId = TraceId::generate(); - $this->spanId = SpanId::generate(); - $this->startTimestamp = microtime(true); - if (null === $context) { - return; - } - - if (null !== $context->getTraceId()) { - $this->traceId = $context->getTraceId(); - } - - if (null !== $context->getSpanId()) { - $this->spanId = $context->getSpanId(); - } + $this->traceId = TraceId::generate(); + $this->spanId = SpanId::generate(); + $this->startTimestamp = microtime(true); - if (null !== $context->getStartTimestamp()) { - $this->startTimestamp = $context->getStartTimestamp(); + return; } + $this->traceId = $context->getTraceId() ?? TraceId::generate(); + $this->spanId = $context->getSpanId() ?? SpanId::generate(); + $this->startTimestamp = $context->getStartTimestamp() ?? microtime(true); $this->parentSpanId = $context->getParentSpanId(); $this->description = $context->getDescription(); $this->op = $context->getOp(); diff --git a/tests/Benchmark/SpanBench.php b/tests/Benchmark/SpanBench.php new file mode 100644 index 000000000..a4414373b --- /dev/null +++ b/tests/Benchmark/SpanBench.php @@ -0,0 +1,57 @@ +context = TransactionContext::fromSentryTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0'); + $this->contextWithTimestamp = TransactionContext::fromSentryTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0'); + $this->contextWithTimestamp->setStartTimestamp(microtime(true)); + } + + /** + * @Revs(100000) + * @Iterations(10) + */ + public function benchConstructor(): void + { + $span = new Span(); + } + + /** + * @Revs(100000) + * @Iterations(10) + */ + public function benchConstructorWithInjectedContext(): void + { + $span = new Span($this->context); + } + + /** + * @Revs(100000) + * @Iterations(10) + */ + public function benchConstructorWithInjectedContextAndStartTimestamp(): void + { + $span = new Span($this->contextWithTimestamp); + } +} From baf96537ef7654e8523aecc31e4435810b963e86 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 13 Jan 2022 23:22:19 +0100 Subject: [PATCH 0703/1161] Merge master into develop (#1275) * Bump the minimum required version of `jean85/pretty-package-versions` (#1267) * Prepare release `3.3.5` * Update PHPStan, Psalm and PHP-CS-Fixer to the latest versions (#1272) * Update PHP-CS-Fixer * Update PHPStan * Update Psalm * Raise PHPStan level to 9 * Run PHP-CS-Fixer on the latest stable PHP version * Fix code style * Fix Psalm type definition and update PHPStan baseline * Relax Composer constraint about PHP-CS-Fixer and make it run on PHP `8.1` * Update PHPStan baseline * meta(gha): Deploy action stale.yml (#1259) * Reduce PSalm baseline Co-authored-by: J. Schreuders <3071062+99linesofcode@users.noreply.github.com> Co-authored-by: Stefano Arlandini Co-authored-by: Vladan Paunovic --- .github/workflows/stale.yml | 47 +++++ .github/workflows/static-analysis.yaml | 2 +- .gitignore | 2 +- .php_cs.dist => .php-cs-fixer.dist.php | 8 +- CHANGELOG.md | 4 + composer.json | 10 +- phpstan-baseline.neon | 218 +++++++++++++++++++- phpstan.neon | 4 +- psalm-baseline.xml | 61 ++++++ psalm.xml.dist | 1 + src/Client.php | 7 - src/ClientBuilder.php | 11 +- src/Context/OsContext.php | 4 +- src/Context/RuntimeContext.php | 4 +- src/Dsn.php | 3 - src/ErrorHandler.php | 12 +- src/Event.php | 4 +- src/Frame.php | 2 +- src/FrameBuilder.php | 29 ++- src/HttpClient/HttpClientFactory.php | 17 -- src/Integration/IgnoreErrorsIntegration.php | 15 +- src/Integration/IntegrationRegistry.php | 2 - src/Integration/RequestIntegration.php | 4 + src/Integration/TransactionIntegration.php | 2 +- src/Serializer/RepresentationSerializer.php | 3 - src/StacktraceBuilder.php | 13 +- src/State/Hub.php | 6 - src/State/HubAdapter.php | 4 - src/Tracing/SpanContext.php | 1 - src/Util/JSON.php | 8 +- src/functions.php | 4 - tests/Util/JSONTest.php | 17 ++ 32 files changed, 411 insertions(+), 118 deletions(-) create mode 100644 .github/workflows/stale.yml rename .php_cs.dist => .php-cs-fixer.dist.php (82%) create mode 100644 psalm-baseline.xml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..5054c94db --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,47 @@ +name: 'close stale issues/PRs' +on: + schedule: + - cron: '* */3 * * *' + workflow_dispatch: +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@87c2b794b9b47a9bec68ae03c01aeb572ffebdb1 + with: + repo-token: ${{ github.token }} + days-before-stale: 21 + days-before-close: 7 + only-labels: "" + operations-per-run: 100 + remove-stale-when-updated: true + debug-only: false + ascending: false + + exempt-issue-labels: "Status: Backlog,Status: In Progress" + stale-issue-label: "Status: Stale" + stale-issue-message: |- + This issue has gone three weeks without activity. In another week, I will close it. + + But! If you comment or otherwise update it, I will reset the clock, and if you label it `Status: Backlog` or `Status: In Progress`, I will leave it alone ... forever! + + ---- + + "A weed is but an unloved flower." ― _Ella Wheeler Wilcox_ 🥀 + skip-stale-issue-message: false + close-issue-label: "" + close-issue-message: "" + + exempt-pr-labels: "Status: Backlog,Status: In Progress" + stale-pr-label: "Status: Stale" + stale-pr-message: |- + This pull request has gone three weeks without activity. In another week, I will close it. + + But! If you comment or otherwise update it, I will reset the clock, and if you label it `Status: Backlog` or `Status: In Progress`, I will leave it alone ... forever! + + ---- + + "A weed is but an unloved flower." ― _Ella Wheeler Wilcox_ 🥀 + skip-stale-pr-message: false + close-pr-label: + close-pr-message: "" diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index b470925b1..b1b7c445a 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -18,7 +18,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.1' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist diff --git a/.gitignore b/.gitignore index 25a58fe9c..83cb61992 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ package.xml /vendor .idea -.php_cs.cache +.php-cs-fixer.cache .phpunit.result.cache docs/_build tests/clover.xml diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 82% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index 0a411e3e2..8f4027a98 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -1,19 +1,15 @@ setRules([ - '@PSR2' => true, + '@PHP71Migration' => true, '@Symfony' => true, '@Symfony:risky' => true, - 'array_syntax' => ['syntax' => 'short'], 'concat_space' => ['spacing' => 'one'], 'ordered_imports' => [ 'imports_order' => ['class', 'function', 'const'], ], 'declare_strict_types' => true, - 'psr0' => true, - 'psr4' => true, - 'random_api_migration' => true, 'yoda_style' => true, 'self_accessor' => false, 'phpdoc_no_useless_inheritdoc' => false, diff --git a/CHANGELOG.md b/CHANGELOG.md index 58eba8a41..09a3ee399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ - Update the Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) - Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization +## 3.3.5 (2021-12-27) + +- Bump the minimum required version of the `jean85/pretty-package-versions` package (#1267) + ## 3.3.4 (2021-11-08) - Avoid overwriting the error level set by the user on the event when capturing an `ErrorException` exception (#1251) diff --git a/composer.json b/composer.json index 733c36312..8057c7154 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ext-mbstring": "*", "guzzlehttp/promises": "^1.4", "guzzlehttp/psr7": "^1.7|^2.0", - "jean85/pretty-package-versions": "^1.5|^2.0.1", + "jean85/pretty-package-versions": "^1.5|^2.0.4", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", "php-http/discovery": "^1.11", @@ -39,17 +39,17 @@ "symfony/polyfill-uuid": "^1.13.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", + "friendsofphp/php-cs-fixer": "^2.19|^3.4", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.3|^2.0", "nikic/php-parser": "^4.10.3", "php-http/mock-client": "^1.3", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^8.5.14|^9.4", "symfony/phpunit-bridge": "^5.2|^6.0", - "vimeo/psalm": "^4.2" + "vimeo/psalm": "^4.17" }, "suggest": { "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fac401684..f59608f82 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,10 @@ parameters: ignoreErrors: + - + message: "#^Constructor of class Sentry\\\\Client has an unused parameter \\$serializer\\.$#" + count: 1 + path: src/Client.php + - message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns T of Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" count: 1 @@ -11,25 +16,30 @@ parameters: path: src/ClientInterface.php - - message: "#^Offset 'host' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + message: "#^Offset 'host' does not exist on array\\{host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string, scheme\\: 'http'\\|'https'\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'path' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + message: "#^Offset 'path' does not exist on array\\{host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string, scheme\\: 'http'\\|'https'\\}\\.$#" count: 4 path: src/Dsn.php - - message: "#^Offset 'scheme' does not exist on array\\(\\?'scheme' \\=\\> string, \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + message: "#^Offset 'scheme' does not exist on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'user' does not exist on array\\('scheme' \\=\\> 'http'\\|'https', \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + message: "#^Offset 'user' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php + - + message: "#^Parameter \\#1 \\$backtrace of method Sentry\\\\ErrorHandler\\:\\:cleanBacktraceFromErrorHandlerFrames\\(\\) expects array\\, array\\\\> given\\.$#" + count: 1 + path: src/ErrorHandler.php + - message: "#^Result of && is always false\\.$#" count: 2 @@ -65,11 +75,181 @@ parameters: count: 1 path: src/HttpClient/HttpClientFactory.php + - + message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$responseFactory\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$uriFactory\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Property Sentry\\\\Integration\\\\IgnoreErrorsIntegration\\:\\:\\$options \\(array\\{ignore_exceptions\\: array\\\\>, ignore_tags\\: array\\\\}\\) does not accept array\\.$#" + count: 1 + path: src/Integration/IgnoreErrorsIntegration.php + + - + message: "#^Property Sentry\\\\Integration\\\\RequestIntegration\\:\\:\\$options \\(array\\{pii_sanitize_headers\\: array\\\\}\\) does not accept array\\.$#" + count: 1 + path: src/Integration/RequestIntegration.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeBreadcrumbCallback\\(\\) should return callable\\(Sentry\\\\Breadcrumb\\)\\: Sentry\\\\Breadcrumb\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: Sentry\\\\Event\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getClassSerializers\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getContextLines\\(\\) should return int\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getDsn\\(\\) should return Sentry\\\\Dsn\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getEnvironment\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getErrorTypes\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpProxy\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getInAppExcludedPaths\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getInAppIncludedPaths\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getIntegrations\\(\\) should return array\\\\|\\(callable\\(array\\\\)\\: array\\\\) but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getLogger\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxBreadcrumbs\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxRequestBodySize\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxValueLength\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getPrefixes\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getRelease\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getSampleRate\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getSendAttempts\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getServerName\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTags\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTracesSampleRate\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTracesSampler\\(\\) should return \\(callable\\(\\)\\: mixed\\)\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:hasDefaultIntegrations\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:isCompressionEnabled\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldAttachStacktrace\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldCaptureSilencedErrors\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldSendDefaultPii\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" count: 1 path: src/Serializer/AbstractSerializer.php + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/Serializer/AbstractSerializer.php + + - + message: "#^Parameter \\#1 \\$backtrace of method Sentry\\\\StacktraceBuilder\\:\\:buildFromBacktrace\\(\\) expects array\\, array\\\\> given\\.$#" + count: 1 + path: src/StacktraceBuilder.php + - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" count: 1 @@ -125,6 +305,36 @@ parameters: count: 1 path: src/Tracing/GuzzleTracingMiddleware.php + - + message: "#^Unsafe usage of new static\\(\\)\\.$#" + count: 1 + path: src/Tracing/SpanContext.php + + - + message: "#^Parameter \\#1 \\$email of method Sentry\\\\UserDataBag\\:\\:setEmail\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$id of method Sentry\\\\UserDataBag\\:\\:setId\\(\\) expects int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$ipAddress of method Sentry\\\\UserDataBag\\:\\:setIpAddress\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$username of method Sentry\\\\UserDataBag\\:\\:setUsername\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Method Sentry\\\\Util\\\\JSON\\:\\:encode\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/Util/JSON.php + - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index f180d1007..43c4ca673 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,9 +4,9 @@ includes: parameters: tipsOfTheDay: false treatPhpDocTypesAsCertain: false - level: 8 + level: 9 paths: - src - excludes_analyse: + excludePaths: - tests/resources - tests/Fixtures diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 000000000..3df2be9a5 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,61 @@ + + + + + $parsedDsn['host'] + $parsedDsn['path'] + $parsedDsn['scheme'] + $parsedDsn['user'] + + + + + GuzzleHttpClientOptions + GuzzleHttpClientOptions + GuzzleHttpClientOptions + SymfonyHttpClient + + + + + $userIntegration + $userIntegrations + + + + + $value + + + representationSerialize + + + + + captureException + captureLastError + captureMessage + + + + + captureException + captureLastError + captureMessage + startTransaction + + + + + new static() + + + + + captureException + captureLastError + captureMessage + startTransaction + + + diff --git a/psalm.xml.dist b/psalm.xml.dist index e1cbbfde2..c60ac2108 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -5,6 +5,7 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" memoizeMethodCallResults="true" + errorBaseline="psalm-baseline.xml" > diff --git a/src/Client.php b/src/Client.php index d159a5793..37a6838ac 100644 --- a/src/Client.php +++ b/src/Client.php @@ -12,7 +12,6 @@ use Sentry\Integration\IntegrationRegistry; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; use Sentry\Transport\TransportInterface; @@ -56,11 +55,6 @@ final class Client implements ClientInterface */ private $integrations; - /** - * @var SerializerInterface The serializer of the client - */ - private $serializer; - /** * @var RepresentationSerializerInterface The representation serializer of the client */ @@ -105,7 +99,6 @@ public function __construct( $this->transport = $transport; $this->logger = $logger ?? new NullLogger(); $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); - $this->serializer = $serializer ?? new Serializer($this->options); $this->representationSerializer = $representationSerializer ?? new RepresentationSerializer($this->options); $this->stacktraceBuilder = new StacktraceBuilder($options, $this->representationSerializer); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 06f6593a6..46ea8c516 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,13 +4,11 @@ namespace Sentry; -use Http\Client\HttpAsyncClient; use Http\Discovery\Psr17FactoryDiscovery; use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; use Sentry\Transport\DefaultTransportFactory; use Sentry\Transport\TransportFactoryInterface; @@ -38,11 +36,6 @@ final class ClientBuilder implements ClientBuilderInterface */ private $transport; - /** - * @var HttpAsyncClient|null The HTTP client - */ - private $httpClient; - /** * @var SerializerInterface|null The serializer to be injected in the client */ @@ -84,7 +77,7 @@ public function __construct(Options $options = null) */ public static function create(array $options = []): ClientBuilderInterface { - return new static(new Options($options)); + return new self(new Options($options)); } /** @@ -189,7 +182,7 @@ private function createDefaultTransportFactory(): DefaultTransportFactory Psr17FactoryDiscovery::findUriFactory(), Psr17FactoryDiscovery::findResponseFactory(), $streamFactory, - $this->httpClient, + null, $this->sdkIdentifier, $this->sdkVersion ); diff --git a/src/Context/OsContext.php b/src/Context/OsContext.php index 7e6928f77..04e9387bd 100644 --- a/src/Context/OsContext.php +++ b/src/Context/OsContext.php @@ -41,7 +41,7 @@ final class OsContext */ public function __construct(string $name, ?string $version = null, ?string $build = null, ?string $kernelVersion = null) { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -66,7 +66,7 @@ public function getName(): string */ public function setName(string $name): void { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 9801e9ee3..d0a114e48 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -29,7 +29,7 @@ final class RuntimeContext */ public function __construct(string $name, ?string $version = null) { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -52,7 +52,7 @@ public function getName(): string */ public function setName(string $name): void { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Dsn.php b/src/Dsn.php index 04109e16d..e96d4484a 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -92,12 +92,10 @@ public static function createFromString(string $value): self throw new \InvalidArgumentException(sprintf('The "%s" DSN must contain a valid secret key.', $value)); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ if (!\in_array($parsedDsn['scheme'], ['http', 'https'], true)) { throw new \InvalidArgumentException(sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value)); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ $segmentPaths = explode('/', $parsedDsn['path']); $projectId = array_pop($segmentPaths); @@ -112,7 +110,6 @@ public static function createFromString(string $value): self $path = substr($parsedDsn['path'], 0, $lastSlashPosition); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ return new self( $parsedDsn['scheme'], $parsedDsn['host'], diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index ac2b63804..1fee7d01c 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -12,6 +12,8 @@ * error types and relays them to all configured listeners. Registering this * error handler more than once is not supported and will lead to nasty * problems. The code is based on the Symfony ErrorHandler component. + * + * @psalm-import-type StacktraceFrame from FrameBuilder */ final class ErrorHandler { @@ -93,7 +95,7 @@ final class ErrorHandler private static $reservedMemory; /** - * @var array List of error levels and their description + * @var string[] List of error levels and their description */ private const ERROR_LEVELS_DESCRIPTION = [ \E_DEPRECATED => 'Deprecated', @@ -379,11 +381,13 @@ private function handleException(\Throwable $exception): void * Cleans and returns the backtrace without the first frames that belong to * this error handler. * - * @param array $backtrace The backtrace to clear - * @param string $file The filename the backtrace was raised in - * @param int $line The line number the backtrace was raised at + * @param array> $backtrace The backtrace to clear + * @param string $file The filename the backtrace was raised in + * @param int $line The line number the backtrace was raised at * * @return array + * + * @psalm-param list $backtrace */ private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array { diff --git a/src/Event.php b/src/Event.php index 6f0e582d4..4c77aae91 100644 --- a/src/Event.php +++ b/src/Event.php @@ -71,7 +71,7 @@ final class Event private $messageFormatted; /** - * @var mixed[] The parameters to use to format the message + * @var string[] The parameters to use to format the message */ private $messageParams = []; @@ -376,7 +376,7 @@ public function getMessageParams(): array * Sets the error message. * * @param string $message The message - * @param mixed[] $params The parameters to use to format the message + * @param string[] $params The parameters to use to format the message * @param string|null $formatted The formatted message */ public function setMessage(string $message, array $params = [], ?string $formatted = null): void diff --git a/src/Frame.php b/src/Frame.php index ade6ac811..4d6a067af 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -67,7 +67,7 @@ final class Frame /** * @var array A mapping of variables which were available within - * this frame (usually context-locals) + * this frame (usually context-locals) */ private $vars = []; diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 21659f585..393c1b946 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -10,6 +10,15 @@ * This class builds a {@see Frame} object out of a backtrace's raw frame. * * @internal + * + * @psalm-type StacktraceFrame array{ + * function?: string, + * line?: int, + * file?: string, + * class?: class-string, + * type?: string, + * args?: mixed[] + * } */ final class FrameBuilder { @@ -42,14 +51,7 @@ public function __construct(Options $options, RepresentationSerializerInterface * @param int $line The line at which the frame originated * @param array $backtraceFrame The raw frame * - * @psalm-param array{ - * function?: callable-string, - * line?: integer, - * file?: string, - * class?: class-string, - * type?: string, - * args?: array - * } $backtraceFrame + * @psalm-param StacktraceFrame $backtraceFrame */ public function buildFromBacktraceFrame(string $file, int $line, array $backtraceFrame): Frame { @@ -153,14 +155,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool * * @return array * - * @throws \ReflectionException - * - * @psalm-param array{ - * function?: callable-string, - * class?: class-string, - * type?: string, - * args?: array - * } $backtraceFrame + * @psalm-param StacktraceFrame $backtraceFrame */ private function getFunctionArguments(array $backtraceFrame): array { @@ -179,7 +174,7 @@ private function getFunctionArguments(array $backtraceFrame): array } else { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); } - } elseif (isset($backtraceFrame['function']) && !\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { + } elseif (!\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { $reflectionFunction = new \ReflectionFunction($backtraceFrame['function']); } } catch (\ReflectionException $e) { diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 05c1d3a8a..a2a267e10 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -42,16 +42,6 @@ final class HttpClientFactory implements HttpClientFactoryInterface */ private const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; - /** - * @var UriFactoryInterface The PSR-7 URI factory - */ - private $uriFactory; - - /** - * @var ResponseFactoryInterface The PSR-7 response factory - */ - private $responseFactory; - /** * @var StreamFactoryInterface The PSR-17 stream factory */ @@ -90,8 +80,6 @@ public function __construct( string $sdkIdentifier, string $sdkVersion ) { - $this->uriFactory = $uriFactory; - $this->responseFactory = $responseFactory; $this->streamFactory = $streamFactory; $this->httpClient = $httpClient; $this->sdkIdentifier = $sdkIdentifier; @@ -142,23 +130,19 @@ private function resolveClient(Options $options) $symfonyConfig['proxy'] = $options->getHttpProxy(); } - /** @psalm-suppress UndefinedClass */ return new SymfonyHttplugClient(SymfonyHttpClient::create($symfonyConfig)); } if (class_exists(GuzzleHttpClient::class)) { - /** @psalm-suppress UndefinedClass */ $guzzleConfig = [ GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, ]; if (null !== $options->getHttpProxy()) { - /** @psalm-suppress UndefinedClass */ $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ return GuzzleHttpClient::createWithConfig($guzzleConfig); } @@ -172,7 +156,6 @@ private function resolveClient(Options $options) $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ return new CurlHttpClient(null, null, $curlConfig); } diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index 4cc5961a0..aae22e10b 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -14,11 +14,18 @@ * to a series of options that must match with its data. * * @author Stefano Arlandini + * + * @psalm-type IntegrationOptions array{ + * ignore_exceptions: list>, + * ignore_tags: array + * } */ final class IgnoreErrorsIntegration implements IntegrationInterface { /** * @var array The options + * + * @psalm-var IntegrationOptions */ private $options; @@ -29,7 +36,7 @@ final class IgnoreErrorsIntegration implements IntegrationInterface * @param array $options The options * * @psalm-param array{ - * ignore_exceptions?: list> + * ignore_exceptions?: list>, * ignore_tags?: array * } $options */ @@ -69,6 +76,8 @@ public function setupOnce(): void * * @param Event $event The event to check * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function shouldDropEvent(Event $event, array $options): bool { @@ -89,6 +98,8 @@ private function shouldDropEvent(Event $event, array $options): bool * * @param Event $event The event instance * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function isIgnoredException(Event $event, array $options): bool { @@ -113,6 +124,8 @@ private function isIgnoredException(Event $event, array $options): bool * * @param Event $event The event instance * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function isIgnoredTag(Event $event, array $options): bool { diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index 731c4469c..ff15b2b60 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -86,7 +86,6 @@ private function getIntegrationsToSetup(Options $options): array $userIntegrations = $options->getIntegrations(); if (\is_array($userIntegrations)) { - /** @psalm-suppress PossiblyInvalidArgument */ $userIntegrationsClasses = array_map('get_class', $userIntegrations); $pickedIntegrationsClasses = []; @@ -100,7 +99,6 @@ private function getIntegrationsToSetup(Options $options): array } foreach ($userIntegrations as $userIntegration) { - /** @psalm-suppress PossiblyInvalidArgument */ $integrationClassName = \get_class($userIntegration); if (!isset($pickedIntegrationsClasses[$integrationClassName])) { diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index b67c39a06..b3b515387 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -69,6 +69,10 @@ final class RequestIntegration implements IntegrationInterface /** * @var array The options + * + * @psalm-var array{ + * pii_sanitize_headers: string[] + * } */ private $options; diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index 7f24be550..d7c775fce 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -36,7 +36,7 @@ public function setupOnce(): void return $event; } - if (isset($hint->extra['transaction'])) { + if (isset($hint->extra['transaction']) && \is_string($hint->extra['transaction'])) { $event->setTransaction($hint->extra['transaction']); } elseif (isset($_SERVER['PATH_INFO'])) { $event->setTransaction($_SERVER['PATH_INFO']); diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index 2d91446f6..f8b9e07e2 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -12,9 +12,6 @@ class RepresentationSerializer extends AbstractSerializer implements Representat { /** * {@inheritdoc} - * - * @psalm-suppress InvalidReturnType - * @psalm-suppress InvalidReturnStatement */ public function representationSerialize($value) { diff --git a/src/StacktraceBuilder.php b/src/StacktraceBuilder.php index d48b2cdf5..bf998cb61 100644 --- a/src/StacktraceBuilder.php +++ b/src/StacktraceBuilder.php @@ -11,14 +11,11 @@ * or from a backtrace. * * @internal + * + * @psalm-import-type StacktraceFrame from FrameBuilder */ final class StacktraceBuilder { - /** - * @var Options The SDK client options - */ - private $options; - /** * @var FrameBuilder An instance of the builder of {@see Frame} objects */ @@ -32,7 +29,6 @@ final class StacktraceBuilder */ public function __construct(Options $options, RepresentationSerializerInterface $representationSerializer) { - $this->options = $options; $this->frameBuilder = new FrameBuilder($options, $representationSerializer); } @@ -53,10 +49,7 @@ public function buildFromException(\Throwable $exception): Stacktrace * @param string $file The file where the backtrace originated from * @param int $line The line from which the backtrace originated from * - * @phpstan-param list $backtrace + * @psalm-param list $backtrace */ public function buildFromBacktrace(array $backtrace, string $file, int $line): Stacktrace { diff --git a/src/State/Hub.php b/src/State/Hub.php index beb2b97be..4d8bf36a3 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -121,7 +121,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?EventH $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope(), $hint); } @@ -136,7 +135,6 @@ public function captureException(\Throwable $exception, ?EventHint $hint = null) $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureException($exception, $this->getScope(), $hint); } @@ -165,7 +163,6 @@ public function captureLastError(?EventHint $hint = null): ?EventId $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureLastError($this->getScope(), $hint); } @@ -267,9 +264,6 @@ public function startTransaction(TransactionContext $context, array $customSampl /** * {@inheritdoc} - * - * @psalm-suppress MoreSpecificReturnType - * @psalm-suppress LessSpecificReturnStatement */ public function getTransaction(): ?Transaction { diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index a047a9dac..4782c8b81 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -108,7 +108,6 @@ public function bindClient(ClientInterface $client): void */ public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint); } @@ -117,7 +116,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?EventH */ public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureException($exception, $hint); } @@ -134,7 +132,6 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId */ public function captureLastError(?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureLastError($hint); } @@ -161,7 +158,6 @@ public function getIntegration(string $className): ?IntegrationInterface */ public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 6b98416ce..1fcaaaf51 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -201,7 +201,6 @@ public static function fromTraceparent(string $header) { @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.', __METHOD__), \E_USER_DEPRECATED); - /** @phpstan-ignore-next-line */ /** @psalm-suppress UnsafeInstantiation */ $context = new static(); if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { diff --git a/src/Util/JSON.php b/src/Util/JSON.php index c56025246..3cd092980 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -22,12 +22,14 @@ final class JSON * @param int $options Bitmask consisting of JSON_* constants * @param int $maxDepth The maximum depth allowed for serializing $data * - * @return mixed - * * @throws JsonException If the encoding failed */ - public static function encode($data, int $options = 0, int $maxDepth = 512) + public static function encode($data, int $options = 0, int $maxDepth = 512): string { + if ($maxDepth < 1) { + throw new \InvalidArgumentException('The $maxDepth argument must be an integer greater than 0.'); + } + $options |= \JSON_UNESCAPED_UNICODE | \JSON_INVALID_UTF8_SUBSTITUTE; $encodedData = json_encode($data, $options, $maxDepth); diff --git a/src/functions.php b/src/functions.php index 7e88a5434..24ef3265b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -28,7 +28,6 @@ function init(array $options = []): void */ function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint); } @@ -40,7 +39,6 @@ function captureMessage(string $message, ?Severity $level = null, ?EventHint $hi */ function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureException($exception, $hint); } @@ -62,7 +60,6 @@ function captureEvent(Event $event, ?EventHint $hint = null): ?EventId */ function captureLastError(?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureLastError($hint); } @@ -120,6 +117,5 @@ function withScope(callable $callback): void */ function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index d2a4ebf1e..ecc086a52 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -113,6 +113,23 @@ public function testEncodeThrowsIfValueIsResource(): void JSON::encode($resource); } + /** + * @dataProvider encodeThrowsExceptionIfMaxDepthArgumentIsInvalidDataProvider + */ + public function testEncodeThrowsExceptionIfMaxDepthArgumentIsInvalid(int $maxDepth): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $maxDepth argument must be an integer greater than 0.'); + + JSON::encode('foo', 0, $maxDepth); + } + + public function encodeThrowsExceptionIfMaxDepthArgumentIsInvalidDataProvider(): \Generator + { + yield [0]; + yield [-1]; + } + public function testEncodeRespectsOptionsArgument(): void { $this->assertSame('{}', JSON::encode([], \JSON_FORCE_OBJECT)); From 0ab186b29a32b33d5b2786360a2b9f81fab9899b Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 13 Jan 2022 23:59:12 +0100 Subject: [PATCH 0704/1161] Handle autoloaders that throws while serializing a possible `callable` (#1276) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + src/Serializer/AbstractSerializer.php | 8 +++- tests/phpt/serialize_broken_class.phpt | 6 +-- ..._callable_that_makes_autoloader_throw.phpt | 45 +++++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 tests/phpt/serialize_callable_that_makes_autoloader_throw.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index 8148b82fe..d0df8506e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Optimize `Span` constructor and add benchmarks (#1274) +- Handle autoloader that throws an exception while trying to serialize a possible callable (#1276) ## 3.3.5 (2021-12-27) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index d744bdb64..2e7ffae42 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -99,8 +99,12 @@ protected function serializeRecursively($value, int $_depth = 0) return $this->serializeValue($value); } - if (\is_callable($value)) { - return $this->serializeCallable($value); + try { + if (\is_callable($value)) { + return $this->serializeCallable($value); + } + } catch (\Throwable $exception) { + // Do nothing on purpose } if (\is_array($value)) { diff --git a/tests/phpt/serialize_broken_class.phpt b/tests/phpt/serialize_broken_class.phpt index 775aa72d5..5511595ae 100644 --- a/tests/phpt/serialize_broken_class.phpt +++ b/tests/phpt/serialize_broken_class.phpt @@ -25,7 +25,7 @@ require $vendor . '/vendor/autoload.php'; function testSerialization($value) { $serializer = new Serializer(new Options()); - echo $serializer->serialize($value); + echo json_encode($serializer->serialize($value)); } testSerialization(BrokenClass::class . '::brokenMethod'); @@ -34,5 +34,5 @@ testSerialization([BrokenClass::class, 'brokenMethod']); ?> --EXPECT-- -Sentry\Tests\Fixtures\code\BrokenClass::brokenMethod {serialization error} -{serialization error} +"Sentry\\Tests\\Fixtures\\code\\BrokenClass::brokenMethod {serialization error}" +["Sentry\\Tests\\Fixtures\\code\\BrokenClass","brokenMethod"] diff --git a/tests/phpt/serialize_callable_that_makes_autoloader_throw.phpt b/tests/phpt/serialize_callable_that_makes_autoloader_throw.phpt new file mode 100644 index 000000000..dc1bd627e --- /dev/null +++ b/tests/phpt/serialize_callable_that_makes_autoloader_throw.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test that serializing a pseudo-callable that doesn't exist (like an array of 2 strings) doesn't brake +when inside an application with an autoloader that throws, like Yii or Magento (see #1112) +--FILE-- +serialize(['FakeClass', 'fakeMethod'])); +} + +testSerialization(); +echo PHP_EOL; +$brokenAutoloader = function (string $classname): void { + throw new \RuntimeException('Autoloader throws while loading ' . $classname); +}; + +spl_autoload_register($brokenAutoloader, true, true); + +try { + testSerialization(); +} finally { + spl_autoload_unregister($brokenAutoloader); +} +?> +--EXPECT-- +["FakeClass","fakeMethod"] +["FakeClass","fakeMethod"] From 11703dda01d3d7a196013fb56e18429d8b0bc392 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 14 Jan 2022 09:44:47 +0100 Subject: [PATCH 0705/1161] Prepare release `3.3.6` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0df8506e..dd32ad8ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.3.6 (2022-01-14) + - Optimize `Span` constructor and add benchmarks (#1274) - Handle autoloader that throws an exception while trying to serialize a possible callable (#1276) From 1b1c35a3f5afe5c7c17d572205a7733b33ab5c87 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 14 Jan 2022 10:05:19 +0100 Subject: [PATCH 0706/1161] Restrict the installable versions of `PHP-CS-Fixer` to workaround issue FriendsOfPHP/PHP-CS-Fixer#6238 (#1277) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index baedd8d10..9362ed919 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "symfony/polyfill-uuid": "^1.13.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.19|^3.4", + "friendsofphp/php-cs-fixer": "^2.19|3.4.*", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.3|^2.0", "nikic/php-parser": "^4.10.3", From e4dd0450ed60597a09a4264ca7d6c8f4c3a18eaf Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Sat, 15 Jan 2022 17:23:21 +0100 Subject: [PATCH 0707/1161] Fix autoloader throwing an exception while trying to serialize a possible `callable` (#1280) --- CHANGELOG.md | 2 ++ src/Serializer/AbstractSerializer.php | 8 ++++++-- tests/phpt/serialize_broken_class.phpt | 2 +- .../serialize_callable_that_makes_autoloader_throw.phpt | 8 +++++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd32ad8ce..ea590c1a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix another case of serialization with autoloader that throws exceptions (#1280) + ## 3.3.6 (2022-01-14) - Optimize `Span` constructor and add benchmarks (#1274) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 2e7ffae42..2da56cfbd 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -248,8 +248,12 @@ protected function serializeValue($value) return 'Resource ' . get_resource_type($value); } - if (\is_callable($value)) { - return $this->serializeCallable($value); + try { + if (\is_callable($value)) { + return $this->serializeCallable($value); + } + } catch (\Throwable $exception) { + // Do nothing on purpose } if (\is_array($value)) { diff --git a/tests/phpt/serialize_broken_class.phpt b/tests/phpt/serialize_broken_class.phpt index 5511595ae..0df9ab8c1 100644 --- a/tests/phpt/serialize_broken_class.phpt +++ b/tests/phpt/serialize_broken_class.phpt @@ -34,5 +34,5 @@ testSerialization([BrokenClass::class, 'brokenMethod']); ?> --EXPECT-- -"Sentry\\Tests\\Fixtures\\code\\BrokenClass::brokenMethod {serialization error}" +"Sentry\\Tests\\Fixtures\\code\\BrokenClass::brokenMethod" ["Sentry\\Tests\\Fixtures\\code\\BrokenClass","brokenMethod"] diff --git a/tests/phpt/serialize_callable_that_makes_autoloader_throw.phpt b/tests/phpt/serialize_callable_that_makes_autoloader_throw.phpt index dc1bd627e..57f31ba66 100644 --- a/tests/phpt/serialize_callable_that_makes_autoloader_throw.phpt +++ b/tests/phpt/serialize_callable_that_makes_autoloader_throw.phpt @@ -20,14 +20,14 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -function testSerialization() { - $serializer = new Serializer(new Options()); +function testSerialization(int $depth = 3) { + $serializer = new Serializer(new Options(), $depth); echo json_encode($serializer->serialize(['FakeClass', 'fakeMethod'])); + echo PHP_EOL; } testSerialization(); -echo PHP_EOL; $brokenAutoloader = function (string $classname): void { throw new \RuntimeException('Autoloader throws while loading ' . $classname); }; @@ -36,6 +36,7 @@ spl_autoload_register($brokenAutoloader, true, true); try { testSerialization(); + testSerialization(0); } finally { spl_autoload_unregister($brokenAutoloader); } @@ -43,3 +44,4 @@ try { --EXPECT-- ["FakeClass","fakeMethod"] ["FakeClass","fakeMethod"] +"Array of length 2" From bdfde4af3bc4962c75ff2d3141dfdf0646e6611b Mon Sep 17 00:00:00 2001 From: HypeMC <2445045+HypeMC@users.noreply.github.com> Date: Mon, 17 Jan 2022 18:05:22 +0100 Subject: [PATCH 0708/1161] Return original return value. (#1278) Co-authored-by: Vaibhav Pandey --- CHANGELOG.md | 3 ++- src/State/Hub.php | 4 ++-- src/State/HubAdapter.php | 4 ++-- src/State/HubInterface.php | 10 +++++++++- src/functions.php | 13 +++++++++++-- tests/FunctionsTest.php | 8 +++----- tests/State/HubAdapterTest.php | 12 +++++++++--- tests/State/HubTest.php | 26 ++++++++++++++++++++++++++ 8 files changed, 64 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 293c971be..4ba690f5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## Unreleased -- Update the Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) +- Update Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) - Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization +- The `withScope` methods now return the callback's return value (#1263) ## 3.3.6 (2022-01-14) diff --git a/src/State/Hub.php b/src/State/Hub.php index 4d8bf36a3..e188bb7db 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -85,12 +85,12 @@ public function popScope(): bool /** * {@inheritdoc} */ - public function withScope(callable $callback): void + public function withScope(callable $callback) { $scope = $this->pushScope(); try { - $callback($scope); + return $callback($scope); } finally { $this->popScope(); } diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 4782c8b81..eb3da1688 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -82,9 +82,9 @@ public function popScope(): bool /** * {@inheritdoc} */ - public function withScope(callable $callback): void + public function withScope(callable $callback) { - SentrySdk::getCurrentHub()->withScope($callback); + return SentrySdk::getCurrentHub()->withScope($callback); } /** diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 5ca1a92e7..07ff16879 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -54,8 +54,16 @@ public function popScope(): bool; * is automatically removed once the operation finishes or throws. * * @param callable $callback The callback to be executed + * + * @return mixed|void The callback's return value, upon successful execution + * + * @psalm-template T + * + * @psalm-param callable(Scope): T $callback + * + * @psalm-return T */ - public function withScope(callable $callback): void; + public function withScope(callable $callback); /** * Calls the given callback passing to it the current scope so that any diff --git a/src/functions.php b/src/functions.php index 24ef3265b..139e40e6c 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,6 +4,7 @@ namespace Sentry; +use Sentry\State\Scope; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -91,10 +92,18 @@ function configureScope(callable $callback): void * is automatically removed once the operation finishes or throws. * * @param callable $callback The callback to be executed + * + * @return mixed|void The callback's return value, upon successful execution + * + * @psalm-template T + * + * @psalm-param callable(Scope): T $callback + * + * @psalm-return T */ -function withScope(callable $callback): void +function withScope(callable $callback) { - SentrySdk::getCurrentHub()->withScope($callback); + return SentrySdk::getCurrentHub()->withScope($callback); } /** diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index d7bad8818..9f055a104 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -197,13 +197,11 @@ public function testAddBreadcrumb(): void public function testWithScope(): void { - $callbackInvoked = false; - - withScope(static function () use (&$callbackInvoked): void { - $callbackInvoked = true; + $returnValue = withScope(static function (): string { + return 'foobarbaz'; }); - $this->assertTrue($callbackInvoked); + $this->assertSame('foobarbaz', $returnValue); } public function testConfigureScope(): void diff --git a/tests/State/HubAdapterTest.php b/tests/State/HubAdapterTest.php index 1b09cb90d..7c8c2cd93 100644 --- a/tests/State/HubAdapterTest.php +++ b/tests/State/HubAdapterTest.php @@ -94,15 +94,21 @@ public function testPopScope(): void public function testWithScope(): void { - $callback = static function () {}; + $callback = static function (): string { + return 'foobarbaz'; + }; $hub = $this->createMock(HubInterface::class); $hub->expects($this->once()) ->method('withScope') - ->with($callback); + ->with($callback) + ->willReturnCallback($callback); SentrySdk::setCurrentHub($hub); - HubAdapter::getInstance()->withScope($callback); + + $returnValue = HubAdapter::getInstance()->withScope($callback); + + $this->assertSame('foobarbaz', $returnValue); } public function testConfigureScope(): void diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 85c39ec38..85b4d58fc 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -135,6 +135,30 @@ public function testPopScope(): void } public function testWithScope(): void + { + $scope = new Scope(); + $hub = new Hub(null, $scope); + + $callbackReturn = $hub->withScope(function (Scope $scopeArg) use ($scope): string { + $this->assertNotSame($scope, $scopeArg); + + return 'foobarbaz'; + }); + + $this->assertSame('foobarbaz', $callbackReturn); + + $callbackInvoked = false; + + $hub->configureScope(function (Scope $scopeArg) use (&$callbackInvoked, $scope): void { + $this->assertSame($scope, $scopeArg); + + $callbackInvoked = true; + }); + + $this->assertTrue($callbackInvoked); + } + + public function testWithScopeWhenExceptionIsThrown(): void { $scope = new Scope(); $hub = new Hub($this->createMock(ClientInterface::class), $scope); @@ -172,6 +196,8 @@ public function testConfigureScope(): void $scope = new Scope(); $hub = new Hub(null, $scope); + $callbackInvoked = false; + $hub->configureScope(function (Scope $scopeArg) use ($scope, &$callbackInvoked): void { $this->assertSame($scope, $scopeArg); From 32e5415803ff0349ccb5e5b5e77b016320762786 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 19 Jan 2022 09:46:27 +0100 Subject: [PATCH 0709/1161] Prepare release `3.3.7` --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea590c1a7..cefb72356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased -- Fix another case of serialization with autoloader that throws exceptions (#1280) +## 3.3.7 (2022-01-19) + +- Fix the serialization of a `callable` when the autoloader throws exceptions (#1280) ## 3.3.6 (2022-01-14) From 087f5a4510bc04e93f963c570a5ff006927d58bc Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 23 Jan 2022 00:03:47 +0100 Subject: [PATCH 0710/1161] Set the event extras by taking the data from the Monolog record's context (#1244) --- CHANGELOG.md | 3 +- src/Monolog/Handler.php | 45 ++++++++++++- tests/Monolog/HandlerTest.php | 122 ++++++++++++++++++++++++++++++---- 3 files changed, 153 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eed4eba49..065cb53a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Update Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) - Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization - The `withScope` methods now return the callback's return value (#1263) +- Set the event extras by taking the data from the Monolog record's context (#1244) ## 3.3.7 (2022-01-19) @@ -41,7 +42,7 @@ ## 3.3.0 (2021-05-26) - Allow setting a custom timestamp on the breadcrumbs (#1193) -- Add option `ignore_tags` to `IgnoreErrorsIntegration` in order to ignore exceptions by tags values. (#1201) +- Add option `ignore_tags` to `IgnoreErrorsIntegration` in order to ignore exceptions by tags values (#1201) ## 3.2.2 (2021-05-06) diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 1075ed3a3..e2b97c098 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -20,21 +20,29 @@ */ final class Handler extends AbstractProcessingHandler { + private const CONTEXT_EXCEPTION_KEY = 'exception'; + /** * @var HubInterface */ private $hub; + /** + * @var bool + */ + private $fillExtraContext; + /** * {@inheritdoc} * * @param HubInterface $hub The hub to which errors are reported */ - public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true) + public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true, bool $fillExtraContext = false) { - $this->hub = $hub; - parent::__construct($level, $bubble); + + $this->hub = $hub; + $this->fillExtraContext = $fillExtraContext; } /** @@ -57,6 +65,12 @@ protected function write(array $record): void $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); + $monologContextData = $this->getMonologContextData($record['context']); + + if (!empty($monologContextData)) { + $scope->setExtra('monolog.context', $monologContextData); + } + $this->hub->captureEvent($event, $hint); }); } @@ -85,4 +99,29 @@ private static function getSeverityFromLevel(int $level): Severity return Severity::info(); } } + + /** + * @param mixed[] $context + * + * @return mixed[] + */ + private function getMonologContextData(array $context): array + { + if (!$this->fillExtraContext) { + return []; + } + + $contextData = []; + + foreach ($context as $key => $value) { + // We skip the `exception` field because it goes in its own context + if (self::CONTEXT_EXCEPTION_KEY === $key) { + continue; + } + + $contextData[$key] = $value; + } + + return $contextData; + } } diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index 2a5c61adc..7be7a093c 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -20,28 +20,32 @@ final class HandlerTest extends TestCase /** * @dataProvider handleDataProvider */ - public function testHandle(array $record, Event $expectedEvent, EventHint $expectedHint, array $expectedExtra): void + public function testHandle(bool $fillExtraContext, array $record, Event $expectedEvent, EventHint $expectedHint, array $expectedExtra): void { /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') - ->with($this->callback(function (Event $event) use ($expectedEvent): bool { - $this->assertEquals($expectedEvent->getLevel(), $event->getLevel()); - $this->assertEquals($expectedEvent->getMessage(), $event->getMessage()); - $this->assertEquals($expectedEvent->getLogger(), $event->getLogger()); + ->with( + $this->callback(function (Event $event) use ($expectedEvent): bool { + $this->assertEquals($expectedEvent->getLevel(), $event->getLevel()); + $this->assertSame($expectedEvent->getMessage(), $event->getMessage()); + $this->assertSame($expectedEvent->getLogger(), $event->getLogger()); - return true; - }), $expectedHint, $this->callback(function (Scope $scopeArg) use ($expectedExtra): bool { - $event = $scopeArg->applyToEvent(Event::createEvent()); + return true; + }), + $expectedHint, + $this->callback(function (Scope $scopeArg) use ($expectedExtra): bool { + $event = $scopeArg->applyToEvent(Event::createEvent()); - $this->assertNotNull($event); - $this->assertSame($expectedExtra, $event->getExtra()); + $this->assertNotNull($event); + $this->assertSame($expectedExtra, $event->getExtra()); - return true; - })); + return true; + }) + ); - $handler = new Handler(new Hub($client, new Scope())); + $handler = new Handler(new Hub($client, new Scope()), Logger::DEBUG, true, $fillExtraContext); $handler->handle($record); } @@ -53,6 +57,7 @@ public function handleDataProvider(): iterable $event->setLevel(Severity::debug()); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::DEBUG, @@ -75,6 +80,7 @@ public function handleDataProvider(): iterable $event->setLevel(Severity::info()); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::INFO, @@ -97,6 +103,7 @@ public function handleDataProvider(): iterable $event->setLevel(Severity::info()); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::NOTICE, @@ -119,6 +126,7 @@ public function handleDataProvider(): iterable $event->setLevel(Severity::warning()); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::WARNING, @@ -141,6 +149,7 @@ public function handleDataProvider(): iterable $event->setLevel(Severity::error()); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::ERROR, @@ -163,6 +172,7 @@ public function handleDataProvider(): iterable $event->setLevel(Severity::fatal()); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::CRITICAL, @@ -185,6 +195,7 @@ public function handleDataProvider(): iterable $event->setLevel(Severity::fatal()); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::ALERT, @@ -207,6 +218,7 @@ public function handleDataProvider(): iterable $event->setLevel(Severity::fatal()); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::EMERGENCY, @@ -231,6 +243,7 @@ public function handleDataProvider(): iterable $exampleException = new \Exception('exception message'); yield [ + false, [ 'message' => 'foo bar', 'level' => Logger::WARNING, @@ -250,5 +263,88 @@ public function handleDataProvider(): iterable 'monolog.level' => Logger::getLevelName(Logger::WARNING), ], ]; + + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::warning()); + + yield 'Monolog\'s context is filled and the handler should fill the "extra" context' => [ + true, + [ + 'message' => 'foo bar', + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'context' => [ + 'foo' => 'bar', + 'bar' => 'baz', + ], + 'channel' => 'channel.foo', + 'extra' => [], + ], + $event, + new EventHint(), + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::WARNING), + 'monolog.context' => [ + 'foo' => 'bar', + 'bar' => 'baz', + ], + ], + ]; + + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::warning()); + + yield 'Monolog\'s context is filled with "exception" field and the handler should fill the "extra" context' => [ + true, + [ + 'message' => 'foo bar', + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'context' => [ + 'exception' => new \Exception('exception message'), + ], + 'channel' => 'channel.foo', + 'extra' => [], + ], + $event, + EventHint::fromArray([ + 'exception' => $exampleException, + ]), + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::WARNING), + ], + ]; + + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::warning()); + + yield 'Monolog\'s context is filled but handler should not fill the "extra" context' => [ + false, + [ + 'message' => 'foo bar', + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'context' => [ + 'foo' => 'bar', + 'bar' => 'baz', + ], + 'channel' => 'channel.foo', + 'extra' => [], + ], + $event, + new EventHint(), + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::WARNING), + ], + ]; } } From b383d161615be2b471b20f92d9297d893cbda775 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 8 Feb 2022 20:34:24 +0100 Subject: [PATCH 0711/1161] Update the PHPStan baseline --- phpstan-baseline.neon | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e9c9d7eab..b735fa566 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -35,11 +35,6 @@ parameters: count: 1 path: src/Dsn.php - - - message: "#^Parameter \\#1 \\$backtrace of method Sentry\\\\ErrorHandler\\:\\:cleanBacktraceFromErrorHandlerFrames\\(\\) expects array\\, array\\\\> given\\.$#" - count: 1 - path: src/ErrorHandler.php - - message: "#^Result of && is always false\\.$#" count: 2 @@ -235,11 +230,6 @@ parameters: count: 1 path: src/Serializer/AbstractSerializer.php - - - message: "#^Parameter \\#1 \\$backtrace of method Sentry\\\\StacktraceBuilder\\:\\:buildFromBacktrace\\(\\) expects array\\, array\\\\> given\\.$#" - count: 1 - path: src/StacktraceBuilder.php - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" count: 1 From c97b8a055e50d81ea25b1190edb7759e25851170 Mon Sep 17 00:00:00 2001 From: mark burdett Date: Tue, 8 Feb 2022 23:42:50 -0800 Subject: [PATCH 0712/1161] Add getter for $client->stacktraceBuilder (#1124) * Add getter for $client->stacktraceBuilder * Add test for attaching custom stacktrace to event * Update tests/ClientTest.php Co-authored-by: Stefano Arlandini * Suggested test fixes * Update CHANGELOG * Fix code style * Mention that StacktraceBuilder is no longer internal * Add PR number * Update CHANGELOG.md Co-authored-by: Stefano Arlandini Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + src/Client.php | 8 ++++++++ src/ClientInterface.php | 2 ++ src/StacktraceBuilder.php | 2 -- tests/ClientTest.php | 41 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 065cb53a1..8050f1a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization - The `withScope` methods now return the callback's return value (#1263) - Set the event extras by taking the data from the Monolog record's context (#1244) +- Make the `StacktraceBuilder` class part of the public API and add the `Client::getStacktraceBuilder()` method to build custom stacktraces (#1124) ## 3.3.7 (2022-01-19) diff --git a/src/Client.php b/src/Client.php index 37a6838ac..308a8c62c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -199,6 +199,14 @@ public function flush(?int $timeout = null): PromiseInterface return $this->transport->close($timeout); } + /** + * {@inheritdoc} + */ + public function getStacktraceBuilder(): StacktraceBuilder + { + return $this->stacktraceBuilder; + } + /** * Assembles an event and prepares it to be sent of to Sentry. * diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 53e1977bc..1cb287f69 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -11,6 +11,8 @@ /** * This interface must be implemented by all Raven client classes. * + * @method StacktraceBuilder getStacktraceBuilder() Returns the stacktrace builder of the client. + * * @author Stefano Arlandini */ interface ClientInterface diff --git a/src/StacktraceBuilder.php b/src/StacktraceBuilder.php index bf998cb61..d2e32b33f 100644 --- a/src/StacktraceBuilder.php +++ b/src/StacktraceBuilder.php @@ -10,8 +10,6 @@ * This class builds {@see Stacktrace} objects from an instance of an exception * or from a backtrace. * - * @internal - * * @psalm-import-type StacktraceFrame from FrameBuilder */ final class StacktraceBuilder diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 1aaef8eed..1982fb901 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -770,6 +770,47 @@ public function testBuildWithStacktrace(): void $client->captureEvent(Event::createEvent()); } + public function testBuildWithCustomStacktrace(): void + { + $options = new Options(); + + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $stacktrace = $event->getStacktrace(); + + $this->assertNotNull($stacktrace); + + /** @var Frame $lastFrame */ + $lastFrame = array_reverse($stacktrace->getFrames())[0]; + + $this->assertSame( + 'MyApp.php', + $lastFrame->getFile() + ); + + return true; + })); + + $client = new Client( + $options, + $transport, + 'sentry.sdk.identifier', + '1.2.3', + new Serializer($options), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $stacktrace = $client->getStacktraceBuilder()->buildFromBacktrace(debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1), 'MyApp.php', 42); + + $event = Event::createEvent(); + $event->setStacktrace($stacktrace); + + $client->captureEvent($event); + } + private function createTransportFactory(TransportInterface $transport): TransportFactoryInterface { return new class($transport) implements TransportFactoryInterface { From ea704bbe145657a507d73cd8bf6cc017d269fffa Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 13 Feb 2022 20:15:11 +0100 Subject: [PATCH 0713/1161] Add missing entries to the `.gitattributes` file (#1288) --- .gitattributes | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index c067dc5ae..71e15013b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,9 +16,14 @@ /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.php_cs.dist export-ignore +/.php-cs-fixer.dist.php export-ignore /.travis.yml export-ignore /Makefile export-ignore /phpstan.neon export-ignore +/phpstan-baseline.neon export-ignore /phpunit.xml.dist export-ignore /psalm.xml.dist export-ignore +/psalm-baseline.xml export-ignore +/codecov.yml export-ignore +/.github export-ignore +/phpbench.json export-ignore From 4450cc45eeb4bd02da3c4a7bb35e110ed75b887a Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Wed, 2 Mar 2022 09:23:57 +0100 Subject: [PATCH 0714/1161] Support handling of the server rate limits (#1291) * Add support for handling rate-limit of API * Remove deprecated attribute from Psalm config file * Simplify the logic of the `RateLimiter::getDisabledUntil()` method * Update the CHANGELOG file --- CHANGELOG.md | 1 + psalm.xml.dist | 1 - src/Event.php | 2 +- src/EventType.php | 5 + src/HttpClient/HttpClientFactory.php | 2 +- src/Transport/HttpTransport.php | 21 ++++- src/Transport/RateLimiter.php | 128 +++++++++++++++++++++++++ tests/Transport/HttpTransportTest.php | 75 +++++++++++++++ tests/Transport/RateLimiterTest.php | 130 ++++++++++++++++++++++++++ tests/bootstrap.php | 2 + 10 files changed, 362 insertions(+), 5 deletions(-) create mode 100644 src/Transport/RateLimiter.php create mode 100644 tests/Transport/RateLimiterTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 8050f1a7f..2c774f113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - The `withScope` methods now return the callback's return value (#1263) - Set the event extras by taking the data from the Monolog record's context (#1244) - Make the `StacktraceBuilder` class part of the public API and add the `Client::getStacktraceBuilder()` method to build custom stacktraces (#1124) +- Support handling the server rate-limits when sending events to Sentry (#1291) ## 3.3.7 (2022-01-19) diff --git a/psalm.xml.dist b/psalm.xml.dist index c60ac2108..8070ef1ef 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -1,6 +1,5 @@ $this->sdkIdentifier . '/' . $this->sdkVersion]), new AuthenticationPlugin(new SentryAuthentication($options, $this->sdkIdentifier, $this->sdkVersion)), new RetryPlugin(['retries' => $options->getSendAttempts()]), - new ErrorPlugin(), + new ErrorPlugin(['only_server_exception' => true]), ]; if ($options->isCompressionEnabled()) { diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 1b018e884..c3ca0d07e 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -58,6 +58,11 @@ final class HttpTransport implements TransportInterface */ private $logger; + /** + * @var RateLimiter The rate limiter + */ + private $rateLimiter; + /** * Constructor. * @@ -82,6 +87,7 @@ public function __construct( $this->requestFactory = $requestFactory; $this->payloadSerializer = $payloadSerializer; $this->logger = $logger ?? new NullLogger(); + $this->rateLimiter = new RateLimiter($this->logger); } /** @@ -95,7 +101,18 @@ public function send(Event $event): PromiseInterface throw new \RuntimeException(sprintf('The DSN option must be set to use the "%s" transport.', self::class)); } - if (EventType::transaction() === $event->getType()) { + $eventType = $event->getType(); + + if ($this->rateLimiter->isRateLimited($eventType)) { + $this->logger->warning( + sprintf('Rate limit exceeded for sending requests of type "%s".', (string) $eventType), + ['event' => $event] + ); + + return new RejectedPromise(new Response(ResponseStatus::rateLimit(), $event)); + } + + if (EventType::transaction() === $eventType) { $request = $this->requestFactory->createRequest('POST', $dsn->getEnvelopeApiEndpointUrl()) ->withHeader('Content-Type', 'application/x-sentry-envelope') ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); @@ -117,7 +134,7 @@ public function send(Event $event): PromiseInterface return new RejectedPromise(new Response(ResponseStatus::failed(), $event)); } - $sendResponse = new Response(ResponseStatus::createFromHttpStatusCode($response->getStatusCode()), $event); + $sendResponse = $this->rateLimiter->handleResponse($event, $response); if (ResponseStatus::success() === $sendResponse->getStatus()) { return new FulfilledPromise($sendResponse); diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php new file mode 100644 index 000000000..4f75f234c --- /dev/null +++ b/src/Transport/RateLimiter.php @@ -0,0 +1,128 @@ + The map of time instants for each event category after + * which an HTTP request can be retried + */ + private $rateLimits = []; + + public function __construct(?LoggerInterface $logger = null) + { + $this->logger = $logger ?? new NullLogger(); + } + + public function handleResponse(Event $event, ResponseInterface $response): Response + { + $sendResponse = new Response(ResponseStatus::createFromHttpStatusCode($response->getStatusCode()), $event); + + if ($this->handleRateLimit($response)) { + $eventType = $event->getType(); + $disabledUntil = $this->getDisabledUntil($eventType); + + $this->logger->warning( + sprintf('Rate limited exceeded for requests of type "%s", backing off until "%s".', (string) $eventType, gmdate(\DATE_ATOM, $disabledUntil)), + ['event' => $event] + ); + } + + return $sendResponse; + } + + public function isRateLimited(EventType $eventType): bool + { + $disabledUntil = $this->getDisabledUntil($eventType); + + return $disabledUntil > time(); + } + + private function getDisabledUntil(EventType $eventType): int + { + $category = (string) $eventType; + + if ($eventType === EventType::event()) { + $category = 'error'; + } + + return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); + } + + private function handleRateLimit(ResponseInterface $response): bool + { + $now = time(); + + if ($response->hasHeader(self::RATE_LIMITS_HEADER)) { + foreach (explode(',', $response->getHeaderLine(self::RATE_LIMITS_HEADER)) as $limit) { + $parameters = explode(':', $limit, 3); + $parameters = array_splice($parameters, 0, 2); + $delay = ctype_digit($parameters[0]) ? (int) $parameters[0] : self::DEFAULT_RETRY_AFTER_DELAY_SECONDS; + + foreach (explode(';', $parameters[1]) as $category) { + $this->rateLimits[$category ?: 'all'] = $now + $delay; + } + } + + return true; + } + + if ($response->hasHeader(self::RETRY_AFTER_HEADER)) { + $delay = $this->parseRetryAfterHeader($now, $response->getHeaderLine(self::RETRY_AFTER_HEADER)); + + $this->rateLimits['all'] = $now + $delay; + + return true; + } + + return false; + } + + private function parseRetryAfterHeader(int $currentTime, string $header): int + { + if (1 === preg_match('/^\d+$/', $header)) { + return (int) $header; + } + + $headerDate = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC1123, $header); + + if (false !== $headerDate && $headerDate->getTimestamp() >= $currentTime) { + return $headerDate->getTimestamp() - $currentTime; + } + + return self::DEFAULT_RETRY_AFTER_DELAY_SECONDS; + } +} diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 8e9796fc5..aad9a44b6 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -24,6 +24,7 @@ use Sentry\ResponseStatus; use Sentry\Serializer\PayloadSerializerInterface; use Sentry\Transport\HttpTransport; +use Symfony\Bridge\PhpUnit\ClockMock; final class HttpTransportTest extends TestCase { @@ -250,6 +251,80 @@ public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientExce $this->assertSame($event, $promiseResult->getEvent()); } + /** + * @group time-sensitive + */ + public function testSendReturnsRejectedPromiseIfExceedingRateLimits(): void + { + ClockMock::withClockMock(1644105600); + + $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); + $event = Event::createEvent(); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->exactly(2)) + ->method('warning') + ->withConsecutive( + ['Rate limited exceeded for requests of type "event", backing off until "2022-02-06T00:01:00+00:00".', ['event' => $event]], + ['Rate limit exceeded for sending requests of type "event".', ['event' => $event]] + ); + + $this->payloadSerializer->expects($this->once()) + ->method('serialize') + ->with($event) + ->willReturn('{"foo":"bar"}'); + + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with('POST', $dsn->getStoreApiEndpointUrl()) + ->willReturn(new Request('POST', 'http://www.example.com')); + + $this->streamFactory->expects($this->once()) + ->method('createStream') + ->with('{"foo":"bar"}') + ->willReturnCallback([Utils::class, 'streamFor']); + + $this->httpClient->expects($this->once()) + ->method('sendAsyncRequest') + ->willReturn(new HttpFullfilledPromise(new Response(429, ['Retry-After' => '60']))); + + $transport = new HttpTransport( + new Options(['dsn' => $dsn]), + $this->httpClient, + $this->streamFactory, + $this->requestFactory, + $this->payloadSerializer, + $logger + ); + + // Event should be sent, but the server should reply with a HTTP 429 + $promise = $transport->send($event); + + try { + $promiseResult = $promise->wait(); + } catch (RejectionException $exception) { + $promiseResult = $exception->getReason(); + } + + $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); + $this->assertSame(ResponseStatus::rateLimit(), $promiseResult->getStatus()); + $this->assertSame($event, $promiseResult->getEvent()); + + // Event should not be sent at all because rate-limit is in effect + $promise = $transport->send($event); + + try { + $promiseResult = $promise->wait(); + } catch (RejectionException $exception) { + $promiseResult = $exception->getReason(); + } + + $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); + $this->assertSame(ResponseStatus::rateLimit(), $promiseResult->getStatus()); + $this->assertSame($event, $promiseResult->getEvent()); + } + public function testClose(): void { $transport = new HttpTransport( diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php new file mode 100644 index 000000000..2fcac521d --- /dev/null +++ b/tests/Transport/RateLimiterTest.php @@ -0,0 +1,130 @@ +logger = $this->createMock(LoggerInterface::class); + $this->rateLimiter = new RateLimiter($this->logger); + } + + /** + * @dataProvider handleResponseDataProvider + */ + public function testHandleResponse(Event $event, ResponseInterface $response, ResponseStatus $responseStatus): void + { + ClockMock::withClockMock(1644105600); + + $this->logger->expects($responseStatus === ResponseStatus::success() ? $this->never() : $this->once()) + ->method('warning') + ->with('Rate limited exceeded for requests of type "event", backing off until "2022-02-06T00:01:00+00:00".', ['event' => $event]); + + $transportResponse = $this->rateLimiter->handleResponse($event, $response); + + $this->assertSame($responseStatus, $transportResponse->getStatus()); + $this->assertSame($event, $transportResponse->getEvent()); + } + + public function handleResponseDataProvider(): \Generator + { + yield 'Rate limits headers missing' => [ + Event::createEvent(), + new Response(), + ResponseStatus::success(), + ]; + + yield 'Back-off using X-Sentry-Rate-Limits header with single category' => [ + Event::createEvent(), + new Response(429, ['X-Sentry-Rate-Limits' => '60:error:org']), + ResponseStatus::rateLimit(), + ]; + + yield 'Back-off using X-Sentry-Rate-Limits header with multiple categories' => [ + Event::createEvent(), + new Response(429, ['X-Sentry-Rate-Limits' => '60:error;transaction:org']), + ResponseStatus::rateLimit(), + ]; + + yield 'Back-off using X-Sentry-Rate-Limits header with missing categories should lock them all' => [ + Event::createEvent(), + new Response(429, ['X-Sentry-Rate-Limits' => '60::org']), + ResponseStatus::rateLimit(), + ]; + + yield 'Back-off using Retry-After header with number-based value' => [ + Event::createEvent(), + new Response(429, ['Retry-After' => '60']), + ResponseStatus::rateLimit(), + ]; + + yield 'Back-off using Retry-After header with date-based value' => [ + Event::createEvent(), + new Response(429, ['Retry-After' => 'Sun, 02 February 2022 00:01:00 GMT']), + ResponseStatus::rateLimit(), + ]; + } + + public function testIsRateLimited(): void + { + // Events should not be rate-limited at all + ClockMock::withClockMock(1644105600); + + $this->assertFalse($this->rateLimiter->isRateLimited(EventType::event())); + $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); + + // Events should be rate-limited for 60 seconds, but transactions should + // still be allowed to be sent + $this->rateLimiter->handleResponse(Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => '60:error:org'])); + + $this->assertTrue($this->rateLimiter->isRateLimited(EventType::event())); + $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); + + // Events should not be rate-limited anymore once the deadline expired + ClockMock::withClockMock(1644105660); + + $this->assertFalse($this->rateLimiter->isRateLimited(EventType::event())); + $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); + + // Both events and transactions should be rate-limited if all categories + // are + $this->rateLimiter->handleResponse(Event::createTransaction(), new Response(429, ['X-Sentry-Rate-Limits' => '60:all:org'])); + + $this->assertTrue($this->rateLimiter->isRateLimited(EventType::event())); + $this->assertTrue($this->rateLimiter->isRateLimited(EventType::transaction())); + + // Both events and transactions should not be rate-limited anymore once + // the deadline expired + ClockMock::withClockMock(1644105720); + + $this->assertFalse($this->rateLimiter->isRateLimited(EventType::event())); + $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 98f7fd129..0632c8aa0 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -7,6 +7,7 @@ use Sentry\Breadcrumb; use Sentry\Event; use Sentry\Tracing\Span; +use Sentry\Transport\RateLimiter; use Symfony\Bridge\PhpUnit\ClockMock; require_once __DIR__ . '/../vendor/autoload.php'; @@ -27,3 +28,4 @@ ClockMock::register(Event::class); ClockMock::register(Breadcrumb::class); ClockMock::register(Span::class); +ClockMock::register(RateLimiter::class); From 602ffc2fc783620d3f3ec68c8723096639655eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Thu, 10 Mar 2022 16:53:04 +0100 Subject: [PATCH 0715/1161] ci: Change stale GitHub workflow to run once a day (#1296) --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 5054c94db..bc092820a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,7 +1,7 @@ name: 'close stale issues/PRs' on: schedule: - - cron: '* */3 * * *' + - cron: '0 0 * * *' workflow_dispatch: jobs: stale: From e9553a112cdf1674d8ece5396dea6b16247861d8 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 13 Mar 2022 12:33:41 +0100 Subject: [PATCH 0716/1161] Treat the project ID component of the DSN as a `string` rather than an `integer` (#1294) --- CHANGELOG.md | 1 + src/Dsn.php | 25 ++++++++++++++----------- tests/DsnTest.php | 45 +++++++++++++++++++++++++-------------------- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c774f113..d6871c0b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Set the event extras by taking the data from the Monolog record's context (#1244) - Make the `StacktraceBuilder` class part of the public API and add the `Client::getStacktraceBuilder()` method to build custom stacktraces (#1124) - Support handling the server rate-limits when sending events to Sentry (#1291) +- Treat the project ID component of the DSN as a `string` rather than an `integer` (#1293) ## 3.3.7 (2022-01-19) diff --git a/src/Dsn.php b/src/Dsn.php index e96d4484a..bf54ea407 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -38,7 +38,7 @@ final class Dsn implements \Stringable private $secretKey; /** - * @var int The ID of the resource to access + * @var string The ID of the resource to access */ private $projectId; @@ -53,12 +53,12 @@ final class Dsn implements \Stringable * @param string $scheme The protocol to be used to access the resource * @param string $host The host that holds the resource * @param int $port The port on which the resource is exposed - * @param int $projectId The ID of the resource to access + * @param string $projectId The ID of the resource to access * @param string $path The specific resource that the web client wants to access * @param string $publicKey The public key to authenticate the SDK * @param string|null $secretKey The secret key to authenticate the SDK */ - private function __construct(string $scheme, string $host, int $port, int $projectId, string $path, string $publicKey, ?string $secretKey) + private function __construct(string $scheme, string $host, int $port, string $projectId, string $path, string $publicKey, ?string $secretKey) { $this->scheme = $scheme; $this->host = $host; @@ -98,11 +98,6 @@ public static function createFromString(string $value): self $segmentPaths = explode('/', $parsedDsn['path']); $projectId = array_pop($segmentPaths); - - if ((int) $projectId <= 0) { - throw new \InvalidArgumentException('"%s" DSN must contain a valid project ID.'); - } - $lastSlashPosition = strrpos($parsedDsn['path'], '/'); $path = $parsedDsn['path']; @@ -114,7 +109,7 @@ public static function createFromString(string $value): self $parsedDsn['scheme'], $parsedDsn['host'], $parsedDsn['port'] ?? ('http' === $parsedDsn['scheme'] ? 80 : 443), - (int) $projectId, + $projectId, $path, $parsedDsn['user'], $parsedDsn['pass'] ?? null @@ -155,10 +150,18 @@ public function getPath(): string /** * Gets the ID of the resource to access. + * + * @return int|string */ - public function getProjectId(): int + public function getProjectId(bool $returnAsString = false) { - return $this->projectId; + if ($returnAsString) { + return $this->projectId; + } + + @trigger_error(sprintf('Calling the method %s() and expecting it to return an integer is deprecated since version 3.4 and will stop working in 4.0.', __METHOD__), \E_USER_DEPRECATED); + + return (int) $this->projectId; } /** diff --git a/tests/DsnTest.php b/tests/DsnTest.php index 1681367ce..6c31e994e 100644 --- a/tests/DsnTest.php +++ b/tests/DsnTest.php @@ -6,9 +6,12 @@ use PHPUnit\Framework\TestCase; use Sentry\Dsn; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; final class DsnTest extends TestCase { + use ExpectDeprecationTrait; + /** * @dataProvider createFromStringDataProvider */ @@ -19,7 +22,7 @@ public function testCreateFromString( int $expectedPort, string $expectedPublicKey, ?string $expectedSecretKey, - int $expectedProjectId, + string $expectedProjectId, string $expectedPath ): void { $dsn = Dsn::createFromString($value); @@ -29,7 +32,7 @@ public function testCreateFromString( $this->assertSame($expectedPort, $dsn->getPort()); $this->assertSame($expectedPublicKey, $dsn->getPublicKey()); $this->assertSame($expectedSecretKey, $dsn->getSecretKey()); - $this->assertSame($expectedProjectId, $dsn->getProjectId()); + $this->assertSame($expectedProjectId, $dsn->getProjectId(true)); $this->assertSame($expectedPath, $dsn->getPath()); } @@ -42,7 +45,7 @@ public function createFromStringDataProvider(): \Generator 80, 'public', null, - 1, + '1', '/sentry', ]; @@ -53,7 +56,7 @@ public function createFromStringDataProvider(): \Generator 80, 'public', null, - 1, + '1', '', ]; @@ -64,7 +67,7 @@ public function createFromStringDataProvider(): \Generator 80, 'public', 'secret', - 1, + '1', '', ]; @@ -75,7 +78,7 @@ public function createFromStringDataProvider(): \Generator 80, 'public', null, - 1, + '1', '', ]; @@ -86,7 +89,7 @@ public function createFromStringDataProvider(): \Generator 8080, 'public', null, - 1, + '1', '', ]; @@ -97,7 +100,7 @@ public function createFromStringDataProvider(): \Generator 443, 'public', null, - 1, + '1', '', ]; @@ -108,7 +111,7 @@ public function createFromStringDataProvider(): \Generator 443, 'public', null, - 1, + '1', '', ]; @@ -119,7 +122,7 @@ public function createFromStringDataProvider(): \Generator 4343, 'public', null, - 1, + '1', '', ]; } @@ -171,16 +174,6 @@ public function createFromStringThrowsExceptionIfValueIsInvalidDataProvider(): \ 'tcp://public:secret@example.com/1', 'The scheme of the "tcp://public:secret@example.com/1" DSN must be either "http" or "https".', ]; - - yield 'invalid project ID (char instead of number)' => [ - 'http://public:secret@example.com/j', - 'DSN must contain a valid project ID.', - ]; - - yield 'invalid project ID (negative number)' => [ - 'http://public:secret@example.com/-2', - 'DSN must contain a valid project ID.', - ]; } /** @@ -240,4 +233,16 @@ public function toStringDataProvider(): array ['https://public@example.com:4343/sentry/1'], ]; } + + /** + * @group legacy + */ + public function testGetProjectIdTriggersDeprecationErrorIfReturningInteger(): void + { + $dsn = Dsn::createFromString('https://public@example.com/sentry/1'); + + $this->expectDeprecation('Calling the method Sentry\\Dsn::getProjectId() and expecting it to return an integer is deprecated since version 3.4 and will stop working in 4.0.'); + + $this->assertSame(1, $dsn->getProjectId()); + } } From a92443883df6a55cbe7a062f76949f23dda66772 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 13 Mar 2022 13:38:01 +0100 Subject: [PATCH 0717/1161] Prepare release `3.4.0` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6871c0b9..a12c90eb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.4.0 (2022-03-14) + - Update Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) - Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization - The `withScope` methods now return the callback's return value (#1263) From 71ebad6d2676c1ddffb513b3b9860e90ea4e9092 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 14 Mar 2022 10:09:12 +0100 Subject: [PATCH 0718/1161] Start development of version `3.5` --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index afdf68387..f9efa61a7 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.4.x-dev" + "dev-master": "3.5.x-dev" } } } From 94bc39351c983be496b9d69fb19808f94971e7ab Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 3 May 2022 11:25:52 +0200 Subject: [PATCH 0719/1161] Fix PHPStan baseline (#1306) --- phpstan-baseline.neon | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6f4d4ad1d..6361feee5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -16,12 +16,12 @@ parameters: path: src/ClientInterface.php - - message: "#^Offset 'host' does not exist on array\\{host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string, scheme\\: 'http'\\|'https'\\}\\.$#" + message: "#^Offset 'host' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'path' does not exist on array\\{host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string, scheme\\: 'http'\\|'https'\\}\\.$#" + message: "#^Offset 'path' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 4 path: src/Dsn.php @@ -61,22 +61,22 @@ parameters: path: src/HttpClient/HttpClientFactory.php - - message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Http\\\\Client\\\\Curl\\\\Client\\.$#" + message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$responseFactory\\.$#" count: 1 path: src/HttpClient/HttpClientFactory.php - - message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Symfony\\\\Component\\\\HttpClient\\\\HttplugClient\\.$#" + message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$uriFactory\\.$#" count: 1 path: src/HttpClient/HttpClientFactory.php - - message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$responseFactory\\.$#" + message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Http\\\\Client\\\\Curl\\\\Client\\.$#" count: 1 path: src/HttpClient/HttpClientFactory.php - - message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$uriFactory\\.$#" + message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Symfony\\\\Component\\\\HttpClient\\\\HttplugClient\\.$#" count: 1 path: src/HttpClient/HttpClientFactory.php From 2c9efaa4ca2857596bc765c38880d8a07c48a577 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 3 May 2022 11:59:09 +0200 Subject: [PATCH 0720/1161] Bump the minimum version of `guzzlehttp/psr7` to avoid `CVE-2022-24775` (#1305) --- CHANGELOG.md | 2 ++ composer.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a12c90eb1..3ad67f984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) + ## 3.4.0 (2022-03-14) - Update Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) diff --git a/composer.json b/composer.json index afdf68387..9d137f1b9 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "ext-json": "*", "ext-mbstring": "*", "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.7|^2.0", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", "jean85/pretty-package-versions": "^1.5|^2.0.4", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", From 722f6b1a2c98c744e974ff39793291ab5ba86516 Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Wed, 4 May 2022 10:41:04 -0700 Subject: [PATCH 0721/1161] Display a different version of the Sentry logo in the README based to the OS theme (#1308) --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8b36bc4a7..860f06ce6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@

- - + + + + + Sentry + -

_Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_ From 64b9696d965994e0bdbd35da8e6de90bc39a269f Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Thu, 5 May 2022 14:53:29 -0700 Subject: [PATCH 0722/1161] chore: Update logo in readme (again) (#1310) --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 860f06ce6..874a0997b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@

- - - - Sentry - + Sentry

From d3fd98e2b6fb5fb5d8fc76914bdcfc02e0c15622 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 12 May 2022 19:28:06 +0200 Subject: [PATCH 0723/1161] Lower default `send_attempts` option to `0` and deprecate it (#1312) --- CHANGELOG.md | 1 + src/HttpClient/HttpClientFactory.php | 2 +- src/Options.php | 14 ++++++++++++-- tests/OptionsTest.php | 4 ++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad67f984..ac3ea3a23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Set the default `send_attempts` to `0` (this disables retries) and deprecate the option. If you require retries you can increase the `send_attempts` option to the desired value. (#1312) - Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) ## 3.4.0 (2022-03-14) diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index ce6a88c75..4fdb413fc 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -104,7 +104,7 @@ public function create(Options $options): HttpAsyncClientInterface $httpClientPlugins = [ new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion]), new AuthenticationPlugin(new SentryAuthentication($options, $this->sdkIdentifier, $this->sdkVersion)), - new RetryPlugin(['retries' => $options->getSendAttempts()]), + new RetryPlugin(['retries' => $options->getSendAttempts(false)]), new ErrorPlugin(['only_server_exception' => true]), ]; diff --git a/src/Options.php b/src/Options.php index 486b4fcaf..9a759181c 100644 --- a/src/Options.php +++ b/src/Options.php @@ -48,9 +48,15 @@ public function __construct(array $options = []) /** * Gets the number of attempts to resend an event that failed to be sent. + * + * @deprecated since version 3.5, to be removed in 4.0 */ - public function getSendAttempts(): int + public function getSendAttempts(/*bool $triggerDeprecation = true*/): int { + if (0 === \func_num_args() || false !== func_get_arg(0)) { + @trigger_error(sprintf('Method %s() is deprecated since version 3.5 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); + } + return $this->options['send_attempts']; } @@ -58,9 +64,13 @@ public function getSendAttempts(): int * Sets the number of attempts to resend an event that failed to be sent. * * @param int $attemptsCount The number of attempts + * + * @deprecated since version 3.5, to be removed in 4.0 */ public function setSendAttempts(int $attemptsCount): void { + @trigger_error(sprintf('Method %s() is deprecated since version 3.5 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); + $options = array_merge($this->options, ['send_attempts' => $attemptsCount]); $this->options = $this->resolver->resolve($options); @@ -699,7 +709,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'integrations' => [], 'default_integrations' => true, - 'send_attempts' => 3, + 'send_attempts' => 0, 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, 'traces_sample_rate' => 0, diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 7adf45485..9740fcdc1 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -91,8 +91,8 @@ public function optionsDataProvider(): \Generator 1, 'getSendAttempts', 'setSendAttempts', - null, - null, + 'Method Sentry\\Options::getSendAttempts() is deprecated since version 3.5 and will be removed in 4.0.', + 'Method Sentry\\Options::setSendAttempts() is deprecated since version 3.5 and will be removed in 4.0.', ]; yield [ From 627c83ca24aeeb2368cebc56023a4ba59dceefab Mon Sep 17 00:00:00 2001 From: matthew-gill <31350541+matthew-gill@users.noreply.github.com> Date: Mon, 16 May 2022 08:04:40 +0100 Subject: [PATCH 0724/1161] Support http_connect_timeout and http_timeout options (#1282) Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 1 + phpstan-baseline.neon | 10 +++++ src/HttpClient/HttpClientFactory.php | 22 +++------- src/Options.php | 60 +++++++++++++++++++++++++++- tests/OptionsTest.php | 40 ++++++++++++++++++- 5 files changed, 114 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac3ea3a23..c7f51901c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Set the default `send_attempts` to `0` (this disables retries) and deprecate the option. If you require retries you can increase the `send_attempts` option to the desired value. (#1312) - Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) +- Add `http_connect_timeout` and `http_timeout` client options (#1282) ## 3.4.0 (2022-03-14) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6361feee5..947091ce3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -125,11 +125,21 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpConnectTimeout\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getHttpProxy\\(\\) should return string\\|null but returns mixed\\.$#" count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpTimeout\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getInAppExcludedPaths\\(\\) should return array\\ but returns mixed\\.$#" count: 1 diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 4fdb413fc..d18f72d3e 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -31,17 +31,6 @@ */ final class HttpClientFactory implements HttpClientFactoryInterface { - /** - * @var int The timeout of the request in seconds - */ - private const DEFAULT_HTTP_TIMEOUT = 5; - - /** - * @var int The default number of seconds to wait while trying to connect - * to a server - */ - private const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; - /** * @var StreamFactoryInterface The PSR-17 stream factory */ @@ -123,7 +112,8 @@ private function resolveClient(Options $options) { if (class_exists(SymfonyHttplugClient::class)) { $symfonyConfig = [ - 'max_duration' => self::DEFAULT_HTTP_TIMEOUT, + 'timeout' => $options->getHttpConnectTimeout(), + 'max_duration' => $options->getHttpTimeout(), ]; if (null !== $options->getHttpProxy()) { @@ -135,8 +125,8 @@ private function resolveClient(Options $options) if (class_exists(GuzzleHttpClient::class)) { $guzzleConfig = [ - GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, - GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + GuzzleHttpClientOptions::TIMEOUT => $options->getHttpTimeout(), + GuzzleHttpClientOptions::CONNECT_TIMEOUT => $options->getHttpConnectTimeout(), ]; if (null !== $options->getHttpProxy()) { @@ -148,8 +138,8 @@ private function resolveClient(Options $options) if (class_exists(CurlHttpClient::class)) { $curlConfig = [ - \CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, - \CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + \CURLOPT_TIMEOUT => $options->getHttpTimeout(), + \CURLOPT_CONNECTTIMEOUT => $options->getHttpConnectTimeout(), ]; if (null !== $options->getHttpProxy()) { diff --git a/src/Options.php b/src/Options.php index 9a759181c..9795b3ca7 100644 --- a/src/Options.php +++ b/src/Options.php @@ -22,10 +22,22 @@ final class Options */ public const DEFAULT_MAX_BREADCRUMBS = 100; + /** + * The default maximum execution time in seconds for the request+response + * as a whole. + */ + public const DEFAULT_HTTP_TIMEOUT = 5; + + /** + * The default maximum number of seconds to wait while trying to connect to a + * server. + */ + public const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; + /** * @var array The configuration options */ - private $options = []; + private $options; /** * @var OptionsResolver The options resolver @@ -588,6 +600,48 @@ public function setHttpProxy(?string $httpProxy): void $this->options = $this->resolver->resolve($options); } + /** + * Gets the maximum number of seconds to wait while trying to connect to a server. + */ + public function getHttpConnectTimeout(): float + { + return $this->options['http_connect_timeout']; + } + + /** + * Sets the maximum number of seconds to wait while trying to connect to a server. + * + * @param float $httpConnectTimeout The amount of time in seconds + */ + public function setHttpConnectTimeout(float $httpConnectTimeout): void + { + $options = array_merge($this->options, ['http_connect_timeout' => $httpConnectTimeout]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the maximum execution time for the request+response as a whole. + */ + public function getHttpTimeout(): float + { + return $this->options['http_timeout']; + } + + /** + * Sets the maximum execution time for the request+response as a whole. The + * value should also include the time for the connect phase, so it should be + * greater than the value set for the `http_connect_timeout` option. + * + * @param float $httpTimeout The amount of time in seconds + */ + public function setHttpTimeout(float $httpTimeout): void + { + $options = array_merge($this->options, ['http_timeout' => $httpTimeout]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets whether the silenced errors should be captured or not. * @@ -736,6 +790,8 @@ private function configureOptions(OptionsResolver $resolver): void 'send_default_pii' => false, 'max_value_length' => 1024, 'http_proxy' => null, + 'http_connect_timeout' => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + 'http_timeout' => self::DEFAULT_HTTP_TIMEOUT, 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', 'class_serializers' => [], @@ -766,6 +822,8 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); $resolver->setAllowedTypes('http_proxy', ['null', 'string']); + $resolver->setAllowedTypes('http_connect_timeout', ['int', 'float']); + $resolver->setAllowedTypes('http_timeout', ['int', 'float']); $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); $resolver->setAllowedTypes('class_serializers', 'array'); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 9740fcdc1..f918228ce 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -51,7 +51,7 @@ public function testConstructor( $options = new Options([$option => $value]); - $this->assertSame($value, $options->$getterMethod()); + $this->assertEquals($value, $options->$getterMethod()); } /** @@ -81,7 +81,7 @@ public function testGettersAndSetters( $options->$setterMethod($value); } - $this->assertSame($value, $options->$getterMethod()); + $this->assertEquals($value, $options->$getterMethod()); } public function optionsDataProvider(): \Generator @@ -293,6 +293,42 @@ static function (): void {}, null, ]; + yield [ + 'http_timeout', + 1, + 'getHttpTimeout', + 'setHttpTimeout', + null, + null, + ]; + + yield [ + 'http_timeout', + 1.2, + 'getHttpTimeout', + 'setHttpTimeout', + null, + null, + ]; + + yield [ + 'http_connect_timeout', + 1, + 'getHttpConnectTimeout', + 'setHttpConnectTimeout', + null, + null, + ]; + + yield [ + 'http_connect_timeout', + 1.2, + 'getHttpConnectTimeout', + 'setHttpConnectTimeout', + null, + null, + ]; + yield [ 'capture_silenced_errors', true, From a9283821d78f9b9aa100d8ca537803d6aa65d7cc Mon Sep 17 00:00:00 2001 From: Thomas Renck Date: Mon, 16 May 2022 10:00:35 -0700 Subject: [PATCH 0725/1161] Fix stripping of memory addresses from stacktrace frames of anonymous classes in PHP `>=7.4.2` (#1315) --- CHANGELOG.md | 1 + src/FrameBuilder.php | 2 +- tests/StacktraceTest.php | 41 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad67f984..2fbcc5ea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) +- Fix stripping of memory addresses from stacktrace frames of anonymous classes in PHP `>=7.4.2` (#1314) ## 3.4.0 (2022-03-14) diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 393c1b946..4bdc50dff 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -76,7 +76,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac } $rawFunctionName = sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); - $functionName = sprintf('%s::%s', preg_replace('/0x[a-fA-F0-9]+$/', '', $functionName), $backtraceFrame['function']); + $functionName = sprintf('%s::%s', preg_replace('/(?::\d+\$|0x)[a-fA-F0-9]+$/', '', $functionName), $backtraceFrame['function']); } elseif (isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['function']; } diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index e3de8fa7e..5ab505955 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -203,7 +203,7 @@ public function buildFromBacktraceDataProvider(): \Generator ], ]; - yield 'Backtrace with frame containing memory address' => [ + yield 'Backtrace with frame containing memory address in PHP <7.4.2 format' => [ new Options([ 'prefixes' => ['/path-prefix'], ]), @@ -241,6 +241,45 @@ public function buildFromBacktraceDataProvider(): \Generator ], ], ]; + + yield 'Backtrace with frame containing memory address in PHP >=7.4.2 format' => [ + new Options([ + 'prefixes' => ['/path-prefix'], + ]), + [ + 'file' => 'path/to/file', + 'line' => 12, + 'backtrace' => [ + [ + 'class' => "class@anonymous\x00/path/to/app/consumer.php:12$3e0a7", + 'function' => 'messageCallback', + 'type' => '->', + ], + [ + 'class' => "class@anonymous\x00/path-prefix/path/to/app/consumer.php:12$3e0a7", + 'function' => 'messageCallback', + 'type' => '->', + ], + ], + ], + [ + [ + null, + Frame::INTERNAL_FRAME_FILENAME, + 0, + ], + [ + "class@anonymous\x00/path/to/app/consumer.php::messageCallback", + Frame::INTERNAL_FRAME_FILENAME, + 0, + ], + [ + "class@anonymous\x00/path/to/app/consumer.php::messageCallback", + 'path/to/file', + 12, + ], + ], + ]; } private function assertFrameEquals(Frame $frame, ?string $method, string $file, int $line): void From 5b611e3f09035f5ad5edf494443e3236bd5ea482 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 19 May 2022 09:14:12 +0200 Subject: [PATCH 0726/1161] Update release date for 3.5.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42cf149de..0664ae91a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 3.5.0 (2022-05-17) +## 3.5.0 (2022-05-19) - Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) - Fix stripping of memory addresses from stacktrace frames of anonymous classes in PHP `>=7.4.2` (#1314) From ae01aecc8dfda2cae0ba61ec9b9155354e5a11ec Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 20 May 2022 01:06:15 +0200 Subject: [PATCH 0727/1161] Start development of version `3.6` --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ba5894691..3f96b2740 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.5.x-dev" + "dev-master": "3.6.x-dev" } } } From 6bf81be28f8557ce22e16502256a21332478e949 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 24 May 2022 23:56:07 +0200 Subject: [PATCH 0728/1161] Fix PHPStan errors after update to latest version (#1323) --- phpstan-baseline.neon | 5 +++++ src/FrameBuilder.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 947091ce3..f6d81901a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -20,6 +20,11 @@ parameters: count: 1 path: src/Dsn.php + - + message: "#^Offset 'host'\\|'path'\\|'scheme'\\|'user' on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: src/Dsn.php + - message: "#^Offset 'path' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 4 diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 4bdc50dff..b2adba6de 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -166,7 +166,7 @@ private function getFunctionArguments(array $backtraceFrame): array $reflectionFunction = null; try { - if (isset($backtraceFrame['class'], $backtraceFrame['function'])) { + if (isset($backtraceFrame['class'])) { if (method_exists($backtraceFrame['class'], $backtraceFrame['function'])) { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], $backtraceFrame['function']); } elseif (isset($backtraceFrame['type']) && '::' === $backtraceFrame['type']) { From 5f9c0115ba60898fbd2519ed8ae00778a2def1d6 Mon Sep 17 00:00:00 2001 From: Dieter Beck Date: Fri, 27 May 2022 09:44:02 +0200 Subject: [PATCH 0729/1161] Add support for Monolog `3.x` (#1321) --- CHANGELOG.md | 2 + composer.json | 2 +- phpstan.neon | 2 + psalm-baseline.xml | 52 ++++- .../CompatibilityProcessingHandlerTrait.php | 104 ++++++++++ src/Monolog/Handler.php | 33 +--- tests/Monolog/HandlerTest.php | 182 ++++++++---------- tests/Monolog/RecordFactory.php | 45 +++++ 8 files changed, 295 insertions(+), 127 deletions(-) create mode 100644 src/Monolog/CompatibilityProcessingHandlerTrait.php create mode 100644 tests/Monolog/RecordFactory.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 0664ae91a..9f1a9ab83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add support for `monolog/monolog:^3.0` (#1321) + ## 3.5.0 (2022-05-19) - Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) diff --git a/composer.json b/composer.json index 3f96b2740..3bab45e52 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.19|3.4.*", "http-interop/http-factory-guzzle": "^1.0", - "monolog/monolog": "^1.3|^2.0", + "monolog/monolog": "^1.6|^2.0|^3.0", "nikic/php-parser": "^4.10.3", "php-http/mock-client": "^1.3", "phpbench/phpbench": "^1.0", diff --git a/phpstan.neon b/phpstan.neon index 43c4ca673..9a3b3577c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,3 +10,5 @@ parameters: excludePaths: - tests/resources - tests/Fixtures + dynamicConstantNames: + - Monolog\Logger::API diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 3df2be9a5..814952ffa 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $parsedDsn['host'] @@ -22,6 +22,28 @@ $userIntegrations + + + CompatibilityProcessingHandlerTrait + + + Level + + + + + $record['channel'] + $record['level'] + $record['message'] + + + $record['context']['exception'] + + + $record['context'] + $record['context'] + + $value @@ -58,4 +80,32 @@ startTransaction + + + $name + , + , + , + , + , + , + , + , + , + Level + case + + + + + \DateTimeImmutable + + + + + : + => + } + + diff --git a/src/Monolog/CompatibilityProcessingHandlerTrait.php b/src/Monolog/CompatibilityProcessingHandlerTrait.php new file mode 100644 index 000000000..586619c96 --- /dev/null +++ b/src/Monolog/CompatibilityProcessingHandlerTrait.php @@ -0,0 +1,104 @@ += 3) { + /** + * Logic which is used if monolog >= 3 is installed. + * + * @internal + */ + trait CompatibilityProcessingHandlerTrait + { + /** + * @param array|LogRecord $record + */ + abstract protected function doWrite($record): void; + + /** + * {@inheritdoc} + */ + protected function write(LogRecord $record): void + { + $this->doWrite($record); + } + + /** + * Translates the Monolog level into the Sentry severity. + */ + private static function getSeverityFromLevel(int $level): Severity + { + $level = Level::from($level); + + switch ($level) { + case Level::Debug: + return Severity::debug(); + case Level::Warning: + return Severity::warning(); + case Level::Error: + return Severity::error(); + case Level::Critical: + case Level::Alert: + case Level::Emergency: + return Severity::fatal(); + case Level::Info: + case Level::Notice: + default: + return Severity::info(); + } + } + } +} else { + /** + * Logic which is used if monolog < 3 is installed. + * + * @internal + */ + trait CompatibilityProcessingHandlerTrait + { + /** + * @param array|LogRecord $record + */ + abstract protected function doWrite($record): void; + + /** + * {@inheritdoc} + */ + protected function write(array $record): void + { + $this->doWrite($record); + } + + /** + * Translates the Monolog level into the Sentry severity. + * + * @param Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY $level The Monolog log level + */ + private static function getSeverityFromLevel(int $level): Severity + { + switch ($level) { + case Logger::DEBUG: + return Severity::debug(); + case Logger::WARNING: + return Severity::warning(); + case Logger::ERROR: + return Severity::error(); + case Logger::CRITICAL: + case Logger::ALERT: + case Logger::EMERGENCY: + return Severity::fatal(); + case Logger::INFO: + case Logger::NOTICE: + default: + return Severity::info(); + } + } + } +} diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index e2b97c098..e0e7bb6e7 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -6,9 +6,9 @@ use Monolog\Handler\AbstractProcessingHandler; use Monolog\Logger; +use Monolog\LogRecord; use Sentry\Event; use Sentry\EventHint; -use Sentry\Severity; use Sentry\State\HubInterface; use Sentry\State\Scope; @@ -20,6 +20,8 @@ */ final class Handler extends AbstractProcessingHandler { + use CompatibilityProcessingHandlerTrait; + private const CONTEXT_EXCEPTION_KEY = 'exception'; /** @@ -46,9 +48,9 @@ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bub } /** - * {@inheritdoc} + * @param array|LogRecord $record */ - protected function write(array $record): void + protected function doWrite($record): void { $event = Event::createEvent(); $event->setLevel(self::getSeverityFromLevel($record['level'])); @@ -75,31 +77,6 @@ protected function write(array $record): void }); } - /** - * Translates the Monolog level into the Sentry severity. - * - * @param int $level The Monolog log level - */ - private static function getSeverityFromLevel(int $level): Severity - { - switch ($level) { - case Logger::DEBUG: - return Severity::debug(); - case Logger::WARNING: - return Severity::warning(); - case Logger::ERROR: - return Severity::error(); - case Logger::CRITICAL: - case Logger::ALERT: - case Logger::EMERGENCY: - return Severity::fatal(); - case Logger::INFO: - case Logger::NOTICE: - default: - return Severity::info(); - } - } - /** * @param mixed[] $context * diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index 7be7a093c..d3c06df23 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -20,7 +20,7 @@ final class HandlerTest extends TestCase /** * @dataProvider handleDataProvider */ - public function testHandle(bool $fillExtraContext, array $record, Event $expectedEvent, EventHint $expectedHint, array $expectedExtra): void + public function testHandle(bool $fillExtraContext, $record, Event $expectedEvent, EventHint $expectedHint, array $expectedExtra): void { /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); @@ -58,14 +58,13 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::DEBUG, - 'level_name' => Logger::getLevelName(Logger::DEBUG), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - ], + RecordFactory::create( + 'foo bar', + Logger::DEBUG, + 'channel.foo', + [], + [] + ), $event, new EventHint(), [ @@ -81,14 +80,13 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::INFO, - 'level_name' => Logger::getLevelName(Logger::INFO), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - ], + RecordFactory::create( + 'foo bar', + Logger::INFO, + 'channel.foo', + [], + [] + ), $event, new EventHint(), [ @@ -104,14 +102,13 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::NOTICE, - 'level_name' => Logger::getLevelName(Logger::NOTICE), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - ], + RecordFactory::create( + 'foo bar', + Logger::NOTICE, + 'channel.foo', + [], + [] + ), $event, new EventHint(), [ @@ -127,14 +124,13 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::WARNING, - 'level_name' => Logger::getLevelName(Logger::WARNING), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - ], + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [], + [] + ), $event, new EventHint(), [ @@ -150,14 +146,13 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::ERROR, - 'level_name' => Logger::getLevelName(Logger::ERROR), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - ], + RecordFactory::create( + 'foo bar', + Logger::ERROR, + 'channel.foo', + [], + [] + ), $event, new EventHint(), [ @@ -173,14 +168,13 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::CRITICAL, - 'level_name' => Logger::getLevelName(Logger::CRITICAL), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - ], + RecordFactory::create( + 'foo bar', + Logger::CRITICAL, + 'channel.foo', + [], + [] + ), $event, new EventHint(), [ @@ -196,14 +190,13 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::ALERT, - 'level_name' => Logger::getLevelName(Logger::ALERT), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - ], + RecordFactory::create( + 'foo bar', + Logger::ALERT, + 'channel.foo', + [], + [] + ), $event, new EventHint(), [ @@ -219,14 +212,13 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::EMERGENCY, - 'level_name' => Logger::getLevelName(Logger::EMERGENCY), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - ], + RecordFactory::create( + 'foo bar', + Logger::EMERGENCY, + 'channel.foo', + [], + [] + ), $event, new EventHint(), [ @@ -244,16 +236,15 @@ public function handleDataProvider(): iterable yield [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::WARNING, - 'level_name' => Logger::getLevelName(Logger::WARNING), - 'context' => [ + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [ 'exception' => $exampleException, ], - 'channel' => 'channel.foo', - 'extra' => [], - ], + [] + ), $event, EventHint::fromArray([ 'exception' => $exampleException, @@ -271,17 +262,16 @@ public function handleDataProvider(): iterable yield 'Monolog\'s context is filled and the handler should fill the "extra" context' => [ true, - [ - 'message' => 'foo bar', - 'level' => Logger::WARNING, - 'level_name' => Logger::getLevelName(Logger::WARNING), - 'context' => [ + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [ 'foo' => 'bar', 'bar' => 'baz', ], - 'channel' => 'channel.foo', - 'extra' => [], - ], + [] + ), $event, new EventHint(), [ @@ -301,16 +291,15 @@ public function handleDataProvider(): iterable yield 'Monolog\'s context is filled with "exception" field and the handler should fill the "extra" context' => [ true, - [ - 'message' => 'foo bar', - 'level' => Logger::WARNING, - 'level_name' => Logger::getLevelName(Logger::WARNING), - 'context' => [ + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [ 'exception' => new \Exception('exception message'), ], - 'channel' => 'channel.foo', - 'extra' => [], - ], + [] + ), $event, EventHint::fromArray([ 'exception' => $exampleException, @@ -328,17 +317,16 @@ public function handleDataProvider(): iterable yield 'Monolog\'s context is filled but handler should not fill the "extra" context' => [ false, - [ - 'message' => 'foo bar', - 'level' => Logger::WARNING, - 'level_name' => Logger::getLevelName(Logger::WARNING), - 'context' => [ + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [ 'foo' => 'bar', 'bar' => 'baz', ], - 'channel' => 'channel.foo', - 'extra' => [], - ], + [] + ), $event, new EventHint(), [ diff --git a/tests/Monolog/RecordFactory.php b/tests/Monolog/RecordFactory.php new file mode 100644 index 000000000..9f65473db --- /dev/null +++ b/tests/Monolog/RecordFactory.php @@ -0,0 +1,45 @@ + $context + * @param array $extra + * + * @return array|LogRecord + */ + public static function create(string $message, int $level, string $channel, array $context, array $extra) + { + if (Logger::API >= 3) { + return new LogRecord( + new DateTimeImmutable(), + $channel, + Logger::toMonologLevel($level), + $message, + $context, + $extra + ); + } + + return [ + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => $channel, + 'extra' => $extra, + ]; + } +} From 1c6094c6baa2a30d1a7b105fc006187b24bea759 Mon Sep 17 00:00:00 2001 From: Tomasz Sawicki Date: Fri, 3 Jun 2022 17:30:55 +0200 Subject: [PATCH 0730/1161] Add the `setTag()` and `removeTag() methods to the `Event` class (#1324) --- CHANGELOG.md | 1 + src/Event.php | 21 +++++++++++++++++++++ tests/EventTest.php | 15 +++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1a9ab83..c67ff52a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Add support for `monolog/monolog:^3.0` (#1321) +- Add `setTag` and `removeTag` public methods to `Event` for easier manipulation of tags (#1324) ## 3.5.0 (2022-05-19) diff --git a/src/Event.php b/src/Event.php index 7fbe50deb..1d3d58840 100644 --- a/src/Event.php +++ b/src/Event.php @@ -489,6 +489,27 @@ public function setTags(array $tags): void $this->tags = $tags; } + /** + * Sets or updates a tag in this event. + * + * @param string $key The key that uniquely identifies the tag + * @param string $value The value + */ + public function setTag(string $key, string $value): void + { + $this->tags[$key] = $value; + } + + /** + * Removes a given tag from the event. + * + * @param string $key The key that uniquely identifies the tag + */ + public function removeTag(string $key): void + { + unset($this->tags[$key]); + } + /** * Gets the user context. */ diff --git a/tests/EventTest.php b/tests/EventTest.php index 26713e253..dcef7802c 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -101,4 +101,19 @@ public function gettersAndSettersDataProvider(): array ['environment', 'foo'], ]; } + + public function testSetAndRemoveTag(): void + { + $tagName = 'tag'; + $tagValue = 'value'; + + $event = Event::createEvent(); + $event->setTag($tagName, $tagValue); + + $this->assertSame([$tagName => $tagValue], $event->getTags()); + + $event->removeTag($tagName); + + $this->assertEmpty($event->getTags()); + } } From 17bb93d3c784388919e1e44e2161d85bd5701a93 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 9 Jun 2022 22:31:28 +0200 Subject: [PATCH 0731/1161] Fix PHPStan errors and update baseline --- phpstan-baseline.neon | 2 +- src/Options.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f6d81901a..b7e8af5cb 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -216,7 +216,7 @@ parameters: path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getTracesSampler\\(\\) should return \\(callable\\(\\)\\: mixed\\)\\|null but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getTracesSampler\\(\\) should return \\(callable\\(Sentry\\\\Tracing\\\\SamplingContext\\)\\: float\\)\\|null but returns mixed\\.$#" count: 1 path: src/Options.php diff --git a/src/Options.php b/src/Options.php index 9795b3ca7..8455e806c 100644 --- a/src/Options.php +++ b/src/Options.php @@ -728,7 +728,7 @@ public function setClassSerializers(array $serializers): void /** * Gets a callback that will be invoked when we sample a Transaction. * - * @psalm-return ?callable(\Sentry\Tracing\SamplingContext): float + * @psalm-return null|callable(\Sentry\Tracing\SamplingContext): float */ public function getTracesSampler(): ?callable { @@ -741,7 +741,7 @@ public function getTracesSampler(): ?callable * * @param ?callable $sampler The sampler * - * @psalm-param ?callable(\Sentry\Tracing\SamplingContext): float $sampler + * @psalm-param null|callable(\Sentry\Tracing\SamplingContext): float $sampler */ public function setTracesSampler(?callable $sampler): void { From 6d1a6ee29c558be373bfe08d454a3c116c02dd0d Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 9 Jun 2022 22:33:39 +0200 Subject: [PATCH 0732/1161] Prepare release `3.6.0` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c67ff52a8..290840d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.6.0 (2022-06-10) + - Add support for `monolog/monolog:^3.0` (#1321) - Add `setTag` and `removeTag` public methods to `Event` for easier manipulation of tags (#1324) From 5f28e73dffb9630e6f4f3a42d8f4ad41d0a66737 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sun, 12 Jun 2022 22:41:10 +0200 Subject: [PATCH 0733/1161] Start development of version `3.7` --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3bab45e52..f6ffb3e4c 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.6.x-dev" + "dev-master": "3.7.x-dev" } } } From 43896defcf224aeaaebaddb18f777e33f6b6ed5c Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 13 Jun 2022 12:04:58 +0300 Subject: [PATCH 0734/1161] Support logging the `extra` data when using Monolog handler (#1330) --- CHANGELOG.md | 2 ++ src/Monolog/Handler.php | 28 +++++++++++++++++++- tests/Monolog/HandlerTest.php | 48 +++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 290840d1e..8b67dc91f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Set the event extras by taking the data from the Monolog record's extra (#1330) + ## 3.6.0 (2022-06-10) - Add support for `monolog/monolog:^3.0` (#1321) diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index e0e7bb6e7..3cba4c341 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -69,10 +69,16 @@ protected function doWrite($record): void $monologContextData = $this->getMonologContextData($record['context']); - if (!empty($monologContextData)) { + if ([] !== $monologContextData) { $scope->setExtra('monolog.context', $monologContextData); } + $monologExtraData = $this->getMonologExtraData($record['extra']); + + if ([] !== $monologExtraData) { + $scope->setExtra('monolog.extra', $monologExtraData); + } + $this->hub->captureEvent($event, $hint); }); } @@ -101,4 +107,24 @@ private function getMonologContextData(array $context): array return $contextData; } + + /** + * @param mixed[] $context + * + * @return mixed[] + */ + private function getMonologExtraData(array $context): array + { + if (!$this->fillExtraContext) { + return []; + } + + $extraData = []; + + foreach ($context as $key => $value) { + $extraData[$key] = $value; + } + + return $extraData; + } } diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index d3c06df23..c838da0ee 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -334,5 +334,53 @@ public function handleDataProvider(): iterable 'monolog.level' => Logger::getLevelName(Logger::WARNING), ], ]; + yield 'Monolog\'s extra is filled and the handler should fill the "extra" context' => [ + true, + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [], + [ + 'foo' => 'bar', + 'bar' => 'baz', + ] + ), + $event, + new EventHint(), + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::WARNING), + 'monolog.extra' => [ + 'foo' => 'bar', + 'bar' => 'baz', + ], + ], + ]; + + $event = Event::createEvent(); + $event->setMessage('foo bar'); + $event->setLogger('monolog.channel.foo'); + $event->setLevel(Severity::warning()); + + yield 'Monolog\'s extra is filled but handler should not fill the "extra" context' => [ + false, + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [], + [ + 'foo' => 'bar', + 'bar' => 'baz', + ] + ), + $event, + new EventHint(), + [ + 'monolog.channel' => 'channel.foo', + 'monolog.level' => Logger::getLevelName(Logger::WARNING), + ], + ]; } } From c44590185b5acbeb288241f6610695f44e745ac7 Mon Sep 17 00:00:00 2001 From: Arun Philip Date: Fri, 17 Jun 2022 18:25:07 -0400 Subject: [PATCH 0735/1161] Fix setting the `sentry-trace` header when using the Guzzle tracing middleware (#1331) --- CHANGELOG.md | 2 ++ src/Tracing/GuzzleTracingMiddleware.php | 2 +- tests/Tracing/GuzzleTracingMiddlewareTest.php | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 290840d1e..b64fa6108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Set the `sentry-trace` header when using the tracing middleware (#1331) + ## 3.6.0 (2022-06-10) - Add support for `monolog/monolog:^3.0` (#1321) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index c68924fef..ddbc29195 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -34,7 +34,7 @@ public static function trace(?HubInterface $hub = null): Closure $childSpan = $span->startChild($spanContext); - $request->withHeader('sentry-trace', $childSpan->toTraceparent()); + $request = $request->withHeader('sentry-trace', $childSpan->toTraceparent()); $handlerPromiseCallback = static function ($responseOrException) use ($hub, $request, $childSpan) { // We finish the span (which means setting the span end timestamp) first to ensure the measured time diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index df22de49a..7dc3bed87 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -104,7 +104,8 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec $hub->setSpan($transaction); $middleware = GuzzleTracingMiddleware::trace($hub); - $function = $middleware(static function () use ($expectedPromiseResult): PromiseInterface { + $function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface { + $this->assertNotEmpty($request->getHeader('sentry-trace')); if ($expectedPromiseResult instanceof \Throwable) { return new RejectedPromise($expectedPromiseResult); } From a1cfe5a4853dd60864e89436d2c391b5a34e6812 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 27 Jun 2022 09:45:34 +0200 Subject: [PATCH 0736/1161] Prepare release `3.6.1` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b64fa6108..d6e57b1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.6.1 (2022-06-27) + - Set the `sentry-trace` header when using the tracing middleware (#1331) ## 3.6.0 (2022-06-10) From 5b8f2934b0b20bb01da11c76985ceb5bd6c6af91 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 27 Jun 2022 09:58:00 +0200 Subject: [PATCH 0737/1161] Update PHPStan baseline --- phpstan-baseline.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index b7e8af5cb..62ea23ef5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6,7 +6,7 @@ parameters: path: src/Client.php - - message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns T of Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" + message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return \\(T of Sentry\\\\Integration\\\\IntegrationInterface\\)\\|null but returns \\(T of Sentry\\\\Integration\\\\IntegrationInterface\\)\\|null\\.$#" count: 1 path: src/Client.php From b5da9f8695ca5401e5b9d05366e485cbec09b23e Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 15 Jul 2022 10:32:00 +0200 Subject: [PATCH 0738/1161] Fix `Scope::getTransaction()` so that it returns also unsampled transactions (#1334) --- CHANGELOG.md | 2 ++ src/State/Scope.php | 8 ++----- src/Tracing/Span.php | 28 ++++++++++++++++------ src/Tracing/Transaction.php | 1 + tests/State/HubTest.php | 47 +++++++++++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6e57b1f6..e065bc66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Fix `Scope::getTransaction()` so that it returns also unsampled transactions (#1334) + ## 3.6.1 (2022-06-27) - Set the `sentry-trace` header when using the tracing middleware (#1331) diff --git a/src/State/Scope.php b/src/State/Scope.php index 78cd452a4..a708f8fdc 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -408,12 +408,8 @@ public function setSpan(?Span $span): self */ public function getTransaction(): ?Transaction { - $span = $this->span; - - if (null !== $span && null !== $span->getSpanRecorder() && !empty($span->getSpanRecorder()->getSpans())) { - // The first span in the recorder is considered to be a Transaction - /** @var Transaction */ - return $span->getSpanRecorder()->getSpans()[0]; + if (null !== $this->span) { + return $this->span->getTransaction(); } return null; diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index a198361c2..2095596df 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -7,7 +7,7 @@ use Sentry\EventId; /** - * This class stores all the information about a Span. + * This class stores all the information about a span. */ class Span { @@ -22,22 +22,22 @@ class Span protected $traceId; /** - * @var string|null Description of the Span + * @var string|null Description of the span */ protected $description; /** - * @var string|null Operation of the Span + * @var string|null Operation of the span */ protected $op; /** - * @var SpanStatus|null Completion status of the Span + * @var SpanStatus|null Completion status of the span */ protected $status; /** - * @var SpanId|null ID of the parent Span + * @var SpanId|null ID of the parent span */ protected $parentSpanId; @@ -47,7 +47,7 @@ class Span protected $sampled; /** - * @var array A List of tags associated to this Span + * @var array A List of tags associated to this span */ protected $tags = []; @@ -67,10 +67,15 @@ class Span protected $endTimestamp; /** - * @var SpanRecorder|null Reference instance to the SpanRecorder + * @var SpanRecorder|null Reference instance to the {@see SpanRecorder} */ protected $spanRecorder; + /** + * @var Transaction|null The transaction containing this span + */ + protected $transaction; + /** * Constructor. * @@ -390,6 +395,7 @@ public function startChild(SpanContext $context): self $context->setTraceId($this->traceId); $span = new self($context); + $span->transaction = $this->transaction; $span->spanRecorder = $this->spanRecorder; if (null != $span->spanRecorder) { @@ -417,6 +423,14 @@ public function detachSpanRecorder(): void $this->spanRecorder = null; } + /** + * Returns the transaction containing this span. + */ + public function getTransaction(): ?Transaction + { + return $this->transaction; + } + /** * Returns a string that can be used for the `sentry-trace` header. */ diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index 222361f19..d3cfb0013 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -38,6 +38,7 @@ public function __construct(TransactionContext $context, ?HubInterface $hub = nu $this->hub = $hub ?? SentrySdk::getCurrentHub(); $this->name = $context->getName(); + $this->transaction = $this; } /** diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 85b4d58fc..5a47f5755 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -731,4 +731,51 @@ public function testStartTransactionWithCustomSamplingContext(): void $hub = new Hub($client); $hub->startTransaction(new TransactionContext(), $customSamplingContext); } + + public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsNotSampled(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 1])); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, false)); + + $hub->configureScope(static function (Scope $scope) use ($transaction): void { + $scope->setSpan($transaction); + }); + + $this->assertSame($transaction, $hub->getTransaction()); + } + + public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsSampled(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 1])); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, true)); + + $hub->configureScope(static function (Scope $scope) use ($transaction): void { + $scope->setSpan($transaction); + }); + + $this->assertSame($transaction, $hub->getTransaction()); + } + + public function testGetTransactionReturnsNullIfNoTransactionIsSetOnTheScope(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 1])); + + $hub = new Hub($client); + $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, true)); + + $this->assertNull($hub->getTransaction()); + } } From 877bca3f0f0ac0fc8ec0a218c6070cccea266795 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 18 Jul 2022 09:55:15 +0200 Subject: [PATCH 0739/1161] Prepare release `3.7.0` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f13e845e..3e0161a60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.7.0 (2022-07-18) + - Fix `Scope::getTransaction()` so that it returns also unsampled transactions (#1334) - Set the event extras by taking the data from the Monolog record's extra (#1330) From 4f91a2426a0ef68e06d3e0e5063f80b1df351c63 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 18 Jul 2022 10:02:55 +0200 Subject: [PATCH 0740/1161] Start development of version `3.8` --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f6ffb3e4c..63f090499 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.7.x-dev" + "dev-master": "3.8.x-dev" } } } From 9e9f245b630e57330acdb3b756bd8709acad14b6 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 11 Aug 2022 15:08:14 +0200 Subject: [PATCH 0741/1161] ci: Fix phpunit deprecations (#1340) --- phpunit.xml.dist | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0f5d14742..0310647ae 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,13 @@ - - + @@ -18,23 +17,11 @@ - - - src - - - - - - src - - - - + - + From 376d1f360a6b586b7fc53adbc75ac6d6cc50007d Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 11 Aug 2022 15:08:24 +0200 Subject: [PATCH 0742/1161] ci: Run test suite against PHP 8.2 (#1339) --- .github/workflows/{ci.yaml => ci.yml} | 17 +++++++++++++---- tests/Serializer/AbstractSerializerTest.php | 8 ++------ 2 files changed, 15 insertions(+), 10 deletions(-) rename .github/workflows/{ci.yaml => ci.yml} (74%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yml similarity index 74% rename from .github/workflows/ci.yaml rename to .github/workflows/ci.yml index 3f4c257a5..cf0ba4eb0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: - '7.4' - '8.0' - '8.1' + - '8.2' dependencies: - lowest - highest @@ -55,15 +56,23 @@ jobs: - name: Install highest dependencies run: composer update --no-progress --no-interaction --prefer-dist - if: ${{ matrix.dependencies == 'highest' }} + if: ${{ matrix.dependencies == 'highest' && matrix.php != '8.2' }} - - name: Restrict lowest Symfony version on PHP 8.1 + - name: Install highest PHP 8.2 dependencies + run: composer update --no-progress --no-interaction --prefer-dist --ignore-platform-req=php + if: ${{ matrix.dependencies == 'highest' && matrix.php == '8.2' }} + + - name: Restrict lowest Symfony version on PHP 8.1 & 8.2 run: composer require symfony/options-resolver:^4.4.30 --no-update - if: ${{ matrix.dependencies == 'lowest' && matrix.php == '8.1' }} + if: ${{ matrix.dependencies == 'lowest' && matrix.php == '8.1' || matrix.php == '8.2' }} - name: Install lowest dependencies run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest - if: ${{ matrix.dependencies == 'lowest' }} + if: ${{ matrix.dependencies == 'lowest' && matrix.php != '8.2' }} + + - name: Install lowest PHP 8.2 dependencies + run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest --ignore-platform-req=php + if: ${{ matrix.dependencies == 'lowest' && matrix.php == '8.2' }} - name: Run tests run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 7d421f17e..af3e89162 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -538,7 +538,7 @@ public function serializationForBadStringsDataProvider(): array $utf8String = 'äöü'; return [ - [utf8_decode($utf8String), $utf8String, 'ISO-8859-1, ASCII, UTF-8'], + [mb_convert_encoding($utf8String, 'ISO-8859-1', 'UTF-8'), $utf8String, 'ISO-8859-1, ASCII, UTF-8'], ["\xC2\xA2\xC2", "\xC2\xA2\x3F"], // ill-formed 2-byte character U+00A2 (CENT SIGN) ]; } @@ -563,11 +563,7 @@ protected function invokeSerialization(AbstractSerializer $serializer, $input) } } -/** - * Class SerializerTestObject. - * - * @property mixed $keys - */ +#[\AllowDynamicProperties] class SerializerTestObject { private $foo = 'bar'; From 6e342bc238a2ce1b115e6700dafd0e2fce637fd4 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Fri, 12 Aug 2022 10:35:35 +0200 Subject: [PATCH 0743/1161] fix: Codecove (#1341) --- .github/workflows/ci.yml | 6 ++---- phpunit.xml.dist | 31 ++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf0ba4eb0..dd0dfd160 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,12 +75,10 @@ jobs: if: ${{ matrix.dependencies == 'lowest' && matrix.php == '8.2' }} - name: Run tests - run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml + run: vendor/bin/phpunit --coverage-clover=coverage.xml - name: Upload code coverage - uses: codecov/codecov-action@v1 - with: - file: build/coverage-report.xml + uses: codecov/codecov-action@v3 - name: Check benchmarks run: vendor/bin/phpbench run --revs=1 --iterations=1 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0310647ae..0f5d14742 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,14 @@ - - + @@ -17,11 +18,23 @@ + + + src + + + + + + src + + + - + - + From 3f4e92b82e98d210f54ccecb9d146205a4a1ff62 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Fri, 12 Aug 2022 11:40:39 +0200 Subject: [PATCH 0744/1161] docs: Update README.md & add CONTRIBUTING.md (#1342) --- CONTRIBUTING.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 54 ++++++++++++------------ 2 files changed, 134 insertions(+), 26 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..095a3813a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,106 @@ +

+ + Sentry + +

+ +# Contributing to the Sentry PHP SDK + +We welcome contributions to `sentry-php` by the community. + +Please search the [issue tracker](https://github.com/getsentry/sentry-php/issues) before creating a new issue (a problem or an improvement request). Please also ask in our [Sentry Community on Discord](https://discord.com/invite/Ww9hbqr) before submitting a new issue. There is a ton of great people in our Discord community ready to help you! + +If you feel that you can fix or implement it yourself, please read on to learn how to submit your changes. + +## Submitting changes + +- Setup the development environment. +- Clone the `sentry-php` repository and prepare necessary changes. +- Add tests for your changes to `tests/`. +- Run tests and make sure all of them pass. +- Submit a pull request, targeting the `develop` branch if you added any new features. For bug fixes of the current release, please target the `master` branch instead. +- Make sure to update the `CHANGELOG.md` file below the `Unreleased` heading. + +We will review your pull request as soon as possible. +Thank you for contributing! + +## Development environment + +### Clone the repository + +```bash +git clone git@github.com:getsentry/sentry-php.git +``` + +Make sure that you have PHP 7.2+ installed. Version 7.4 or higher is required to run style checkers. On macOS, we recommend using brew to install PHP. For Windows, we recommend an official PHP.net release. + +### Install the dependencies + +Dependencies are managed through [Composer](https://getcomposer.org/): + +```bash +composer install +``` + +### Running tests + +Tests can then be run via [PHPUnit](https://phpunit.de/): + +```bash +vendor/bin/phpunit +``` + +## Releasing a new version + +(only relevant for Sentry employees) + +Prerequisites: + +- All changes that should be released must be in the `master` branch. +- Every commit should follow the [Commit Message Format](https://develop.sentry.dev/commit-messages/#commit-message-format) convention. + +Manual Process: + +- Update CHANGELOG.md with the version to be released. Example commit: https://github.com/getsentry/sentry-php/commit/877bca3f0f0ac0fc8ec0a218c6070cccea266795. +- On GitHub in the `sentry-php` repository go to "Actions" select the "Release" workflow. +- Click on "Run workflow" on the right side and make sure the `master` branch is selected. +- Set "Version to release" input field. Here you decide if it is a major, minor or patch release. (See "Versioning Policy" below) +- Click "Run Workflow" + +This will trigger [Craft](https://github.com/getsentry/craft) to prepare everything needed for a release. (For more information see [craft prepare](https://github.com/getsentry/craft#craft-prepare-preparing-a-new-release)) At the end of this process, a release issue is created in the [Publish](https://github.com/getsentry/publish) repository. (Example release issue: https://github.com/getsentry/publish/issues/815) + +Now one of the persons with release privileges (most probably your engineering manager) will review this Issue and then add the `accepted` label to the issue. + +There are always two persons involved in a release. + +If you are in a hurry and the release should be out immediately there is a Slack channel called `#proj-release-approval` where you can see your release issue and where you can ping people to please have a look immediately. + +When the release issue is labeled `accepted` [Craft](https://github.com/getsentry/craft) is triggered again to publish the release to all the right platforms. (See [craft publish](https://github.com/getsentry/craft#craft-publish-publishing-the-release) for more information). At the end of this process, the release issue on GitHub will be closed and the release is completed! Congratulations! + +There is a sequence diagram visualizing all this in the [README.md](https://github.com/getsentry/publish) of the `Publish` repository. + +### Versioning Policy + +This project follows [semver](https://semver.org/), with three additions: + +- Semver says that major version `0` can include breaking changes at any time. Still, it is common practice to assume that only `0.x` releases (minor versions) can contain breaking changes while `0.x.y` releases (patch versions) are used for backwards-compatible changes (bugfixes and features). This project also follows that practice. + +- All undocumented APIs are considered internal. They are not part of this contract. + +- Certain features (e.g. integrations) may be explicitly called out as "experimental" or "unstable" in the documentation. They come with their own versioning policy described in the documentation. + +We recommend pinning your version requirements against `1.x.*` or `1.x.y`. +Either one of the following is fine: + +```json +"sentry/sentry": "^1.0", +"sentry/sentry": "^1", +``` + +A major release `N` implies the previous release `N-1` will no longer receive updates. We generally do not backport bugfixes to older versions unless they are security relevant. However, feel free to ask for backports of specific commits on the bug tracker. + +## Commit message format guidelines + +See the documentation on commit messages here: + +https://develop.sentry.dev/commit-messages/#commit-message-format diff --git a/README.md b/README.md index 874a0997b..eba0b77ed 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_ -# Sentry SDK for PHP +# Offical Sentry SDK for PHP -[![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) -[![Monthly Downloads](https://poser.pugx.org/sentry/sentry/d/monthly)](https://packagist.org/packages/sentry/sentry) [![Latest Stable Version](https://poser.pugx.org/sentry/sentry/v/stable)](https://packagist.org/packages/sentry/sentry) [![License](https://poser.pugx.org/sentry/sentry/license)](https://packagist.org/packages/sentry/sentry) +[![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) +[![Monthly Downloads](https://poser.pugx.org/sentry/sentry/d/monthly)](https://packagist.org/packages/sentry/sentry) [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/cWnMQeA) | Version | Build Status | Code Coverage | @@ -23,7 +23,9 @@ The Sentry PHP error reporter tracks errors and exceptions that happen during th execution of your application and provides instant notification with detailed information needed to prioritize, identify, reproduce and fix each issue. -## Install +## Getting started + +### Install To install the SDK you will need to be using [Composer]([https://getcomposer.org/) in your project. To install it please see the [docs](https://getcomposer.org/download/). @@ -54,11 +56,15 @@ you want to use because it's an implementation detail of your application. You m use any package that provides [`php-http/async-client-implementation`](https://packagist.org/providers/php-http/async-client-implementation) and [`http-message-implementation`](https://packagist.org/providers/psr/http-message-implementation). -## Usage +### Configuration ```php \Sentry\init(['dsn' => '___PUBLIC_DSN___' ]); +``` + +### Usage +```php try { thisFunctionThrows(); // -> throw new \Exception('foo bar'); } catch (\Exception $exception) { @@ -66,14 +72,14 @@ try { } ``` -### Official integrations +## Official integrations The following integrations are fully supported and maintained by the Sentry team. - [Symfony](https://github.com/getsentry/sentry-symfony) - [Laravel](https://github.com/getsentry/sentry-laravel) -### 3rd party integrations +## 3rd party integrations The following integrations are available and maintained by members of the Sentry community. @@ -86,14 +92,14 @@ The following integrations are available and maintained by members of the Sentry - [CakePHP](https://github.com/Connehito/cake-sentry) - ... feel free to be famous, create a port to your favourite platform! -### 3rd party integrations using old SDK 2.x +## 3rd party integrations using old SDK 2.x - [Neos Flow](https://github.com/networkteam/Networkteam.SentryClient) - [OXID eShop](https://github.com/OXIDprojects/sentry) - [TYPO3](https://github.com/networkteam/sentry_client) - [CakePHP](https://github.com/Connehito/cake-sentry/tree/3.x) -### 3rd party integrations using old SDK 1.x +## 3rd party integrations using old SDK 1.x - [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient) - [OpenCart](https://github.com/BurdaPraha/oc_sentry) @@ -105,25 +111,21 @@ The following integrations are available and maintained by members of the Sentry - [Bug Tracker](http://github.com/getsentry/sentry-php/issues) - [Code](http://github.com/getsentry/sentry-php) -## Contributing +## Contributing to the SDK -Dependencies are managed through `composer`: +Please refer to [CONTRIBUTING.md](CONTRIBUTING.md). -``` -$ composer install -``` +## Getting help/support -Tests can then be run via phpunit: +If you need help setting up or configuring the PHP SDK (or anything else in the Sentry universe) please head over to the [Sentry Community on Discord](https://discord.com/invite/Ww9hbqr). There is a ton of great people in our Discord community ready to help you! -``` -$ vendor/bin/phpunit -``` +## Resources + +- [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/quickstart/) +- [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) +- [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](http://stackoverflow.com/questions/tagged/sentry) +- [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry) + +## License -[master Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Amaster -[master Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=master -[develop Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Adevelop -[develop Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=develop -[master Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/master -[master Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/master?logo=codecov -[develop Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/develop -[develop Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/develop?logo=codecov +Licensed under the BSD license, see [`LICENSE`](LICENSE) From f05044f71be3548f2febc677dd10b85741b10150 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Fri, 12 Aug 2022 12:34:41 +0200 Subject: [PATCH 0745/1161] fix: Add back badges (#1344) --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index eba0b77ed..1735a46fe 100644 --- a/README.md +++ b/README.md @@ -129,3 +129,12 @@ If you need help setting up or configuring the PHP SDK (or anything else in the ## License Licensed under the BSD license, see [`LICENSE`](LICENSE) + +[master Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Amaster +[master Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=master +[develop Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Adevelop +[develop Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=develop +[master Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/master +[master Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/master?logo=codecov +[develop Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/develop +[develop Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/develop?logo=codecov From afd46a2eeda007813e00ac8c781b3eb84c1caa35 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 17 Aug 2022 15:18:43 +0200 Subject: [PATCH 0746/1161] ref: Drop symfony/polyfill-uuid in favour of a standalone implementation (#1346) --- CHANGELOG.md | 2 ++ composer.json | 3 +-- src/EventId.php | 6 +++++- src/Tracing/SpanId.php | 4 +++- src/Tracing/TraceId.php | 4 +++- src/Util/SentryUid.php | 42 ++++++++++++++++++++++++++++++++++++ tests/Util/SentryUidTest.php | 19 ++++++++++++++++ 7 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 src/Util/SentryUid.php create mode 100644 tests/Util/SentryUidTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e0161a60..6f0b58cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Drop symfony/polyfill-uuid in favour of a standalone implementation (#1346) + ## 3.7.0 (2022-07-18) - Fix `Scope::getTransaction()` so that it returns also unsampled transactions (#1334) diff --git a/composer.json b/composer.json index 63f090499..6044898ab 100644 --- a/composer.json +++ b/composer.json @@ -35,8 +35,7 @@ "psr/http-message-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", - "symfony/polyfill-php80": "^1.17", - "symfony/polyfill-uuid": "^1.13.1" + "symfony/polyfill-php80": "^1.17" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.19|3.4.*", diff --git a/src/EventId.php b/src/EventId.php index 76c36b272..e88de486c 100644 --- a/src/EventId.php +++ b/src/EventId.php @@ -4,6 +4,8 @@ namespace Sentry; +use Sentry\Util\SentryUid; + /** * This class represents an event ID. * @@ -32,10 +34,12 @@ public function __construct(string $value) /** * Generates a new event ID. + * + * @copyright Matt Farina MIT License https://github.com/lootils/uuid/blob/master/LICENSE */ public static function generate(): self { - return new self(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM))); + return new self(SentryUid::generate()); } public function __toString(): string diff --git a/src/Tracing/SpanId.php b/src/Tracing/SpanId.php index d36224383..f0843681f 100644 --- a/src/Tracing/SpanId.php +++ b/src/Tracing/SpanId.php @@ -4,6 +4,8 @@ namespace Sentry\Tracing; +use Sentry\Util\SentryUid; + /** * This class represents an span ID. */ @@ -33,7 +35,7 @@ public function __construct(string $value) */ public static function generate(): self { - return new self(substr(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM)), 0, 16)); + return new self(substr(SentryUid::generate(), 0, 16)); } /** diff --git a/src/Tracing/TraceId.php b/src/Tracing/TraceId.php index 2e9194757..596ec33b3 100644 --- a/src/Tracing/TraceId.php +++ b/src/Tracing/TraceId.php @@ -4,6 +4,8 @@ namespace Sentry\Tracing; +use Sentry\Util\SentryUid; + /** * This class represents an trace ID. */ @@ -33,7 +35,7 @@ public function __construct(string $value) */ public static function generate(): self { - return new self(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM))); + return new self(SentryUid::generate()); } public function __toString(): string diff --git a/src/Util/SentryUid.php b/src/Util/SentryUid.php new file mode 100644 index 000000000..50c117f69 --- /dev/null +++ b/src/Util/SentryUid.php @@ -0,0 +1,42 @@ +assertTrue($match); + } +} From 1289e7c528d3f759b0b7eaaabce83ee95d709af0 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 17 Aug 2022 15:20:52 +0200 Subject: [PATCH 0747/1161] feat: Add setter for type on ExceptionDataBag (#1347) --- CHANGELOG.md | 1 + src/ExceptionDataBag.php | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f0b58cc0..01dfaffa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add setter for type on the `ExceptionDataBag` (#1347) - Drop symfony/polyfill-uuid in favour of a standalone implementation (#1346) ## 3.7.0 (2022-07-18) diff --git a/src/ExceptionDataBag.php b/src/ExceptionDataBag.php index 0173e3a61..e93b6c617 100644 --- a/src/ExceptionDataBag.php +++ b/src/ExceptionDataBag.php @@ -13,7 +13,7 @@ final class ExceptionDataBag { /** - * @var class-string<\Throwable> The type of exception, e.g. RuntimeException + * @var string The type of exception, e.g. RuntimeException */ private $type; @@ -42,14 +42,22 @@ public function __construct(\Throwable $exception, ?Stacktrace $stacktrace = nul /** * Gets the type of exception, e.g. RuntimeException. - * - * @return class-string<\Throwable> */ public function getType(): string { return $this->type; } + /** + * Sets the type of the exception. + * + * @param string $type The exception type + */ + public function setType(string $type): void + { + $this->type = $type; + } + /** * Gets the value of the exception. */ From 277b5cf47c2d408853769256ec1200c3ce82b4a4 Mon Sep 17 00:00:00 2001 From: Vladan Paunovic Date: Thu, 18 Aug 2022 10:25:57 +0200 Subject: [PATCH 0748/1161] chore: add issue templates (#1351) --- .github/ISSUE_TEMPLATE/bug.yml | 50 ++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 6 ++++ .github/ISSUE_TEMPLATE/feature.yml | 30 ++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature.yml diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 000000000..8785950d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,50 @@ +name: 🞠Bug Report +description: Tell us about something that's not working the way we (probably) intend. +body: + - type: dropdown + id: type + attributes: + label: How do you use Sentry? + options: + - Sentry Saas (sentry.io) + - Self-hosted/on-premise + validations: + required: true + - type: input + id: version + attributes: + label: Version + description: Which SDK version? + placeholder: ex. 3.7.0 + validations: + required: true + - type: textarea + id: repro + attributes: + label: Steps to Reproduce + description: How can we see what you're seeing? Specific is terrific. + placeholder: |- + 1. What + 2. you + 3. did. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Result + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual Result + description: Logs? Screenshots? Yes, please. + validations: + required: true + - type: markdown + attributes: + value: |- + ## Thanks 🙠+ validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..a6467ab0a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false +contact_links: + - name: Support Request + url: https://sentry.io/support + about: Use our dedicated support channel for paid accounts. + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 000000000..615bbc9f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,30 @@ +name: 💡 Feature Request +description: Create a feature request for sentry-php SDK. +labels: 'enhancement' +body: + - type: markdown + attributes: + value: Thanks for taking the time to file a feature request! Please fill out this form as completely as possible. + - type: textarea + id: problem + attributes: + label: Problem Statement + description: A clear and concise description of what you want and what your use case is. + placeholder: |- + I want to make whirled peas, but Sentry doesn't blend. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Solution Brainstorm + description: We know you have bright ideas to share ... share away, friend. + placeholder: |- + Add a blender to Sentry. + validations: + required: true + - type: markdown + attributes: + value: |- + ## Thanks 🙠+ Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. \ No newline at end of file From 39fa6472286a567db09f43503673f4963a744db9 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Fri, 19 Aug 2022 12:18:15 +0200 Subject: [PATCH 0749/1161] ref: Do not register the error handler if the DSN is null (#1349) --- CHANGELOG.md | 1 + src/Integration/IntegrationRegistry.php | 11 +++--- tests/Integration/IntegrationRegistryTest.php | 27 +++++++++++++++ ...errors_not_silencable_on_php_8_and_up.phpt | 8 ++++- .../error_handler_captures_fatal_error.phpt | 6 +++- ...spects_capture_silenced_errors_option.phpt | 7 +++- ...espects_current_error_reporting_level.phpt | 34 +++++++++++++++++-- ..._option_regardless_of_error_reporting.phpt | 7 +++- 8 files changed, 91 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01dfaffa4..637113d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Do not setup any error handlers if the DSN is null (#1349) - Add setter for type on the `ExceptionDataBag` (#1347) - Drop symfony/polyfill-uuid in favour of a standalone implementation (#1346) diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index ff15b2b60..f117c43f1 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -126,14 +126,17 @@ private function getDefaultIntegrations(Options $options): array return []; } - return [ - new ExceptionListenerIntegration(), - new ErrorListenerIntegration(), - new FatalErrorListenerIntegration(), + $integrations = [ new RequestIntegration(), new TransactionIntegration(), new FrameContextifierIntegration(), new EnvironmentIntegration(), ]; + + if (null !== $options->getDsn()) { + array_unshift($integrations, new ExceptionListenerIntegration(), new ErrorListenerIntegration(), new FatalErrorListenerIntegration()); + } + + return $integrations; } } diff --git a/tests/Integration/IntegrationRegistryTest.php b/tests/Integration/IntegrationRegistryTest.php index d25ba992d..259565b30 100644 --- a/tests/Integration/IntegrationRegistryTest.php +++ b/tests/Integration/IntegrationRegistryTest.php @@ -67,6 +67,7 @@ public function setupOnce(): void yield 'No default integrations and no user integrations' => [ new Options([ + 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => false, ]), [], @@ -75,6 +76,7 @@ public function setupOnce(): void yield 'Default integrations and no user integrations' => [ new Options([ + 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, ]), [ @@ -117,6 +119,7 @@ public function setupOnce(): void yield 'Default integrations and some user integrations' => [ new Options([ + 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, 'integrations' => [ $integration1, @@ -149,6 +152,7 @@ public function setupOnce(): void yield 'Default integrations and some user integrations, one of which is also a default integration' => [ new Options([ + 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, 'integrations' => [ new TransactionIntegration(), @@ -206,6 +210,7 @@ public function setupOnce(): void yield 'Default integrations and a callable as user integrations' => [ new Options([ + 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, 'integrations' => static function (array $defaultIntegrations): array { return $defaultIntegrations; @@ -230,6 +235,28 @@ public function setupOnce(): void EnvironmentIntegration::class => new EnvironmentIntegration(), ], ]; + + yield 'Default integrations with DSN set to null' => [ + new Options([ + 'dsn' => null, + 'default_integrations' => true, + 'integrations' => static function (array $defaultIntegrations): array { + return $defaultIntegrations; + }, + ]), + [ + 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', + 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', + 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + ], + [ + RequestIntegration::class => new RequestIntegration(), + TransactionIntegration::class => new TransactionIntegration(), + FrameContextifierIntegration::class => new FrameContextifierIntegration(), + EnvironmentIntegration::class => new EnvironmentIntegration(), + ], + ]; } /** diff --git a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt index f26fba50e..facb159b9 100644 --- a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt +++ b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt @@ -55,7 +55,13 @@ $transportFactory = new class implements TransportFactoryInterface { error_reporting(E_ALL & ~E_USER_ERROR); -$client = ClientBuilder::create(['error_types' => E_ALL, 'capture_silenced_errors' => false]) +$options = [ + 'dsn' => 'http://public@example.com/sentry/1', + 'error_types' => E_ALL, + 'capture_silenced_errors' => false, +]; + +$client = ClientBuilder::create($options) ->setTransportFactory($transportFactory) ->getClient(); diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index 1e970c329..2f684a12a 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -46,7 +46,11 @@ $transportFactory = new class implements TransportFactoryInterface { } }; -$client = ClientBuilder::create([]) +$options = [ + 'dsn' => 'http://public@example.com/sentry/1', +]; + +$client = ClientBuilder::create($options) ->setTransportFactory($transportFactory) ->getClient(); diff --git a/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt index b03612bf7..ac2c4e223 100644 --- a/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt +++ b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt @@ -47,7 +47,12 @@ $transportFactory = new class implements TransportFactoryInterface { } }; -$client = ClientBuilder::create(['capture_silenced_errors' => true]) +$options = [ + 'dsn' => 'http://public@example.com/sentry/1', + 'capture_silenced_errors' => true +]; + +$client = ClientBuilder::create($options) ->setTransportFactory($transportFactory) ->getClient(); diff --git a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt index 44db31512..6b4d8e93c 100644 --- a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt +++ b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt @@ -10,8 +10,16 @@ declare(strict_types=1); namespace Sentry\Tests; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; +use Sentry\Event; +use Sentry\Options; +use Sentry\Response; +use Sentry\ResponseStatus; use Sentry\SentrySdk; +use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -21,14 +29,36 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$client = ClientBuilder::create([ +$transportFactory = new class implements TransportFactoryInterface { + public function create(Options $options): TransportInterface + { + return new class implements TransportInterface { + public function send(Event $event): PromiseInterface + { + return new FulfilledPromise(new Response(ResponseStatus::success())); + } + + public function close(?int $timeout = null): PromiseInterface + { + return new FulfilledPromise(true); + } + }; + } +}; + +$options = [ + 'dsn' => 'http://public@example.com/sentry/1', 'error_types' => null, 'before_send' => static function () { echo 'Before send callback called' . PHP_EOL; return null; }, -])->getClient(); +]; + +$client = ClientBuilder::create($options) + ->setTransportFactory($transportFactory) + ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt index bd73e1208..e47750b5c 100644 --- a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt @@ -47,7 +47,12 @@ $transportFactory = new class implements TransportFactoryInterface { error_reporting(E_ALL & ~E_USER_NOTICE & ~E_USER_WARNING & ~E_USER_ERROR); -$client = ClientBuilder::create(['error_types' => E_ALL & ~E_USER_WARNING]) +$options = [ + 'dsn' => 'http://public@example.com/sentry/1', + 'error_types' => E_ALL & ~E_USER_WARNING, +]; + +$client = ClientBuilder::create($options) ->setTransportFactory($transportFactory) ->getClient(); From c3f235fc86f1c4007e3a218ec82d666586e73cbf Mon Sep 17 00:00:00 2001 From: Daniel Schmelz Date: Sat, 20 Aug 2022 20:35:17 +0200 Subject: [PATCH 0750/1161] Fix typo (#1353) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1735a46fe..23ac9c4cf 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_ -# Offical Sentry SDK for PHP +# Official Sentry SDK for PHP [![Latest Stable Version](https://poser.pugx.org/sentry/sentry/v/stable)](https://packagist.org/packages/sentry/sentry) [![License](https://poser.pugx.org/sentry/sentry/license)](https://packagist.org/packages/sentry/sentry) From 66951027c17ea1f550eed4054800025578c3a146 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 1 Sep 2022 13:05:07 +0200 Subject: [PATCH 0751/1161] Add breadcrumb monolog handler (#1199) --- CHANGELOG.md | 1 + phpstan-baseline.neon | 5 ++ psalm-baseline.xml | 45 ++++------- src/Monolog/BreadcrumbHandler.php | 101 ++++++++++++++++++++++++ tests/Monolog/BreadcrumbHandlerTest.php | 88 +++++++++++++++++++++ tests/Monolog/RecordFactory.php | 1 + 6 files changed, 212 insertions(+), 29 deletions(-) create mode 100644 src/Monolog/BreadcrumbHandler.php create mode 100644 tests/Monolog/BreadcrumbHandlerTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 637113d77..c4b5030d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add `Sentry\Monolog\BreadcrumbHandler`, a Monolog handler to allow registration of logs as breadcrumbs (#1199) - Do not setup any error handlers if the DSN is null (#1349) - Add setter for type on the `ExceptionDataBag` (#1347) - Drop symfony/polyfill-uuid in favour of a standalone implementation (#1346) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 62ea23ef5..34c2f4654 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -95,6 +95,11 @@ parameters: count: 1 path: src/Integration/RequestIntegration.php + - + message: "#^Parameter \\#1 \\$level of method Monolog\\\\Handler\\\\AbstractHandler\\:\\:__construct\\(\\) expects 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|'ALERT'\\|'alert'\\|'CRITICAL'\\|'critical'\\|'DEBUG'\\|'debug'\\|'EMERGENCY'\\|'emergency'\\|'ERROR'\\|'error'\\|'INFO'\\|'info'\\|'NOTICE'\\|'notice'\\|'WARNING'\\|'warning'\\|Monolog\\\\Level, int\\|Monolog\\\\Level\\|string given\\.$#" + count: 1 + path: src/Monolog/BreadcrumbHandler.php + - message: "#^Method Sentry\\\\Options\\:\\:getBeforeBreadcrumbCallback\\(\\) should return callable\\(Sentry\\\\Breadcrumb\\)\\: Sentry\\\\Breadcrumb\\|null but returns mixed\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 814952ffa..93bf65e2c 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $parsedDsn['host'] @@ -22,6 +22,21 @@ $userIntegrations + + + $record['channel'] + $record['level'] + $record['level'] + $record['message'] + + + getTimestamp + + + Level|int + int|string|Level|LogLevel::* + + CompatibilityProcessingHandlerTrait @@ -80,32 +95,4 @@ startTransaction - - - $name - , - , - , - , - , - , - , - , - , - Level - case - - - - - \DateTimeImmutable - - - - - : - => - } - - diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php new file mode 100644 index 000000000..8c5e03b0a --- /dev/null +++ b/src/Monolog/BreadcrumbHandler.php @@ -0,0 +1,101 @@ +hub = $hub; + + parent::__construct($level, $bubble); + } + + /** + * @psalm-suppress MoreSpecificImplementedParamType + * + * @param LogRecord|array{ + * level: int, + * channel: string, + * datetime: \DateTimeImmutable, + * message: string, + * extra?: array + * } $record {@see https://github.com/Seldaek/monolog/blob/main/doc/message-structure.md} + */ + protected function write($record): void + { + $breadcrumb = new Breadcrumb( + $this->getBreadcrumbLevel($record['level']), + $this->getBreadcrumbType($record['level']), + $record['channel'], + $record['message'], + ($record['context'] ?? []) + ($record['extra'] ?? []), + $record['datetime']->getTimestamp() + ); + + $this->hub->addBreadcrumb($breadcrumb); + } + + /** + * @param Level|int $level + */ + private function getBreadcrumbLevel($level): string + { + if ($level instanceof Level) { + $level = $level->value; + } + + switch ($level) { + case Logger::DEBUG: + return Breadcrumb::LEVEL_DEBUG; + case Logger::INFO: + case Logger::NOTICE: + return Breadcrumb::LEVEL_INFO; + case Logger::WARNING: + return Breadcrumb::LEVEL_WARNING; + case Logger::ERROR: + return Breadcrumb::LEVEL_ERROR; + default: + return Breadcrumb::LEVEL_FATAL; + } + } + + private function getBreadcrumbType(int $level): string + { + if ($level >= Logger::ERROR) { + return Breadcrumb::TYPE_ERROR; + } + + return Breadcrumb::TYPE_DEFAULT; + } +} diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php new file mode 100644 index 000000000..14802d951 --- /dev/null +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -0,0 +1,88 @@ +createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('addBreadcrumb') + ->with($this->callback(function (Breadcrumb $breadcrumb) use ($expectedBreadcrumb, $record): bool { + $this->assertSame($expectedBreadcrumb->getMessage(), $breadcrumb->getMessage()); + $this->assertSame($expectedBreadcrumb->getLevel(), $breadcrumb->getLevel()); + $this->assertSame($expectedBreadcrumb->getType(), $breadcrumb->getType()); + $this->assertEquals($record['datetime']->getTimestamp(), $breadcrumb->getTimestamp()); + $this->assertSame($expectedBreadcrumb->getCategory(), $breadcrumb->getCategory()); + $this->assertEquals($expectedBreadcrumb->getMetadata(), $breadcrumb->getMetadata()); + + return true; + })); + + $handler = new BreadcrumbHandler($hub); + $handler->handle($record); + } + + /** + * @return iterable, Breadcrumb}> + */ + public function handleDataProvider(): iterable + { + $defaultBreadcrumb = new Breadcrumb( + Breadcrumb::LEVEL_DEBUG, + Breadcrumb::TYPE_DEFAULT, + 'channel.foo', + 'foo bar', + [] + ); + + $levelsToBeTested = [ + Logger::DEBUG => Breadcrumb::LEVEL_DEBUG, + Logger::INFO => Breadcrumb::LEVEL_INFO, + Logger::NOTICE => Breadcrumb::LEVEL_INFO, + Logger::WARNING => Breadcrumb::LEVEL_WARNING, + ]; + + foreach ($levelsToBeTested as $loggerLevel => $breadcrumbLevel) { + yield 'with level ' . Logger::getLevelName($loggerLevel) => [ + RecordFactory::create('foo bar', $loggerLevel, 'channel.foo', [], []), + $defaultBreadcrumb->withLevel($breadcrumbLevel), + ]; + } + + yield 'with level ERROR' => [ + RecordFactory::create('foo bar', Logger::ERROR, 'channel.foo', [], []), + $defaultBreadcrumb->withLevel(Breadcrumb::LEVEL_ERROR) + ->withType(Breadcrumb::TYPE_ERROR), + ]; + + yield 'with level ALERT' => [ + RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo', [], []), + $defaultBreadcrumb->withLevel(Breadcrumb::LEVEL_FATAL) + ->withType(Breadcrumb::TYPE_ERROR), + ]; + + yield 'with context' => [ + RecordFactory::create('foo bar', Logger::DEBUG, 'channel.foo', ['context' => ['foo' => 'bar']], []), + $defaultBreadcrumb->withMetadata('context', ['foo' => 'bar']), + ]; + + yield 'with extra' => [ + RecordFactory::create('foo bar', Logger::DEBUG, 'channel.foo', [], ['extra' => ['foo' => 'bar']]), + $defaultBreadcrumb->withMetadata('extra', ['foo' => 'bar']), + ]; + } +} diff --git a/tests/Monolog/RecordFactory.php b/tests/Monolog/RecordFactory.php index 9f65473db..b302c509b 100644 --- a/tests/Monolog/RecordFactory.php +++ b/tests/Monolog/RecordFactory.php @@ -40,6 +40,7 @@ public static function create(string $message, int $level, string $channel, arra 'level_name' => Logger::getLevelName($level), 'channel' => $channel, 'extra' => $extra, + 'datetime' => new \DateTimeImmutable(), ]; } } From 86ec009e0a0540e575f6981f52f0fcab39fedc1b Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 5 Sep 2022 12:18:20 +0200 Subject: [PATCH 0752/1161] Update phpstan-baseline.neon (#1357) --- phpstan-baseline.neon | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 34c2f4654..83ea011fa 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -16,27 +16,27 @@ parameters: path: src/ClientInterface.php - - message: "#^Offset 'host' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" + message: "#^Offset 'host' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'host'\\|'path'\\|'scheme'\\|'user' on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\} in isset\\(\\) always exists and is not nullable\\.$#" + message: "#^Offset 'host'\\|'path'\\|'scheme'\\|'user' on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\} in isset\\(\\) always exists and is not nullable\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'path' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" + message: "#^Offset 'path' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 4 path: src/Dsn.php - - message: "#^Offset 'scheme' does not exist on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" + message: "#^Offset 'scheme' does not exist on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'user' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" + message: "#^Offset 'user' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php From ca21a81d2f162cfae7975354262e32cf8858cbd8 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 5 Sep 2022 12:20:00 +0200 Subject: [PATCH 0753/1161] Prepare release 3.8.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4b5030d8..657621c4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.8.0 (2022-09-05) + - Add `Sentry\Monolog\BreadcrumbHandler`, a Monolog handler to allow registration of logs as breadcrumbs (#1199) - Do not setup any error handlers if the DSN is null (#1349) - Add setter for type on the `ExceptionDataBag` (#1347) From 0abc27b4e2a53c5eec5bf3893d289e601c98542a Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 5 Sep 2022 13:47:05 +0200 Subject: [PATCH 0754/1161] Start development of version 3.9 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6044898ab..f89a85b5d 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.8.x-dev" + "dev-master": "3.9.x-dev" } } } From dc599ef4ac5459fef95cc0414d26ac47e300e1dc Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 5 Sep 2022 16:25:47 +0200 Subject: [PATCH 0755/1161] test: Add tests for ExceptionDataBag (#1358) --- tests/ExceptionDataBagTest.php | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/ExceptionDataBagTest.php diff --git a/tests/ExceptionDataBagTest.php b/tests/ExceptionDataBagTest.php new file mode 100644 index 000000000..c7ce0eca2 --- /dev/null +++ b/tests/ExceptionDataBagTest.php @@ -0,0 +1,100 @@ +assertSame($expectedType, $exceptionDataBag->getType()); + $this->assertSame($expectedValue, $exceptionDataBag->getValue()); + $this->assertSame($expectedStackTrace, $exceptionDataBag->getStacktrace()); + $this->assertSame($expectedExceptionMechansim, $exceptionDataBag->getMechanism()); + } + + public function constructorDataProvider(): \Generator + { + yield [ + [ + new \RuntimeException('foo bar'), + null, + null, + ], + \RuntimeException::class, + 'foo bar', + null, + null, + ]; + + $strackTarce = new Stacktrace([ + new Frame('test_function', '/path/to/file', 10, null, '/path/to/file'), + ]); + $exceptionMechansim = new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false); + + yield [ + [ + new \RuntimeException('foo bar'), + $strackTarce, + $exceptionMechansim, + ], + \RuntimeException::class, + 'foo bar', + $strackTarce, + $exceptionMechansim, + ]; + } + + public function testSetType(): void + { + $exceptionDataBag = new ExceptionDataBag(new \RuntimeException()); + + $exceptionDataBag->setType('foo bar'); + + $this->assertSame('foo bar', $exceptionDataBag->getType()); + } + + public function testSetValue(): void + { + $exceptionDataBag = new ExceptionDataBag(new \RuntimeException()); + + $exceptionDataBag->setValue('foo bar'); + + $this->assertSame('foo bar', $exceptionDataBag->getValue()); + } + + public function testSetStacktrace(): void + { + $exceptionDataBag = new ExceptionDataBag(new \RuntimeException()); + + $stacktrace = new Stacktrace([ + new Frame('test_function', '/path/to/file', 10, null, '/path/to/file'), + ]); + + $exceptionDataBag->setStacktrace($stacktrace); + + $this->assertSame($stacktrace, $exceptionDataBag->getStacktrace()); + } + + public function testSetMechanism(): void + { + $exceptionDataBag = new ExceptionDataBag(new \RuntimeException()); + $exceptionMechanism = new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false); + + $exceptionDataBag->setMechanism($exceptionMechanism); + + $this->assertSame($exceptionMechanism, $exceptionDataBag->getMechanism()); + } +} From c0037a571336722a8853068d56e2e6ecfdf1ee1f Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 5 Sep 2022 17:11:04 +0200 Subject: [PATCH 0756/1161] ci: Update ci.yml (#1359) --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd0dfd160..2fbba9743 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: branches: - master - develop + - release/** jobs: tests: From a4595a3d16a4aca285abf7f7d03c5e8eb657e1d5 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Tue, 20 Sep 2022 11:00:44 +0200 Subject: [PATCH 0757/1161] ref: Use constant for the SDK version (#1367) --- .craft.yml | 1 - CHANGELOG.md | 2 ++ scripts/bump-version.sh | 24 ++++++++++++++++++++++ src/Client.php | 8 ++++++-- src/ClientBuilder.php | 4 +--- src/Event.php | 4 +--- tests/ClientBuilderTest.php | 6 ++---- tests/Serializer/PayloadSerializerTest.php | 4 ++-- 8 files changed, 38 insertions(+), 15 deletions(-) create mode 100755 scripts/bump-version.sh diff --git a/.craft.yml b/.craft.yml index 2b133fc7a..79ad7a232 100644 --- a/.craft.yml +++ b/.craft.yml @@ -7,7 +7,6 @@ statusProvider: name: github artifactProvider: name: none -preReleaseCommand: "" targets: - name: github - name: registry diff --git a/CHANGELOG.md b/CHANGELOG.md index 657621c4d..96e163600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Use constant for the SDK version (#1367) + ## 3.8.0 (2022-09-05) - Add `Sentry\Monolog\BreadcrumbHandler`, a Monolog handler to allow registration of logs as breadcrumbs (#1199) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 000000000..bba505e2b --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -eux + +if [ "$(uname -s)" != "Linux" ]; then + echo "Please use the GitHub Action." + exit 1 +fi + +SCRIPT_DIR="$( dirname "$0" )" +cd $SCRIPT_DIR/.. + +OLD_VERSION="${1}" +NEW_VERSION="${2}" + +echo "Current version: $OLD_VERSION" +echo "Bumping version: $NEW_VERSION" + +function replace() { + ! grep "$2" $3 + perl -i -pe "s/$1/$2/g" $3 + grep "$2" $3 # verify that replacement was successful +} + +replace "SDK_VERSION = '[0-9.]+'" "SDK_VERSION = '$NEW_VERSION'" ./src/Client.php \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index 308a8c62c..9bf92a43a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,7 +5,6 @@ namespace Sentry; use GuzzleHttp\Promise\PromiseInterface; -use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Integration\IntegrationInterface; @@ -33,6 +32,11 @@ final class Client implements ClientInterface */ public const SDK_IDENTIFIER = 'sentry.php'; + /** + * The version of the SDK. + */ + public const SDK_VERSION = '3.8.0'; + /** * @var Options The client options */ @@ -102,7 +106,7 @@ public function __construct( $this->representationSerializer = $representationSerializer ?? new RepresentationSerializer($this->options); $this->stacktraceBuilder = new StacktraceBuilder($options, $this->representationSerializer); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; - $this->sdkVersion = $sdkVersion ?? PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + $this->sdkVersion = $sdkVersion ?? self::SDK_VERSION; } /** diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 46ea8c516..39d1e6cc3 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -5,7 +5,6 @@ namespace Sentry; use Http\Discovery\Psr17FactoryDiscovery; -use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; use Sentry\Serializer\RepresentationSerializerInterface; @@ -59,7 +58,7 @@ final class ClientBuilder implements ClientBuilderInterface /** * @var string The SDK version of the Client */ - private $sdkVersion; + private $sdkVersion = Client::SDK_VERSION; /** * Class constructor. @@ -69,7 +68,6 @@ final class ClientBuilder implements ClientBuilderInterface public function __construct(Options $options = null) { $this->options = $options ?? new Options(); - $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); } /** diff --git a/src/Event.php b/src/Event.php index 1d3d58840..9834f1b13 100644 --- a/src/Event.php +++ b/src/Event.php @@ -4,7 +4,6 @@ namespace Sentry; -use Jean85\PrettyVersions; use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; use Sentry\Tracing\Span; @@ -153,7 +152,7 @@ final class Event /** * @var string The Sentry SDK version */ - private $sdkVersion; + private $sdkVersion = Client::SDK_VERSION; /** * @var EventType The type of the Event @@ -164,7 +163,6 @@ private function __construct(?EventId $eventId, EventType $eventType) { $this->id = $eventId ?? EventId::generate(); $this->timestamp = microtime(true); - $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); $this->type = $eventType; } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 2f123c9b2..acfdb1b88 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -4,7 +4,6 @@ namespace Sentry\Tests; -use Jean85\PrettyVersions; use PHPUnit\Framework\TestCase; use Sentry\Client; use Sentry\ClientBuilder; @@ -38,14 +37,13 @@ public function testNullTransportIsUsedWhenNoServerIsConfigured(): void public function testClientBuilderFallbacksToDefaultSdkIdentifierAndVersion(): void { $callbackCalled = false; - $expectedVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); $options = new Options(); - $options->setBeforeSendCallback(function (Event $event) use ($expectedVersion, &$callbackCalled) { + $options->setBeforeSendCallback(function (Event $event) use (&$callbackCalled) { $callbackCalled = true; $this->assertSame(Client::SDK_IDENTIFIER, $event->getSdkIdentifier()); - $this->assertSame($expectedVersion, $event->getSdkVersion()); + $this->assertSame(Client::SDK_VERSION, $event->getSdkVersion()); return null; }); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 1d2c047bd..55c9a5ef0 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -4,9 +4,9 @@ namespace Sentry\Tests\Serializer; -use Jean85\PrettyVersions; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\Client; use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; use Sentry\Event; @@ -65,7 +65,7 @@ public function serializeDataProvider(): iterable { ClockMock::withClockMock(1597790835); - $sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + $sdkVersion = Client::SDK_VERSION; yield [ Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), From 83e50c807646a825bf298494df42f311846911ef Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Tue, 20 Sep 2022 21:34:49 +0200 Subject: [PATCH 0758/1161] fix: Do not throw an TypeError on numeric HTTP headers (#1370) Co-authored-by: Alessandro Lai --- CHANGELOG.md | 2 ++ src/Integration/RequestIntegration.php | 5 ++++- tests/Integration/RequestIntegrationTest.php | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 657621c4d..81346ea8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fix: Do not throw an TypeError on numeric HTTP headers (#1370) + ## 3.8.0 (2022-09-05) - Add `Sentry\Monolog\BreadcrumbHandler`, a Monolog handler to allow registration of logs as breadcrumbs (#1199) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index b3b515387..23ea88a32 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -169,13 +169,16 @@ private function processEvent(Event $event, Options $options): void /** * Removes headers containing potential PII. * - * @param array $headers Array containing request headers + * @param array $headers Array containing request headers * * @return array */ private function sanitizeHeaders(array $headers): array { foreach ($headers as $name => $values) { + // Cast the header name into a string, to avoid errors on numeric headers + $name = (string) $name; + if (!\in_array(strtolower($name), $this->options['pii_sanitize_headers'], true)) { continue; } diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index f57a9b1f9..6e5e85b54 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -422,6 +422,22 @@ public function invokeDataProvider(): iterable null, null, ]; + + yield [ + [], + (new ServerRequest('GET', 'http://www.example.com/foo')) + ->withHeader('123', 'test'), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'GET', + 'headers' => [ + 'Host' => ['www.example.com'], + '123' => ['test'], + ], + ], + null, + null, + ]; } private function createRequestFetcher(ServerRequestInterface $request): RequestFetcherInterface From d1916d1fc9e998d513fdc45b7d8378a6991354a3 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 21 Sep 2022 11:04:36 +0200 Subject: [PATCH 0759/1161] Prepare release 3.8.1 (#1373) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81346ea8e..3a006fc56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.8.1 (2022-09-21) + - fix: Do not throw an TypeError on numeric HTTP headers (#1370) ## 3.8.0 (2022-09-05) From 0195e65f43c1913e201399b9485b69874f3afd3c Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 21 Sep 2022 11:58:02 +0200 Subject: [PATCH 0760/1161] ref: Use constant for the SDK version (#1374) --- .craft.yml | 1 - CHANGELOG.md | 2 ++ scripts/bump-version.sh | 24 ++++++++++++++++++++++ src/Client.php | 8 ++++++-- src/ClientBuilder.php | 4 +--- src/Event.php | 4 +--- tests/ClientBuilderTest.php | 6 ++---- tests/Serializer/PayloadSerializerTest.php | 4 ++-- 8 files changed, 38 insertions(+), 15 deletions(-) create mode 100755 scripts/bump-version.sh diff --git a/.craft.yml b/.craft.yml index 2b133fc7a..79ad7a232 100644 --- a/.craft.yml +++ b/.craft.yml @@ -7,7 +7,6 @@ statusProvider: name: github artifactProvider: name: none -preReleaseCommand: "" targets: - name: github - name: registry diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a006fc56..c1aadd781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Use constant for the SDK version (#1367) + ## 3.8.1 (2022-09-21) - fix: Do not throw an TypeError on numeric HTTP headers (#1370) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 000000000..bba505e2b --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -eux + +if [ "$(uname -s)" != "Linux" ]; then + echo "Please use the GitHub Action." + exit 1 +fi + +SCRIPT_DIR="$( dirname "$0" )" +cd $SCRIPT_DIR/.. + +OLD_VERSION="${1}" +NEW_VERSION="${2}" + +echo "Current version: $OLD_VERSION" +echo "Bumping version: $NEW_VERSION" + +function replace() { + ! grep "$2" $3 + perl -i -pe "s/$1/$2/g" $3 + grep "$2" $3 # verify that replacement was successful +} + +replace "SDK_VERSION = '[0-9.]+'" "SDK_VERSION = '$NEW_VERSION'" ./src/Client.php \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index 308a8c62c..9bf92a43a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,7 +5,6 @@ namespace Sentry; use GuzzleHttp\Promise\PromiseInterface; -use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Integration\IntegrationInterface; @@ -33,6 +32,11 @@ final class Client implements ClientInterface */ public const SDK_IDENTIFIER = 'sentry.php'; + /** + * The version of the SDK. + */ + public const SDK_VERSION = '3.8.0'; + /** * @var Options The client options */ @@ -102,7 +106,7 @@ public function __construct( $this->representationSerializer = $representationSerializer ?? new RepresentationSerializer($this->options); $this->stacktraceBuilder = new StacktraceBuilder($options, $this->representationSerializer); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; - $this->sdkVersion = $sdkVersion ?? PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + $this->sdkVersion = $sdkVersion ?? self::SDK_VERSION; } /** diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 46ea8c516..39d1e6cc3 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -5,7 +5,6 @@ namespace Sentry; use Http\Discovery\Psr17FactoryDiscovery; -use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; use Sentry\Serializer\RepresentationSerializerInterface; @@ -59,7 +58,7 @@ final class ClientBuilder implements ClientBuilderInterface /** * @var string The SDK version of the Client */ - private $sdkVersion; + private $sdkVersion = Client::SDK_VERSION; /** * Class constructor. @@ -69,7 +68,6 @@ final class ClientBuilder implements ClientBuilderInterface public function __construct(Options $options = null) { $this->options = $options ?? new Options(); - $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); } /** diff --git a/src/Event.php b/src/Event.php index 1d3d58840..9834f1b13 100644 --- a/src/Event.php +++ b/src/Event.php @@ -4,7 +4,6 @@ namespace Sentry; -use Jean85\PrettyVersions; use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; use Sentry\Tracing\Span; @@ -153,7 +152,7 @@ final class Event /** * @var string The Sentry SDK version */ - private $sdkVersion; + private $sdkVersion = Client::SDK_VERSION; /** * @var EventType The type of the Event @@ -164,7 +163,6 @@ private function __construct(?EventId $eventId, EventType $eventType) { $this->id = $eventId ?? EventId::generate(); $this->timestamp = microtime(true); - $this->sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); $this->type = $eventType; } diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 2f123c9b2..acfdb1b88 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -4,7 +4,6 @@ namespace Sentry\Tests; -use Jean85\PrettyVersions; use PHPUnit\Framework\TestCase; use Sentry\Client; use Sentry\ClientBuilder; @@ -38,14 +37,13 @@ public function testNullTransportIsUsedWhenNoServerIsConfigured(): void public function testClientBuilderFallbacksToDefaultSdkIdentifierAndVersion(): void { $callbackCalled = false; - $expectedVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); $options = new Options(); - $options->setBeforeSendCallback(function (Event $event) use ($expectedVersion, &$callbackCalled) { + $options->setBeforeSendCallback(function (Event $event) use (&$callbackCalled) { $callbackCalled = true; $this->assertSame(Client::SDK_IDENTIFIER, $event->getSdkIdentifier()); - $this->assertSame($expectedVersion, $event->getSdkVersion()); + $this->assertSame(Client::SDK_VERSION, $event->getSdkVersion()); return null; }); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 1d2c047bd..55c9a5ef0 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -4,9 +4,9 @@ namespace Sentry\Tests\Serializer; -use Jean85\PrettyVersions; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\Client; use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; use Sentry\Event; @@ -65,7 +65,7 @@ public function serializeDataProvider(): iterable { ClockMock::withClockMock(1597790835); - $sdkVersion = PrettyVersions::getVersion('sentry/sentry')->getPrettyVersion(); + $sdkVersion = Client::SDK_VERSION; yield [ Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), From dec9967bf4a8e1567f595ba2fb421354953dfb1e Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 21 Sep 2022 12:42:46 +0200 Subject: [PATCH 0761/1161] test: Add tests for ClientBuilder::getOptions (#1375) --- tests/ClientBuilderTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index acfdb1b88..153986970 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -16,6 +16,14 @@ final class ClientBuilderTest extends TestCase { + public function testGetOptions() + { + $options = new Options(); + $clientBuilder = new ClientBuilder($options); + + $this->assertSame($options, $clientBuilder->getOptions()); + } + public function testHttpTransportIsUsedWhenServerIsConfigured(): void { $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); From 7138658a0e24e41431ddba1dd9d984efe905b153 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 21 Sep 2022 12:56:27 +0200 Subject: [PATCH 0762/1161] Update release 3.8.1 (#1376) --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1aadd781..34a2d4662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,9 @@ ## Unreleased -- Use constant for the SDK version (#1367) - ## 3.8.1 (2022-09-21) +- fix: Use constant for the SDK version (#1374) - fix: Do not throw an TypeError on numeric HTTP headers (#1370) ## 3.8.0 (2022-09-05) From 5150776a0a9835c4ea56ff0ecd94e0a109b6c163 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 21 Sep 2022 11:01:17 +0000 Subject: [PATCH 0763/1161] release: 3.8.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 9bf92a43a..e8995000c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.8.0'; + public const SDK_VERSION = '3.8.1'; /** * @var Options The client options From 9d1ea6fec8bdfdfb6ab8efa5e0d52a4f18dfa214 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 26 Sep 2022 09:54:54 +0200 Subject: [PATCH 0764/1161] Expose a function to retrieve the URL of the CSP endpoint (#1378) --- CHANGELOG.md | 2 ++ src/Client.php | 24 ++++++++++++++++++ src/ClientInterface.php | 1 + src/Dsn.php | 8 ++++++ tests/ClientTest.php | 56 +++++++++++++++++++++++++++++++++++++++++ tests/DsnTest.php | 38 ++++++++++++++++++++++++++++ 6 files changed, 129 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34a2d4662..517d04c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Expose a function to retrieve the URL of the CSP endpoint (#1378) + ## 3.8.1 (2022-09-21) - fix: Use constant for the SDK version (#1374) diff --git a/src/Client.php b/src/Client.php index e8995000c..3972ef6c4 100644 --- a/src/Client.php +++ b/src/Client.php @@ -117,6 +117,30 @@ public function getOptions(): Options return $this->options; } + /** + * {@inheritdoc} + */ + public function getCspReportUrl(): ?string + { + $dsn = $this->options->getDsn(); + + if (null === $dsn) { + return null; + } + + $endpoint = $dsn->getCspReportEndpointUrl(); + $query = array_filter([ + 'sentry_release' => $this->options->getRelease(), + 'sentry_environment' => $this->options->getEnvironment(), + ]); + + if (!empty($query)) { + $endpoint .= '&' . http_build_query($query, '', '&'); + } + + return $endpoint; + } + /** * {@inheritdoc} */ diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 1cb287f69..3f6d06f38 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -12,6 +12,7 @@ * This interface must be implemented by all Raven client classes. * * @method StacktraceBuilder getStacktraceBuilder() Returns the stacktrace builder of the client. + * @method string|null getCspReportUrl() Returns an URL for security policy reporting that's generated from the given DSN * * @author Stefano Arlandini */ diff --git a/src/Dsn.php b/src/Dsn.php index bf54ea407..ee09d68d0 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -196,6 +196,14 @@ public function getEnvelopeApiEndpointUrl(): string return $this->getBaseEndpointUrl() . '/envelope/'; } + /** + * Returns the URL of the API for the CSP report endpoint. + */ + public function getCspReportEndpointUrl(): string + { + return $this->getBaseEndpointUrl() . '/security/?sentry_key=' . $this->publicKey; + } + /** * @see https://www.php.net/manual/en/language.oop5.magic.php#object.tostring */ diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 1982fb901..9bfc9826c 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -811,6 +811,62 @@ public function testBuildWithCustomStacktrace(): void $client->captureEvent($event); } + /** + * @dataProvider getCspReportUrlDataProvider + */ + public function testGetCspReportUrl(array $options, ?string $expectedUrl): void + { + $client = new Client( + new Options($options), + $this->createMock(TransportInterface::class), + 'sentry.sdk.identifier', + '1.2.3', + $this->createMock(SerializerInterface::class), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $this->assertSame($expectedUrl, $client->getCspReportUrl()); + } + + public function getCspReportUrlDataProvider(): \Generator + { + yield [ + ['dsn' => null], + null, + null, + ]; + + yield [ + ['dsn' => 'https://public:secret@example.com/1'], + 'https://example.com/api/1/security/?sentry_key=public', + ]; + + yield [ + [ + 'dsn' => 'https://public:secret@example.com/1', + 'release' => 'dev-release', + ], + 'https://example.com/api/1/security/?sentry_key=public&sentry_release=dev-release', + ]; + + yield [ + [ + 'dsn' => 'https://public:secret@example.com/1', + 'environment' => 'development', + ], + 'https://example.com/api/1/security/?sentry_key=public&sentry_environment=development', + ]; + + yield [ + [ + 'dsn' => 'https://public:secret@example.com/1', + 'release' => 'dev-release', + 'environment' => 'development', + ], + 'https://example.com/api/1/security/?sentry_key=public&sentry_release=dev-release&sentry_environment=development', + ]; + } + private function createTransportFactory(TransportInterface $transport): TransportFactoryInterface { return new class($transport) implements TransportFactoryInterface { diff --git a/tests/DsnTest.php b/tests/DsnTest.php index 6c31e994e..1b73519ea 100644 --- a/tests/DsnTest.php +++ b/tests/DsnTest.php @@ -214,6 +214,44 @@ public function getStoreApiEndpointUrlDataProvider(): \Generator ]; } + /** + * @dataProvider getCspReportEndpointUrlDataProvider + */ + public function testGetCspReportEndpointUrl(string $value, string $expectedUrl): void + { + $dsn = Dsn::createFromString($value); + + $this->assertSame($expectedUrl, $dsn->getCspReportEndpointUrl()); + } + + public function getCspReportEndpointUrlDataProvider(): \Generator + { + yield [ + 'http://public@example.com/sentry/1', + 'http://example.com/sentry/api/1/security/?sentry_key=public', + ]; + + yield [ + 'http://public@example.com/1', + 'http://example.com/api/1/security/?sentry_key=public', + ]; + + yield [ + 'http://public@example.com:8080/sentry/1', + 'http://example.com:8080/sentry/api/1/security/?sentry_key=public', + ]; + + yield [ + 'https://public@example.com/sentry/1', + 'https://example.com/sentry/api/1/security/?sentry_key=public', + ]; + + yield [ + 'https://public@example.com:4343/sentry/1', + 'https://example.com:4343/sentry/api/1/security/?sentry_key=public', + ]; + } + /** * @dataProvider toStringDataProvider */ From 7ab1fcbb98db53c0fc5c5766ac358266daf3c57a Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Tue, 27 Sep 2022 12:25:03 +0200 Subject: [PATCH 0765/1161] test: Check if the correct trace_id is applied to the event context (#1379) --- tests/State/ScopeTest.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index a5c5618a1..46d8b7bc6 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -10,6 +10,9 @@ use Sentry\EventHint; use Sentry\Severity; use Sentry\State\Scope; +use Sentry\Tracing\Span; +use Sentry\Tracing\SpanId; +use Sentry\Tracing\TraceId; use Sentry\UserDataBag; final class ScopeTest extends TestCase @@ -354,6 +357,10 @@ public function testApplyToEvent(): void $event = Event::createEvent(); $event->setContext('foocontext', ['foo' => 'foo', 'bar' => 'bar']); + $span = new Span(); + $span->setSpanId(new SpanId('566e3688a61d4bc8')); + $span->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $scope = new Scope(); $scope->setLevel(Severity::warning()); $scope->setFingerprint(['foo']); @@ -363,6 +370,7 @@ public function testApplyToEvent(): void $scope->setUser($user); $scope->setContext('foocontext', ['foo' => 'bar']); $scope->setContext('barcontext', ['bar' => 'foo']); + $scope->setSpan($span); $this->assertSame($event, $scope->applyToEvent($event)); $this->assertTrue($event->getLevel()->isEqualTo(Severity::warning())); @@ -371,6 +379,18 @@ public function testApplyToEvent(): void $this->assertSame(['foo' => 'bar'], $event->getTags()); $this->assertSame(['bar' => 'foo'], $event->getExtra()); $this->assertSame($user, $event->getUser()); - $this->assertSame(['foocontext' => ['foo' => 'foo', 'bar' => 'bar'], 'barcontext' => ['bar' => 'foo']], $event->getContexts()); + $this->assertSame([ + 'foocontext' => [ + 'foo' => 'foo', + 'bar' => 'bar', + ], + 'trace' => [ + 'span_id' => '566e3688a61d4bc8', + 'trace_id' => '566e3688a61d4bc888951642d6f14a19', + ], + 'barcontext' => [ + 'bar' => 'foo', + ], + ], $event->getContexts()); } } From b032de922c57eaaa59a366993900a2b1df30e39e Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 28 Sep 2022 11:25:19 +0200 Subject: [PATCH 0766/1161] feat: Add support for Dynamic Sampling (#1360) Co-authored-by: Alex Bouma Co-authored-by: Stefano Arlandini --- CHANGELOG.md | 6 +- Makefile | 6 +- phpstan-baseline.neon | 5 + psalm-baseline.xml | 7 +- src/Event.php | 39 ++++ src/Serializer/PayloadSerializer.php | 45 +++- src/State/Hub.php | 13 +- src/State/Scope.php | 8 + src/Tracing/DynamicSamplingContext.php | 196 ++++++++++++++++++ src/Tracing/GuzzleTracingMiddleware.php | 4 +- src/Tracing/Span.php | 16 +- src/Tracing/SpanContext.php | 2 +- src/Tracing/Transaction.php | 36 ++++ src/Tracing/TransactionContext.php | 86 +++++++- src/Tracing/TransactionMetadata.php | 76 +++++++ src/Tracing/TransactionSource.php | 90 ++++++++ src/Transport/DefaultTransportFactory.php | 2 +- src/UserDataBag.php | 39 +++- tests/EventTest.php | 9 + tests/Serializer/PayloadSerializerTest.php | 28 ++- tests/Tracing/DynamicSamplingContextTest.php | 179 ++++++++++++++++ tests/Tracing/GuzzleTracingMiddlewareTest.php | 3 +- tests/Tracing/SpanContextTest.php | 2 +- tests/Tracing/SpanTest.php | 39 ++++ tests/Tracing/TransactionContextTest.php | 105 +++++++++- tests/Tracing/TransactionMetadataTest.php | 90 ++++++++ tests/Tracing/TransactionSourceTest.php | 53 +++++ tests/Tracing/TransactionTest.php | 10 + tests/UserDataBagTest.php | 23 +- 29 files changed, 1179 insertions(+), 38 deletions(-) create mode 100644 src/Tracing/DynamicSamplingContext.php create mode 100644 src/Tracing/TransactionMetadata.php create mode 100644 src/Tracing/TransactionSource.php create mode 100644 tests/Tracing/DynamicSamplingContextTest.php create mode 100644 tests/Tracing/TransactionMetadataTest.php create mode 100644 tests/Tracing/TransactionSourceTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 517d04c55..39f545b2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ## Unreleased -- Expose a function to retrieve the URL of the CSP endpoint (#1378) +- feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) +- feat: Add support for Dynamic Sampling (#1360) + - Add `segment` to `UserDataBag` + - Add `TransactionSource`, to set information about the transaction name + - Deprecate `TransactionContext::fromSentryTrace()` in favor of `TransactionContext::fromHeaders()` ## 3.8.1 (2022-09-21) diff --git a/Makefile b/Makefile index 4a5163ad8..11d78096c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ gc -am .PHONY: test develop: update-submodules - composer install --dev + composer install make setup-git update-submodules: @@ -9,10 +9,10 @@ update-submodules: git submodule update cs: - vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff + vendor/bin/php-cs-fixer fix --verbose --diff cs-dry-run: - vendor/bin/php-cs-fixer fix --config=.php_cs --verbose --diff --dry-run + vendor/bin/php-cs-fixer fix --verbose --diff --dry-run cs-fix: vendor/bin/php-cs-fixer fix diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 83ea011fa..13a94bc31 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -340,6 +340,11 @@ parameters: count: 1 path: src/UserDataBag.php + - + message: "#^Parameter \\#1 \\$segment of method Sentry\\\\UserDataBag\\:\\:setSegment\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + - message: "#^Method Sentry\\\\Util\\\\JSON\\:\\:encode\\(\\) should return string but returns string\\|false\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 93bf65e2c..abad2bb9b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $parsedDsn['host'] @@ -87,6 +87,11 @@ new static() + + + $transaction + + captureException diff --git a/src/Event.php b/src/Event.php index 9834f1b13..fee255bc1 100644 --- a/src/Event.php +++ b/src/Event.php @@ -144,6 +144,14 @@ final class Event */ private $stacktrace; + /** + * A place to stash data which is needed at some point in the SDK's + * event processing pipeline but which shouldn't get sent to Sentry. + * + * @var array + */ + private $sdkMetadata = []; + /** * @var string The Sentry SDK identifier */ @@ -671,6 +679,37 @@ public function getType(): EventType return $this->type; } + /** + * Sets the SDK metadata with the given name. + * + * @param string $name The name that uniquely identifies the SDK metadata + * @param mixed $data The data of the SDK metadata + */ + public function setSdkMetadata(string $name, $data): void + { + $this->sdkMetadata[$name] = $data; + } + + /** + * Gets the SDK metadata. + * + * @return mixed + * + * @psalm-template T of string|null + * + * @psalm-param T $name + * + * @psalm-return (T is string ? mixed : array|null) + */ + public function getSdkMetadata(?string $name = null) + { + if (null !== $name) { + return $this->sdkMetadata[$name] ?? null; + } + + return $this->sdkMetadata; + } + /** * Gets a timestamp representing when the measuring of a transaction started. */ diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 241db37c8..baf3edec1 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -9,7 +9,10 @@ use Sentry\EventType; use Sentry\ExceptionDataBag; use Sentry\Frame; +use Sentry\Options; +use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\Span; +use Sentry\Tracing\TransactionMetadata; use Sentry\Util\JSON; /** @@ -20,6 +23,16 @@ */ final class PayloadSerializer implements PayloadSerializerInterface { + /** + * @var Options The SDK client options + */ + private $options; + + public function __construct(Options $options) + { + $this->options = $options; + } + /** * {@inheritdoc} */ @@ -106,6 +119,7 @@ public function toArray(Event $event): array 'username' => $user->getUsername(), 'email' => $user->getEmail(), 'ip_address' => $user->getIpAddress(), + 'segment' => $user->getSegment(), ]); } @@ -160,6 +174,11 @@ public function toArray(Event $event): array if (EventType::transaction() === $event->getType()) { $result['spans'] = array_values(array_map([$this, 'serializeSpan'], $event->getSpans())); + $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); + + if ($transactionMetadata instanceof TransactionMetadata) { + $result['transaction_info']['source'] = (string) $transactionMetadata->getSource(); + } } $stacktrace = $event->getStacktrace(); @@ -175,17 +194,33 @@ public function toArray(Event $event): array private function serializeAsEnvelope(Event $event): string { - $envelopeHeader = JSON::encode([ + // @see https://develop.sentry.dev/sdk/envelopes/#envelope-headers + $envelopeHeader = [ 'event_id' => (string) $event->getId(), 'sent_at' => gmdate('Y-m-d\TH:i:s\Z'), - ]); + 'dsn' => (string) $this->options->getDsn(), + 'sdk' => [ + 'name' => $event->getSdkIdentifier(), + 'version' => $event->getSdkVersion(), + ], + ]; + + $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); + + if ($dynamicSamplingContext instanceof DynamicSamplingContext) { + $entries = $dynamicSamplingContext->getEntries(); - $itemHeader = JSON::encode([ + if (!empty($entries)) { + $envelopeHeader['trace'] = $entries; + } + } + + $itemHeader = [ 'type' => (string) $event->getType(), 'content_type' => 'application/json', - ]); + ]; - return sprintf("%s\n%s\n%s", $envelopeHeader, $itemHeader, $this->serializeAsEvent($event)); + return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $this->serializeAsEvent($event)); } /** diff --git a/src/State/Hub.php b/src/State/Hub.php index e188bb7db..213114686 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -234,9 +234,14 @@ public function startTransaction(TransactionContext $context, array $customSampl $tracesSampler = $options->getTracesSampler(); if (null === $transaction->getSampled()) { - $sampleRate = null !== $tracesSampler - ? $tracesSampler($samplingContext) - : $this->getSampleRate($samplingContext->getParentSampled(), $options->getTracesSampleRate()); + if (null !== $tracesSampler) { + $sampleRate = $tracesSampler($samplingContext); + } else { + $sampleRate = $this->getSampleRate( + $samplingContext->getParentSampled(), + $options->getTracesSampleRate() + ); + } if (!$this->isValidSampleRate($sampleRate)) { $transaction->setSampled(false); @@ -244,6 +249,8 @@ public function startTransaction(TransactionContext $context, array $customSampl return $transaction; } + $transaction->getMetadata()->setSamplingRate($sampleRate); + if (0.0 === $sampleRate) { $transaction->setSampled(false); diff --git a/src/State/Scope.php b/src/State/Scope.php index a708f8fdc..d30257cc0 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -175,6 +175,14 @@ public function setExtras(array $extras): self return $this; } + /** + * Get the user context. + */ + public function getUser(): ?UserDataBag + { + return $this->user; + } + /** * Merges the given data in the user context. * diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php new file mode 100644 index 000000000..7fab07005 --- /dev/null +++ b/src/Tracing/DynamicSamplingContext.php @@ -0,0 +1,196 @@ + The dsc entries + */ + private $entries = []; + + /** + * @var bool Indicates if the dsc is mutable or immutable + */ + private $isFrozen = false; + + /** + * Construct a new dsc object. + */ + private function __construct() + { + } + + /** + * Set a new key value pair on the dsc. + * + * @param string $key the list member key + * @param string $value the list member value + */ + public function set(string $key, string $value): void + { + if ($this->isFrozen) { + return; + } + + $this->entries[$key] = $value; + } + + /** + * Check if a key value pair is set on the dsc. + * + * @param string $key the list member key + */ + public function has(string $key): bool + { + return isset($this->entries[$key]); + } + + /** + * Get a value from the dsc. + * + * @param string $key the list member key + * @param string|null $default the default value to return if no value exists + */ + public function get(string $key, ?string $default = null): ?string + { + return $this->entries[$key] ?? $default; + } + + /** + * Mark the dsc as frozen. + */ + public function freeze(): void + { + $this->isFrozen = true; + } + + /** + * Indicates that the dsc is frozen and cannot be mutated. + */ + public function isFrozen(): bool + { + return $this->isFrozen; + } + + /** + * Check if there are any entries set. + */ + public function hasEntries(): bool + { + return !empty($this->entries); + } + + /** + * Gets the dsc entries. + * + * @return array + */ + public function getEntries(): array + { + return $this->entries; + } + + /** + * Parse the baggage header. + * + * @param string $header the baggage header contents + */ + public static function fromHeader(string $header): self + { + $samplingContext = new self(); + + foreach (explode(',', $header) as $listMember) { + if (empty(trim($listMember))) { + continue; + } + + $keyValueAndProperties = explode(';', $listMember, 2); + $keyValue = trim($keyValueAndProperties[0]); + + if (!str_contains($keyValue, '=')) { + continue; + } + + [$key, $value] = explode('=', $keyValue, 2); + + if (str_starts_with($key, self::SENTRY_ENTRY_PREFIX)) { + $samplingContext->set(rawurldecode(mb_substr($key, mb_strlen(self::SENTRY_ENTRY_PREFIX))), rawurldecode($value)); + } + } + + // Once we receive a baggage header with Sentry entries from an upstream SDK, + // we freeze the contents so it cannot be mutated anymore by this SDK. + // It should only be propagated to the next downstream SDK or the Sentry server itself. + $samplingContext->isFrozen = $samplingContext->hasEntries(); + + return $samplingContext; + } + + /** + * Create a dsc object. + * + * @see https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#baggage-header + */ + public static function fromTransaction(Transaction $transaction, HubInterface $hub): self + { + $samplingContext = new self(); + $samplingContext->set('trace_id', (string) $transaction->getTraceId()); + $samplingContext->set('sample_rate', (string) $transaction->getMetaData()->getSamplingRate()); + $samplingContext->set('transaction', $transaction->getName()); + + $client = $hub->getClient(); + + if (null !== $client) { + $options = $client->getOptions(); + + if (null !== $options->getDsn() && null !== $options->getDsn()->getPublicKey()) { + $samplingContext->set('public_key', $options->getDsn()->getPublicKey()); + } + + if (null !== $options->getRelease()) { + $samplingContext->set('release', $options->getRelease()); + } + + if (null !== $options->getEnvironment()) { + $samplingContext->set('environment', $options->getEnvironment()); + } + } + + $hub->configureScope(static function (Scope $scope) use ($samplingContext): void { + if (null !== $scope->getUser() && null !== $scope->getUser()->getSegment()) { + $samplingContext->set('user_segment', $scope->getUser()->getSegment()); + } + }); + + $samplingContext->freeze(); + + return $samplingContext; + } + + /** + * Serialize the dsc as a string. + */ + public function __toString(): string + { + $result = []; + + foreach ($this->entries as $key => $value) { + $result[] = rawurlencode(self::SENTRY_ENTRY_PREFIX . $key) . '=' . rawurlencode($value); + } + + return implode(',', $result); + } +} diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index ddbc29195..2d074e54b 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -34,7 +34,9 @@ public static function trace(?HubInterface $hub = null): Closure $childSpan = $span->startChild($spanContext); - $request = $request->withHeader('sentry-trace', $childSpan->toTraceparent()); + $request = $request + ->withHeader('sentry-trace', $childSpan->toTraceparent()) + ->withHeader('baggage', $childSpan->toBaggage()); $handlerPromiseCallback = static function ($responseOrException) use ($hub, $request, $childSpan) { // We finish the span (which means setting the span end timestamp) first to ensure the measured time diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 2095596df..1b503482f 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -432,7 +432,7 @@ public function getTransaction(): ?Transaction } /** - * Returns a string that can be used for the `sentry-trace` header. + * Returns a string that can be used for the `sentry-trace` header & meta tag. */ public function toTraceparent(): string { @@ -444,4 +444,18 @@ public function toTraceparent(): string return sprintf('%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled); } + + /** + * Returns a string that can be used for the `baggage` header & meta tag. + */ + public function toBaggage(): string + { + $transaction = $this->getTransaction(); + + if (null !== $transaction) { + return (string) $transaction->getDynamicSamplingContext(); + } + + return ''; + } } diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 1fcaaaf51..0aa336ee2 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -199,7 +199,7 @@ public function setEndTimestamp(?float $endTimestamp): void */ public static function fromTraceparent(string $header) { - @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.', __METHOD__), \E_USER_DEPRECATED); + @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromHeaders() instead.', __METHOD__), \E_USER_DEPRECATED); $context = new static(); diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index d3cfb0013..4bc501b5f 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -24,6 +24,16 @@ final class Transaction extends Span */ private $name; + /** + * @var Transaction The transaction + */ + protected $transaction; + + /** + * @var TransactionMetadata + */ + protected $metadata; + /** * Span constructor. * @@ -38,6 +48,7 @@ public function __construct(TransactionContext $context, ?HubInterface $hub = nu $this->hub = $hub ?? SentrySdk::getCurrentHub(); $this->name = $context->getName(); + $this->metadata = $context->getMetadata(); $this->transaction = $this; } @@ -59,6 +70,29 @@ public function setName(string $name): void $this->name = $name; } + /** + * Gets the transaction metadata. + */ + public function getMetadata(): TransactionMetadata + { + return $this->metadata; + } + + /** + * Gets the transaction dynamic sampling context. + */ + public function getDynamicSamplingContext(): DynamicSamplingContext + { + if (null !== $this->metadata->getDynamicSamplingContext()) { + return $this->metadata->getDynamicSamplingContext(); + } + + $samplingContext = DynamicSamplingContext::fromTransaction($this->transaction, $this->hub); + $this->getMetadata()->setDynamicSamplingContext($samplingContext); + + return $samplingContext; + } + /** * Attaches a {@see SpanRecorder} to the transaction itself. * @@ -106,6 +140,8 @@ public function finish(?float $endTimestamp = null): ?EventId $event->setTags($this->tags); $event->setTransaction($this->name); $event->setContext('trace', $this->getTraceContext()); + $event->setSdkMetadata('dynamic_sampling_context', $this->getDynamicSamplingContext()); + $event->setSdkMetadata('transaction_metadata', $this->getMetadata()); return $this->hub->captureEvent($event); } diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index 765fdb24b..edee415ea 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -20,16 +20,26 @@ final class TransactionContext extends SpanContext */ private $parentSampled; + /** + * @var TransactionMetadata The transaction metadata + */ + private $metadata; + /** * Constructor. * - * @param string $name The name of the transaction - * @param bool|null $parentSampled The parent's sampling decision + * @param string $name The name of the transaction + * @param bool|null $parentSampled The parent's sampling decision + * @param TransactionMetadata|null $metadata The transaction metadata */ - public function __construct(string $name = self::DEFAULT_NAME, ?bool $parentSampled = null) - { + public function __construct( + string $name = self::DEFAULT_NAME, + ?bool $parentSampled = null, + ?TransactionMetadata $metadata = null + ) { $this->name = $name; $this->parentSampled = $parentSampled; + $this->metadata = $metadata ?? new TransactionMetadata(); } /** @@ -68,14 +78,32 @@ public function setParentSampled(?bool $parentSampled): void $this->parentSampled = $parentSampled; } + /** + * Gets the transaction metadata. + */ + public function getMetadata(): TransactionMetadata + { + return $this->metadata; + } + + /** + * Sets the transaction metadata. + * + * @param TransactionMetadata $metadata The transaction metadata + */ + public function setMetadata(TransactionMetadata $metadata): void + { + $this->metadata = $metadata; + } + /** * Returns a context populated with the data of the given header. * * @param string $header The sentry-trace header from the request * - * @return self + * @deprecated since version 3.9, to be removed in 4.0 */ - public static function fromSentryTrace(string $header) + public static function fromSentryTrace(string $header): self { $context = new self(); @@ -97,4 +125,50 @@ public static function fromSentryTrace(string $header) return $context; } + + /** + * Returns a context populated with the data of the given headers. + * + * @param string $sentryTraceHeader The sentry-trace header from an incoming request + * @param string $baggageHeader The baggage header from an incoming request + */ + public static function fromHeaders(string $sentryTraceHeader, string $baggageHeader): self + { + $context = new self(); + $hasSentryTrace = false; + + if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTraceHeader, $matches)) { + if (!empty($matches['trace_id'])) { + $context->traceId = new TraceId($matches['trace_id']); + $hasSentryTrace = true; + } + + if (!empty($matches['span_id'])) { + $context->parentSpanId = new SpanId($matches['span_id']); + $hasSentryTrace = true; + } + + if (isset($matches['sampled'])) { + $context->parentSampled = '1' === $matches['sampled']; + $hasSentryTrace = true; + } + } + + $samplingContext = DynamicSamplingContext::fromHeader($baggageHeader); + + if ($hasSentryTrace && !$samplingContext->hasEntries()) { + // The request comes from an old SDK which does not support Dynamic Sampling. + // Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries. + $samplingContext->freeze(); + $context->getMetadata()->setDynamicSamplingContext($samplingContext); + } + + if ($hasSentryTrace && $samplingContext->hasEntries()) { + // The baggage header contains Dynamic Sampling Context data from an upstream SDK. + // Propagate this Dynamic Sampling Context. + $context->getMetadata()->setDynamicSamplingContext($samplingContext); + } + + return $context; + } } diff --git a/src/Tracing/TransactionMetadata.php b/src/Tracing/TransactionMetadata.php new file mode 100644 index 000000000..a217fc831 --- /dev/null +++ b/src/Tracing/TransactionMetadata.php @@ -0,0 +1,76 @@ +samplingRate = $samplingRate; + $this->dynamicSamplingContext = $dynamicSamplingContext; + $this->source = $source ?? TransactionSource::custom(); + } + + /** + * @return float|int|null + */ + public function getSamplingRate() + { + return $this->samplingRate; + } + + /** + * @param float|int|null $samplingRate + */ + public function setSamplingRate($samplingRate): void + { + $this->samplingRate = $samplingRate; + } + + public function getDynamicSamplingContext(): ?DynamicSamplingContext + { + return $this->dynamicSamplingContext; + } + + public function setDynamicSamplingContext(?DynamicSamplingContext $dynamicSamplingContext): void + { + $this->dynamicSamplingContext = $dynamicSamplingContext; + } + + public function getSource(): ?TransactionSource + { + return $this->source; + } + + public function setSource(?TransactionSource $source): void + { + $this->source = $source; + } +} diff --git a/src/Tracing/TransactionSource.php b/src/Tracing/TransactionSource.php new file mode 100644 index 000000000..e751edd62 --- /dev/null +++ b/src/Tracing/TransactionSource.php @@ -0,0 +1,90 @@ + A list of cached enum instances + */ + private static $instances = []; + + private function __construct(string $value) + { + $this->value = $value; + } + + /** + * User-defined name. + */ + public static function custom(): self + { + return self::getInstance('custom'); + } + + /** + * Raw URL, potentially containing identifiers. + */ + public static function url(): self + { + return self::getInstance('url'); + } + + /** + * Parametrized URL / route. + */ + public static function route(): self + { + return self::getInstance('route'); + } + + /** + * Name of the view handling the request. + */ + public static function view(): self + { + return self::getInstance('view'); + } + + /** + * Named after a software component, such as a function or class name. + */ + public static function component(): self + { + return self::getInstance('component'); + } + + /** + * Name of a background task (e.g. a Celery task). + */ + public static function task(): self + { + return self::getInstance('task'); + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php index a4b56ecc4..2d0c1f0b8 100644 --- a/src/Transport/DefaultTransportFactory.php +++ b/src/Transport/DefaultTransportFactory.php @@ -67,7 +67,7 @@ public function create(Options $options): TransportInterface $this->httpClientFactory->create($options), $this->streamFactory, $this->requestFactory, - new PayloadSerializer(), + new PayloadSerializer($options), $this->logger ); } diff --git a/src/UserDataBag.php b/src/UserDataBag.php index ac1a3973c..fa043b571 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -6,6 +6,8 @@ /** * This class stores the information about the authenticated user for a request. + * + * @see https://develop.sentry.dev/sdk/event-payloads/types/#user */ final class UserDataBag { @@ -29,6 +31,11 @@ final class UserDataBag */ private $username; + /** + * @var string|null the user segment, for apps that divide users in user segments + */ + private $segment; + /** * @var array Additional data */ @@ -39,12 +46,18 @@ final class UserDataBag * * @param string|int|null $id */ - public function __construct($id = null, ?string $email = null, ?string $ipAddress = null, ?string $username = null) - { + public function __construct( + $id = null, + ?string $email = null, + ?string $ipAddress = null, + ?string $username = null, + ?string $segment = null + ) { $this->setId($id); $this->setEmail($email); $this->setIpAddress($ipAddress); $this->setUsername($username); + $this->setSegment($segment); } /** @@ -90,6 +103,9 @@ public static function createFromArray(array $data): self case 'username': $instance->setUsername($value); break; + case 'segment': + $instance->setSegment($value); + break; default: $instance->setMetadata($field, $value); break; @@ -159,6 +175,24 @@ public function setEmail(?string $email): void $this->email = $email; } + /** + * Gets the segement of the user. + */ + public function getSegment(): ?string + { + return $this->segment; + } + + /** + * Sets the segment of the user. + * + * @param string|null $segment The segment + */ + public function setSegment(?string $segment): void + { + $this->segment = $segment; + } + /** * Gets the ip address of the user. */ @@ -225,6 +259,7 @@ public function merge(self $other): self $this->email = $other->email; $this->ipAddress = $other->ipAddress; $this->username = $other->username; + $this->segment = $other->segment; $this->metadata = array_merge($this->metadata, $other->metadata); return $this; diff --git a/tests/EventTest.php b/tests/EventTest.php index dcef7802c..116d6d239 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -116,4 +116,13 @@ public function testSetAndRemoveTag(): void $this->assertEmpty($event->getTags()); } + + public function testSetGetSdkMetadata(): void + { + $event = Event::createEvent(); + $event->setSdkMetadata('foo', ['bar', 'baz']); + + $this->assertSame(['bar', 'baz'], $event->getSdkMetadata('foo')); + $this->assertSame(['foo' => ['bar', 'baz']], $event->getSdkMetadata()); + } } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 55c9a5ef0..73f60a36c 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -15,13 +15,16 @@ use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; +use Sentry\Options; use Sentry\Serializer\PayloadSerializer; use Sentry\Severity; use Sentry\Stacktrace; +use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\SpanId; use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TraceId; +use Sentry\Tracing\TransactionMetadata; use Sentry\UserDataBag; use Symfony\Bridge\PhpUnit\ClockMock; @@ -37,7 +40,9 @@ final class PayloadSerializerTest extends TestCase protected function setUp(): void { - $this->serializer = new PayloadSerializer(); + $this->serializer = new PayloadSerializer(new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + ])); } /** @@ -104,6 +109,7 @@ public function serializeDataProvider(): iterable 'username' => 'my_user', 'email' => 'foo@example.com', 'ip_address' => '127.0.0.1', + 'segment' => 'my_segment', ])); $event->setTags([ @@ -215,7 +221,8 @@ public function serializeDataProvider(): iterable "id": "unique_id", "username": "my_user", "email": "foo@example.com", - "ip_address": "127.0.0.1" + "ip_address": "127.0.0.1", + "segment": "my_segment" }, "contexts": { "os": { @@ -421,7 +428,7 @@ public function serializeDataProvider(): iterable yield [ $event, <<setSdkMetadata('dynamic_sampling_context', DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-sample_rate=1')); + $event->setSdkMetadata('transaction_metadata', new TransactionMetadata()); + + yield [ + $event, + <<setStacktrace(new Stacktrace([new Frame(null, '', 0)])); diff --git a/tests/Tracing/DynamicSamplingContextTest.php b/tests/Tracing/DynamicSamplingContextTest.php new file mode 100644 index 000000000..36a689eb7 --- /dev/null +++ b/tests/Tracing/DynamicSamplingContextTest.php @@ -0,0 +1,179 @@ +assertSame($expectedTraceId, $samplingContext->get('trace_id')); + $this->assertSame($expectedPublicKey, $samplingContext->get('public_key')); + $this->assertSame($expectedSampleRate, $samplingContext->get('sample_rate')); + $this->assertSame($expectedRelease, $samplingContext->get('release')); + $this->assertSame($expectedEnvironment, $samplingContext->get('environment')); + $this->assertSame($expectedUserSegment, $samplingContext->get('user_segment')); + $this->assertSame($expectedTransaction, $samplingContext->get('transaction')); + } + + public function fromHeaderDataProvider(): \Generator + { + yield [ + '', + null, + null, + null, + null, + null, + null, + null, + ]; + + yield [ + 'sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1', + 'd49d9bf66f13450b81f65bc51cf49c03', + 'public', + '1', + null, + null, + null, + null, + ]; + + yield [ + 'sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1,sentry-release=1.0.0,sentry-environment=test,sentry-user_segment=my_segment,sentry-transaction=', + 'd49d9bf66f13450b81f65bc51cf49c03', + 'public', + '1', + '1.0.0', + 'test', + 'my_segment', + '', + ]; + } + + public function testFromTransaction(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + 'release' => '1.0.0', + 'environment' => 'test', + ])); + + $user = new UserDataBag(); + $user->setSegment('my_segment'); + + $scope = new Scope(); + $scope->setUser($user); + + $hub = new Hub($client, $scope); + + $transactionContext = new TransactionContext(); + $transactionContext->setName('foo'); + + $transaction = new Transaction($transactionContext, $hub); + + $samplingContext = DynamicSamplingContext::fromTransaction($transaction, $hub); + + $this->assertSame((string) $transaction->getTraceId(), $samplingContext->get('trace_id')); + $this->assertSame((string) $transaction->getMetaData()->getSamplingRate(), $samplingContext->get('sample_rate')); + $this->assertSame('foo', $samplingContext->get('transaction')); + $this->assertSame('public', $samplingContext->get('public_key')); + $this->assertSame('1.0.0', $samplingContext->get('release')); + $this->assertSame('test', $samplingContext->get('environment')); + $this->assertSame('my_segment', $samplingContext->get('user_segment')); + $this->assertTrue($samplingContext->isFrozen()); + } + + /** + * @dataProvider getEntriesDataProvider + */ + public function testGetEntries(DynamicSamplingContext $samplingContext, array $expectedDynamicSamplingContext): void + { + $this->assertSame($expectedDynamicSamplingContext, $samplingContext->getEntries()); + } + + public function getEntriesDataProvider(): \Generator + { + yield [ + DynamicSamplingContext::fromHeader(''), + [], + ]; + + yield [ + DynamicSamplingContext::fromHeader('sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1'), + [ + 'trace_id' => 'd49d9bf66f13450b81f65bc51cf49c03', + 'public_key' => 'public', + 'sample_rate' => '1', + ], + ]; + + yield [ + DynamicSamplingContext::fromHeader('sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1,foo=bar;foo;bar;bar=baz'), + [ + 'trace_id' => 'd49d9bf66f13450b81f65bc51cf49c03', + 'public_key' => 'public', + 'sample_rate' => '1', + ], + ]; + } + + /** + * @dataProvider toStringDataProvider + */ + public function testToString(DynamicSamplingContext $samplingContext, string $expectedString): void + { + $this->assertSame($expectedString, (string) $samplingContext); + } + + public function toStringDataProvider(): \Generator + { + yield [ + DynamicSamplingContext::fromHeader(''), + '', + ]; + + yield [ + DynamicSamplingContext::fromHeader('sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1'), + 'sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1', + ]; + + yield [ + DynamicSamplingContext::fromHeader('sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1,foo=bar;foo;bar;bar=baz'), + 'sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1', + ]; + + yield [ + DynamicSamplingContext::fromHeader('foo=bar;foo;bar;bar=baz'), + '', + ]; + } +} diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 7dc3bed87..c5546b121 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -61,7 +61,7 @@ public function testTraceDoesNothingIfSpanIsNotSet(): void public function testTrace(Request $request, $expectedPromiseResult, array $expectedBreadcrumbData): void { $client = $this->createMock(ClientInterface::class); - $client->expects($this->exactly(2)) + $client->expects($this->exactly(3)) ->method('getOptions') ->willReturn(new Options(['traces_sample_rate' => 1])); @@ -106,6 +106,7 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec $middleware = GuzzleTracingMiddleware::trace($hub); $function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface { $this->assertNotEmpty($request->getHeader('sentry-trace')); + $this->assertNotEmpty($request->getHeader('baggage')); if ($expectedPromiseResult instanceof \Throwable) { return new RejectedPromise($expectedPromiseResult); } diff --git a/tests/Tracing/SpanContextTest.php b/tests/Tracing/SpanContextTest.php index d5ee39046..e308d9591 100644 --- a/tests/Tracing/SpanContextTest.php +++ b/tests/Tracing/SpanContextTest.php @@ -21,7 +21,7 @@ final class SpanContextTest extends TestCase */ public function testFromTraceparent(string $header, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedSampled): void { - $this->expectDeprecation('The Sentry\\Tracing\\SpanContext::fromTraceparent() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.'); + $this->expectDeprecation('The Sentry\\Tracing\\SpanContext::fromTraceparent() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromHeaders() instead.'); $spanContext = SpanContext::fromTraceparent($header); diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index df039f7af..7cfce7c43 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -9,6 +9,8 @@ use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; +use Sentry\Tracing\Transaction; +use Sentry\Tracing\TransactionContext; use Symfony\Bridge\PhpUnit\ClockMock; /** @@ -101,4 +103,41 @@ public function toTraceparentDataProvider(): iterable '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', ]; } + + /** + * @dataProvider toBaggageDataProvider + */ + public function testToBaggage(string $baggageHeader, string $expectedValue): void + { + $context = TransactionContext::fromHeaders( + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + $baggageHeader + ); + $transaction = new Transaction($context); + + $this->assertSame($expectedValue, $transaction->toBaggage()); + } + + public function toBaggageDataProvider(): iterable + { + yield [ + '', + '', + ]; + + yield [ + 'foo=bar,bar=baz', + '', + ]; + + yield [ + 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', + 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', + ]; + + yield [ + 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1,foo=bar,bar=baz', + 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', + ]; + } } diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index 1a1387cea..c65952344 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -5,79 +5,170 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; +use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; use Sentry\Tracing\TransactionContext; +use Sentry\Tracing\TransactionMetadata; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; final class TransactionContextTest extends TestCase { + use ExpectDeprecationTrait; + public function testGettersAndSetters(): void { $transactionContext = new TransactionContext(); + $transactionMetadata = new TransactionMetadata(); $this->assertSame('', $transactionContext->getName()); $this->assertNull($transactionContext->getParentSampled()); $transactionContext->setName('foo'); $transactionContext->setParentSampled(true); + $transactionContext->setMetadata($transactionMetadata); $this->assertSame('foo', $transactionContext->getName()); $this->assertTrue($transactionContext->getParentSampled()); + $this->assertSame($transactionMetadata, $transactionContext->getMetadata()); } /** * @dataProvider fromSentryTraceDataProvider + * + * @group legacy */ public function testFromTraceparent(string $header, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedParentSampled): void { $spanContext = TransactionContext::fromSentryTrace($header); - if (null !== $expectedSpanId) { - $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); - } + $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); + $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); + $this->assertSame($expectedParentSampled, $spanContext->getParentSampled()); + } + + public function fromSentryTraceDataProvider(): iterable + { + yield [ + '0', + null, + null, + false, + ]; + + yield [ + '1', + null, + null, + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + false, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + null, + ]; + } - if (null !== $expectedTraceId) { - $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); - } + /** + * @dataProvider fromHeadersDataProvider + */ + public function testFromHeaders(string $sentryTraceHeader, string $baggageHeader, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedParentSampled, ?string $expectedDynamicSamplingContextClass, ?bool $expectedDynamicSamplingContextFrozen) + { + $spanContext = TransactionContext::fromHeaders($sentryTraceHeader, $baggageHeader); + $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); + $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); $this->assertSame($expectedParentSampled, $spanContext->getParentSampled()); + $this->assertInstanceOf($expectedDynamicSamplingContextClass, $spanContext->getMetadata()->getDynamicSamplingContext()); + $this->assertSame($expectedDynamicSamplingContextFrozen, $spanContext->getMetadata()->getDynamicSamplingContext()->isFrozen()); } - public function fromSentryTraceDataProvider(): iterable + public function fromHeadersDataProvider(): iterable { yield [ '0', + '', null, null, false, + DynamicSamplingContext::class, + true, ]; yield [ '1', + '', null, null, true, + DynamicSamplingContext::class, + true, ]; yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', + '', new SpanId('566e3688a61d4bc8'), new TraceId('566e3688a61d4bc888951642d6f14a19'), false, + DynamicSamplingContext::class, + true, ]; yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + '', new SpanId('566e3688a61d4bc8'), new TraceId('566e3688a61d4bc888951642d6f14a19'), true, + DynamicSamplingContext::class, + true, ]; yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', + '', new SpanId('566e3688a61d4bc8'), new TraceId('566e3688a61d4bc888951642d6f14a19'), null, + DynamicSamplingContext::class, + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + true, + DynamicSamplingContext::class, + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + 'foo=bar', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + true, + DynamicSamplingContext::class, + true, ]; } } diff --git a/tests/Tracing/TransactionMetadataTest.php b/tests/Tracing/TransactionMetadataTest.php new file mode 100644 index 000000000..3852b017f --- /dev/null +++ b/tests/Tracing/TransactionMetadataTest.php @@ -0,0 +1,90 @@ +assertSame($expectedSamplingRate, $transactionMetadata->getSamplingRate()); + $this->assertSame($expectedDynamicSamplingContext, $transactionMetadata->getDynamicSamplingContext()); + $this->assertSame($expectedSource, $transactionMetadata->getSource()); + } + + public function constructorDataProvider(): \Generator + { + $samplingContext = DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-sample_rate=1'); + $source = TransactionSource::custom(); + + yield [ + new TransactionMetadata(), + null, + null, + $source, + ]; + + yield [ + new TransactionMetadata(1), + 1, + null, + $source, + ]; + + yield [ + new TransactionMetadata( + null, + $samplingContext + ), + null, + $samplingContext, + $source, + ]; + + yield [ + new TransactionMetadata( + null, + null, + $source + ), + null, + null, + $source, + ]; + + yield [ + new TransactionMetadata( + 0.5, + $samplingContext, + $source + ), + 0.5, + $samplingContext, + TransactionSource::custom(), + ]; + } + + public function testGettersAndSetters(): void + { + $samplingContext = DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-sample_rate=1'); + $source = TransactionSource::task(); + + $transactionMetadata = new TransactionMetadata(); + $transactionMetadata->setSamplingRate(0); + $transactionMetadata->setDynamicSamplingContext($samplingContext); + $transactionMetadata->setSource($source); + + $this->assertSame(0, $transactionMetadata->getSamplingRate()); + $this->assertSame($samplingContext, $transactionMetadata->getDynamicSamplingContext()); + $this->assertSame($source, $transactionMetadata->getSource()); + } +} diff --git a/tests/Tracing/TransactionSourceTest.php b/tests/Tracing/TransactionSourceTest.php new file mode 100644 index 000000000..508614bf0 --- /dev/null +++ b/tests/Tracing/TransactionSourceTest.php @@ -0,0 +1,53 @@ +assertSame('custom', (string) $transactionSource); + } + + public function testUrl(): void + { + $transactionSource = TransactionSource::url(); + + $this->assertSame('url', (string) $transactionSource); + } + + public function testRoute(): void + { + $transactionSource = TransactionSource::route(); + + $this->assertSame('route', (string) $transactionSource); + } + + public function testView(): void + { + $transactionSource = TransactionSource::view(); + + $this->assertSame('view', (string) $transactionSource); + } + + public function testComponent(): void + { + $transactionSource = TransactionSource::component(); + + $this->assertSame('component', (string) $transactionSource); + } + + public function testTask(): void + { + $transactionSource = TransactionSource::task(); + + $this->assertSame('task', (string) $transactionSource); + } +} diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index 31fb419cf..d7843d5c7 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -5,9 +5,11 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; +use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventId; use Sentry\EventType; +use Sentry\Options; use Sentry\State\HubInterface; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; @@ -30,7 +32,15 @@ public function testFinish(): void $transactionContext->setSampled(true); $transactionContext->setStartTimestamp(1600640865); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options()); + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('getClient') + ->willReturn($client); $transaction = new Transaction($transactionContext, $hub); $transaction->initSpanRecorder(); diff --git a/tests/UserDataBagTest.php b/tests/UserDataBagTest.php index e4ca87a1c..1a34bb8e7 100644 --- a/tests/UserDataBagTest.php +++ b/tests/UserDataBagTest.php @@ -16,19 +16,21 @@ public function testGettersAndSetters(): void $userDataBag->setIpAddress('127.0.0.1'); $userDataBag->setEmail('foo@example.com'); $userDataBag->setUsername('my_user'); + $userDataBag->setSegment('my_segment'); $userDataBag->setMetadata('subscription', 'basic'); $this->assertSame('unique_id', $userDataBag->getId()); $this->assertSame('127.0.0.1', $userDataBag->getIpAddress()); $this->assertSame('foo@example.com', $userDataBag->getEmail()); $this->assertSame('my_user', $userDataBag->getUsername()); + $this->assertSame('my_segment', $userDataBag->getSegment()); $this->assertSame(['subscription' => 'basic'], $userDataBag->getMetadata()); } /** * @dataProvider createFromArrayDataProvider */ - public function testCreateFromArray(array $data, $expectedId, ?string $expectedIpAddress, ?string $expectedEmail, ?string $expectedUsername, array $expectedMetadata): void + public function testCreateFromArray(array $data, $expectedId, ?string $expectedIpAddress, ?string $expectedEmail, ?string $expectedUsername, ?string $expectedSegment, array $expectedMetadata): void { $userDataBag = UserDataBag::createFromArray($data); @@ -36,6 +38,7 @@ public function testCreateFromArray(array $data, $expectedId, ?string $expectedI $this->assertSame($expectedIpAddress, $userDataBag->getIpAddress()); $this->assertSame($expectedEmail, $userDataBag->getEmail()); $this->assertSame($expectedUsername, $userDataBag->getUsername()); + $this->assertSame($expectedSegment, $userDataBag->getSegment()); $this->assertSame($expectedMetadata, $userDataBag->getMetadata()); } @@ -47,6 +50,7 @@ public function createFromArrayDataProvider(): iterable null, null, null, + null, [], ]; @@ -56,6 +60,7 @@ public function createFromArrayDataProvider(): iterable null, null, null, + null, [], ]; @@ -65,6 +70,7 @@ public function createFromArrayDataProvider(): iterable '127.0.0.1', null, null, + null, [], ]; @@ -74,6 +80,7 @@ public function createFromArrayDataProvider(): iterable null, 'foo@example.com', null, + null, [], ]; @@ -83,6 +90,17 @@ public function createFromArrayDataProvider(): iterable null, null, 'my_user', + null, + [], + ]; + + yield [ + ['segment' => 'my_segment'], + null, + null, + null, + null, + 'my_segment', [], ]; @@ -92,6 +110,7 @@ public function createFromArrayDataProvider(): iterable null, null, null, + null, ['subscription' => 'basic'], ]; } @@ -176,6 +195,7 @@ public function testMerge(): void $userDataBagToMergeWith = UserDataBag::createFromUserIpAddress('127.0.0.1'); $userDataBagToMergeWith->setEmail('foo@example.com'); $userDataBagToMergeWith->setUsername('my_user'); + $userDataBagToMergeWith->setSegment('my_segment'); $userDataBagToMergeWith->setMetadata('subscription', 'lifetime'); $userDataBagToMergeWith->setMetadata('subscription_expires_at', '2020-08-20'); @@ -185,6 +205,7 @@ public function testMerge(): void $this->assertSame('127.0.0.1', $userDataBag->getIpAddress()); $this->assertSame('foo@example.com', $userDataBag->getEmail()); $this->assertSame('my_user', $userDataBag->getUsername()); + $this->assertSame('my_segment', $userDataBag->getSegment()); $this->assertSame(['subscription' => 'lifetime', 'subscription_expires_at' => '2020-08-20'], $userDataBag->getMetadata()); } } From 09dc734ef631517a0e16960455e44e1822738fd0 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 29 Sep 2022 10:55:16 +0200 Subject: [PATCH 0767/1161] feat: Add setSource to TransactionContext (#1382) --- CHANGELOG.md | 2 +- src/Tracing/TransactionContext.php | 10 ++++++++++ tests/Tracing/TransactionContextTest.php | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f545b2a..24a309ea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ - feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) - feat: Add support for Dynamic Sampling (#1360) - Add `segment` to `UserDataBag` - - Add `TransactionSource`, to set information about the transaction name + - Add `TransactionSource`, to set information about the transaction name via `TransactionContext::setSource()` (#1382) - Deprecate `TransactionContext::fromSentryTrace()` in favor of `TransactionContext::fromHeaders()` ## 3.8.1 (2022-09-21) diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index edee415ea..13083ed35 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -96,6 +96,16 @@ public function setMetadata(TransactionMetadata $metadata): void $this->metadata = $metadata; } + /** + * Sets the transaction source. + * + * @param TransactionSource $transactionSource The transaction source + */ + public function setSource(TransactionSource $transactionSource): void + { + $this->metadata->setSource($transactionSource); + } + /** * Returns a context populated with the data of the given header. * diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index c65952344..8fdea8a0b 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -10,6 +10,7 @@ use Sentry\Tracing\TraceId; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionMetadata; +use Sentry\Tracing\TransactionSource; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; final class TransactionContextTest extends TestCase @@ -20,6 +21,7 @@ public function testGettersAndSetters(): void { $transactionContext = new TransactionContext(); $transactionMetadata = new TransactionMetadata(); + $transactionSource = TransactionSource::custom(); $this->assertSame('', $transactionContext->getName()); $this->assertNull($transactionContext->getParentSampled()); @@ -27,10 +29,12 @@ public function testGettersAndSetters(): void $transactionContext->setName('foo'); $transactionContext->setParentSampled(true); $transactionContext->setMetadata($transactionMetadata); + $transactionContext->setSource($transactionSource); $this->assertSame('foo', $transactionContext->getName()); $this->assertTrue($transactionContext->getParentSampled()); $this->assertSame($transactionMetadata, $transactionContext->getMetadata()); + $this->assertSame($transactionSource, $transactionContext->getMetadata()->getSource()); } /** From 0d33d2a9e6e4bb74fbfa3052bc7929d2e179cf39 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 30 Sep 2022 10:30:12 +0200 Subject: [PATCH 0768/1161] ci: Cancel in progress ci runs when there is a new commit pushed. (#1386) --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fbba9743..acbf74fff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,11 @@ on: - master - develop - release/** - +# Cancel in progress workflows on pull_requests. +# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true jobs: tests: name: Tests From 8fb9516c799d5d78b260077be31a48d6b0d744ab Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 3 Oct 2022 08:56:38 +0200 Subject: [PATCH 0769/1161] meta: Introduce LOGAF (#1387) --- CONTRIBUTING.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 095a3813a..75c3a4260 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,23 @@ If you feel that you can fix or implement it yourself, please read on to learn h We will review your pull request as soon as possible. Thank you for contributing! +## PR reviews + +For feedback in PRs, we use the [LOGAF scale](https://blog.danlew.net/2020/04/15/the-logaf-scale/) to specify how important a comment is: + +* `l`: low - nitpick. You may address this comment, but you don't have to. +* `m`: medium - normal comment. Worth addressing and fixing. +* `h`: high - Very important. We must not merge this PR without addressing this issue. + +You only need one approval from a maintainer to be able to merge. For some PRs, asking specific or multiple people for review might be adequate. + +Our different types of reviews: + + 1. **LGTM without any comments.** You can merge immediately. + 2. **LGTM with low and medium comments.** The reviewer trusts you to resolve these comments yourself, and you don't need to wait for another approval. + 3. **Only comments.** You must address all the comments and need another review until you merge. + 4. **Request changes.** Only use if something critical is in the PR that absolutely must be addressed. We usually use `h` comments for that. When someone requests changes, the same person must approve the changes to allow merging. Use this sparingly. + ## Development environment ### Clone the repository From c64f996c334ced7401d6068b2e4b1abb63260e3a Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 3 Oct 2022 16:14:41 +0200 Subject: [PATCH 0770/1161] meta: Check for github-actions updates (#1389) --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..71e9c1b27 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 \ No newline at end of file From add2d93df889154609c6759d5579fd88fe544577 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 16:39:52 +0200 Subject: [PATCH 0771/1161] ci: Bump actions/checkout from 2 to 3 (#1393) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/publish-release.yaml | 2 +- .github/workflows/static-analysis.yaml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acbf74fff..d5cd8d8fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index ea19fe8d9..80a6975b0 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest name: Release version steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: token: ${{ secrets.GH_RELEASE_PAT }} fetch-depth: 0 diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index b1b7c445a..03a8ab35b 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 # needed by codecov sometimes From 3fe57ba49ae5e22d6dbc00a617e082e413d495b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 16:40:25 +0200 Subject: [PATCH 0772/1161] ci: Bump actions/cache from 2 to 3 (#1392) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Michi Hoffmann --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5cd8d8fb..9fef029ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: run: echo "::set-output name=directory::$(composer config cache-dir)" - name: Cache Composer dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.directory }} key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} From 3cefa7b69f2ebb4e0042c4f39e5c18bf573a4cab Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 3 Oct 2022 17:27:33 +0200 Subject: [PATCH 0773/1161] Prepare release 3.9.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a309ea4..942b3a8bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.9.0 (2022-10-04) + - feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) - feat: Add support for Dynamic Sampling (#1360) - Add `segment` to `UserDataBag` From 2e7607dc7cd3faa2565646ede3277d540231d0a5 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 4 Oct 2022 17:58:13 +0200 Subject: [PATCH 0774/1161] feat: Add tracePropagationTargets option (#1396) --- CHANGELOG.md | 1 + phpstan-baseline.neon | 9 +++++-- src/Options.php | 24 +++++++++++++++++++ src/Tracing/GuzzleTracingMiddleware.php | 14 +++++++++-- tests/OptionsTest.php | 9 +++++++ tests/Tracing/GuzzleTracingMiddlewareTest.php | 9 +++++-- 6 files changed, 60 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 942b3a8bd..d7bf09614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## 3.9.0 (2022-10-04) +- feat: Add tracePropagationTargets option (#1396) - feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) - feat: Add support for Dynamic Sampling (#1360) - Add `segment` to `UserDataBag` diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 13a94bc31..3b6e79699 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -215,6 +215,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getTracePropagationTargets\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getTracesSampleRate\\(\\) should return float but returns mixed\\.$#" count: 1 @@ -336,12 +341,12 @@ parameters: path: src/UserDataBag.php - - message: "#^Parameter \\#1 \\$username of method Sentry\\\\UserDataBag\\:\\:setUsername\\(\\) expects string\\|null, mixed given\\.$#" + message: "#^Parameter \\#1 \\$segment of method Sentry\\\\UserDataBag\\:\\:setSegment\\(\\) expects string\\|null, mixed given\\.$#" count: 1 path: src/UserDataBag.php - - message: "#^Parameter \\#1 \\$segment of method Sentry\\\\UserDataBag\\:\\:setSegment\\(\\) expects string\\|null, mixed given\\.$#" + message: "#^Parameter \\#1 \\$username of method Sentry\\\\UserDataBag\\:\\:setUsername\\(\\) expects string\\|null, mixed given\\.$#" count: 1 path: src/UserDataBag.php diff --git a/src/Options.php b/src/Options.php index 8455e806c..c506b88d3 100644 --- a/src/Options.php +++ b/src/Options.php @@ -396,6 +396,28 @@ public function setBeforeSendCallback(callable $callback): void $this->options = $this->resolver->resolve($options); } + /** + * Gets an allow list of trace propagation targets. + * + * @return string[] + */ + public function getTracePropagationTargets(): array + { + return $this->options['trace_propagation_targets']; + } + + /** + * Set an allow list of trace propagation targets. + * + * @param string[] $tracePropagationTargets Trace propagation targets + */ + public function setTracePropagationTargets(array $tracePropagationTargets): void + { + $options = array_merge($this->options, ['trace_propagation_targets' => $tracePropagationTargets]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets a list of default tags for events. * @@ -779,6 +801,7 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send' => static function (Event $event): Event { return $event; }, + 'trace_propagation_targets' => [], 'tags' => [], 'error_types' => null, 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, @@ -813,6 +836,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); + $resolver->setAllowedTypes('trace_propagation_targets', 'string[]'); $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['null', 'int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 2d074e54b..d7c1c9a18 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -22,6 +22,7 @@ public static function trace(?HubInterface $hub = null): Closure return static function (callable $handler) use ($hub): Closure { return static function (RequestInterface $request, array $options) use ($hub, $handler) { $hub = $hub ?? SentrySdk::getCurrentHub(); + $client = $hub->getClient(); $span = $hub->getSpan(); if (null === $span) { @@ -35,8 +36,17 @@ public static function trace(?HubInterface $hub = null): Closure $childSpan = $span->startChild($spanContext); $request = $request - ->withHeader('sentry-trace', $childSpan->toTraceparent()) - ->withHeader('baggage', $childSpan->toBaggage()); + ->withHeader('sentry-trace', $childSpan->toTraceparent()); + + // Check if the request destination is allow listed in the trace_propagation_targets option. + if (null !== $client) { + $sdkOptions = $client->getOptions(); + + if (\in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets())) { + $request = $request + ->withHeader('baggage', $childSpan->toBaggage()); + } + } $handlerPromiseCallback = static function ($responseOrException) use ($hub, $request, $childSpan) { // We finish the span (which means setting the span end timestamp) first to ensure the measured time diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index f918228ce..b1635da5c 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -248,6 +248,15 @@ static function (): void {}, null, ]; + yield [ + 'trace_propagation_targets', + ['www.example.com'], + 'getTracePropagationTargets', + 'setTracePropagationTargets', + null, + null, + ]; + yield [ 'before_breadcrumb', static function (): void {}, diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index c5546b121..9a27de875 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -61,9 +61,14 @@ public function testTraceDoesNothingIfSpanIsNotSet(): void public function testTrace(Request $request, $expectedPromiseResult, array $expectedBreadcrumbData): void { $client = $this->createMock(ClientInterface::class); - $client->expects($this->exactly(3)) + $client->expects($this->exactly(4)) ->method('getOptions') - ->willReturn(new Options(['traces_sample_rate' => 1])); + ->willReturn(new Options([ + 'traces_sample_rate' => 1, + 'trace_propagation_targets' => [ + 'www.example.com', + ], + ])); $hub = new Hub($client); From e291353db661d24af20f8475bf2d04cb620c6697 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 5 Oct 2022 11:24:25 +0200 Subject: [PATCH 0775/1161] meta: Update Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7bf09614..76401831f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## 3.9.0 (2022-10-04) +## 3.9.0 (2022-10-05) - feat: Add tracePropagationTargets option (#1396) - feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) From 0d162e2516f8254e821a1376018e1e021f910bff Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 5 Oct 2022 09:35:42 +0000 Subject: [PATCH 0776/1161] release: 3.9.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 3972ef6c4..e32472a00 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.8.1'; + public const SDK_VERSION = '3.9.0'; /** * @var Options The client options From c8a17f39c6e27b8b282540f2663b396dd8c1eb88 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 5 Oct 2022 12:25:03 +0200 Subject: [PATCH 0777/1161] Start development of version 3.10 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f89a85b5d..448a765f2 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.9.x-dev" + "dev-master": "3.10.x-dev" } } } From 2dc5139490327889379bfbf6872d8a824f88b4e3 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 5 Oct 2022 14:42:10 +0200 Subject: [PATCH 0778/1161] ref: Add correct never option for max_request_body_size (#1397) --- CHANGELOG.md | 3 +++ src/Integration/RequestIntegration.php | 5 ++++- src/Options.php | 2 +- tests/Integration/RequestIntegrationTest.php | 19 +++++++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76401831f..155b2da97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- ref: Add correct `never` option for `max_request_body_size` (#1397) + - Deprecate `max_request_body_size.none` in favour of `max_request_body_size.never` + ## 3.9.0 (2022-10-05) - feat: Add tracePropagationTargets option (#1396) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 23ea88a32..61748ec77 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -42,9 +42,12 @@ final class RequestIntegration implements IntegrationInterface /** * This constant is a map of maximum allowed sizes for each value of the * `max_request_body_size` option. + * + * @deprecated The 'none' option is deprecated since version 3.10, to be removed in 4.0 */ private const MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP = [ 'none' => 0, + 'never' => 0, 'small' => self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH, 'medium' => self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH, 'always' => -1, @@ -269,7 +272,7 @@ private function isRequestBodySizeWithinReadBounds(int $requestBodySize, string return false; } - if ('none' === $maxRequestBodySize) { + if ('none' === $maxRequestBodySize || 'never' === $maxRequestBodySize) { return false; } diff --git a/src/Options.php b/src/Options.php index c506b88d3..d9a980258 100644 --- a/src/Options.php +++ b/src/Options.php @@ -852,7 +852,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('max_request_body_size', 'string'); $resolver->setAllowedTypes('class_serializers', 'array'); - $resolver->setAllowedValues('max_request_body_size', ['none', 'small', 'medium', 'always']); + $resolver->setAllowedValues('max_request_body_size', ['none', 'never', 'small', 'medium', 'always']); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); $resolver->setAllowedValues('max_breadcrumbs', \Closure::fromCallable([$this, 'validateMaxBreadcrumbsOptions'])); $resolver->setAllowedValues('class_serializers', \Closure::fromCallable([$this, 'validateClassSerializersOption'])); diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 6e5e85b54..64c78521a 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -226,6 +226,25 @@ public function invokeDataProvider(): iterable null, ]; + yield [ + [ + 'max_request_body_size' => 'never', + ], + (new ServerRequest('POST', 'http://www.example.com/foo')) + ->withHeader('Content-Length', '3') + ->withBody(Utils::streamFor('foo')), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + 'Content-Length' => ['3'], + ], + ], + null, + null, + ]; + yield [ [ 'max_request_body_size' => 'small', From eb2c502e94d4d385669a90ea60407215fd7f43aa Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 11 Oct 2022 10:52:17 +0200 Subject: [PATCH 0779/1161] fix: Suppress errors on is_callable (#1401) --- CHANGELOG.md | 2 ++ src/Serializer/AbstractSerializer.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76401831f..74374bc81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fix: Suppress errors on is_callable (#1401) + ## 3.9.0 (2022-10-05) - feat: Add tracePropagationTargets option (#1396) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 2da56cfbd..f41d16cad 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -100,7 +100,7 @@ protected function serializeRecursively($value, int $_depth = 0) } try { - if (\is_callable($value)) { + if (@\is_callable($value)) { return $this->serializeCallable($value); } } catch (\Throwable $exception) { From bce5fb307333a7293cb6929f4c1bdd48f83c7d1c Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 11 Oct 2022 10:58:55 +0200 Subject: [PATCH 0780/1161] Prepare release 3.9.1 (#1402) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74374bc81..bef035489 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.9.1 (2022-10-11) + - fix: Suppress errors on is_callable (#1401) ## 3.9.0 (2022-10-05) From 45b618b2265d11bc9b5a15f31bcc573a2dc3b956 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 11 Oct 2022 09:00:25 +0000 Subject: [PATCH 0781/1161] release: 3.9.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index e32472a00..a8e45fa5e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.9.0'; + public const SDK_VERSION = '3.9.1'; /** * @var Options The client options From 27c8b04c5e1e56ced4ccd28bf518f1265413153c Mon Sep 17 00:00:00 2001 From: anthony sottile <103459774+asottile-sentry@users.noreply.github.com> Date: Wed, 12 Oct 2022 13:05:45 -0400 Subject: [PATCH 0782/1161] ref(ci): fix set-output / set-state deprecation (#1404) --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fef029ca..2c6d2df04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,8 @@ jobs: - name: Determine Composer cache directory id: composer-cache - run: echo "::set-output name=directory::$(composer config cache-dir)" + run: echo "directory=$(composer config cache-dir)" >> "$GITHUB_OUTPUT" + shell: bash - name: Cache Composer dependencies uses: actions/cache@v3 From 5a31babcac694e7fda8e62021abbc9aea1706980 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 19 Oct 2022 11:04:47 +0200 Subject: [PATCH 0783/1161] fix: Fix sampling decision when parent is sampled (#1407) --- CHANGELOG.md | 2 ++ src/State/Hub.php | 2 +- tests/Tracing/TransactionTest.php | 45 +++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bef035489..0a6b64eca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fix: Sampling now correctly takes in account the parent sampling decision if available instead of always being `false` when tracing is disabled (#1407) + ## 3.9.1 (2022-10-11) - fix: Suppress errors on is_callable (#1401) diff --git a/src/State/Hub.php b/src/State/Hub.php index 213114686..d22baf080 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -222,7 +222,7 @@ public function startTransaction(TransactionContext $context, array $customSampl $client = $this->getClient(); $options = null !== $client ? $client->getOptions() : null; - if (null === $options || !$options->isTracingEnabled()) { + if (null === $options || (!$options->isTracingEnabled() && true !== $context->getParentSampled())) { $transaction->setSampled(false); return $transaction; diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index d7843d5c7..c0fe44677 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -4,12 +4,14 @@ namespace Sentry\Tests\Tracing; +use Generator; use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventId; use Sentry\EventType; use Sentry\Options; +use Sentry\State\Hub; use Sentry\State\HubInterface; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; @@ -84,4 +86,47 @@ public function testFinishDoesNothingIfSampledFlagIsNotTrue(): void $transaction = new Transaction(new TransactionContext(), $hub); $transaction->finish(); } + + /** + * @dataProvider parentTransactionContextDataProvider + */ + public function testTransactionIsSampledCorrectlyWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn( + new Options([ + 'traces_sampler' => null, + 'traces_sample_rate' => 0, + ]) + ); + + $transaction = (new Hub($client))->startTransaction($context); + + $this->assertSame($expectedSampled, $transaction->getSampled()); + } + + public function parentTransactionContextDataProvider(): Generator + { + yield [ + new TransactionContext(TransactionContext::DEFAULT_NAME, true), + true, + ]; + + yield [ + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + + yield [ + TransactionContext::fromHeaders('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', ''), + true, + ]; + + yield [ + TransactionContext::fromHeaders('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', ''), + false, + ]; + } } From 55bb4e5799b5dcf329c50e4d53ce80788da943e0 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 19 Oct 2022 11:23:54 +0200 Subject: [PATCH 0784/1161] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af933525..f4609ac4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.10.0 (2022-10-19) + - ref: Add correct `never` option for `max_request_body_size` (#1397) - Deprecate `max_request_body_size.none` in favour of `max_request_body_size.never` - fix: Sampling now correctly takes in account the parent sampling decision if available instead of always being `false` when tracing is disabled (#1407) From 2fdbda9b3569df08dd4a300db8a563d0ec7241f9 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 19 Oct 2022 09:28:02 +0000 Subject: [PATCH 0785/1161] release: 3.10.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index a8e45fa5e..a5ca1b262 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.9.1'; + public const SDK_VERSION = '3.10.0'; /** * @var Options The client options From ab268bdcd980abd1af86a3118e27fba1b38e96c3 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 19 Oct 2022 11:47:48 +0200 Subject: [PATCH 0786/1161] Start development of version 3.11 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 448a765f2..006c6796d 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.10.x-dev" + "dev-master": "3.11.x-dev" } } } From f865c6ce9841226b8200c36ac26ed0665c8ab700 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 20 Oct 2022 12:28:07 +0200 Subject: [PATCH 0787/1161] fix: Only include the transaction name to the DSC if it has good quality (#1410) --- src/Tracing/DynamicSamplingContext.php | 6 +++++- tests/Tracing/DynamicSamplingContextTest.php | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index 7fab07005..179140998 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -149,7 +149,11 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h $samplingContext = new self(); $samplingContext->set('trace_id', (string) $transaction->getTraceId()); $samplingContext->set('sample_rate', (string) $transaction->getMetaData()->getSamplingRate()); - $samplingContext->set('transaction', $transaction->getName()); + + // Only include the transaction name if it has good quality + if ($transaction->getMetadata()->getSource() !== TransactionSource::url()) { + $samplingContext->set('transaction', $transaction->getName()); + } $client = $hub->getClient(); diff --git a/tests/Tracing/DynamicSamplingContextTest.php b/tests/Tracing/DynamicSamplingContextTest.php index 36a689eb7..c0d520397 100644 --- a/tests/Tracing/DynamicSamplingContextTest.php +++ b/tests/Tracing/DynamicSamplingContextTest.php @@ -12,6 +12,7 @@ use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; +use Sentry\Tracing\TransactionSource; use Sentry\UserDataBag; final class DynamicSamplingContextTest extends TestCase @@ -112,6 +113,21 @@ public function testFromTransaction(): void $this->assertTrue($samplingContext->isFrozen()); } + public function testFromTransactionSourceUrl(): void + { + $hub = new Hub(); + + $transactionContext = new TransactionContext(); + $transactionContext->setName('/foo/bar/123'); + $transactionContext->setSource(TransactionSource::url()); + + $transaction = new Transaction($transactionContext, $hub); + + $samplingContext = DynamicSamplingContext::fromTransaction($transaction, $hub); + + $this->assertNull($samplingContext->get('transaction')); + } + /** * @dataProvider getEntriesDataProvider */ From 80b5ad0ac23978b6505e681164cb67e8da2cd253 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 21 Oct 2022 14:39:21 +0200 Subject: [PATCH 0788/1161] ref: Enable the ModulesIntegration by default (#1415) --- CHANGELOG.md | 2 + src/Integration/IntegrationRegistry.php | 1 + tests/Integration/IntegrationRegistryTest.php | 41 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4609ac4c..e6487e455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +ref: Enable the ModulesIntegration by default (#1415) + ## 3.10.0 (2022-10-19) - ref: Add correct `never` option for `max_request_body_size` (#1397) diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index f117c43f1..2c33c83ad 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -131,6 +131,7 @@ private function getDefaultIntegrations(Options $options): array new TransactionIntegration(), new FrameContextifierIntegration(), new EnvironmentIntegration(), + new ModulesIntegration(), ]; if (null !== $options->getDsn()) { diff --git a/tests/Integration/IntegrationRegistryTest.php b/tests/Integration/IntegrationRegistryTest.php index 259565b30..e4ad5d5f2 100644 --- a/tests/Integration/IntegrationRegistryTest.php +++ b/tests/Integration/IntegrationRegistryTest.php @@ -13,6 +13,7 @@ use Sentry\Integration\FrameContextifierIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\IntegrationRegistry; +use Sentry\Integration\ModulesIntegration; use Sentry\Integration\RequestIntegration; use Sentry\Integration\TransactionIntegration; use Sentry\Options; @@ -87,6 +88,7 @@ public function setupOnce(): void 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', ], [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), @@ -96,6 +98,7 @@ public function setupOnce(): void TransactionIntegration::class => new TransactionIntegration(), FrameContextifierIntegration::class => new FrameContextifierIntegration(), EnvironmentIntegration::class => new EnvironmentIntegration(), + ModulesIntegration::class => new ModulesIntegration(), ], ]; @@ -134,6 +137,7 @@ public function setupOnce(): void 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', "The \"$integration1ClassName\" integration has been installed.", "The \"$integration2ClassName\" integration has been installed.", ], @@ -145,6 +149,7 @@ public function setupOnce(): void TransactionIntegration::class => new TransactionIntegration(), FrameContextifierIntegration::class => new FrameContextifierIntegration(), EnvironmentIntegration::class => new EnvironmentIntegration(), + ModulesIntegration::class => new ModulesIntegration(), $integration1ClassName => $integration1, $integration2ClassName => $integration2, ], @@ -166,6 +171,7 @@ public function setupOnce(): void 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', "The \"$integration1ClassName\" integration has been installed.", ], @@ -176,11 +182,42 @@ public function setupOnce(): void RequestIntegration::class => new RequestIntegration(), FrameContextifierIntegration::class => new FrameContextifierIntegration(), EnvironmentIntegration::class => new EnvironmentIntegration(), + ModulesIntegration::class => new ModulesIntegration(), TransactionIntegration::class => new TransactionIntegration(), $integration1ClassName => $integration1, ], ]; + yield 'Default integrations and one user integration, the ModulesIntegration is also a default integration' => [ + new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + 'default_integrations' => true, + 'integrations' => [ + new ModulesIntegration(), + ], + ]), + [ + 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', + 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', + 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', + 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', + 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', + ], + [ + ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), + ErrorListenerIntegration::class => new ErrorListenerIntegration(), + FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), + RequestIntegration::class => new RequestIntegration(), + TransactionIntegration::class => new TransactionIntegration(), + FrameContextifierIntegration::class => new FrameContextifierIntegration(), + EnvironmentIntegration::class => new EnvironmentIntegration(), + ModulesIntegration::class => new ModulesIntegration(), + ], + ]; + yield 'No default integrations and some user integrations are repeated twice' => [ new Options([ 'default_integrations' => false, @@ -224,6 +261,7 @@ public function setupOnce(): void 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', ], [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), @@ -233,6 +271,7 @@ public function setupOnce(): void TransactionIntegration::class => new TransactionIntegration(), FrameContextifierIntegration::class => new FrameContextifierIntegration(), EnvironmentIntegration::class => new EnvironmentIntegration(), + ModulesIntegration::class => new ModulesIntegration(), ], ]; @@ -249,12 +288,14 @@ public function setupOnce(): void 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', + 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', ], [ RequestIntegration::class => new RequestIntegration(), TransactionIntegration::class => new TransactionIntegration(), FrameContextifierIntegration::class => new FrameContextifierIntegration(), EnvironmentIntegration::class => new EnvironmentIntegration(), + ModulesIntegration::class => new ModulesIntegration(), ], ]; } From 8942e12078b1e01d3b261516aaf50cc4b29d7cdd Mon Sep 17 00:00:00 2001 From: Vladan Paunovic Date: Mon, 24 Oct 2022 05:02:36 -0700 Subject: [PATCH 0789/1161] chore(ci): add danger.yml to police CHANGELOG updates (#1417) --- .github/workflows/danger.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/workflows/danger.yml diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 000000000..000b75ff3 --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,9 @@ +name: Danger + +on: + pull_request: + types: [opened, synchronize, reopened, edited, ready_for_review] + +jobs: + danger: + uses: getsentry/github-workflows/.github/workflows/danger.yml@v2 From 04d4280cd3e94ce7ff902dc0ae925f3607f11095 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 25 Oct 2022 15:45:12 +0200 Subject: [PATCH 0790/1161] ref: Expose the ExceptionMechanism through the event hint (#1416) --- CHANGELOG.md | 1 + src/Client.php | 7 ++++--- src/EventHint.php | 13 +++++++++++++ tests/ClientTest.php | 36 ++++++++++++++++++++++++++++++++++++ tests/EventHintTest.php | 9 +++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6487e455..d5f4dad1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ref: Enable the ModulesIntegration by default (#1415) +ref: Expose the ExceptionMechanism through the event hint (#1416) ## 3.10.0 (2022-10-19) diff --git a/src/Client.php b/src/Client.php index a5ca1b262..e91926c9b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -248,7 +248,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco { if (null !== $hint) { if (null !== $hint->exception && empty($event->getExceptions())) { - $this->addThrowableToEvent($event, $hint->exception); + $this->addThrowableToEvent($event, $hint->exception, $hint); } if (null !== $hint->stacktrace && null === $event->getStacktrace()) { @@ -338,8 +338,9 @@ private function addMissingStacktraceToEvent(Event $event): void * * @param Event $event The event that will be enriched with the exception * @param \Throwable $exception The exception that will be processed and added to the event + * @param EventHint $hint Contains additional information about the event */ - private function addThrowableToEvent(Event $event, \Throwable $exception): void + private function addThrowableToEvent(Event $event, \Throwable $exception, EventHint $hint): void { if ($exception instanceof \ErrorException && null === $event->getLevel()) { $event->setLevel(Severity::fromError($exception->getSeverity())); @@ -351,7 +352,7 @@ private function addThrowableToEvent(Event $event, \Throwable $exception): void $exceptions[] = new ExceptionDataBag( $exception, $this->stacktraceBuilder->buildFromException($exception), - new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true) + $hint->mechanism ?? new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true) ); } while ($exception = $exception->getPrevious()); diff --git a/src/EventHint.php b/src/EventHint.php index 145156e34..f1f5d6a81 100644 --- a/src/EventHint.php +++ b/src/EventHint.php @@ -16,6 +16,13 @@ final class EventHint */ public $exception; + /** + * An object describing the mechanism of the original exception. + * + * @var ExceptionMechanism|null + */ + public $mechanism; + /** * The stacktrace to set on the event. * @@ -43,6 +50,7 @@ public static function fromArray(array $hintData): self { $hint = new self(); $exception = $hintData['exception'] ?? null; + $mechanism = $hintData['mechanism'] ?? null; $stacktrace = $hintData['stacktrace'] ?? null; $extra = $hintData['extra'] ?? []; @@ -50,6 +58,10 @@ public static function fromArray(array $hintData): self throw new \InvalidArgumentException(sprintf('The value of the "exception" field must be an instance of a class implementing the "%s" interface. Got: "%s".', \Throwable::class, get_debug_type($exception))); } + if (null !== $mechanism && !$mechanism instanceof ExceptionMechanism) { + throw new \InvalidArgumentException(sprintf('The value of the "mechanism" field must be an instance of the "%s" class. Got: "%s".', ExceptionMechanism::class, get_debug_type($mechanism))); + } + if (null !== $stacktrace && !$stacktrace instanceof Stacktrace) { throw new \InvalidArgumentException(sprintf('The value of the "stacktrace" field must be an instance of the "%s" class. Got: "%s".', Stacktrace::class, get_debug_type($stacktrace))); } @@ -59,6 +71,7 @@ public static function fromArray(array $hintData): self } $hint->exception = $exception; + $hint->mechanism = $mechanism; $hint->stacktrace = $stacktrace; $hint->extra = $extra; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 9bfc9826c..cbc3334de 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -705,6 +705,42 @@ public function testBuildEventWithException(): void ])); } + public function testBuildEventWithExceptionAndMechansim(): void + { + $options = new Options(); + $exception = new \Exception('testMessage'); + + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event): bool { + $capturedExceptions = $event->getExceptions(); + + $this->assertCount(1, $capturedExceptions); + $this->assertNotNull($capturedExceptions[0]->getStacktrace()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false), $capturedExceptions[0]->getMechanism()); + $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); + $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); + + return true; + })); + + $client = new Client( + $options, + $transport, + 'sentry.sdk.identifier', + '1.2.3', + new Serializer($options), + $this->createMock(RepresentationSerializerInterface::class) + ); + + $client->captureEvent(Event::createEvent(), EventHint::fromArray([ + 'exception' => $exception, + 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false), + ])); + } + public function testBuildWithErrorException(): void { $options = new Options(); diff --git a/tests/EventHintTest.php b/tests/EventHintTest.php index 4a5b01c83..3c8bffb56 100644 --- a/tests/EventHintTest.php +++ b/tests/EventHintTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Sentry\EventHint; +use Sentry\ExceptionMechanism; use Sentry\Frame; use Sentry\Stacktrace; @@ -14,17 +15,20 @@ final class EventHintTest extends TestCase public function testCreateFromArray(): void { $exception = new \Exception(); + $mechanism = new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false); $stacktrace = new Stacktrace([ new Frame('function_1', 'path/to/file_1', 10), ]); $hint = EventHint::fromArray([ 'exception' => $exception, + 'mechanism' => $mechanism, 'stacktrace' => $stacktrace, 'extra' => ['foo' => 'bar'], ]); $this->assertSame($exception, $hint->exception); + $this->assertSame($mechanism, $hint->mechanism); $this->assertSame($stacktrace, $hint->stacktrace); $this->assertSame(['foo' => 'bar'], $hint->extra); } @@ -47,6 +51,11 @@ public function createFromArrayWithInvalidValuesDataProvider(): \Generator 'The value of the "exception" field must be an instance of a class implementing the "Throwable" interface. Got: "string".', ]; + yield [ + ['mechanism' => 'foo'], + 'The value of the "mechanism" field must be an instance of the "Sentry\\ExceptionMechanism" class. Got: "string".', + ]; + yield [ ['stacktrace' => 'foo'], 'The value of the "stacktrace" field must be an instance of the "Sentry\\Stacktrace" class. Got: "string".', From b455e35fa60c4aabc22ba322ccdef740d48fa03a Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Tue, 25 Oct 2022 15:47:20 +0200 Subject: [PATCH 0791/1161] Prepare 3.11.0 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5f4dad1c..78e1e09c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +## 3.11.0 (2022-10-25) + +fix: Only include the transaction name to the DSC if it has good quality (#1410) ref: Enable the ModulesIntegration by default (#1415) ref: Expose the ExceptionMechanism through the event hint (#1416) From 5a53bcc846b3df2e4f995fc09fdcdc7303c38276 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 25 Oct 2022 16:28:35 +0200 Subject: [PATCH 0792/1161] docs: Fix changelog formatting (#1421) --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e1e09c9..b450ca9ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,9 @@ ## 3.11.0 (2022-10-25) -fix: Only include the transaction name to the DSC if it has good quality (#1410) -ref: Enable the ModulesIntegration by default (#1415) -ref: Expose the ExceptionMechanism through the event hint (#1416) +- fix: Only include the transaction name to the DSC if it has good quality (#1410) +- ref: Enable the ModulesIntegration by default (#1415) +- ref: Expose the ExceptionMechanism through the event hint (#1416) ## 3.10.0 (2022-10-19) From 91bd6e54d9352754bbdd1d800d2b88618f8130d4 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 25 Oct 2022 14:36:50 +0000 Subject: [PATCH 0793/1161] release: 3.11.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index e91926c9b..64c38d94d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.10.0'; + public const SDK_VERSION = '3.11.0'; /** * @var Options The client options From 68ee84af06adacd2b86496892d737f28c329b0fa Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Thu, 3 Nov 2022 16:08:39 +0100 Subject: [PATCH 0794/1161] docs: Add lordsimal/cakephp-sentry to README.md (#1419) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 23ac9c4cf..edbe5a808 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,8 @@ The following integrations are available and maintained by members of the Sentry - [ZendFramework](https://github.com/facile-it/sentry-module) - [Yii2](https://github.com/notamedia/yii2-sentry) - [Silverstripe](https://github.com/phptek/silverstripe-sentry) -- [CakePHP](https://github.com/Connehito/cake-sentry) +- [CakePHP 3.0 - 4.3](https://github.com/Connehito/cake-sentry) +- [CakePHP 4.4+](https://github.com/lordsimal/cakephp-sentry) - ... feel free to be famous, create a port to your favourite platform! ## 3rd party integrations using old SDK 2.x From 8caae8758e6190be463e3af389b6b6b8add51b35 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 14 Nov 2022 13:04:40 +0100 Subject: [PATCH 0795/1161] Introduce `before_send_transaction` option (#1424) Fixes https://github.com/getsentry/sentry-php/issues/1423 --- CHANGELOG.md | 10 +++++--- phpstan-baseline.neon | 6 ++++- src/Client.php | 47 +++++++++++++++++++++++++++++------ src/Options.php | 30 ++++++++++++++++++++++ tests/ClientTest.php | 58 ++++++++++++++++++++++++++++++++++++++++++- tests/OptionsTest.php | 9 +++++++ 6 files changed, 146 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e1e09c9..f3564ce6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,13 @@ ## Unreleased +- feat: Add `before_send_transaction` option (#1424) + ## 3.11.0 (2022-10-25) -fix: Only include the transaction name to the DSC if it has good quality (#1410) -ref: Enable the ModulesIntegration by default (#1415) -ref: Expose the ExceptionMechanism through the event hint (#1416) +- fix: Only include the transaction name to the DSC if it has good quality (#1410) +- ref: Enable the ModulesIntegration by default (#1415) +- ref: Expose the ExceptionMechanism through the event hint (#1416) ## 3.10.0 (2022-10-19) @@ -20,7 +22,7 @@ ref: Expose the ExceptionMechanism through the event hint (#1416) ## 3.9.0 (2022-10-05) -- feat: Add tracePropagationTargets option (#1396) +- feat: Add `trace_propagation_targets` option (#1396) - feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) - feat: Add support for Dynamic Sampling (#1360) - Add `segment` to `UserDataBag` diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3b6e79699..42d41a239 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -110,6 +110,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendTransactionCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: Sentry\\\\Event\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getClassSerializers\\(\\) should return array\\ but returns mixed\\.$#" count: 1 @@ -374,4 +379,3 @@ parameters: message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 path: src/functions.php - diff --git a/src/Client.php b/src/Client.php index e91926c9b..f32b099df 100644 --- a/src/Client.php +++ b/src/Client.php @@ -288,28 +288,59 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco } if (null !== $scope) { - $previousEvent = $event; + $beforeEventProcessors = $event; $event = $scope->applyToEvent($event, $hint); if (null === $event) { - $this->logger->info('The event will be discarded because one of the event processors returned "null".', ['event' => $previousEvent]); + $this->logger->info( + 'The event will be discarded because one of the event processors returned "null".', + ['event' => $beforeEventProcessors] + ); return null; } } - if (!$isTransaction) { - $previousEvent = $event; - $event = ($this->options->getBeforeSendCallback())($event, $hint); + $beforeSendCallback = $event; + $event = $this->applyBeforeSendCallback($event, $hint); - if (null === $event) { - $this->logger->info('The event will be discarded because the "before_send" callback returned "null".', ['event' => $previousEvent]); - } + if (null === $event) { + $this->logger->info( + sprintf( + 'The event will be discarded because the "%s" callback returned "null".', + $this->getBeforeSendCallbackName($beforeSendCallback) + ), + ['event' => $beforeSendCallback] + ); + } + + return $event; + } + + private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event + { + if ($event->getType() === EventType::event()) { + return ($this->options->getBeforeSendCallback())($event, $hint); + } + + if ($event->getType() === EventType::transaction()) { + return ($this->options->getBeforeSendTransactionCallback())($event, $hint); } return $event; } + private function getBeforeSendCallbackName(Event $event): string + { + $beforeSendCallbackName = 'before_send'; + + if ($event->getType() === EventType::transaction()) { + $beforeSendCallbackName = 'before_send_transaction'; + } + + return $beforeSendCallbackName; + } + /** * Optionally adds a missing stacktrace to the Event if the client is configured to do so. * diff --git a/src/Options.php b/src/Options.php index d9a980258..c3028e16d 100644 --- a/src/Options.php +++ b/src/Options.php @@ -396,6 +396,32 @@ public function setBeforeSendCallback(callable $callback): void $this->options = $this->resolver->resolve($options); } + /** + * Gets a callback that will be invoked before an transaction is sent to the server. + * If `null` is returned it won't be sent. + * + * @psalm-return callable(Event, ?EventHint): ?Event + */ + public function getBeforeSendTransactionCallback(): callable + { + return $this->options['before_send_transaction']; + } + + /** + * Sets a callable to be called to decide whether an transaction should + * be captured or not. + * + * @param callable $callback The callable + * + * @psalm-param callable(Event, ?EventHint): ?Event $callback + */ + public function setBeforeSendTransactionCallback(callable $callback): void + { + $options = array_merge($this->options, ['before_send_transaction' => $callback]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets an allow list of trace propagation targets. * @@ -801,6 +827,9 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send' => static function (Event $event): Event { return $event; }, + 'before_send_transaction' => static function (Event $transaction): Event { + return $transaction; + }, 'trace_propagation_targets' => [], 'tags' => [], 'error_types' => null, @@ -836,6 +865,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); + $resolver->setAllowedTypes('before_send_transaction', ['callable']); $resolver->setAllowedTypes('trace_propagation_targets', 'string[]'); $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['null', 'int']); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index cbc3334de..4bee02020 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -510,6 +510,39 @@ public function processEventChecksBeforeSendOptionDataProvider(): \Generator ]; } + /** + * @dataProvider processEventChecksBeforeSendTransactionOptionDataProvider + */ + public function testProcessEventChecksBeforeSendTransactionOption(Event $event, bool $expectedBeforeSendCall): void + { + $beforeSendTransactionCalled = false; + $options = [ + 'before_send_transaction' => static function () use (&$beforeSendTransactionCalled) { + $beforeSendTransactionCalled = true; + + return null; + }, + ]; + + $client = ClientBuilder::create($options)->getClient(); + $client->captureEvent($event); + + $this->assertSame($expectedBeforeSendCall, $beforeSendTransactionCalled); + } + + public function processEventChecksBeforeSendTransactionOptionDataProvider(): \Generator + { + yield [ + Event::createEvent(), + false, + ]; + + yield [ + Event::createTransaction(), + true, + ]; + } + /** * @dataProvider processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDataProvider */ @@ -574,7 +607,30 @@ public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull() ->setLogger($logger) ->getClient(); - $client->captureMessage('foo'); + $client->captureEvent(Event::createEvent()); + } + + public function testProcessEventDiscardsEventWhenBeforeSendTransactionCallbackReturnsNull(): void + { + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because the "before_send_transaction" callback returned "null".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'before_send_transaction' => static function () { + return null; + }, + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureEvent(Event::createTransaction()); } public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): void diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index b1635da5c..85b6aed04 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -248,6 +248,15 @@ static function (): void {}, null, ]; + yield [ + 'before_send_transaction', + static function (): void {}, + 'getBeforeSendTransactionCallback', + 'setBeforeSendTransactionCallback', + null, + null, + ]; + yield [ 'trace_propagation_targets', ['www.example.com'], From 16668f71d0e597effe1fb901d625540157cc39c9 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Fri, 18 Nov 2022 18:27:13 +0000 Subject: [PATCH 0796/1161] Fix broken docblock (#1430) --- CHANGELOG.md | 2 ++ phpstan-baseline.neon | 2 +- src/EventHint.php | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b450ca9ca..0709df111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fix: Add missing array key from docblock (#1430) + ## 3.11.0 (2022-10-25) - fix: Only include the transaction name to the DSC if it has good quality (#1410) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3b6e79699..e38d65cc8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -42,7 +42,7 @@ parameters: - message: "#^Result of && is always false\\.$#" - count: 2 + count: 3 path: src/EventHint.php - diff --git a/src/EventHint.php b/src/EventHint.php index f1f5d6a81..ed3b0b9f1 100644 --- a/src/EventHint.php +++ b/src/EventHint.php @@ -42,6 +42,7 @@ final class EventHint * * @psalm-param array{ * exception?: \Throwable|null, + * mechanism?: ExceptionMechanism|null, * stacktrace?: Stacktrace|null, * extra?: array * } $hintData From 70c2269b450d9b5d629bd29ef01f4b7e893f881f Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 21 Nov 2022 11:43:13 +0100 Subject: [PATCH 0797/1161] docs: Remove changelog entry (#1431) --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0709df111..b450ca9ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ ## Unreleased -- fix: Add missing array key from docblock (#1430) - ## 3.11.0 (2022-10-25) - fix: Only include the transaction name to the DSC if it has good quality (#1410) From ce0ea493ddbcf639cfed4549064ed7257cc8d3f7 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 21 Nov 2022 16:10:10 +0100 Subject: [PATCH 0798/1161] fix: Set `traces_sample_rate` to `null` by default (#1428) --- CHANGELOG.md | 1 + src/Options.php | 6 ++--- src/State/Hub.php | 2 +- tests/Tracing/TransactionTest.php | 45 ++++++++++++++++++++++++++++++- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3564ce6f..0a8428831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - feat: Add `before_send_transaction` option (#1424) +- fix: Set `traces_sample_rate` to `null` by default (#1428) ## 3.11.0 (2022-10-25) diff --git a/src/Options.php b/src/Options.php index c3028e16d..1c70186da 100644 --- a/src/Options.php +++ b/src/Options.php @@ -163,7 +163,7 @@ public function setTracesSampleRate(float $sampleRate): void */ public function isTracingEnabled(): bool { - return 0 != $this->options['traces_sample_rate'] || null !== $this->options['traces_sampler']; + return null !== $this->options['traces_sample_rate'] || null !== $this->options['traces_sampler']; } /** @@ -814,7 +814,7 @@ private function configureOptions(OptionsResolver $resolver): void 'send_attempts' => 0, 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, - 'traces_sample_rate' => 0, + 'traces_sample_rate' => null, 'traces_sampler' => null, 'attach_stacktrace' => false, 'context_lines' => 5, @@ -852,7 +852,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'string[]'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); - $resolver->setAllowedTypes('traces_sample_rate', ['int', 'float']); + $resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); diff --git a/src/State/Hub.php b/src/State/Hub.php index d22baf080..213114686 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -222,7 +222,7 @@ public function startTransaction(TransactionContext $context, array $customSampl $client = $this->getClient(); $options = null !== $client ? $client->getOptions() : null; - if (null === $options || (!$options->isTracingEnabled() && true !== $context->getParentSampled())) { + if (null === $options || !$options->isTracingEnabled()) { $transaction->setSampled(false); return $transaction; diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index c0fe44677..d1d0b3f89 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -90,7 +90,7 @@ public function testFinishDoesNothingIfSampledFlagIsNotTrue(): void /** * @dataProvider parentTransactionContextDataProvider */ - public function testTransactionIsSampledCorrectlyWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void + public function testTransactionIsSampledCorrectlyWhenTracingIsSetToZeroInOptions(TransactionContext $context, bool $expectedSampled): void { $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -129,4 +129,47 @@ public function parentTransactionContextDataProvider(): Generator false, ]; } + + /** + * @dataProvider parentTransactionContextDataProviderDisabled + */ + public function testTransactionIsNotSampledWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn( + new Options([ + 'traces_sampler' => null, + 'traces_sample_rate' => null, + ]) + ); + + $transaction = (new Hub($client))->startTransaction($context); + + $this->assertSame($expectedSampled, $transaction->getSampled()); + } + + public function parentTransactionContextDataProviderDisabled(): Generator + { + yield [ + new TransactionContext(TransactionContext::DEFAULT_NAME, true), + false, + ]; + + yield [ + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + + yield [ + TransactionContext::fromHeaders('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', ''), + false, + ]; + + yield [ + TransactionContext::fromHeaders('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', ''), + false, + ]; + } } From a84fa9b4124b2240f9c75e7e9e2fe17227f3b459 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 21 Nov 2022 16:11:35 +0100 Subject: [PATCH 0799/1161] Start development of version 3.12 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 006c6796d..62e38b1d6 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.11.x-dev" + "dev-master": "3.12.x-dev" } } } From 914900b2ac22629c67751af350c43a28152d49d4 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 22 Nov 2022 11:28:46 +0100 Subject: [PATCH 0800/1161] Prepare 3.12.0 (#1432) --- CHANGELOG.md | 5 ++- composer.json | 2 +- phpstan-baseline.neon | 6 +++- src/Client.php | 47 ++++++++++++++++++++----- src/Options.php | 36 +++++++++++++++++-- src/State/Hub.php | 2 +- tests/ClientTest.php | 58 ++++++++++++++++++++++++++++++- tests/OptionsTest.php | 9 +++++ tests/Tracing/TransactionTest.php | 45 +++++++++++++++++++++++- 9 files changed, 193 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b450ca9ca..0a8428831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- feat: Add `before_send_transaction` option (#1424) +- fix: Set `traces_sample_rate` to `null` by default (#1428) + ## 3.11.0 (2022-10-25) - fix: Only include the transaction name to the DSC if it has good quality (#1410) @@ -20,7 +23,7 @@ ## 3.9.0 (2022-10-05) -- feat: Add tracePropagationTargets option (#1396) +- feat: Add `trace_propagation_targets` option (#1396) - feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) - feat: Add support for Dynamic Sampling (#1360) - Add `segment` to `UserDataBag` diff --git a/composer.json b/composer.json index 006c6796d..62e38b1d6 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.11.x-dev" + "dev-master": "3.12.x-dev" } } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e38d65cc8..11c0ed88c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -110,6 +110,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendTransactionCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: Sentry\\\\Event\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getClassSerializers\\(\\) should return array\\ but returns mixed\\.$#" count: 1 @@ -374,4 +379,3 @@ parameters: message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 path: src/functions.php - diff --git a/src/Client.php b/src/Client.php index 64c38d94d..8f01c59a7 100644 --- a/src/Client.php +++ b/src/Client.php @@ -288,28 +288,59 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco } if (null !== $scope) { - $previousEvent = $event; + $beforeEventProcessors = $event; $event = $scope->applyToEvent($event, $hint); if (null === $event) { - $this->logger->info('The event will be discarded because one of the event processors returned "null".', ['event' => $previousEvent]); + $this->logger->info( + 'The event will be discarded because one of the event processors returned "null".', + ['event' => $beforeEventProcessors] + ); return null; } } - if (!$isTransaction) { - $previousEvent = $event; - $event = ($this->options->getBeforeSendCallback())($event, $hint); + $beforeSendCallback = $event; + $event = $this->applyBeforeSendCallback($event, $hint); - if (null === $event) { - $this->logger->info('The event will be discarded because the "before_send" callback returned "null".', ['event' => $previousEvent]); - } + if (null === $event) { + $this->logger->info( + sprintf( + 'The event will be discarded because the "%s" callback returned "null".', + $this->getBeforeSendCallbackName($beforeSendCallback) + ), + ['event' => $beforeSendCallback] + ); + } + + return $event; + } + + private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event + { + if ($event->getType() === EventType::event()) { + return ($this->options->getBeforeSendCallback())($event, $hint); + } + + if ($event->getType() === EventType::transaction()) { + return ($this->options->getBeforeSendTransactionCallback())($event, $hint); } return $event; } + private function getBeforeSendCallbackName(Event $event): string + { + $beforeSendCallbackName = 'before_send'; + + if ($event->getType() === EventType::transaction()) { + $beforeSendCallbackName = 'before_send_transaction'; + } + + return $beforeSendCallbackName; + } + /** * Optionally adds a missing stacktrace to the Event if the client is configured to do so. * diff --git a/src/Options.php b/src/Options.php index d9a980258..1c70186da 100644 --- a/src/Options.php +++ b/src/Options.php @@ -163,7 +163,7 @@ public function setTracesSampleRate(float $sampleRate): void */ public function isTracingEnabled(): bool { - return 0 != $this->options['traces_sample_rate'] || null !== $this->options['traces_sampler']; + return null !== $this->options['traces_sample_rate'] || null !== $this->options['traces_sampler']; } /** @@ -396,6 +396,32 @@ public function setBeforeSendCallback(callable $callback): void $this->options = $this->resolver->resolve($options); } + /** + * Gets a callback that will be invoked before an transaction is sent to the server. + * If `null` is returned it won't be sent. + * + * @psalm-return callable(Event, ?EventHint): ?Event + */ + public function getBeforeSendTransactionCallback(): callable + { + return $this->options['before_send_transaction']; + } + + /** + * Sets a callable to be called to decide whether an transaction should + * be captured or not. + * + * @param callable $callback The callable + * + * @psalm-param callable(Event, ?EventHint): ?Event $callback + */ + public function setBeforeSendTransactionCallback(callable $callback): void + { + $options = array_merge($this->options, ['before_send_transaction' => $callback]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets an allow list of trace propagation targets. * @@ -788,7 +814,7 @@ private function configureOptions(OptionsResolver $resolver): void 'send_attempts' => 0, 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, - 'traces_sample_rate' => 0, + 'traces_sample_rate' => null, 'traces_sampler' => null, 'attach_stacktrace' => false, 'context_lines' => 5, @@ -801,6 +827,9 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send' => static function (Event $event): Event { return $event; }, + 'before_send_transaction' => static function (Event $transaction): Event { + return $transaction; + }, 'trace_propagation_targets' => [], 'tags' => [], 'error_types' => null, @@ -823,7 +852,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'string[]'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); - $resolver->setAllowedTypes('traces_sample_rate', ['int', 'float']); + $resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); @@ -836,6 +865,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); + $resolver->setAllowedTypes('before_send_transaction', ['callable']); $resolver->setAllowedTypes('trace_propagation_targets', 'string[]'); $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['null', 'int']); diff --git a/src/State/Hub.php b/src/State/Hub.php index d22baf080..213114686 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -222,7 +222,7 @@ public function startTransaction(TransactionContext $context, array $customSampl $client = $this->getClient(); $options = null !== $client ? $client->getOptions() : null; - if (null === $options || (!$options->isTracingEnabled() && true !== $context->getParentSampled())) { + if (null === $options || !$options->isTracingEnabled()) { $transaction->setSampled(false); return $transaction; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index cbc3334de..4bee02020 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -510,6 +510,39 @@ public function processEventChecksBeforeSendOptionDataProvider(): \Generator ]; } + /** + * @dataProvider processEventChecksBeforeSendTransactionOptionDataProvider + */ + public function testProcessEventChecksBeforeSendTransactionOption(Event $event, bool $expectedBeforeSendCall): void + { + $beforeSendTransactionCalled = false; + $options = [ + 'before_send_transaction' => static function () use (&$beforeSendTransactionCalled) { + $beforeSendTransactionCalled = true; + + return null; + }, + ]; + + $client = ClientBuilder::create($options)->getClient(); + $client->captureEvent($event); + + $this->assertSame($expectedBeforeSendCall, $beforeSendTransactionCalled); + } + + public function processEventChecksBeforeSendTransactionOptionDataProvider(): \Generator + { + yield [ + Event::createEvent(), + false, + ]; + + yield [ + Event::createTransaction(), + true, + ]; + } + /** * @dataProvider processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDataProvider */ @@ -574,7 +607,30 @@ public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull() ->setLogger($logger) ->getClient(); - $client->captureMessage('foo'); + $client->captureEvent(Event::createEvent()); + } + + public function testProcessEventDiscardsEventWhenBeforeSendTransactionCallbackReturnsNull(): void + { + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because the "before_send_transaction" callback returned "null".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'before_send_transaction' => static function () { + return null; + }, + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureEvent(Event::createTransaction()); } public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): void diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index b1635da5c..85b6aed04 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -248,6 +248,15 @@ static function (): void {}, null, ]; + yield [ + 'before_send_transaction', + static function (): void {}, + 'getBeforeSendTransactionCallback', + 'setBeforeSendTransactionCallback', + null, + null, + ]; + yield [ 'trace_propagation_targets', ['www.example.com'], diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index c0fe44677..d1d0b3f89 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -90,7 +90,7 @@ public function testFinishDoesNothingIfSampledFlagIsNotTrue(): void /** * @dataProvider parentTransactionContextDataProvider */ - public function testTransactionIsSampledCorrectlyWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void + public function testTransactionIsSampledCorrectlyWhenTracingIsSetToZeroInOptions(TransactionContext $context, bool $expectedSampled): void { $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -129,4 +129,47 @@ public function parentTransactionContextDataProvider(): Generator false, ]; } + + /** + * @dataProvider parentTransactionContextDataProviderDisabled + */ + public function testTransactionIsNotSampledWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn( + new Options([ + 'traces_sampler' => null, + 'traces_sample_rate' => null, + ]) + ); + + $transaction = (new Hub($client))->startTransaction($context); + + $this->assertSame($expectedSampled, $transaction->getSampled()); + } + + public function parentTransactionContextDataProviderDisabled(): Generator + { + yield [ + new TransactionContext(TransactionContext::DEFAULT_NAME, true), + false, + ]; + + yield [ + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + + yield [ + TransactionContext::fromHeaders('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', ''), + false, + ]; + + yield [ + TransactionContext::fromHeaders('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', ''), + false, + ]; + } } From 2cc0431674fdd466985767420cae7144bfae4d32 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 22 Nov 2022 11:41:12 +0100 Subject: [PATCH 0801/1161] Update changelog (#1433) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a8428831..719f1934b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.12.0 (2022-11-22) + - feat: Add `before_send_transaction` option (#1424) - fix: Set `traces_sample_rate` to `null` by default (#1428) From acb7f5ebdfc8f83e20b4a92806ab3aff7ee0edba Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 22 Nov 2022 11:51:38 +0100 Subject: [PATCH 0802/1161] Revert "Prepare 3.12.0 (#1432)" (#1434) --- composer.json | 2 +- phpstan-baseline.neon | 6 +--- src/Client.php | 47 +++++-------------------- src/Options.php | 36 ++----------------- src/State/Hub.php | 2 +- tests/ClientTest.php | 58 +------------------------------ tests/OptionsTest.php | 9 ----- tests/Tracing/TransactionTest.php | 45 +----------------------- 8 files changed, 16 insertions(+), 189 deletions(-) diff --git a/composer.json b/composer.json index 62e38b1d6..006c6796d 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.12.x-dev" + "dev-master": "3.11.x-dev" } } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 11c0ed88c..e38d65cc8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -110,11 +110,6 @@ parameters: count: 1 path: src/Options.php - - - message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendTransactionCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: Sentry\\\\Event\\|null but returns mixed\\.$#" - count: 1 - path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getClassSerializers\\(\\) should return array\\ but returns mixed\\.$#" count: 1 @@ -379,3 +374,4 @@ parameters: message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 path: src/functions.php + diff --git a/src/Client.php b/src/Client.php index 8f01c59a7..64c38d94d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -288,59 +288,28 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco } if (null !== $scope) { - $beforeEventProcessors = $event; + $previousEvent = $event; $event = $scope->applyToEvent($event, $hint); if (null === $event) { - $this->logger->info( - 'The event will be discarded because one of the event processors returned "null".', - ['event' => $beforeEventProcessors] - ); + $this->logger->info('The event will be discarded because one of the event processors returned "null".', ['event' => $previousEvent]); return null; } } - $beforeSendCallback = $event; - $event = $this->applyBeforeSendCallback($event, $hint); + if (!$isTransaction) { + $previousEvent = $event; + $event = ($this->options->getBeforeSendCallback())($event, $hint); - if (null === $event) { - $this->logger->info( - sprintf( - 'The event will be discarded because the "%s" callback returned "null".', - $this->getBeforeSendCallbackName($beforeSendCallback) - ), - ['event' => $beforeSendCallback] - ); - } - - return $event; - } - - private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event - { - if ($event->getType() === EventType::event()) { - return ($this->options->getBeforeSendCallback())($event, $hint); - } - - if ($event->getType() === EventType::transaction()) { - return ($this->options->getBeforeSendTransactionCallback())($event, $hint); + if (null === $event) { + $this->logger->info('The event will be discarded because the "before_send" callback returned "null".', ['event' => $previousEvent]); + } } return $event; } - private function getBeforeSendCallbackName(Event $event): string - { - $beforeSendCallbackName = 'before_send'; - - if ($event->getType() === EventType::transaction()) { - $beforeSendCallbackName = 'before_send_transaction'; - } - - return $beforeSendCallbackName; - } - /** * Optionally adds a missing stacktrace to the Event if the client is configured to do so. * diff --git a/src/Options.php b/src/Options.php index 1c70186da..d9a980258 100644 --- a/src/Options.php +++ b/src/Options.php @@ -163,7 +163,7 @@ public function setTracesSampleRate(float $sampleRate): void */ public function isTracingEnabled(): bool { - return null !== $this->options['traces_sample_rate'] || null !== $this->options['traces_sampler']; + return 0 != $this->options['traces_sample_rate'] || null !== $this->options['traces_sampler']; } /** @@ -396,32 +396,6 @@ public function setBeforeSendCallback(callable $callback): void $this->options = $this->resolver->resolve($options); } - /** - * Gets a callback that will be invoked before an transaction is sent to the server. - * If `null` is returned it won't be sent. - * - * @psalm-return callable(Event, ?EventHint): ?Event - */ - public function getBeforeSendTransactionCallback(): callable - { - return $this->options['before_send_transaction']; - } - - /** - * Sets a callable to be called to decide whether an transaction should - * be captured or not. - * - * @param callable $callback The callable - * - * @psalm-param callable(Event, ?EventHint): ?Event $callback - */ - public function setBeforeSendTransactionCallback(callable $callback): void - { - $options = array_merge($this->options, ['before_send_transaction' => $callback]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets an allow list of trace propagation targets. * @@ -814,7 +788,7 @@ private function configureOptions(OptionsResolver $resolver): void 'send_attempts' => 0, 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, - 'traces_sample_rate' => null, + 'traces_sample_rate' => 0, 'traces_sampler' => null, 'attach_stacktrace' => false, 'context_lines' => 5, @@ -827,9 +801,6 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send' => static function (Event $event): Event { return $event; }, - 'before_send_transaction' => static function (Event $transaction): Event { - return $transaction; - }, 'trace_propagation_targets' => [], 'tags' => [], 'error_types' => null, @@ -852,7 +823,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'string[]'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); - $resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']); + $resolver->setAllowedTypes('traces_sample_rate', ['int', 'float']); $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); @@ -865,7 +836,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); - $resolver->setAllowedTypes('before_send_transaction', ['callable']); $resolver->setAllowedTypes('trace_propagation_targets', 'string[]'); $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['null', 'int']); diff --git a/src/State/Hub.php b/src/State/Hub.php index 213114686..d22baf080 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -222,7 +222,7 @@ public function startTransaction(TransactionContext $context, array $customSampl $client = $this->getClient(); $options = null !== $client ? $client->getOptions() : null; - if (null === $options || !$options->isTracingEnabled()) { + if (null === $options || (!$options->isTracingEnabled() && true !== $context->getParentSampled())) { $transaction->setSampled(false); return $transaction; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 4bee02020..cbc3334de 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -510,39 +510,6 @@ public function processEventChecksBeforeSendOptionDataProvider(): \Generator ]; } - /** - * @dataProvider processEventChecksBeforeSendTransactionOptionDataProvider - */ - public function testProcessEventChecksBeforeSendTransactionOption(Event $event, bool $expectedBeforeSendCall): void - { - $beforeSendTransactionCalled = false; - $options = [ - 'before_send_transaction' => static function () use (&$beforeSendTransactionCalled) { - $beforeSendTransactionCalled = true; - - return null; - }, - ]; - - $client = ClientBuilder::create($options)->getClient(); - $client->captureEvent($event); - - $this->assertSame($expectedBeforeSendCall, $beforeSendTransactionCalled); - } - - public function processEventChecksBeforeSendTransactionOptionDataProvider(): \Generator - { - yield [ - Event::createEvent(), - false, - ]; - - yield [ - Event::createTransaction(), - true, - ]; - } - /** * @dataProvider processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDataProvider */ @@ -607,30 +574,7 @@ public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull() ->setLogger($logger) ->getClient(); - $client->captureEvent(Event::createEvent()); - } - - public function testProcessEventDiscardsEventWhenBeforeSendTransactionCallbackReturnsNull(): void - { - /** @var LoggerInterface&MockObject $logger */ - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because the "before_send_transaction" callback returned "null".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); - - $options = [ - 'before_send_transaction' => static function () { - return null; - }, - ]; - - $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); - - $client->captureEvent(Event::createTransaction()); + $client->captureMessage('foo'); } public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): void diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 85b6aed04..b1635da5c 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -248,15 +248,6 @@ static function (): void {}, null, ]; - yield [ - 'before_send_transaction', - static function (): void {}, - 'getBeforeSendTransactionCallback', - 'setBeforeSendTransactionCallback', - null, - null, - ]; - yield [ 'trace_propagation_targets', ['www.example.com'], diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index d1d0b3f89..c0fe44677 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -90,7 +90,7 @@ public function testFinishDoesNothingIfSampledFlagIsNotTrue(): void /** * @dataProvider parentTransactionContextDataProvider */ - public function testTransactionIsSampledCorrectlyWhenTracingIsSetToZeroInOptions(TransactionContext $context, bool $expectedSampled): void + public function testTransactionIsSampledCorrectlyWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void { $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -129,47 +129,4 @@ public function parentTransactionContextDataProvider(): Generator false, ]; } - - /** - * @dataProvider parentTransactionContextDataProviderDisabled - */ - public function testTransactionIsNotSampledWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void - { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn( - new Options([ - 'traces_sampler' => null, - 'traces_sample_rate' => null, - ]) - ); - - $transaction = (new Hub($client))->startTransaction($context); - - $this->assertSame($expectedSampled, $transaction->getSampled()); - } - - public function parentTransactionContextDataProviderDisabled(): Generator - { - yield [ - new TransactionContext(TransactionContext::DEFAULT_NAME, true), - false, - ]; - - yield [ - new TransactionContext(TransactionContext::DEFAULT_NAME, false), - false, - ]; - - yield [ - TransactionContext::fromHeaders('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', ''), - false, - ]; - - yield [ - TransactionContext::fromHeaders('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', ''), - false, - ]; - } } From 4902f43640963ed45517fd7c1da7fdd5511bb304 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 22 Nov 2022 10:57:08 +0000 Subject: [PATCH 0803/1161] release: 3.12.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 8f01c59a7..b47184fc0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.11.0'; + public const SDK_VERSION = '3.12.0'; /** * @var Options The client options From 123bfb5dfc3a956b4d9a4c0893db01ae9df8982e Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 23 Nov 2022 15:43:54 +0100 Subject: [PATCH 0804/1161] Start development of version 3.13 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 62e38b1d6..03fbd2d08 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,7 @@ "prefer-stable": true, "extra": { "branch-alias": { - "dev-master": "3.12.x-dev" + "dev-master": "3.13.x-dev" } } } From 29c19a772f32122a742d2c840a3852e027b90e3b Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 12 Dec 2022 09:32:33 +0100 Subject: [PATCH 0805/1161] ref: Bump CI to PHP 8.2 (#1440) --- .github/workflows/ci.yml | 10 +++------- .github/workflows/static-analysis.yaml | 4 ++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c6d2df04..5daa262c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: - name: Install highest dependencies run: composer update --no-progress --no-interaction --prefer-dist - if: ${{ matrix.dependencies == 'highest' && matrix.php != '8.2' }} + if: ${{ matrix.dependencies == 'highest' }} - name: Install highest PHP 8.2 dependencies run: composer update --no-progress --no-interaction --prefer-dist --ignore-platform-req=php @@ -74,11 +74,7 @@ jobs: - name: Install lowest dependencies run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest - if: ${{ matrix.dependencies == 'lowest' && matrix.php != '8.2' }} - - - name: Install lowest PHP 8.2 dependencies - run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest --ignore-platform-req=php - if: ${{ matrix.dependencies == 'lowest' && matrix.php == '8.2' }} + if: ${{ matrix.dependencies == 'lowest' }} - name: Run tests run: vendor/bin/phpunit --coverage-clover=coverage.xml @@ -88,4 +84,4 @@ jobs: - name: Check benchmarks run: vendor/bin/phpbench run --revs=1 --iterations=1 - if: ${{ matrix.dependencies == 'highest' && matrix.php == '8.1' }} + if: ${{ matrix.dependencies == 'highest' && matrix.php == '8.2' }} diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 03a8ab35b..df0356e0e 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -35,6 +35,8 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist @@ -53,6 +55,8 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist From 31b6208968185d4a5559ba6765d286c8d2370d66 Mon Sep 17 00:00:00 2001 From: Jesper Noordsij <45041769+jnoordsij@users.noreply.github.com> Date: Mon, 9 Jan 2023 11:05:49 +0100 Subject: [PATCH 0806/1161] Fix nullability in getters and setters for traces_sample_rate in Options (#1441) Co-authored-by: Michi Hoffmann --- CHANGELOG.md | 2 ++ phpstan-baseline.neon | 2 +- src/Options.php | 8 ++++---- src/State/Hub.php | 2 +- tests/OptionsTest.php | 9 +++++++++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 719f1934b..aecb0720f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- fix: Allow `null` on `getTracesSampleRate` and `setTracesSampleRate` in `Options` class (#1441) + ## 3.12.0 (2022-11-22) - feat: Add `before_send_transaction` option (#1424) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 11c0ed88c..5c896d25b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -226,7 +226,7 @@ parameters: path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getTracesSampleRate\\(\\) should return float but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getTracesSampleRate\\(\\) should return float\\|null but returns mixed\\.$#" count: 1 path: src/Options.php diff --git a/src/Options.php b/src/Options.php index 1c70186da..319c3c5ae 100644 --- a/src/Options.php +++ b/src/Options.php @@ -138,7 +138,7 @@ public function setSampleRate(float $sampleRate): void * Gets the sampling factor to apply to transaction. A value of 0 will deny * sending any transaction, and a value of 1 will send 100% of transaction. */ - public function getTracesSampleRate(): float + public function getTracesSampleRate(): ?float { return $this->options['traces_sample_rate']; } @@ -147,9 +147,9 @@ public function getTracesSampleRate(): float * Sets the sampling factor to apply to transactions. A value of 0 will deny * sending any transactions, and a value of 1 will send 100% of transactions. * - * @param float $sampleRate The sampling factor + * @param ?float $sampleRate The sampling factor */ - public function setTracesSampleRate(float $sampleRate): void + public function setTracesSampleRate(?float $sampleRate): void { $options = array_merge($this->options, ['traces_sample_rate' => $sampleRate]); @@ -163,7 +163,7 @@ public function setTracesSampleRate(float $sampleRate): void */ public function isTracingEnabled(): bool { - return null !== $this->options['traces_sample_rate'] || null !== $this->options['traces_sampler']; + return null !== $this->getTracesSampleRate() || null !== $this->getTracesSampler(); } /** diff --git a/src/State/Hub.php b/src/State/Hub.php index 213114686..c4e93c263 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -239,7 +239,7 @@ public function startTransaction(TransactionContext $context, array $customSampl } else { $sampleRate = $this->getSampleRate( $samplingContext->getParentSampled(), - $options->getTracesSampleRate() + $options->getTracesSampleRate() ?? 0 ); } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 85b6aed04..3559e466d 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -122,6 +122,15 @@ public function optionsDataProvider(): \Generator null, ]; + yield [ + 'traces_sample_rate', + null, + 'getTracesSampleRate', + 'setTracesSampleRate', + null, + null, + ]; + yield [ 'traces_sampler', static function (): void {}, From 9f6c5670a38e1c41314e0697ddfc2c0b73f8a7cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jan 2023 10:14:08 +0000 Subject: [PATCH 0807/1161] chore(deps): bump actions/stale from 3.0.14 to 7.0.0 (#1444) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Michi Hoffmann --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index bc092820a..fbc372132 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@87c2b794b9b47a9bec68ae03c01aeb572ffebdb1 + - uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b with: repo-token: ${{ github.token }} days-before-stale: 21 From b848368bbd8b337080cba5ad64f2444be0429421 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 12 Jan 2023 13:23:43 +0100 Subject: [PATCH 0808/1161] Prepare 3.12.1 (#1446) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aecb0720f..b6baae2d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.12.1 (2023-01-12) + - fix: Allow `null` on `getTracesSampleRate` and `setTracesSampleRate` in `Options` class (#1441) ## 3.12.0 (2022-11-22) From 155bb9b78438999de4529d6f051465be15a58bc5 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 12 Jan 2023 12:24:27 +0000 Subject: [PATCH 0809/1161] release: 3.12.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index b47184fc0..875947c6a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.12.0'; + public const SDK_VERSION = '3.12.1'; /** * @var Options The client options From bd40d05589e688e052410af6909d1fa2c2bd0397 Mon Sep 17 00:00:00 2001 From: Anne van de Venis Date: Mon, 16 Jan 2023 14:48:18 +0100 Subject: [PATCH 0810/1161] feat: add object ID to serialized value (#1443) Co-authored-by: Michi Hoffmann --- CHANGELOG.md | 2 ++ src/Serializer/AbstractSerializer.php | 9 +++++- tests/Serializer/AbstractSerializerTest.php | 33 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6baae2d0..b473afc8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- feat: add object ID to serialized value (#1443) + ## 3.12.1 (2023-01-12) - fix: Allow `null` on `getTracesSampleRate` and `setTracesSampleRate` in `Options` class (#1441) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index f41d16cad..b1f6f66b1 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -241,7 +241,14 @@ protected function serializeValue($value) } if (\is_object($value)) { - return 'Object ' . \get_class($value); + $objectId = null; + if (isset($value->id)) { + $objectId = $value->id; + } elseif (\is_callable([$value, 'getId']) && method_exists($value, 'getId')) { + $objectId = $value->getId(); + } + + return 'Object ' . \get_class($value) . ((null !== $objectId) ? '(#' . $objectId . ')' : ''); } if (\is_resource($value)) { diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index af3e89162..2a3b44734 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -54,6 +54,24 @@ public function testObjectsAreStrings(): void $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObject', $result); } + public function testObjectsWithIdPropertyAreStrings(): void + { + $serializer = $this->createSerializer(); + $input = new SerializerTestObjectWithIdProperty(); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty(#bar)', $result); + } + + public function testObjectsWithSerializerTestObjectWithGetIdMethodAreStrings(): void + { + $serializer = $this->createSerializer(); + $input = new SerializerTestObjectWithGetIdMethod(); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObjectWithGetIdMethod(#foobar)', $result); + } + public function testObjectsAreNotStrings(): void { $serializer = $this->createSerializer(); @@ -576,6 +594,21 @@ public static function testy(): void } } +class SerializerTestObjectWithIdProperty extends SerializerTestObject +{ + public $id = 'bar'; +} + +class SerializerTestObjectWithGetIdMethod extends SerializerTestObject +{ + private $id = 'foo'; + + public function getId() + { + return $this->id . 'bar'; + } +} + class Invokable { public function __invoke(): bool From bfa3193944fe385af7d6b6b063962fc45956a817 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 20 Jan 2023 18:58:03 +0100 Subject: [PATCH 0811/1161] Minor cleanups (#1449) --- src/ClientBuilder.php | 4 ++-- src/HttpClient/HttpClientFactory.php | 8 ++++---- tests/HttpClient/HttpClientFactoryTest.php | 12 ++++++------ tests/HttpClient/Plugin/GzipEncoderPluginTest.php | 6 ++++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 39d1e6cc3..8cdcc7317 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -177,8 +177,8 @@ private function createDefaultTransportFactory(): DefaultTransportFactory { $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); $httpClientFactory = new HttpClientFactory( - Psr17FactoryDiscovery::findUriFactory(), - Psr17FactoryDiscovery::findResponseFactory(), + null, + null, $streamFactory, null, $this->sdkIdentifier, diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index d18f72d3e..7e9cea0f7 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -54,16 +54,16 @@ final class HttpClientFactory implements HttpClientFactoryInterface /** * Constructor. * - * @param UriFactoryInterface $uriFactory The PSR-7 URI factory - * @param ResponseFactoryInterface $responseFactory The PSR-7 response factory + * @param UriFactoryInterface|null $uriFactory The PSR-7 URI factory + * @param ResponseFactoryInterface|null $responseFactory The PSR-7 response factory * @param StreamFactoryInterface $streamFactory The PSR-17 stream factory * @param HttpAsyncClientInterface|null $httpClient The HTTP client * @param string $sdkIdentifier The SDK identifier * @param string $sdkVersion The SDK version */ public function __construct( - UriFactoryInterface $uriFactory, - ResponseFactoryInterface $responseFactory, + ?UriFactoryInterface $uriFactory, + ?ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, ?HttpAsyncClientInterface $httpClient, string $sdkIdentifier, diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php index 852dc430d..182a52a74 100644 --- a/tests/HttpClient/HttpClientFactoryTest.php +++ b/tests/HttpClient/HttpClientFactoryTest.php @@ -23,8 +23,8 @@ public function testCreate(bool $isCompressionEnabled, string $expectedRequestBo $mockHttpClient = new HttpMockClient(); $httpClientFactory = new HttpClientFactory( - Psr17FactoryDiscovery::findUrlFactory(), - Psr17FactoryDiscovery::findResponseFactory(), + null, + null, $streamFactory, $mockHttpClient, 'sentry.php.test', @@ -67,8 +67,8 @@ public function createDataProvider(): \Generator public function testCreateThrowsIfDsnOptionIsNotConfigured(): void { $httpClientFactory = new HttpClientFactory( - Psr17FactoryDiscovery::findUrlFactory(), - Psr17FactoryDiscovery::findResponseFactory(), + null, + null, Psr17FactoryDiscovery::findStreamFactory(), null, 'sentry.php.test', @@ -84,8 +84,8 @@ public function testCreateThrowsIfDsnOptionIsNotConfigured(): void public function testCreateThrowsIfHttpProxyOptionIsUsedWithCustomHttpClient(): void { $httpClientFactory = new HttpClientFactory( - Psr17FactoryDiscovery::findUrlFactory(), - Psr17FactoryDiscovery::findResponseFactory(), + null, + null, Psr17FactoryDiscovery::findStreamFactory(), $this->createMock(HttpAsyncClientInterface::class), 'sentry.php.test', diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php index 638406393..0e5423015 100644 --- a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php +++ b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php @@ -17,11 +17,13 @@ final class GzipEncoderPluginTest extends TestCase { public function testHandleRequest(): void { - $plugin = new GzipEncoderPlugin(Psr17FactoryDiscovery::findStreamFactory()); + $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); + + $plugin = new GzipEncoderPlugin($streamFactory); $expectedPromise = $this->createMock(PromiseInterface::class); $request = Psr17FactoryDiscovery::findRequestFactory() ->createRequest('POST', 'http://www.example.com') - ->withBody(Psr17FactoryDiscovery::findStreamFactory()->createStream('foo')); + ->withBody($streamFactory->createStream('foo')); $this->assertSame('foo', (string) $request->getBody()); $this->assertSame($expectedPromise, $plugin->handleRequest( From 33331665838904c6128240e381c824d4604b5eba Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Jan 2023 13:15:13 +0000 Subject: [PATCH 0812/1161] docs: Remove the need to add Changelogs (#1455) --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75c3a4260..b32f588ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,6 @@ If you feel that you can fix or implement it yourself, please read on to learn h - Add tests for your changes to `tests/`. - Run tests and make sure all of them pass. - Submit a pull request, targeting the `develop` branch if you added any new features. For bug fixes of the current release, please target the `master` branch instead. -- Make sure to update the `CHANGELOG.md` file below the `Unreleased` heading. We will review your pull request as soon as possible. Thank you for contributing! From 8cf074301c6e7ee094255e8eb114c16d74356521 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Jan 2023 13:15:23 +0000 Subject: [PATCH 0813/1161] ci: remove danger (#1454) --- .github/workflows/danger.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .github/workflows/danger.yml diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml deleted file mode 100644 index 000b75ff3..000000000 --- a/.github/workflows/danger.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Danger - -on: - pull_request: - types: [opened, synchronize, reopened, edited, ready_for_review] - -jobs: - danger: - uses: getsentry/github-workflows/.github/workflows/danger.yml@v2 From 2aa961e8650f786053da50e40530ff23902aea03 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 31 Jan 2023 15:21:12 +0100 Subject: [PATCH 0814/1161] feat: Log exception's code (#1451) --- CHANGELOG.md | 2 + src/Client.php | 2 +- src/ExceptionMechanism.php | 40 +++++++++++-- .../AbstractErrorListenerIntegration.php | 8 ++- src/Serializer/PayloadSerializer.php | 7 ++- tests/ClientTest.php | 6 +- tests/ExceptionMechanismTest.php | 58 +++++++++++++++++++ tests/Serializer/PayloadSerializerTest.php | 7 ++- 8 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 tests/ExceptionMechanismTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b473afc8d..7dd858d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased - feat: add object ID to serialized value (#1443) +- feat: Add support for sending arbitrary extra data as part of the exception mechanism (#1450) +- feat: Log exception's `code` to the `ExceptionMechanism::data` (#1450) ## 3.12.1 (2023-01-12) diff --git a/src/Client.php b/src/Client.php index 875947c6a..616491f5d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -383,7 +383,7 @@ private function addThrowableToEvent(Event $event, \Throwable $exception, EventH $exceptions[] = new ExceptionDataBag( $exception, $this->stacktraceBuilder->buildFromException($exception), - $hint->mechanism ?? new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true) + $hint->mechanism ?? new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => $exception->getCode()]) ); } while ($exception = $exception->getPrevious()); diff --git a/src/ExceptionMechanism.php b/src/ExceptionMechanism.php index 8dc83a9ea..9a3cf23ee 100644 --- a/src/ExceptionMechanism.php +++ b/src/ExceptionMechanism.php @@ -26,18 +26,27 @@ final class ExceptionMechanism */ private $handled; + /** + * @var array Arbitrary extra data that might help the user + * understand the error thrown by this mechanism + */ + private $data; + /** * Class constructor. * - * @param string $type Unique identifier of this mechanism determining - * rendering and processing of the mechanism data - * @param bool $handled Flag indicating whether the exception has been - * handled by the user (e.g. via try..catch) + * @param string $type Unique identifier of this mechanism determining + * rendering and processing of the mechanism data + * @param bool $handled Flag indicating whether the exception has been + * handled by the user (e.g. via try..catch) + * @param array $data Arbitrary extra data that might help the user + * understand the error thrown by this mechanism */ - public function __construct(string $type, bool $handled) + public function __construct(string $type, bool $handled, array $data = []) { $this->type = $type; $this->handled = $handled; + $this->data = $data; } /** @@ -57,4 +66,25 @@ public function isHandled(): bool { return $this->handled; } + + /** + * Returns arbitrary extra data that might help the user understand the error + * thrown by this mechanism. + * + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * Sets the arbitrary extra data. + * + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } } diff --git a/src/Integration/AbstractErrorListenerIntegration.php b/src/Integration/AbstractErrorListenerIntegration.php index 487933a09..530dc86af 100644 --- a/src/Integration/AbstractErrorListenerIntegration.php +++ b/src/Integration/AbstractErrorListenerIntegration.php @@ -36,7 +36,13 @@ protected function addExceptionMechanismToEvent(Event $event): Event $exceptions = $event->getExceptions(); foreach ($exceptions as $exception) { - $exception->setMechanism(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false)); + $data = []; + $mechanism = $exception->getMechanism(); + if (null !== $mechanism) { + $data = $mechanism->getData(); + } + + $exception->setMechanism(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false, $data)); } return $event; diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index baf3edec1..6a719918c 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -266,7 +266,8 @@ private function serializeBreadcrumb(Breadcrumb $breadcrumb): array * }, * mechanism?: array{ * type: string, - * handled: boolean + * handled: boolean, + * data?: array * } * } */ @@ -290,6 +291,10 @@ private function serializeException(ExceptionDataBag $exception): array 'type' => $exceptionMechanism->getType(), 'handled' => $exceptionMechanism->isHandled(), ]; + + if ([] !== $exceptionMechanism->getData()) { + $result['mechanism']['data'] = $exceptionMechanism->getData(); + } } return $result; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 4bee02020..078ddc1b1 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -724,7 +724,7 @@ public function testBuildEventWithException(): void { $options = new Options(); $previousException = new \RuntimeException('testMessage2'); - $exception = new \Exception('testMessage', 0, $previousException); + $exception = new \Exception('testMessage', 1, $previousException); /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -735,12 +735,12 @@ public function testBuildEventWithException(): void $this->assertCount(2, $capturedExceptions); $this->assertNotNull($capturedExceptions[0]->getStacktrace()); - $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), $capturedExceptions[0]->getMechanism()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 1]), $capturedExceptions[0]->getMechanism()); $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); $this->assertNotNull($capturedExceptions[1]->getStacktrace()); - $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), $capturedExceptions[1]->getMechanism()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 0]), $capturedExceptions[1]->getMechanism()); $this->assertSame(\RuntimeException::class, $capturedExceptions[1]->getType()); $this->assertSame('testMessage2', $capturedExceptions[1]->getValue()); diff --git a/tests/ExceptionMechanismTest.php b/tests/ExceptionMechanismTest.php new file mode 100644 index 000000000..60e935355 --- /dev/null +++ b/tests/ExceptionMechanismTest.php @@ -0,0 +1,58 @@ +assertSame($expectedType, $exceptionMechanism->getType()); + $this->assertSame($expectedHandled, $exceptionMechanism->isHandled()); + $this->assertSame($expectedData, $exceptionMechanism->getData()); + } + + public function constructorDataProvider(): iterable + { + yield [ + [ + ExceptionMechanism::TYPE_GENERIC, + true, + ], + ExceptionMechanism::TYPE_GENERIC, + true, + [], + ]; + + yield [ + [ + 'custom', + false, + ['key' => 'value'], + ], + 'custom', + false, + ['key' => 'value'], + ]; + } + + public function testSetData(): void + { + $exceptionDataBag = new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['replace' => 'me']); + $exceptionDataBag->setData(['new' => 'value']); + $this->assertSame(['new' => 'value'], $exceptionDataBag->getData()); + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 73f60a36c..3165f63c0 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -179,7 +179,7 @@ public function serializeDataProvider(): iterable $frame1, $frame2, ]), - new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true) + new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 123]) ), ]); @@ -314,7 +314,10 @@ public function serializeDataProvider(): iterable }, "mechanism": { "type": "generic", - "handled": true + "handled": true, + "data": { + "code": 123 + } } }, { From ef56c222f64a46a40a8aaadc998633ab954c9a38 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 2 Feb 2023 14:10:34 +0100 Subject: [PATCH 0815/1161] WIP --- CHANGELOG.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd858d2f..c52ab45e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,37 @@ ## Unreleased -- feat: add object ID to serialized value (#1443) -- feat: Add support for sending arbitrary extra data as part of the exception mechanism (#1450) -- feat: Log exception's `code` to the `ExceptionMechanism::data` (#1450) +## 3.13.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.13.0. + +### Features + +- Object IDs are now automatically seralized as part of a stracktrace frame [(#1443)](https://github.com/getsentry/sentry-php/pull/1443) + - Display the ID of an seralized object in the stacktrace frame on the issues details page, if `Obj::getID()` or `Obj->id` is accessible. + +- Add more functionality to the `ExceptionMechanism::class` [(#1450)](https://github.com/getsentry/sentry-php/pull/1450) + - Attach arbitrary data as part of the `ExceptionMechanism` + ```php + $hint = EventHint::fromArray([ + 'exception' => $exception, + 'mechanism' => new ExceptionMechanism( + ExceptionMechanism::TYPE_GENERIC, + false, + [ + 'key' => 'value', + //... + ], + ), + ]); + captureEvent(Event::createEvent(), $hint); + ``` + Learn more about the interface of the `ExceptionMechanism` on https://develop.sentry.dev/sdk/event-payloads/exception/#exception-mechanism + - Access or mutate `ExceptionMechanism::data` via `ExceptionMechanism::getData()` and `ExceptionMechanism::setData()` + - If an exception contains a user provided `code`, the value will be seralized into the event and being displayed on the issues details page. + ```php + throw new \Exception('Oh no!', 123); + ``` ## 3.12.1 (2023-01-12) From ab0e53f701d67175a3924b0ca347c50f92e4fe00 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 2 Feb 2023 14:17:23 +0100 Subject: [PATCH 0816/1161] Update CHANGELOG.md --- CHANGELOG.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c52ab45e8..2dba0ece8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,13 @@ The Sentry SDK team is happy to announce the immediate availability of Sentry PH ### Features -- Object IDs are now automatically seralized as part of a stracktrace frame [(#1443)](https://github.com/getsentry/sentry-php/pull/1443) - - Display the ID of an seralized object in the stacktrace frame on the issues details page, if `Obj::getID()` or `Obj->id` is accessible. +- Object IDs are now automatically serialized as part of a stack trace frame [(#1443)](https://github.com/getsentry/sentry-php/pull/1443) + - If `Obj::getID()` or `Obj->id` is accessible, this value will be displayed inside the stack trace frame on the issue details page. + To attach local variables to your stack trace, make sure `zend.exception_ignore_arg: 0` is set in your `php.ini`. + See https://docs.sentry.io/platforms/php/troubleshooting/#missing-variables-in-stack-traces - Add more functionality to the `ExceptionMechanism::class` [(#1450)](https://github.com/getsentry/sentry-php/pull/1450) - - Attach arbitrary data as part of the `ExceptionMechanism` + - Attach arbitrary data ```php $hint = EventHint::fromArray([ 'exception' => $exception, @@ -29,10 +31,10 @@ The Sentry SDK team is happy to announce the immediate availability of Sentry PH ``` Learn more about the interface of the `ExceptionMechanism` on https://develop.sentry.dev/sdk/event-payloads/exception/#exception-mechanism - Access or mutate `ExceptionMechanism::data` via `ExceptionMechanism::getData()` and `ExceptionMechanism::setData()` - - If an exception contains a user provided `code`, the value will be seralized into the event and being displayed on the issues details page. - ```php - throw new \Exception('Oh no!', 123); - ``` + - If an exception contains a user-provided `code`, the value will be serialized into the event and displayed on the issues details page. + ```php + throw new \Exception('Oh no!', 123); + ``` ## 3.12.1 (2023-01-12) From 2d66a1f27d7f9c704b03c7fddc657efcab2a09a5 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 2 Feb 2023 14:47:31 +0000 Subject: [PATCH 0817/1161] release: 3.13.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 616491f5d..8b3ab33f2 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.12.1'; + public const SDK_VERSION = '3.13.0'; /** * @var Options The client options From a046ff5a37f5a0a0c285a6543dc17a7fc93b47f8 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Fri, 3 Feb 2023 11:03:13 +0100 Subject: [PATCH 0818/1161] ci: Set threshold for codecov --- codecov.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codecov.yml b/codecov.yml index b8989a5a7..5c45b27ee 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,3 +3,9 @@ ignore: - tests/data - tests/resources - tests/Fixtures + +coverage: + status: + project: + default: + threshold: 0.1% # allow for 0.1% reduction of coverage without failing From 78591ce7bb83dbf4d9435bd2ae9f6984ea8515c9 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 9 Feb 2023 11:02:55 +0100 Subject: [PATCH 0819/1161] fix: Sanatize HTTP client spans & breadcrumbs (#1453) --- src/Tracing/GuzzleTracingMiddleware.php | 20 +++++++++++-- tests/Tracing/GuzzleTracingMiddlewareTest.php | 30 ++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index d7c1c9a18..38d8c8082 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -6,6 +6,7 @@ use Closure; use GuzzleHttp\Exception\RequestException as GuzzleRequestException; +use GuzzleHttp\Psr7\Uri; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Sentry\Breadcrumb; @@ -29,9 +30,20 @@ public static function trace(?HubInterface $hub = null): Closure return $handler($request, $options); } + $partialUri = Uri::fromParts([ + 'scheme' => $request->getUri()->getScheme(), + 'host' => $request->getUri()->getHost(), + 'port' => $request->getUri()->getPort(), + 'path' => $request->getUri()->getPath(), + ]); + $spanContext = new SpanContext(); $spanContext->setOp('http.client'); - $spanContext->setDescription($request->getMethod() . ' ' . $request->getUri()); + $spanContext->setDescription($request->getMethod() . ' ' . (string) $partialUri); + $spanContext->setData([ + 'http.query' => $request->getUri()->getQuery(), + 'http.fragment' => $request->getUri()->getFragment(), + ]); $childSpan = $span->startChild($spanContext); @@ -48,7 +60,7 @@ public static function trace(?HubInterface $hub = null): Closure } } - $handlerPromiseCallback = static function ($responseOrException) use ($hub, $request, $childSpan) { + $handlerPromiseCallback = static function ($responseOrException) use ($hub, $request, $childSpan, $partialUri) { // We finish the span (which means setting the span end timestamp) first to ensure the measured time // the span spans is as close to only the HTTP request time and do the data collection afterwards $childSpan->finish(); @@ -63,9 +75,11 @@ public static function trace(?HubInterface $hub = null): Closure } $breadcrumbData = [ - 'url' => (string) $request->getUri(), + 'url' => (string) $partialUri, 'method' => $request->getMethod(), 'request_body_size' => $request->getBody()->getSize(), + 'http.query' => $request->getUri()->getQuery(), + 'http.fragment' => $request->getUri()->getFragment(), ]; if (null !== $response) { diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 9a27de875..2b6587b1c 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -9,6 +9,7 @@ use GuzzleHttp\Promise\RejectedPromise; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Uri; use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; use Sentry\Event; @@ -90,8 +91,15 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec $guzzleSpan = $spans[0]; $guzzleBreadcrumb = $breadcrumbs[0]; + $partialUri = Uri::fromParts([ + 'scheme' => $request->getUri()->getScheme(), + 'host' => $request->getUri()->getHost(), + 'port' => $request->getUri()->getPort(), + 'path' => $request->getUri()->getPath(), + ]); + $this->assertSame('http.client', $guzzleSpan->getOp()); - $this->assertSame("{$request->getMethod()} {$request->getUri()}", $guzzleSpan->getDescription()); + $this->assertSame("{$request->getMethod()} {$partialUri}", $guzzleSpan->getDescription()); if ($expectedPromiseResult instanceof Response) { $this->assertSame(SpanStatus::createFromHttpStatusCode($expectedPromiseResult->getStatusCode()), $guzzleSpan->getStatus()); @@ -142,6 +150,22 @@ public function traceDataProvider(): iterable 'url' => 'https://www.example.com', 'method' => 'GET', 'request_body_size' => 0, + 'http.query' => '', + 'http.fragment' => '', + 'status_code' => 200, + 'response_body_size' => 0, + ], + ]; + + yield [ + new Request('GET', 'https://user:password@www.example.com?query=string#fragment=1'), + new Response(), + [ + 'url' => 'https://www.example.com', + 'method' => 'GET', + 'request_body_size' => 0, + 'http.query' => 'query=string', + 'http.fragment' => 'fragment=1', 'status_code' => 200, 'response_body_size' => 0, ], @@ -154,6 +178,8 @@ public function traceDataProvider(): iterable 'url' => 'https://www.example.com', 'method' => 'POST', 'request_body_size' => 10, + 'http.query' => '', + 'http.fragment' => '', 'status_code' => 403, 'response_body_size' => 6, ], @@ -166,6 +192,8 @@ public function traceDataProvider(): iterable 'url' => 'https://www.example.com', 'method' => 'GET', 'request_body_size' => 0, + 'http.query' => '', + 'http.fragment' => '', ], ]; } From 377e044ab7bdd50f66b3390a929e5d81b70da9ca Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 9 Feb 2023 16:05:40 +0100 Subject: [PATCH 0820/1161] fix: set php-http/discovery to allow false (#1462) --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 03fbd2d08..8096714b0 100644 --- a/composer.json +++ b/composer.json @@ -89,6 +89,7 @@ "sort-packages": true, "allow-plugins": { "composer/package-versions-deprecated": true, + "php-http/discovery": false, "phpstan/extension-installer": true } }, From a5a3af8a46cca260e982335a3465126d5e825ccb Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 9 Feb 2023 16:15:46 +0100 Subject: [PATCH 0821/1161] feat: Add enableTracing Option (#1458) --- phpstan-baseline.neon | 5 +++++ src/Options.php | 35 ++++++++++++++++++++++++++++++++++- tests/OptionsTest.php | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5c896d25b..f96d985ae 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -130,6 +130,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getEnableTracing\\(\\) should return bool\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getEnvironment\\(\\) should return string\\|null but returns mixed\\.$#" count: 1 diff --git a/src/Options.php b/src/Options.php index 319c3c5ae..d553b9b30 100644 --- a/src/Options.php +++ b/src/Options.php @@ -56,6 +56,10 @@ public function __construct(array $options = []) $this->configureOptions($this->resolver); $this->options = $this->resolver->resolve($options); + + if (true === $this->options['enable_tracing'] && null === $this->options['traces_sample_rate']) { + $this->options = array_merge($this->options, ['traces_sample_rate' => 1]); + } } /** @@ -143,6 +147,29 @@ public function getTracesSampleRate(): ?float return $this->options['traces_sample_rate']; } + /** + * Sets if tracing should be enabled or not. If null tracesSampleRate takes + * precedence. + * + * @param bool|null $enableTracing Boolean if tracing should be enabled or not + */ + public function setEnableTracing(?bool $enableTracing): void + { + $options = array_merge($this->options, ['enable_tracing' => $enableTracing]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets if tracing is enabled or not. + * + * @return bool|null If the option `enable_tracing` is set or not + */ + public function getEnableTracing(): ?bool + { + return $this->options['enable_tracing']; + } + /** * Sets the sampling factor to apply to transactions. A value of 0 will deny * sending any transactions, and a value of 1 will send 100% of transactions. @@ -159,10 +186,14 @@ public function setTracesSampleRate(?float $sampleRate): void /** * Gets whether tracing is enabled or not. The feature is enabled when at * least one of the `traces_sample_rate` and `traces_sampler` options is - * set. + * set and `enable_tracing` is set and not false. */ public function isTracingEnabled(): bool { + if (null !== $this->getEnableTracing() && false === $this->getEnableTracing()) { + return false; + } + return null !== $this->getTracesSampleRate() || null !== $this->getTracesSampler(); } @@ -814,6 +845,7 @@ private function configureOptions(OptionsResolver $resolver): void 'send_attempts' => 0, 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, + 'enable_tracing' => null, 'traces_sample_rate' => null, 'traces_sampler' => null, 'attach_stacktrace' => false, @@ -852,6 +884,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'string[]'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); + $resolver->setAllowedTypes('enable_tracing', ['null', 'bool']); $resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 3559e466d..35affb885 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -140,6 +140,15 @@ static function (): void {}, null, ]; + yield [ + 'enable_tracing', + true, + 'getEnableTracing', + 'setEnableTracing', + null, + null, + ]; + yield [ 'attach_stacktrace', false, @@ -615,4 +624,31 @@ public function testErrorTypesOptionIsNotDynamiclyReadFromErrorReportingLevelWhe $this->assertSame($errorTypesOptionValue, $options->getErrorTypes()); } + + /** + * @dataProvider enableTracingDataProvider + */ + public function testEnableTracing(?bool $enabledTracing, ?float $tracesSampleRate, $expectedResult): void + { + $options = new Options([ + 'enable_tracing' => $enabledTracing, + 'traces_sample_rate' => $tracesSampleRate, + ]); + + $this->assertSame($expectedResult, $options->isTracingEnabled()); + } + + public function enableTracingDataProvider(): array + { + return [ + [null, null, false], + [null, 1.0, true], + [false, 1.0, false], + [true, 1.0, true], + [null, 0.0, true], // We use this as - it's configured but turned off + [false, 0.0, false], + [true, 0.0, true], // We use this as - it's configured but turned off + [true, null, true], + ]; + } } From 748bcf5aa13d2bf092445cac2c440492dd49d119 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 9 Feb 2023 16:58:13 +0100 Subject: [PATCH 0822/1161] ci: Remove --ignore-platform-req=php from PHP 8.2 (#1463) --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5daa262c1..3b2098430 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,10 +64,6 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist if: ${{ matrix.dependencies == 'highest' }} - - name: Install highest PHP 8.2 dependencies - run: composer update --no-progress --no-interaction --prefer-dist --ignore-platform-req=php - if: ${{ matrix.dependencies == 'highest' && matrix.php == '8.2' }} - - name: Restrict lowest Symfony version on PHP 8.1 & 8.2 run: composer require symfony/options-resolver:^4.4.30 --no-update if: ${{ matrix.dependencies == 'lowest' && matrix.php == '8.1' || matrix.php == '8.2' }} From 70c99041cf5121912fe2a5e61820b0ddc592f947 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 9 Feb 2023 17:41:10 +0100 Subject: [PATCH 0823/1161] fix: pin php-http/discovery to < 1.15 (#1464) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8096714b0..429782152 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "jean85/pretty-package-versions": "^1.5|^2.0.4", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", - "php-http/discovery": "^1.11", + "php-http/discovery": "^1.11, <1.15", "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", "psr/http-factory": "^1.0", From 5f337d45909ada7411e6459dd5829ea802392a55 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 10 Feb 2023 11:14:48 +0100 Subject: [PATCH 0824/1161] Prepare 3.13.1 (#1465) --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dba0ece8..a8ce58d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # CHANGELOG -## Unreleased +## 3.13.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.13.1. + +## Bug Fixes + +- Sanatize HTTP client spans & breadcrumbs [(#1453)](https://github.com/getsentry/sentry-php/pull/1453) +- Pin php-http/discovery to < 1.15 to disable some unwanted side-effect introduced in this new minor version [(#1464)](https://github.com/getsentry/sentry-php/pull/1464) ## 3.13.0 From d0cbf08419f93c79ead3bc34b7ccbd2b207d273f Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Fri, 10 Feb 2023 10:16:58 +0000 Subject: [PATCH 0825/1161] release: 3.13.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 8b3ab33f2..61c1f62c6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.13.0'; + public const SDK_VERSION = '3.13.1'; /** * @var Options The client options From 71c86fe4699a7f1a40c7d985f3dc7667045152f0 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Fri, 10 Feb 2023 11:17:57 +0100 Subject: [PATCH 0826/1161] Update Changelog.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ce58d1b..91a15dfc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,10 @@ The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.13.1. -## Bug Fixes +### Bug Fixes - Sanatize HTTP client spans & breadcrumbs [(#1453)](https://github.com/getsentry/sentry-php/pull/1453) -- Pin php-http/discovery to < 1.15 to disable some unwanted side-effect introduced in this new minor version [(#1464)](https://github.com/getsentry/sentry-php/pull/1464) +- Pin php-http/discovery to `< 1.15` to disable some unwanted side-effect introduced in this new minor version [(#1464)](https://github.com/getsentry/sentry-php/pull/1464) ## 3.13.0 From 9c181eb1e56e665593d8942e94bdda22e157c4b3 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 14 Feb 2023 13:10:28 +0100 Subject: [PATCH 0827/1161] fix: Use array_replace to preserve request body keys (#1470) --- src/Integration/RequestIntegration.php | 2 +- tests/Integration/RequestIntegrationTest.php | 25 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 61748ec77..792ef8300 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -215,7 +215,7 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re } $requestData = $request->getParsedBody(); - $requestData = array_merge( + $requestData = array_replace( $this->parseUploadedFiles($request->getUploadedFiles()), \is_array($requestData) ? $requestData : [] ); diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 64c78521a..3d04e456d 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -399,6 +399,31 @@ public function invokeDataProvider(): iterable null, ]; + yield [ + [ + 'max_request_body_size' => 'always', + ], + (new ServerRequest('POST', 'http://www.example.com/foo')) + ->withHeader('Content-Type', 'application/json') + ->withHeader('Content-Length', '23') + ->withBody(Utils::streamFor('{"1":"foo","bar":"baz"}')), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + 'headers' => [ + 'Host' => ['www.example.com'], + 'Content-Type' => ['application/json'], + 'Content-Length' => ['23'], + ], + 'data' => [ + '1' => 'foo', + 'bar' => 'baz', + ], + ], + null, + null, + ]; + yield [ [ 'max_request_body_size' => 'always', From 9c4f8d9dbd2e4e5b4a7df24c55b30754761b2896 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 16 Feb 2023 18:16:06 +0100 Subject: [PATCH 0828/1161] Switch to MIT license (#1471) Co-authored-by: Chad Whitacre --- LICENSE | 25 +++++++++++++++++-------- composer.json | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/LICENSE b/LICENSE index bef63ca82..05f20e91f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,12 +1,21 @@ -Copyright (c) 2012 Sentry Team and individual contributors. -All rights reserved. +MIT License -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Copyright (c) 2012 Functional Software, Inc. dba Sentry - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - 3. Neither the name of the copyright holder, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/composer.json b/composer.json index 429782152..069274dc6 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "crash-reports" ], "homepage": "http://sentry.io", - "license": "BSD-3-Clause", + "license": "MIT", "authors": [ { "name": "Sentry", From 5cfe256f0d792dc93a799499128d9eb235434259 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 28 Feb 2023 12:00:09 +0100 Subject: [PATCH 0829/1161] Prevent calling magic methods to retrieve ID from object (#1483) Co-authored-by: Michael Hoffmann --- phpstan-baseline.neon | 10 ------- src/Serializer/AbstractSerializer.php | 16 ++++++---- tests/Serializer/AbstractSerializerTest.php | 33 +++++++++++++++++++++ 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f96d985ae..c3ddeb163 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -20,11 +20,6 @@ parameters: count: 1 path: src/Dsn.php - - - message: "#^Offset 'host'\\|'path'\\|'scheme'\\|'user' on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\} in isset\\(\\) always exists and is not nullable\\.$#" - count: 1 - path: src/Dsn.php - - message: "#^Offset 'path' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 4 @@ -40,11 +35,6 @@ parameters: count: 1 path: src/Dsn.php - - - message: "#^Result of && is always false\\.$#" - count: 3 - path: src/EventHint.php - - message: "#^Access to constant CONNECT_TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" count: 1 diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index b1f6f66b1..510a50446 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -241,14 +241,20 @@ protected function serializeValue($value) } if (\is_object($value)) { + $reflection = new \ReflectionObject($value); + $objectId = null; - if (isset($value->id)) { - $objectId = $value->id; - } elseif (\is_callable([$value, 'getId']) && method_exists($value, 'getId')) { - $objectId = $value->getId(); + if ($reflection->hasProperty('id') && ($idProperty = $reflection->getProperty('id'))->isPublic()) { + $objectId = $idProperty->getValue($value); + } elseif ($reflection->hasMethod('getId') && ($getIdMethod = $reflection->getMethod('getId'))->isPublic()) { + try { + $objectId = $getIdMethod->invoke($value); + } catch (\Throwable $e) { + // Do nothing on purpose + } } - return 'Object ' . \get_class($value) . ((null !== $objectId) ? '(#' . $objectId . ')' : ''); + return 'Object ' . $reflection->getName() . ((null !== $objectId) ? '(#' . $objectId . ')' : ''); } if (\is_resource($value)) { diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 2a3b44734..a1a14f996 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -63,6 +63,15 @@ public function testObjectsWithIdPropertyAreStrings(): void $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty(#bar)', $result); } + public function testObjectsWithMagicIdPropertyDoesNotInvokeMagicMethods(): void + { + $serializer = $this->createSerializer(); + $input = new SerializerTestObjectWithMagicGetAndIssetMethods(); + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObjectWithMagicGetAndIssetMethods', $result); + } + public function testObjectsWithSerializerTestObjectWithGetIdMethodAreStrings(): void { $serializer = $this->createSerializer(); @@ -609,6 +618,30 @@ public function getId() } } +class SerializerTestObjectWithMagicGetAndIssetMethods +{ + public function __isset(string $name): bool + { + throw new \RuntimeException('We should not reach this!'); + } + + /** + * @return mixed + */ + public function __get(string $name) + { + throw new \RuntimeException('We should not reach this!'); + } + + /** + * @param mixed $value + */ + public function __set(string $name, $value): void + { + throw new \RuntimeException('We should not reach this!'); + } +} + class Invokable { public function __invoke(): bool From 00d13a4ebfc2240c4920d86eed683bd60d163d36 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 28 Feb 2023 12:00:50 +0100 Subject: [PATCH 0830/1161] Try to encode anything when encountering JSON encoding error (#1481) --- src/Util/JSON.php | 8 ++++++-- tests/Util/JSONTest.php | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 3cd092980..3c9360c69 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -30,11 +30,15 @@ public static function encode($data, int $options = 0, int $maxDepth = 512): str throw new \InvalidArgumentException('The $maxDepth argument must be an integer greater than 0.'); } - $options |= \JSON_UNESCAPED_UNICODE | \JSON_INVALID_UTF8_SUBSTITUTE; + $options |= \JSON_UNESCAPED_UNICODE | \JSON_INVALID_UTF8_SUBSTITUTE | \JSON_PARTIAL_OUTPUT_ON_ERROR; $encodedData = json_encode($data, $options, $maxDepth); - if (\JSON_ERROR_NONE !== json_last_error()) { + $allowedErrors = [\JSON_ERROR_NONE, \JSON_ERROR_RECURSION, \JSON_ERROR_INF_OR_NAN, \JSON_ERROR_UNSUPPORTED_TYPE]; + + $encounteredAnyError = \JSON_ERROR_NONE !== json_last_error(); + + if (($encounteredAnyError && ('null' === $encodedData || false === $encodedData)) || !\in_array(json_last_error(), $allowedErrors, true)) { throw new JsonException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index ecc086a52..a5b382fd9 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -60,6 +60,14 @@ public function encodeDataProvider(): \Generator new JsonSerializableClass(), '{"key":"value"}', ]; + + $data = []; + $data['recursive'] = &$data; + + yield [ + $data, + '{"recursive":{"recursive":null}}', + ]; } /** From 5de8f7dd41aa87f57d947506838f00daf2eda72f Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 28 Feb 2023 12:04:57 +0100 Subject: [PATCH 0831/1161] Fix missing deprecation message (#1482) --- src/Client.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Client.php b/src/Client.php index 61c1f62c6..f8cf527fa 100644 --- a/src/Client.php +++ b/src/Client.php @@ -59,11 +59,6 @@ final class Client implements ClientInterface */ private $integrations; - /** - * @var RepresentationSerializerInterface The representation serializer of the client - */ - private $representationSerializer; - /** * @var StacktraceBuilder */ @@ -86,7 +81,7 @@ final class Client implements ClientInterface * @param TransportInterface $transport The transport * @param string|null $sdkIdentifier The Sentry SDK identifier * @param string|null $sdkVersion The Sentry SDK version - * @param SerializerInterface|null $serializer The serializer + * @param SerializerInterface|null $serializer The serializer argument is deprecated since version 3.3 and will be removed in 4.0. It's currently unused. * @param RepresentationSerializerInterface|null $representationSerializer The serializer for function arguments * @param LoggerInterface|null $logger The PSR-3 logger */ @@ -103,8 +98,7 @@ public function __construct( $this->transport = $transport; $this->logger = $logger ?? new NullLogger(); $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); - $this->representationSerializer = $representationSerializer ?? new RepresentationSerializer($this->options); - $this->stacktraceBuilder = new StacktraceBuilder($options, $this->representationSerializer); + $this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer ?? new RepresentationSerializer($this->options)); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; $this->sdkVersion = $sdkVersion ?? self::SDK_VERSION; } From abf55915b9f47d7bf2b348dd2040a6a9133ac4b1 Mon Sep 17 00:00:00 2001 From: Adam Lutka <38178278+AdamLutka@users.noreply.github.com> Date: Thu, 2 Mar 2023 04:09:50 +0100 Subject: [PATCH 0832/1161] fix(serializer): serializeValue uses only scalar object IDs (#1485) Co-authored-by: Michi Hoffmann --- src/Serializer/AbstractSerializer.php | 2 +- tests/Serializer/AbstractSerializerTest.php | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 510a50446..1e2a72b25 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -254,7 +254,7 @@ protected function serializeValue($value) } } - return 'Object ' . $reflection->getName() . ((null !== $objectId) ? '(#' . $objectId . ')' : ''); + return 'Object ' . $reflection->getName() . (is_scalar($objectId) ? '(#' . $objectId . ')' : ''); } if (\is_resource($value)) { diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index a1a14f996..86efed288 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -54,13 +54,28 @@ public function testObjectsAreStrings(): void $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObject', $result); } - public function testObjectsWithIdPropertyAreStrings(): void + public function objectsWithIdPropertyDataProvider(): array + { + return [ + ['bar', 'Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty(#bar)'], + [123, 'Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty(#123)'], + [[1, 2, 3], 'Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty'], + [(object) ['id' => 321], 'Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty'], + [null, 'Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty'], + ]; + } + + /** + * @dataProvider objectsWithIdPropertyDataProvider + */ + public function testObjectsWithIdPropertyAreStrings($id, string $expectedResult): void { $serializer = $this->createSerializer(); $input = new SerializerTestObjectWithIdProperty(); + $input->id = $id; $result = $this->invokeSerialization($serializer, $input); - $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty(#bar)', $result); + $this->assertSame($expectedResult, $result); } public function testObjectsWithMagicIdPropertyDoesNotInvokeMagicMethods(): void From 841ab48daba3a94de65f65ced0c7bb7c6825b3de Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Sun, 5 Mar 2023 22:11:11 +0100 Subject: [PATCH 0833/1161] docs: Add 3.14.0 changelog (#1489) Co-authored-by: Alex Bouma --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91a15dfc5..802945f4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # CHANGELOG +## 3.14.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.14.0. + +### Features + +- Add a new `enable_tracing: true/false` option, an alternative for `traces_sample_rate: 1.0/null` [(#1458)](https://github.com/getsentry/sentry-php/pull/1458) + +### Bug Fixes + +- Fix missing keys in the request body [(#1470)](https://github.com/getsentry/sentry-php/pull/1470) +- Add support for partial JSON encoding [(#1481)](https://github.com/getsentry/sentry-php/pull/1481) +- Prevent calling *magic methods* when retrieving the ID from an object [(#1483)](https://github.com/getsentry/sentry-php/pull/1483) +- Only serialize scalar object IDs [(#1485)](https://github.com/getsentry/sentry-php/pull/1485) + +### Misc + +- The SDK is now licensed under MIT [(#1471)](https://github.com/getsentry/sentry-php/pull/1471) + - Read more about Sentry's licensing [here](https://open.sentry.io/licensing/). +- Deprecate `Client::__construct` `$serializer` argument. It is currently un-used [(#1482)](https://github.com/getsentry/sentry-php/pull/1482) + ## 3.13.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.13.1. From 60dd3f74ab21cc2bf53f39bf7c342798bc185c23 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Sun, 5 Mar 2023 21:31:25 +0000 Subject: [PATCH 0834/1161] release: 3.14.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index f8cf527fa..782199a70 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.13.1'; + public const SDK_VERSION = '3.14.0'; /** * @var Options The client options From 2714b2d7d36a9d61813b830f277dcabcea3421bc Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 13 Mar 2023 11:18:05 +0100 Subject: [PATCH 0835/1161] feat: Add support for profiling (#1477) Co-authored-by: Alex Bouma --- phpstan.neon | 2 + psalm-baseline.xml | 8 +- psalm.xml.dist | 8 + src/Context/OsContext.php | 29 +- src/Event.php | 27 ++ src/Integration/EnvironmentIntegration.php | 4 + src/Options.php | 17 + src/Profiling/Profile.php | 341 +++++++++++++++++++++ src/Profiling/Profiler.php | 71 +++++ src/Serializer/PayloadSerializer.php | 33 +- src/State/Hub.php | 27 +- src/Tracing/Transaction.php | 39 +++ stubs/ExcimerLog.stub | 201 ++++++++++++ stubs/ExcimerLogEntry.stub | 60 ++++ stubs/ExcimerProfiler.stub | 135 ++++++++ stubs/ExcimerTimer.stub | 105 +++++++ stubs/LICENSE | 202 ++++++++++++ stubs/autoload.php | 9 + stubs/globals.stub | 32 ++ tests/Context/OsContextTest.php | 11 +- tests/OptionsTest.php | 9 + tests/Profiling/ProfileTest.php | 267 ++++++++++++++++ tests/Serializer/PayloadSerializerTest.php | 58 +++- 23 files changed, 1686 insertions(+), 9 deletions(-) create mode 100644 src/Profiling/Profile.php create mode 100644 src/Profiling/Profiler.php create mode 100644 stubs/ExcimerLog.stub create mode 100644 stubs/ExcimerLogEntry.stub create mode 100644 stubs/ExcimerProfiler.stub create mode 100644 stubs/ExcimerTimer.stub create mode 100644 stubs/LICENSE create mode 100644 stubs/autoload.php create mode 100644 stubs/globals.stub create mode 100644 tests/Profiling/ProfileTest.php diff --git a/phpstan.neon b/phpstan.neon index 9a3b3577c..91af9c48e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,3 +12,5 @@ parameters: - tests/Fixtures dynamicConstantNames: - Monolog\Logger::API + bootstrapFiles: + - stubs/autoload.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index abad2bb9b..2705d0520 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $parsedDsn['host'] @@ -59,6 +59,12 @@ $record['context'] + + + + SentryProfile|null + + $value diff --git a/psalm.xml.dist b/psalm.xml.dist index 8070ef1ef..ea60216a6 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -64,4 +64,12 @@
+ + + + + + + + diff --git a/src/Context/OsContext.php b/src/Context/OsContext.php index 04e9387bd..5578dc4be 100644 --- a/src/Context/OsContext.php +++ b/src/Context/OsContext.php @@ -31,6 +31,11 @@ final class OsContext */ private $kernelVersion; + /** + * @var string|null + */ + private $machineType; + /** * Constructor. * @@ -38,9 +43,15 @@ final class OsContext * @param string|null $version The version of the operating system * @param string|null $build The internal build revision of the operating system * @param string|null $kernelVersion An independent kernel version string + * @param string|null $machineType The machine type */ - public function __construct(string $name, ?string $version = null, ?string $build = null, ?string $kernelVersion = null) - { + public function __construct( + string $name, + ?string $version = null, + ?string $build = null, + ?string $kernelVersion = null, + ?string $machineType = null + ) { if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -49,6 +60,7 @@ public function __construct(string $name, ?string $version = null, ?string $buil $this->version = $version; $this->build = $build; $this->kernelVersion = $kernelVersion; + $this->machineType = $machineType; } /** @@ -126,4 +138,17 @@ public function setKernelVersion(?string $kernelVersion): void { $this->kernelVersion = $kernelVersion; } + + public function getMachineType(): ?string + { + return $this->machineType; + } + + /** + * @param string|null $machineType The machine type + */ + public function setMachineType(?string $machineType): void + { + $this->machineType = $machineType; + } } diff --git a/src/Event.php b/src/Event.php index fee255bc1..8f38d7aa5 100644 --- a/src/Event.php +++ b/src/Event.php @@ -6,6 +6,7 @@ use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; +use Sentry\Profiling\Profile; use Sentry\Tracing\Span; /** @@ -167,6 +168,11 @@ final class Event */ private $type; + /** + * @var Profile|null The profile data + */ + private $profile; + private function __construct(?EventId $eventId, EventType $eventType) { $this->id = $eventId ?? EventId::generate(); @@ -747,4 +753,25 @@ public function setSpans(array $spans): void { $this->spans = $spans; } + + public function setProfile(?Profile $profile): void + { + $this->profile = $profile; + } + + public function getProfile(): ?Profile + { + return $this->profile; + } + + public function getTraceId(): ?string + { + $traceId = $this->getContexts()['trace']['trace_id']; + + if (\is_string($traceId) && !empty($traceId)) { + return $traceId; + } + + return null; + } } diff --git a/src/Integration/EnvironmentIntegration.php b/src/Integration/EnvironmentIntegration.php index 2098ced49..9552fafc6 100644 --- a/src/Integration/EnvironmentIntegration.php +++ b/src/Integration/EnvironmentIntegration.php @@ -70,6 +70,10 @@ private function updateServerOsContext(?OsContext $osContext): ?OsContext $osContext->setKernelVersion(php_uname('a')); } + if (null === $osContext->getMachineType()) { + $osContext->setMachineType(php_uname('m')); + } + return $osContext; } } diff --git a/src/Options.php b/src/Options.php index d553b9b30..dbee7d128 100644 --- a/src/Options.php +++ b/src/Options.php @@ -183,6 +183,21 @@ public function setTracesSampleRate(?float $sampleRate): void $this->options = $this->resolver->resolve($options); } + public function getProfilesSampleRate(): ?float + { + /** @var int|float|null $value */ + $value = $this->options['profiles_sample_rate'] ?? null; + + return $value ?? null; + } + + public function setProfilesSampleRate(?float $sampleRate): void + { + $options = array_merge($this->options, ['profiles_sample_rate' => $sampleRate]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets whether tracing is enabled or not. The feature is enabled when at * least one of the `traces_sample_rate` and `traces_sampler` options is @@ -848,6 +863,7 @@ private function configureOptions(OptionsResolver $resolver): void 'enable_tracing' => null, 'traces_sample_rate' => null, 'traces_sampler' => null, + 'profiles_sample_rate' => null, 'attach_stacktrace' => false, 'context_lines' => 5, 'enable_compression' => true, @@ -887,6 +903,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('enable_tracing', ['null', 'bool']); $resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); + $resolver->setAllowedTypes('profiles_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); $resolver->setAllowedTypes('enable_compression', 'bool'); diff --git a/src/Profiling/Profile.php b/src/Profiling/Profile.php new file mode 100644 index 000000000..b6e974d37 --- /dev/null +++ b/src/Profiling/Profile.php @@ -0,0 +1,341 @@ +, + * samples: array, + * stacks: array>, + * }, + * } + * + * @phpstan-type ExcimerLogStackEntryTrace array{ + * file: string|null, + * line: string|int|null, + * class: string, + * function: string, + * closure_line: int + * } + * + * @phpstan-type ExcimerLogStackEntry array{ + * trace: array, + * timestamp: float + * } + * + * @internal + */ +final class Profile +{ + /** + * @var string The version of the profile format + */ + private const VERSION = '1'; + + /** + * @var string The thread ID + */ + private const THREAD_ID = '0'; + + /** + * @var int The minimum number of samples required for a profile + */ + private const MIN_SAMPLE_COUNT = 2; + + /** + * @var int The maximum duration of a profile in seconds + */ + private const MAX_PROFILE_DURATION = 30; + + /** + * @var float The start time of the profile as a Unix timestamp with microseconds + */ + private $startTimeStamp; + + /** + * @var \ExcimerLog|array The data of the profile + */ + private $excimerLog; + + /** + * @var EventId|null The event ID of the profile + */ + private $eventId; + + public function setStartTimeStamp(float $startTimeStamp): void + { + $this->startTimeStamp = $startTimeStamp; + } + + /** + * @param \ExcimerLog|array $excimerLog + */ + public function setExcimerLog($excimerLog): void + { + $this->excimerLog = $excimerLog; + } + + public function setEventId(EventId $eventId): void + { + $this->eventId = $eventId; + } + + /** + * @return SentryProfile|null + */ + public function getFormattedData(Event $event): ?array + { + if (!$this->validateExcimerLog()) { + return null; + } + + $osContext = $event->getOsContext(); + if (!$this->validateOsContext($osContext)) { + return null; + } + + $runtimeContext = $event->getRuntimeContext(); + if (!$this->validateRuntimeContext($runtimeContext)) { + return null; + } + + if (!$this->validateEvent($event)) { + return null; + } + + $frames = []; + $samples = []; + $stacks = []; + + $frameIndex = 0; + $duration = 0; + + $loggedStacks = $this->prepareStacks(); + foreach ($loggedStacks as $stackId => $stack) { + foreach ($stack['trace'] as $frame) { + $absolutePath = (string) $frame['file']; + // TODO(michi) Strip the file path based on the `prefixes` option + $file = $absolutePath; + $module = null; + + if (isset($frame['class'], $frame['function'])) { + // Class::method + $function = $frame['class'] . '::' . $frame['function']; + $module = $frame['class']; + } elseif (isset($frame['function'])) { + // {clousre} + $function = $frame['function']; + } else { + // /index.php + $function = $file; + } + + $frames[] = [ + 'filename' => $file, + 'abs_path' => $absolutePath, + 'module' => $module, + 'function' => $function, + 'lineno' => !empty($frame['line']) ? (int) $frame['line'] : null, + ]; + + $stacks[$stackId][] = $frameIndex; + ++$frameIndex; + } + + $duration = $stack['timestamp']; + + $samples[] = [ + 'elapsed_since_start_ns' => (int) round($duration * 1e+9), + 'stack_id' => $stackId, + 'thread_id' => self::THREAD_ID, + ]; + } + + if (!$this->validateMaxDuration((float) $duration)) { + return null; + } + + $startTime = \DateTime::createFromFormat('U.u', number_format($this->startTimeStamp, 4, '.', ''), new \DateTimeZone('UTC')); + if (false === $startTime) { + return null; + } + + return [ + 'device' => [ + 'architecture' => $osContext->getMachineType(), + ], + 'event_id' => $this->eventId ? (string) $this->eventId : SentryUid::generate(), + 'os' => [ + 'name' => $osContext->getName(), + 'version' => $osContext->getVersion(), + 'build_number' => $osContext->getBuild() ?? '', + ], + 'platform' => 'php', + 'release' => $event->getRelease() ?? '', + 'environment' => $event->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, + 'runtime' => [ + 'name' => $runtimeContext->getName(), + 'version' => $runtimeContext->getVersion(), + ], + 'timestamp' => $startTime->format(\DATE_RFC3339_EXTENDED), + 'transaction' => [ + 'id' => (string) $event->getId(), + 'name' => $event->getTransaction(), + 'trace_id' => $event->getTraceId(), + 'active_thread_id' => self::THREAD_ID, + ], + 'version' => self::VERSION, + 'profile' => [ + 'frames' => $frames, + 'samples' => $samples, + 'stacks' => $stacks, + ], + ]; + } + + /** + * This method is mainly used to be able to mock the ExcimerLog class in the tests. + * + * @return array + */ + private function prepareStacks(): array + { + $stacks = []; + + foreach ($this->excimerLog as $stack) { + if ($stack instanceof \ExcimerLogEntry) { + $stacks[] = [ + 'trace' => $stack->getTrace(), + 'timestamp' => $stack->getTimestamp(), + ]; + } else { + /** @var ExcimerLogStackEntry $stack */ + $stacks[] = $stack; + } + } + + return $stacks; + } + + private function validateExcimerLog(): bool + { + if (\is_array($this->excimerLog)) { + $sampleCount = \count($this->excimerLog); + } else { + $sampleCount = $this->excimerLog->count(); + } + + return self::MIN_SAMPLE_COUNT <= $sampleCount; + } + + private function validateMaxDuration(float $duration): bool + { + if ($duration > self::MAX_PROFILE_DURATION) { + return false; + } + + return true; + } + + /** + * @phpstan-assert-if-true OsContext $osContext + * @phpstan-assert-if-true !null $osContext->getVersion() + * @phpstan-assert-if-true !null $osContext->getMachineType() + */ + private function validateOsContext(?OsContext $osContext): bool + { + if (null === $osContext) { + return false; + } + + if (null === $osContext->getVersion()) { + return false; + } + + if (null === $osContext->getMachineType()) { + return false; + } + + return true; + } + + /** + * @phpstan-assert-if-true RuntimeContext $runtimeContext + * @phpstan-assert-if-true !null $runtimeContext->getVersion() + */ + private function validateRuntimeContext(?RuntimeContext $runtimeContext): bool + { + if (null === $runtimeContext) { + return false; + } + + if (null === $runtimeContext->getVersion()) { + return false; + } + + return true; + } + + /** + * @phpstan-assert-if-true !null $event->getTransaction() + * @phpstan-assert-if-true !null $event->getTraceId() + */ + private function validateEvent(Event $event): bool + { + if (null === $event->getTransaction()) { + return false; + } + + if (null === $event->getTraceId()) { + return false; + } + + return true; + } +} diff --git a/src/Profiling/Profiler.php b/src/Profiling/Profiler.php new file mode 100644 index 000000000..b2c714a2e --- /dev/null +++ b/src/Profiling/Profiler.php @@ -0,0 +1,71 @@ +profile = new Profile(); + + $this->initProfiler(); + } + + public function start(): void + { + if (null !== $this->profiler) { + $this->profiler->start(); + } + } + + public function stop(): void + { + if (null !== $this->profiler) { + $this->profiler->stop(); + + $this->profile->setExcimerLog($this->profiler->flush()); + } + } + + public function getProfile(): Profile + { + return $this->profile; + } + + private function initProfiler(): void + { + if (\extension_loaded('excimer') && \PHP_VERSION_ID >= 70300) { + $this->profiler = new \ExcimerProfiler(); + $this->profile->setStartTimeStamp(microtime(true)); + + $this->profiler->setEventType(EXCIMER_REAL); + $this->profiler->setPeriod(self::SAMPLE_RATE); + $this->profiler->setMaxDepth(self::MAX_STACK_DEPTH); + } + } +} diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 6a719918c..9eeaff0d7 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -10,6 +10,7 @@ use Sentry\ExceptionDataBag; use Sentry\Frame; use Sentry\Options; +use Sentry\Profiling\Profile; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\TransactionMetadata; @@ -39,7 +40,17 @@ public function __construct(Options $options) public function serialize(Event $event): string { if (EventType::transaction() === $event->getType()) { - return $this->serializeAsEnvelope($event); + $transactionEnvelope = $this->serializeAsEnvelope($event); + + // Attach a new envelope item containing the profile data + if (null !== $event->getSdkMetadata('profile')) { + $profileEnvelope = $this->seralizeProfileAsEnvelope($event); + if (null !== $profileEnvelope) { + return sprintf("%s\n%s", $transactionEnvelope, $profileEnvelope); + } + } + + return $transactionEnvelope; } return $this->serializeAsEvent($event); @@ -223,6 +234,26 @@ private function serializeAsEnvelope(Event $event): string return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $this->serializeAsEvent($event)); } + private function seralizeProfileAsEnvelope(Event $event): ?string + { + $itemHeader = [ + 'type' => 'profile', + 'content_type' => 'application/json', + ]; + + $profile = $event->getSdkMetadata('profile'); + if (!$profile instanceof Profile) { + return null; + } + + $profileData = $profile->getFormattedData($event); + if (null === $profileData) { + return null; + } + + return sprintf("%s\n%s", JSON::encode($itemHeader), JSON::encode($profileData)); + } + /** * @return array * diff --git a/src/State/Hub.php b/src/State/Hub.php index c4e93c263..152dec95a 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -257,7 +257,7 @@ public function startTransaction(TransactionContext $context, array $customSampl return $transaction; } - $transaction->setSampled(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < $sampleRate); + $transaction->setSampled($this->sample($sampleRate)); } if (!$transaction->getSampled()) { @@ -266,6 +266,15 @@ public function startTransaction(TransactionContext $context, array $customSampl $transaction->initSpanRecorder(); + $profilesSampleRate = $options->getProfilesSampleRate(); + if ($this->sample($profilesSampleRate)) { + $transaction->initProfiler(); + $profiler = $transaction->getProfiler(); + if (null !== $profiler) { + $profiler->start(); + } + } + return $transaction; } @@ -324,6 +333,22 @@ private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampl return $fallbackSampleRate; } + /** + * @param mixed $sampleRate + */ + private function sample($sampleRate): bool + { + if (0.0 === $sampleRate) { + return false; + } + + if (1.0 === $sampleRate) { + return true; + } + + return mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < $sampleRate; + } + /** * @param mixed $sampleRate */ diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index 4bc501b5f..ab9bbe485 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -6,6 +6,7 @@ use Sentry\Event; use Sentry\EventId; +use Sentry\Profiling\Profiler; use Sentry\SentrySdk; use Sentry\State\HubInterface; @@ -34,6 +35,11 @@ final class Transaction extends Span */ protected $metadata; + /** + * @var Profiler|null Reference instance to the {@see Profiler} + */ + protected $profiler = null; + /** * Span constructor. * @@ -107,11 +113,37 @@ public function initSpanRecorder(int $maxSpans = 1000): void $this->spanRecorder->add($this); } + public function detachSpanRecorder(): void + { + $this->spanRecorder = null; + } + + public function initProfiler(): void + { + if (null === $this->profiler) { + $this->profiler = new Profiler(); + } + } + + public function getProfiler(): ?Profiler + { + return $this->profiler; + } + + public function detachProfiler(): void + { + $this->profiler = null; + } + /** * {@inheritdoc} */ public function finish(?float $endTimestamp = null): ?EventId { + if (null !== $this->profiler) { + $this->profiler->stop(); + } + if (null !== $this->endTimestamp) { // Transaction was already finished once and we don't want to re-flush it return null; @@ -143,6 +175,13 @@ public function finish(?float $endTimestamp = null): ?EventId $event->setSdkMetadata('dynamic_sampling_context', $this->getDynamicSamplingContext()); $event->setSdkMetadata('transaction_metadata', $this->getMetadata()); + if (null !== $this->profiler) { + $profile = $this->profiler->getProfile(); + if (null !== $profile) { + $event->setSdkMetadata('profile', $profile); + } + } + return $this->hub->captureEvent($event); } } diff --git a/stubs/ExcimerLog.stub b/stubs/ExcimerLog.stub new file mode 100644 index 000000000..6cc5e2fe4 --- /dev/null +++ b/stubs/ExcimerLog.stub @@ -0,0 +1,201 @@ +getLog() as $entry ) { + * var_dump( $entry->getTrace() ); + * } + * + * @implements ArrayAccess + * @implements Iterator + */ + class ExcimerLog implements ArrayAccess, Iterator + { + /** + * ExcimerLog is not constructible by user code. Objects of this type + * are available via: + * - ExcimerProfiler::getLog() + * - ExcimerProfiler::flush() + * - The callback to ExcimerProfiler::setFlushCallback() + */ + private final function __construct() + { + } + + /** + * Aggregate the stack traces and convert them to a line-based format + * understood by Brendan Gregg's FlameGraph utility. Each stack trace is + * represented as a series of function names, separated by semicolons. + * After this identifier, there is a single space character, then a number + * giving the number of times the stack appeared. Then there is a line + * break. This is repeated for each unique stack trace. + * + * @return string + */ + function formatCollapsed() + { + } + + /** + * Produce an array with an element for every function which appears in + * the log. The key is a human-readable unique identifier for the function, + * method or closure. The value is an associative array with the following + * elements: + * + * - self: The number of events in which the function itself was running, + * no other userspace function was being called. This includes time + * spent in internal functions that this function called. + * - inclusive: The number of events in which this function appeared + * somewhere in the stack. + * + * And optionally the following elements, if they are relevant: + * + * - file: The filename in which the function appears + * - line: The exact line number at which the first relevant event + * occurred. + * - class: The class name in which the method is defined + * - function: The name of the function or method + * - closure_line: The line number at which the closure was defined + * + * The event counts in the "self" and "inclusive" fields are adjusted for + * overruns. They represent an estimate of the number of profiling periods + * in which those functions were present. + * + * @return array + */ + function aggregateByFunction() + { + } + + /** + * Get an array which can be JSON encoded for import into speedscope + * + * @return array + */ + function getSpeedscopeData() + { + } + + /** + * Get the total number of profiling periods represented by this log. + * + * @return int + */ + function getEventCount() + { + } + + /** + * Get the current ExcimerLogEntry object. Part of the Iterator interface. + * + * @return ExcimerLogEntry + */ + #[\ReturnTypeWillChange] + function current() + { + } + + /** + * Get the current integer key or null. Part of the Iterator interface. + * + * @return int + */ + #[\ReturnTypeWillChange] + function key() + { + } + + /** + * Advance to the next log entry. Part of the Iterator interface. + * + * @return void + */ + #[\ReturnTypeWillChange] + function next() + { + } + + /** + * Rewind back to the first log entry. Part of the Iterator interface. + * + * @return void + */ + #[\ReturnTypeWillChange] + function rewind() + { + } + + /** + * Check if the current position is valid. Part of the Iterator interface. + * + * @return bool + */ + #[\ReturnTypeWillChange] + function valid() + { + } + + /** + * Get the number of log entries contained in this log. This is always less + * than or equal to the number returned by getEventCount(), which includes + * overruns. + * + * @return int + */ + function count() + { + } + + /** + * Determine whether a log entry exists at the specified array offset. + * Part of the ArrayAccess interface. + * + * @param int $offset + * @return bool + */ + #[\ReturnTypeWillChange] + function offsetExists($offset) + { + } + + /** + * Get the ExcimerLogEntry object at the specified array offset. + * + * @param int $offset + * @return ExcimerLogEntry + */ + #[\ReturnTypeWillChange] + function offsetGet($offset) + { + } + + /** + * This function is included for compliance with the ArrayAccess interface. + * It raises a warning and does nothing. + * + * @param int|null $offset + * @param ExcimerLogEntry $value + */ + #[\ReturnTypeWillChange] + function offsetSet($offset, $value) + { + } + + /** + * This function is included for compliance with the ArrayAccess interface. + * It raises a warning and does nothing. + * + * @param int $offset + */ + #[\ReturnTypeWillChange] + function offsetUnset($offset) + { + } + } +} diff --git a/stubs/ExcimerLogEntry.stub b/stubs/ExcimerLogEntry.stub new file mode 100644 index 000000000..6bf317e4e --- /dev/null +++ b/stubs/ExcimerLogEntry.stub @@ -0,0 +1,60 @@ + + */ + public function getTrace(): array + { + } + } + +} diff --git a/stubs/ExcimerProfiler.stub b/stubs/ExcimerProfiler.stub new file mode 100644 index 000000000..ed852c6da --- /dev/null +++ b/stubs/ExcimerProfiler.stub @@ -0,0 +1,135 @@ +setCallback( $callback ); + * $timer->setInterval( $interval ); + * $timer->start(); + * return $timer; + * + * Note that you must keep a copy of the return value. If it goes out of scope, + * the object will be destroyed and the timer will stop. + * + * If the callback is not callable, a warning is raised and null is returned. + * + * @param callable $callback + * @param float $interval + * @return ExcimerTimer|null + */ + function excimer_set_timeout( $callback, $interval ) { + } + +} diff --git a/tests/Context/OsContextTest.php b/tests/Context/OsContextTest.php index 3ec5b3f6e..df34275cb 100644 --- a/tests/Context/OsContextTest.php +++ b/tests/Context/OsContextTest.php @@ -12,14 +12,15 @@ final class OsContextTest extends TestCase /** * @dataProvider valuesDataProvider */ - public function testConstructor(string $expectedName, ?string $expectedVersion, ?string $expectedBuild, ?string $expectedKernelVersion): void + public function testConstructor(string $expectedName, ?string $expectedVersion, ?string $expectedBuild, ?string $expectedKernelVersion, ?string $expectedMachineType): void { - $context = new OsContext($expectedName, $expectedVersion, $expectedBuild, $expectedKernelVersion); + $context = new OsContext($expectedName, $expectedVersion, $expectedBuild, $expectedKernelVersion, $expectedMachineType); $this->assertSame($expectedName, $context->getName()); $this->assertSame($expectedVersion, $context->getVersion()); $this->assertSame($expectedBuild, $context->getBuild()); $this->assertSame($expectedKernelVersion, $context->getKernelVersion()); + $this->assertSame($expectedMachineType, $context->getMachineType()); } public function testConstructorThrowsOnInvalidArgument(): void @@ -33,18 +34,20 @@ public function testConstructorThrowsOnInvalidArgument(): void /** * @dataProvider valuesDataProvider */ - public function testGettersAndSetters(string $expectedName, ?string $expectedVersion, ?string $expectedBuild, ?string $expectedKernelVersion): void + public function testGettersAndSetters(string $expectedName, ?string $expectedVersion, ?string $expectedBuild, ?string $expectedKernelVersion, ?string $expectedMachineType): void { $context = new OsContext('Windows'); $context->setName($expectedName); $context->setVersion($expectedVersion); $context->setBuild($expectedBuild); $context->setKernelVersion($expectedKernelVersion); + $context->setMachineType($expectedMachineType); $this->assertSame($expectedName, $context->getName()); $this->assertSame($expectedVersion, $context->getVersion()); $this->assertSame($expectedBuild, $context->getBuild()); $this->assertSame($expectedKernelVersion, $context->getKernelVersion()); + $this->assertSame($expectedMachineType, $context->getMachineType()); } public function valuesDataProvider(): iterable @@ -54,6 +57,7 @@ public function valuesDataProvider(): iterable '4.19.104-microsoft-standard', '#1 SMP Wed Feb 19 06:37:35 UTC 2020', 'Linux c03a247f5e13 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64', + 'x86_64', ]; yield [ @@ -61,6 +65,7 @@ public function valuesDataProvider(): iterable null, null, null, + null, ]; } } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 35affb885..c58424878 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -149,6 +149,15 @@ static function (): void {}, null, ]; + yield [ + 'profiles_sample_rate', + 0.5, + 'getProfilesSampleRate', + 'setProfilesSampleRate', + null, + null, + ]; + yield [ 'attach_stacktrace', false, diff --git a/tests/Profiling/ProfileTest.php b/tests/Profiling/ProfileTest.php new file mode 100644 index 000000000..34f5cd29c --- /dev/null +++ b/tests/Profiling/ProfileTest.php @@ -0,0 +1,267 @@ +setStartTimeStamp(1677573660.0000); + + $profile->setExcimerLog($excimerLog); + $profile->setEventId((new EventId('815e57b4bb134056ab1840919834689d'))); + + $this->assertSame($expectedData, $profile->getFormattedData($event)); + } + + public function formattedDataDataProvider(): \Generator + { + $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setRelease('1.0.0'); + $event->setEnvironment('dev'); + $event->setTransaction('GET /'); + $event->setContext('trace', [ + 'trace_id' => '566e3688a61d4bc888951642d6f14a19', + 'span_id' => '566e3688a61d4bc8', + ]); + $event->setRuntimeContext(new RuntimeContext( + 'php', + '8.2.3' + )); + $event->setOsContext(new OsContext( + 'macOS', + '13.2.1', + '22D68', + 'Darwin Kernel Version 22.2.0', + 'aarch64' + )); + + yield [ + $event, + [ + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + ], + 'timestamp' => 0.001, + ], + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + [ + 'class' => 'Function', + 'function' => 'doStuff', + 'file' => '/var/www/html/function.php', + 'line' => 84, + ], + ], + 'timestamp' => 0.002, + ], + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + [ + 'class' => 'Function', + 'function' => 'doStuff', + 'file' => '/var/www/html/function.php', + 'line' => 84, + ], + [ + 'class' => 'Class\Something', + 'function' => 'run', + 'file' => '/var/www/html/class.php', + 'line' => 42, + ], + [ + 'function' => '{closure}', + 'file' => '/var/www/html/index.php', + 'line' => 126, + ], + ], + 'timestamp' => 0.003, + ], + ], + [ + 'device' => [ + 'architecture' => 'aarch64', + ], + 'event_id' => '815e57b4bb134056ab1840919834689d', + 'os' => [ + 'name' => 'macOS', + 'version' => '13.2.1', + 'build_number' => '22D68', + ], + 'platform' => 'php', + 'release' => '1.0.0', + 'environment' => 'dev', + 'runtime' => [ + 'name' => 'php', + 'version' => '8.2.3', + ], + 'timestamp' => '2023-02-28T08:41:00.000+00:00', + 'transaction' => [ + 'id' => 'fc9442f5aef34234bb22b9a615e30ccd', + 'name' => 'GET /', + 'trace_id' => '566e3688a61d4bc888951642d6f14a19', + 'active_thread_id' => '0', + ], + 'version' => '1', + 'profile' => [ + 'frames' => [ + [ + 'filename' => '/var/www/html/index.php', + 'abs_path' => '/var/www/html/index.php', + 'module' => null, + 'function' => '/var/www/html/index.php', + 'lineno' => 42, + ], + [ + 'filename' => '/var/www/html/index.php', + 'abs_path' => '/var/www/html/index.php', + 'module' => null, + 'function' => '/var/www/html/index.php', + 'lineno' => 42, + ], + [ + 'filename' => '/var/www/html/function.php', + 'abs_path' => '/var/www/html/function.php', + 'module' => 'Function', + 'function' => 'Function::doStuff', + 'lineno' => 84, + ], + [ + 'filename' => '/var/www/html/index.php', + 'abs_path' => '/var/www/html/index.php', + 'module' => null, + 'function' => '/var/www/html/index.php', + 'lineno' => 42, + ], + [ + 'filename' => '/var/www/html/function.php', + 'abs_path' => '/var/www/html/function.php', + 'module' => 'Function', + 'function' => 'Function::doStuff', + 'lineno' => 84, + ], + [ + 'filename' => '/var/www/html/class.php', + 'abs_path' => '/var/www/html/class.php', + 'module' => 'Class\Something', + 'function' => 'Class\Something::run', + 'lineno' => 42, + ], + [ + 'filename' => '/var/www/html/index.php', + 'abs_path' => '/var/www/html/index.php', + 'module' => null, + 'function' => '{closure}', + 'lineno' => 126, + ], + ], + 'samples' => [ + [ + 'elapsed_since_start_ns' => 1000000, + 'stack_id' => 0, + 'thread_id' => '0', + ], + [ + 'elapsed_since_start_ns' => 2000000, + 'stack_id' => 1, + 'thread_id' => '0', + ], + [ + 'elapsed_since_start_ns' => 3000000, + 'stack_id' => 2, + 'thread_id' => '0', + ], + ], + 'stacks' => [ + [ + 0, + ], + [ + 1, + 2, + ], + [ + 3, + 4, + 5, + 6, + ], + ], + ], + ], + ]; + + yield 'Too little samples' => [ + $event, + [ + [ + 'trace' => [ + [ + 'file' => 'index.php', + 'line' => 42, + ], + ], + 'timestamp' => 0.001, + ], + ], + null, + ]; + + yield 'Too long duration' => [ + $event, + [ + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + ], + 'timestamp' => 15.000, + ], + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + [ + 'class' => 'Function', + 'function' => 'doStuff', + 'file' => '/var/www/html/function.php', + 'line' => 84, + ], + ], + 'timestamp' => 30.001, + ], + ], + null, + ]; + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 3165f63c0..3d111cf48 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -16,6 +16,7 @@ use Sentry\ExceptionMechanism; use Sentry\Frame; use Sentry\Options; +use Sentry\Profiling\Profile; use Sentry\Serializer\PayloadSerializer; use Sentry\Severity; use Sentry\Stacktrace; @@ -427,13 +428,68 @@ public function serializeDataProvider(): iterable $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setSpans([$span1, $span2]); + $event->setRelease('1.0.0'); + $event->setEnvironment('dev'); + $event->setTransaction('GET /'); + $event->setContext('trace', [ + 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', + 'span_id' => '5dd538dc297544cc', + ]); + $event->setRuntimeContext(new RuntimeContext( + 'php', + '8.2.3' + )); + $event->setOsContext(new OsContext( + 'macOS', + '13.2.1', + '22D68', + 'Darwin Kernel Version 22.2.0', + 'aarch64' + )); + + $excimerLog = [ + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + ], + 'timestamp' => 0.001, + ], + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + [ + 'class' => 'Function', + 'function' => 'doStuff', + 'file' => '/var/www/html/function.php', + 'line' => 84, + ], + ], + 'timestamp' => 0.002, + ], + ]; + + $profile = new Profile(); + // 2022-02-28T09:41:00Z + $profile->setStartTimeStamp(1677573660.0000); + $profile->setExcimerLog($excimerLog); + $profile->setEventId($event->getId()); + + $event->setSdkMetadata('profile', $profile); yield [ $event, << Date: Mon, 13 Mar 2023 12:11:11 +0100 Subject: [PATCH 0836/1161] fix: Use $sdkVersion in test --- tests/Serializer/PayloadSerializerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 3d111cf48..ea8663113 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -487,7 +487,7 @@ public function serializeDataProvider(): iterable << Date: Mon, 13 Mar 2023 12:22:58 +0100 Subject: [PATCH 0837/1161] Prepare 3.15.0 (#1492) Co-authored-by: Alex Bouma --- CHANGELOG.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 802945f4b..d656fc3bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,71 @@ # CHANGELOG +## 3.15.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.15.0. +This release adds initial support for [Profiling](https://docs.sentry.io/product/profiling/). + +> **Warning** +> Profiling is currently in beta. Beta features are still in-progress and may have bugs. We recognize the irony. +> If you have any questions or feedback, please email us at profiling@sentry.io, reach out via Discord (#profiling), or open an issue. + +Profiling is only available on Sentry SaaS (sentry.io). Support for Sentry self-hosted is planned once Profiling is released into GA. + +### Features + +- Add initial support for profiling [(#1477)](https://github.com/getsentry/sentry-php/pull/1477) + + Under the hood, we're using Wikipedia's sampling profiler [Excimer](https://github.com/wikimedia/mediawiki-php-excimer). + We chose this profiler for its low overhead and for being used in production by one of the largest PHP-powered websites in the world. + + Excimer works with PHP 7.2 and up, for PHP 8.2 support, make sure to use Excimer version 1.1.0. + + There is currently no support for either Windows or macOS. + + You can install Excimer via your operating systems package manager. + + ```bash + apt-get install php-excimer + ``` + + If no suitable version is available, you may build Excimer from source. + + ```bash + git clone https://github.com/wikimedia/mediawiki-php-excimer.git + + cd excimer/ + phpize && ./configure && make && sudo make install + ``` + + Depending on your environment, you may need to enable the Excimer extension afterward. + + ```bash + phpenmod -s fpm excimer + # or + phpenmod -s apache2 excimer + ``` + + Once the extension is installed, you may enable profiling by adding the new `profiles_sample_rate` config option to your `Sentry::init` method. + + ```php + \Sentry\init([ + 'dsn' => '__DSN__', + 'traces_sample_rate' => 1.0, + 'profiles_sample_rate' => 1.0, + ]); + ``` + + Profiles are being sampled in relation to your `traces_sample_rate`. + + Please note that the profiler is started inside transactions only. If you're not using our [Laravel](https://github.com/getsentry/sentry-laravel) or [Symfony](https://github.com/getsentry/sentry-symfony) SDKs, you may need to manually add transactions to your application as described [here](https://docs.sentry.io/platforms/php/performance/instrumentation/custom-instrumentation/). + + #### Other things you should consider: + + - The current sample rate of the profiler is set to 101Hz (every ~10ms). A minimum of two samples is required for a profile being sent, hence requests that finish in less than ~20ms won't hail any profiles. + - The maximum duration of a profile is 30s, hence we do not recommend enabling the extension in an CLI environment. + - By design, the profiler will take samples at the end of any userland functions. You may see long sample durations on tasks like HTTP client requests and DB queries. + You can read more about Excimer's architecture [here](https://techblog.wikimedia.org/2021/03/03/profiling-php-in-production-at-scale/). + ## 3.14.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.14.0. From c6a0e24d2f8da8d8f57cdcc87ca635248c1a91c5 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 13 Mar 2023 11:45:26 +0000 Subject: [PATCH 0838/1161] release: 3.15.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 782199a70..0674cf9ae 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.14.0'; + public const SDK_VERSION = '3.15.0'; /** * @var Options The client options From efe73312a3667547c6cf2dc1b0ac67402f2d6c72 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 14 Mar 2023 21:24:59 +0100 Subject: [PATCH 0839/1161] Introduce `trace` helper function (#1490) Co-authored-by: Michi Hoffmann --- src/functions.php | 39 ++++++++++++++++++++++++++++++++++++++ tests/FunctionsTest.php | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/functions.php b/src/functions.php index 139e40e6c..b24a45c22 100644 --- a/src/functions.php +++ b/src/functions.php @@ -5,6 +5,7 @@ namespace Sentry; use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -128,3 +129,41 @@ function startTransaction(TransactionContext $context, array $customSamplingCont { return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } + +/** + * Execute the given callable while wrapping it in a span added as a child to the current transaction and active span. + * + * If there is no transaction active this is a no-op and the scope passed to the trace callable will be unused. + * + * @template T + * + * @param callable(Scope): T $trace The callable that is going to be traced + * @param SpanContext $context The context of the span to be created + * + * @return T + */ +function trace(callable $trace, SpanContext $context) +{ + return SentrySdk::getCurrentHub()->withScope(function (Scope $scope) use ($context, $trace) { + $parentSpan = $scope->getSpan(); + + // If there's a span set on the scope there is a transaction + // active currently. If that is the case we create a child span + // and set it on the scope. Otherwise we only execute the callable + if (null !== $parentSpan) { + $span = $parentSpan->startChild($context); + + $scope->setSpan($span); + } + + try { + return $trace($scope); + } finally { + if (isset($span)) { + $span->finish(); + + $scope->setSpan($parentSpan); + } + } + }); +} diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 9f055a104..0bf3ca83e 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use RuntimeException; use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Event; @@ -14,8 +15,10 @@ use Sentry\Options; use Sentry\SentrySdk; use Sentry\Severity; +use Sentry\State\Hub; use Sentry\State\HubInterface; use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use function Sentry\addBreadcrumb; @@ -26,6 +29,7 @@ use function Sentry\configureScope; use function Sentry\init; use function Sentry\startTransaction; +use function Sentry\trace; use function Sentry\withScope; final class FunctionsTest extends TestCase @@ -231,4 +235,42 @@ public function testStartTransaction(): void $this->assertSame($transaction, startTransaction($transactionContext, $customSamplingContext)); } + + public function testTraceReturnsClosureResult(): void + { + $returnValue = 'foo'; + + $result = trace(function () use ($returnValue) { + return $returnValue; + }, new SpanContext()); + + $this->assertSame($returnValue, $result); + } + + public function testTraceCorrectlyReplacesAndRestoresCurrentSpan(): void + { + $hub = new Hub(); + + $transaction = new Transaction(new TransactionContext()); + + $hub->setSpan($transaction); + + SentrySdk::setCurrentHub($hub); + + $this->assertSame($transaction, $hub->getSpan()); + + trace(function () use ($transaction, $hub) { + $this->assertNotSame($transaction, $hub->getSpan()); + }, new SpanContext()); + + $this->assertSame($transaction, $hub->getSpan()); + + try { + trace(function () { + throw new RuntimeException('Throwing should still restore the previous span'); + }, new SpanContext()); + } catch (RuntimeException $e) { + $this->assertSame($transaction, $hub->getSpan()); + } + } } From 192e88ef9898cfdff1a97d9ac19c4c87cf2edc06 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 14 Mar 2023 21:38:21 +0100 Subject: [PATCH 0840/1161] feat: Add support for Cron Monitoring (#1467) --- src/CheckIn.php | 117 +++++++++++++++++++++ src/CheckInStatus.php | 55 ++++++++++ src/Event.php | 20 ++++ src/EventType.php | 5 + src/Serializer/PayloadSerializer.php | 33 +++++- src/Transport/HttpTransport.php | 5 +- tests/CheckInTest.php | 59 +++++++++++ tests/Serializer/PayloadSerializerTest.php | 53 +++++++++- 8 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 src/CheckIn.php create mode 100644 src/CheckInStatus.php create mode 100644 tests/CheckInTest.php diff --git a/src/CheckIn.php b/src/CheckIn.php new file mode 100644 index 000000000..4257b5af0 --- /dev/null +++ b/src/CheckIn.php @@ -0,0 +1,117 @@ +setMonitorSlug($monitorSlug); + $this->setStatus($status); + + $this->setId($id ?? SentryUid::generate()); + $this->setRelease($release ?? ''); + $this->setEnvironment($environment ?? Event::DEFAULT_ENVIRONMENT); + $this->setDuration($duration); + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): void + { + $this->id = $id; + } + + public function getMonitorSlug(): string + { + return $this->monitorSlug; + } + + public function setMonitorSlug(string $monitorSlug): void + { + $this->monitorSlug = $monitorSlug; + } + + public function getStatus(): CheckInStatus + { + return $this->status; + } + + public function setStatus(CheckInStatus $status): void + { + $this->status = $status; + } + + public function getRelease(): ?string + { + return $this->release; + } + + public function setRelease(string $release): void + { + $this->release = $release; + } + + public function getEnvironment(): ?string + { + return $this->environment; + } + + public function setEnvironment(string $environment): void + { + $this->environment = $environment; + } + + public function getDuration(): ?int + { + return $this->duration; + } + + public function setDuration(?int $duration): void + { + $this->duration = $duration; + } +} diff --git a/src/CheckInStatus.php b/src/CheckInStatus.php new file mode 100644 index 000000000..c29547107 --- /dev/null +++ b/src/CheckInStatus.php @@ -0,0 +1,55 @@ + A list of cached enum instances + */ + private static $instances = []; + + private function __construct(string $value) + { + $this->value = $value; + } + + public static function ok(): self + { + return self::getInstance('ok'); + } + + public static function error(): self + { + return self::getInstance('error'); + } + + public static function inProgress(): self + { + return self::getInstance('in_progress'); + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Event.php b/src/Event.php index 8f38d7aa5..41d93ef13 100644 --- a/src/Event.php +++ b/src/Event.php @@ -50,6 +50,11 @@ final class Event */ private $transaction; + /** + * @var CheckIn|null The check in data + */ + private $checkIn; + /** * @var string|null The name of the server (e.g. the host name) */ @@ -200,6 +205,11 @@ public static function createTransaction(EventId $eventId = null): self return new self($eventId, EventType::transaction()); } + public static function createCheckIn(?EventId $eventId = null): self + { + return new self($eventId, EventType::checkIn()); + } + /** * Gets the ID of this event. */ @@ -322,6 +332,16 @@ public function setTransaction(?string $transaction): void $this->transaction = $transaction; } + public function setCheckIn(?CheckIn $checkIn): void + { + $this->checkIn = $checkIn; + } + + public function getCheckIn(): ?CheckIn + { + return $this->checkIn; + } + /** * Gets the name of the server. */ diff --git a/src/EventType.php b/src/EventType.php index 0dc9e5398..c4e79c4d0 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -48,6 +48,11 @@ public static function transaction(): self return self::getInstance('transaction'); } + public static function checkIn(): self + { + return self::getInstance('check_in'); + } + public function __toString(): string { return $this->value; diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 9eeaff0d7..09155fce4 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -53,6 +53,10 @@ public function serialize(Event $event): string return $transactionEnvelope; } + if (EventType::checkIn() === $event->getType()) { + return $this->serializeAsEnvelope($event); + } + return $this->serializeAsEvent($event); } @@ -63,6 +67,25 @@ private function serializeAsEvent(Event $event): string return JSON::encode($result); } + private function serializeAsCheckInEvent(Event $event): string + { + $result = []; + + $checkIn = $event->getCheckIn(); + if (null !== $checkIn) { + $result = [ + 'check_in_id' => $checkIn->getId(), + 'monitor_slug' => $checkIn->getMonitorSlug(), + 'status' => (string) $checkIn->getStatus(), + 'duration' => $checkIn->getDuration(), + 'release' => $checkIn->getRelease(), + 'environment' => $checkIn->getEnvironment(), + ]; + } + + return JSON::encode($result); + } + /** * @return array */ @@ -231,7 +254,15 @@ private function serializeAsEnvelope(Event $event): string 'content_type' => 'application/json', ]; - return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $this->serializeAsEvent($event)); + $seralizedEvent = ''; + if (EventType::transaction() === $event->getType()) { + $seralizedEvent = $this->serializeAsEvent($event); + } + if (EventType::checkIn() === $event->getType()) { + $seralizedEvent = $this->serializeAsCheckInEvent($event); + } + + return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $seralizedEvent); } private function seralizeProfileAsEnvelope(Event $event): ?string diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index c3ca0d07e..74186be85 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -112,7 +112,10 @@ public function send(Event $event): PromiseInterface return new RejectedPromise(new Response(ResponseStatus::rateLimit(), $event)); } - if (EventType::transaction() === $eventType) { + if ( + EventType::transaction() === $eventType || + EventType::checkIn() === $eventType + ) { $request = $this->requestFactory->createRequest('POST', $dsn->getEnvelopeApiEndpointUrl()) ->withHeader('Content-Type', 'application/x-sentry-envelope') ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); diff --git a/tests/CheckInTest.php b/tests/CheckInTest.php new file mode 100644 index 000000000..4380ebd40 --- /dev/null +++ b/tests/CheckInTest.php @@ -0,0 +1,59 @@ +assertEquals($checkInId, $checkIn->getId()); + $this->assertEquals('my-monitor', $checkIn->getMonitorSlug()); + $this->assertEquals('ok', $checkIn->getStatus()); + $this->assertEquals('1.0.0', $checkIn->getRelease()); + $this->assertEquals('dev', $checkIn->getEnvironment()); + $this->assertEquals(10, $checkIn->getDuration()); + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters(string $getterMethod, string $setterMethod, $expectedData): void + { + $checkIn = new CheckIn( + 'my-monitor', + CheckInStatus::ok() + ); + $checkIn->$setterMethod($expectedData); + + $this->assertEquals($expectedData, $checkIn->$getterMethod()); + } + + public function gettersAndSettersDataProvider(): array + { + return [ + ['getId', 'setId', SentryUid::generate()], + ['getMonitorSlug', 'setMonitorSlug', 'my-monitor'], + ['getStatus', 'setStatus', CheckInStatus::ok()], + ['getRelease', 'setRelease', '1.0.0'], + ['getEnvironment', 'setEnvironment', 'dev'], + ['getDuration', 'setDuration', 10], + ]; + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index ea8663113..afafc33de 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -6,6 +6,8 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\CheckIn; +use Sentry\CheckInStatus; use Sentry\Client; use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; @@ -27,6 +29,7 @@ use Sentry\Tracing\TraceId; use Sentry\Tracing\TransactionMetadata; use Sentry\UserDataBag; +use Sentry\Util\SentryUid; use Symfony\Bridge\PhpUnit\ClockMock; /** @@ -55,7 +58,10 @@ public function testSerialize(Event $event, string $expectedResult, bool $isOutp $result = $this->serializer->serialize($event); - if (EventType::transaction() !== $event->getType()) { + if ( + EventType::transaction() !== $event->getType() && + EventType::checkIn() !== $event->getType() + ) { $resultArray = $this->serializer->toArray($event); $this->assertJsonStringEqualsJsonString($result, json_encode($resultArray)); } @@ -538,5 +544,50 @@ public function serializeDataProvider(): iterable , true, ]; + + $checkinId = SentryUid::generate(); + $checkIn = new CheckIn( + 'my-monitor', + CheckInStatus::ok(), + $checkinId, + '1.0.0', + 'dev', + 10 + ); + + $event = Event::createCheckIn(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setCheckIn($checkIn); + + yield [ + $event, + <<setCheckIn($checkIn); + + yield [ + $event, + << Date: Tue, 14 Mar 2023 21:49:28 +0100 Subject: [PATCH 0841/1161] Merge branch 'develop' into master (#1494) --- src/CheckIn.php | 117 +++++++++++++++++++++ src/CheckInStatus.php | 55 ++++++++++ src/Event.php | 20 ++++ src/EventType.php | 5 + src/Serializer/PayloadSerializer.php | 33 +++++- src/Transport/HttpTransport.php | 5 +- tests/CheckInTest.php | 59 +++++++++++ tests/Serializer/PayloadSerializerTest.php | 53 +++++++++- 8 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 src/CheckIn.php create mode 100644 src/CheckInStatus.php create mode 100644 tests/CheckInTest.php diff --git a/src/CheckIn.php b/src/CheckIn.php new file mode 100644 index 000000000..4257b5af0 --- /dev/null +++ b/src/CheckIn.php @@ -0,0 +1,117 @@ +setMonitorSlug($monitorSlug); + $this->setStatus($status); + + $this->setId($id ?? SentryUid::generate()); + $this->setRelease($release ?? ''); + $this->setEnvironment($environment ?? Event::DEFAULT_ENVIRONMENT); + $this->setDuration($duration); + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): void + { + $this->id = $id; + } + + public function getMonitorSlug(): string + { + return $this->monitorSlug; + } + + public function setMonitorSlug(string $monitorSlug): void + { + $this->monitorSlug = $monitorSlug; + } + + public function getStatus(): CheckInStatus + { + return $this->status; + } + + public function setStatus(CheckInStatus $status): void + { + $this->status = $status; + } + + public function getRelease(): ?string + { + return $this->release; + } + + public function setRelease(string $release): void + { + $this->release = $release; + } + + public function getEnvironment(): ?string + { + return $this->environment; + } + + public function setEnvironment(string $environment): void + { + $this->environment = $environment; + } + + public function getDuration(): ?int + { + return $this->duration; + } + + public function setDuration(?int $duration): void + { + $this->duration = $duration; + } +} diff --git a/src/CheckInStatus.php b/src/CheckInStatus.php new file mode 100644 index 000000000..c29547107 --- /dev/null +++ b/src/CheckInStatus.php @@ -0,0 +1,55 @@ + A list of cached enum instances + */ + private static $instances = []; + + private function __construct(string $value) + { + $this->value = $value; + } + + public static function ok(): self + { + return self::getInstance('ok'); + } + + public static function error(): self + { + return self::getInstance('error'); + } + + public static function inProgress(): self + { + return self::getInstance('in_progress'); + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Event.php b/src/Event.php index 8f38d7aa5..41d93ef13 100644 --- a/src/Event.php +++ b/src/Event.php @@ -50,6 +50,11 @@ final class Event */ private $transaction; + /** + * @var CheckIn|null The check in data + */ + private $checkIn; + /** * @var string|null The name of the server (e.g. the host name) */ @@ -200,6 +205,11 @@ public static function createTransaction(EventId $eventId = null): self return new self($eventId, EventType::transaction()); } + public static function createCheckIn(?EventId $eventId = null): self + { + return new self($eventId, EventType::checkIn()); + } + /** * Gets the ID of this event. */ @@ -322,6 +332,16 @@ public function setTransaction(?string $transaction): void $this->transaction = $transaction; } + public function setCheckIn(?CheckIn $checkIn): void + { + $this->checkIn = $checkIn; + } + + public function getCheckIn(): ?CheckIn + { + return $this->checkIn; + } + /** * Gets the name of the server. */ diff --git a/src/EventType.php b/src/EventType.php index 0dc9e5398..c4e79c4d0 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -48,6 +48,11 @@ public static function transaction(): self return self::getInstance('transaction'); } + public static function checkIn(): self + { + return self::getInstance('check_in'); + } + public function __toString(): string { return $this->value; diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 9eeaff0d7..09155fce4 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -53,6 +53,10 @@ public function serialize(Event $event): string return $transactionEnvelope; } + if (EventType::checkIn() === $event->getType()) { + return $this->serializeAsEnvelope($event); + } + return $this->serializeAsEvent($event); } @@ -63,6 +67,25 @@ private function serializeAsEvent(Event $event): string return JSON::encode($result); } + private function serializeAsCheckInEvent(Event $event): string + { + $result = []; + + $checkIn = $event->getCheckIn(); + if (null !== $checkIn) { + $result = [ + 'check_in_id' => $checkIn->getId(), + 'monitor_slug' => $checkIn->getMonitorSlug(), + 'status' => (string) $checkIn->getStatus(), + 'duration' => $checkIn->getDuration(), + 'release' => $checkIn->getRelease(), + 'environment' => $checkIn->getEnvironment(), + ]; + } + + return JSON::encode($result); + } + /** * @return array */ @@ -231,7 +254,15 @@ private function serializeAsEnvelope(Event $event): string 'content_type' => 'application/json', ]; - return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $this->serializeAsEvent($event)); + $seralizedEvent = ''; + if (EventType::transaction() === $event->getType()) { + $seralizedEvent = $this->serializeAsEvent($event); + } + if (EventType::checkIn() === $event->getType()) { + $seralizedEvent = $this->serializeAsCheckInEvent($event); + } + + return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $seralizedEvent); } private function seralizeProfileAsEnvelope(Event $event): ?string diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index c3ca0d07e..74186be85 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -112,7 +112,10 @@ public function send(Event $event): PromiseInterface return new RejectedPromise(new Response(ResponseStatus::rateLimit(), $event)); } - if (EventType::transaction() === $eventType) { + if ( + EventType::transaction() === $eventType || + EventType::checkIn() === $eventType + ) { $request = $this->requestFactory->createRequest('POST', $dsn->getEnvelopeApiEndpointUrl()) ->withHeader('Content-Type', 'application/x-sentry-envelope') ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); diff --git a/tests/CheckInTest.php b/tests/CheckInTest.php new file mode 100644 index 000000000..4380ebd40 --- /dev/null +++ b/tests/CheckInTest.php @@ -0,0 +1,59 @@ +assertEquals($checkInId, $checkIn->getId()); + $this->assertEquals('my-monitor', $checkIn->getMonitorSlug()); + $this->assertEquals('ok', $checkIn->getStatus()); + $this->assertEquals('1.0.0', $checkIn->getRelease()); + $this->assertEquals('dev', $checkIn->getEnvironment()); + $this->assertEquals(10, $checkIn->getDuration()); + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters(string $getterMethod, string $setterMethod, $expectedData): void + { + $checkIn = new CheckIn( + 'my-monitor', + CheckInStatus::ok() + ); + $checkIn->$setterMethod($expectedData); + + $this->assertEquals($expectedData, $checkIn->$getterMethod()); + } + + public function gettersAndSettersDataProvider(): array + { + return [ + ['getId', 'setId', SentryUid::generate()], + ['getMonitorSlug', 'setMonitorSlug', 'my-monitor'], + ['getStatus', 'setStatus', CheckInStatus::ok()], + ['getRelease', 'setRelease', '1.0.0'], + ['getEnvironment', 'setEnvironment', 'dev'], + ['getDuration', 'setDuration', 10], + ]; + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index ea8663113..afafc33de 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -6,6 +6,8 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\CheckIn; +use Sentry\CheckInStatus; use Sentry\Client; use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; @@ -27,6 +29,7 @@ use Sentry\Tracing\TraceId; use Sentry\Tracing\TransactionMetadata; use Sentry\UserDataBag; +use Sentry\Util\SentryUid; use Symfony\Bridge\PhpUnit\ClockMock; /** @@ -55,7 +58,10 @@ public function testSerialize(Event $event, string $expectedResult, bool $isOutp $result = $this->serializer->serialize($event); - if (EventType::transaction() !== $event->getType()) { + if ( + EventType::transaction() !== $event->getType() && + EventType::checkIn() !== $event->getType() + ) { $resultArray = $this->serializer->toArray($event); $this->assertJsonStringEqualsJsonString($result, json_encode($resultArray)); } @@ -538,5 +544,50 @@ public function serializeDataProvider(): iterable , true, ]; + + $checkinId = SentryUid::generate(); + $checkIn = new CheckIn( + 'my-monitor', + CheckInStatus::ok(), + $checkinId, + '1.0.0', + 'dev', + 10 + ); + + $event = Event::createCheckIn(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setCheckIn($checkIn); + + yield [ + $event, + <<setCheckIn($checkIn); + + yield [ + $event, + << Date: Tue, 14 Mar 2023 21:53:23 +0100 Subject: [PATCH 0842/1161] Revert "Merge branch 'develop' into master (#1494)" (#1496) --- src/CheckIn.php | 117 --------------------- src/CheckInStatus.php | 55 ---------- src/Event.php | 20 ---- src/EventType.php | 5 - src/Serializer/PayloadSerializer.php | 33 +----- src/Transport/HttpTransport.php | 5 +- tests/CheckInTest.php | 59 ----------- tests/Serializer/PayloadSerializerTest.php | 53 +--------- 8 files changed, 3 insertions(+), 344 deletions(-) delete mode 100644 src/CheckIn.php delete mode 100644 src/CheckInStatus.php delete mode 100644 tests/CheckInTest.php diff --git a/src/CheckIn.php b/src/CheckIn.php deleted file mode 100644 index 4257b5af0..000000000 --- a/src/CheckIn.php +++ /dev/null @@ -1,117 +0,0 @@ -setMonitorSlug($monitorSlug); - $this->setStatus($status); - - $this->setId($id ?? SentryUid::generate()); - $this->setRelease($release ?? ''); - $this->setEnvironment($environment ?? Event::DEFAULT_ENVIRONMENT); - $this->setDuration($duration); - } - - public function getId(): string - { - return $this->id; - } - - public function setId(string $id): void - { - $this->id = $id; - } - - public function getMonitorSlug(): string - { - return $this->monitorSlug; - } - - public function setMonitorSlug(string $monitorSlug): void - { - $this->monitorSlug = $monitorSlug; - } - - public function getStatus(): CheckInStatus - { - return $this->status; - } - - public function setStatus(CheckInStatus $status): void - { - $this->status = $status; - } - - public function getRelease(): ?string - { - return $this->release; - } - - public function setRelease(string $release): void - { - $this->release = $release; - } - - public function getEnvironment(): ?string - { - return $this->environment; - } - - public function setEnvironment(string $environment): void - { - $this->environment = $environment; - } - - public function getDuration(): ?int - { - return $this->duration; - } - - public function setDuration(?int $duration): void - { - $this->duration = $duration; - } -} diff --git a/src/CheckInStatus.php b/src/CheckInStatus.php deleted file mode 100644 index c29547107..000000000 --- a/src/CheckInStatus.php +++ /dev/null @@ -1,55 +0,0 @@ - A list of cached enum instances - */ - private static $instances = []; - - private function __construct(string $value) - { - $this->value = $value; - } - - public static function ok(): self - { - return self::getInstance('ok'); - } - - public static function error(): self - { - return self::getInstance('error'); - } - - public static function inProgress(): self - { - return self::getInstance('in_progress'); - } - - public function __toString(): string - { - return $this->value; - } - - private static function getInstance(string $value): self - { - if (!isset(self::$instances[$value])) { - self::$instances[$value] = new self($value); - } - - return self::$instances[$value]; - } -} diff --git a/src/Event.php b/src/Event.php index 41d93ef13..8f38d7aa5 100644 --- a/src/Event.php +++ b/src/Event.php @@ -50,11 +50,6 @@ final class Event */ private $transaction; - /** - * @var CheckIn|null The check in data - */ - private $checkIn; - /** * @var string|null The name of the server (e.g. the host name) */ @@ -205,11 +200,6 @@ public static function createTransaction(EventId $eventId = null): self return new self($eventId, EventType::transaction()); } - public static function createCheckIn(?EventId $eventId = null): self - { - return new self($eventId, EventType::checkIn()); - } - /** * Gets the ID of this event. */ @@ -332,16 +322,6 @@ public function setTransaction(?string $transaction): void $this->transaction = $transaction; } - public function setCheckIn(?CheckIn $checkIn): void - { - $this->checkIn = $checkIn; - } - - public function getCheckIn(): ?CheckIn - { - return $this->checkIn; - } - /** * Gets the name of the server. */ diff --git a/src/EventType.php b/src/EventType.php index c4e79c4d0..0dc9e5398 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -48,11 +48,6 @@ public static function transaction(): self return self::getInstance('transaction'); } - public static function checkIn(): self - { - return self::getInstance('check_in'); - } - public function __toString(): string { return $this->value; diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 09155fce4..9eeaff0d7 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -53,10 +53,6 @@ public function serialize(Event $event): string return $transactionEnvelope; } - if (EventType::checkIn() === $event->getType()) { - return $this->serializeAsEnvelope($event); - } - return $this->serializeAsEvent($event); } @@ -67,25 +63,6 @@ private function serializeAsEvent(Event $event): string return JSON::encode($result); } - private function serializeAsCheckInEvent(Event $event): string - { - $result = []; - - $checkIn = $event->getCheckIn(); - if (null !== $checkIn) { - $result = [ - 'check_in_id' => $checkIn->getId(), - 'monitor_slug' => $checkIn->getMonitorSlug(), - 'status' => (string) $checkIn->getStatus(), - 'duration' => $checkIn->getDuration(), - 'release' => $checkIn->getRelease(), - 'environment' => $checkIn->getEnvironment(), - ]; - } - - return JSON::encode($result); - } - /** * @return array */ @@ -254,15 +231,7 @@ private function serializeAsEnvelope(Event $event): string 'content_type' => 'application/json', ]; - $seralizedEvent = ''; - if (EventType::transaction() === $event->getType()) { - $seralizedEvent = $this->serializeAsEvent($event); - } - if (EventType::checkIn() === $event->getType()) { - $seralizedEvent = $this->serializeAsCheckInEvent($event); - } - - return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $seralizedEvent); + return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $this->serializeAsEvent($event)); } private function seralizeProfileAsEnvelope(Event $event): ?string diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 74186be85..c3ca0d07e 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -112,10 +112,7 @@ public function send(Event $event): PromiseInterface return new RejectedPromise(new Response(ResponseStatus::rateLimit(), $event)); } - if ( - EventType::transaction() === $eventType || - EventType::checkIn() === $eventType - ) { + if (EventType::transaction() === $eventType) { $request = $this->requestFactory->createRequest('POST', $dsn->getEnvelopeApiEndpointUrl()) ->withHeader('Content-Type', 'application/x-sentry-envelope') ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); diff --git a/tests/CheckInTest.php b/tests/CheckInTest.php deleted file mode 100644 index 4380ebd40..000000000 --- a/tests/CheckInTest.php +++ /dev/null @@ -1,59 +0,0 @@ -assertEquals($checkInId, $checkIn->getId()); - $this->assertEquals('my-monitor', $checkIn->getMonitorSlug()); - $this->assertEquals('ok', $checkIn->getStatus()); - $this->assertEquals('1.0.0', $checkIn->getRelease()); - $this->assertEquals('dev', $checkIn->getEnvironment()); - $this->assertEquals(10, $checkIn->getDuration()); - } - - /** - * @dataProvider gettersAndSettersDataProvider - */ - public function testGettersAndSetters(string $getterMethod, string $setterMethod, $expectedData): void - { - $checkIn = new CheckIn( - 'my-monitor', - CheckInStatus::ok() - ); - $checkIn->$setterMethod($expectedData); - - $this->assertEquals($expectedData, $checkIn->$getterMethod()); - } - - public function gettersAndSettersDataProvider(): array - { - return [ - ['getId', 'setId', SentryUid::generate()], - ['getMonitorSlug', 'setMonitorSlug', 'my-monitor'], - ['getStatus', 'setStatus', CheckInStatus::ok()], - ['getRelease', 'setRelease', '1.0.0'], - ['getEnvironment', 'setEnvironment', 'dev'], - ['getDuration', 'setDuration', 10], - ]; - } -} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index afafc33de..ea8663113 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -6,8 +6,6 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; -use Sentry\CheckIn; -use Sentry\CheckInStatus; use Sentry\Client; use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; @@ -29,7 +27,6 @@ use Sentry\Tracing\TraceId; use Sentry\Tracing\TransactionMetadata; use Sentry\UserDataBag; -use Sentry\Util\SentryUid; use Symfony\Bridge\PhpUnit\ClockMock; /** @@ -58,10 +55,7 @@ public function testSerialize(Event $event, string $expectedResult, bool $isOutp $result = $this->serializer->serialize($event); - if ( - EventType::transaction() !== $event->getType() && - EventType::checkIn() !== $event->getType() - ) { + if (EventType::transaction() !== $event->getType()) { $resultArray = $this->serializer->toArray($event); $this->assertJsonStringEqualsJsonString($result, json_encode($resultArray)); } @@ -544,50 +538,5 @@ public function serializeDataProvider(): iterable , true, ]; - - $checkinId = SentryUid::generate(); - $checkIn = new CheckIn( - 'my-monitor', - CheckInStatus::ok(), - $checkinId, - '1.0.0', - 'dev', - 10 - ); - - $event = Event::createCheckIn(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); - $event->setCheckIn($checkIn); - - yield [ - $event, - <<setCheckIn($checkIn); - - yield [ - $event, - << Date: Tue, 14 Mar 2023 22:18:17 +0100 Subject: [PATCH 0843/1161] docs: Update CONTRIBUTING.md (#1495) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b32f588ad..8607f8012 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ If you feel that you can fix or implement it yourself, please read on to learn h - Clone the `sentry-php` repository and prepare necessary changes. - Add tests for your changes to `tests/`. - Run tests and make sure all of them pass. -- Submit a pull request, targeting the `develop` branch if you added any new features. For bug fixes of the current release, please target the `master` branch instead. +- Open a pull request. We will review your pull request as soon as possible. Thank you for contributing! From 9b02dfa1756b32ab7a5183c14ad40ed9062f11c3 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 16 Mar 2023 11:15:40 +0100 Subject: [PATCH 0844/1161] reaf: Accept check-in duration as float (#1500) --- src/CheckIn.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/CheckIn.php b/src/CheckIn.php index 4257b5af0..bcc0f3e28 100644 --- a/src/CheckIn.php +++ b/src/CheckIn.php @@ -34,17 +34,20 @@ final class CheckIn private $environment; /** - * @var int|null The duration of the check in seconds + * @var int|float|null The duration of the check-in in seconds */ private $duration; + /** + * @param int|float|null $duration The duration of the check-in in seconds + */ public function __construct( string $monitorSlug, CheckInStatus $status, string $id = null, ?string $release = null, ?string $environment = null, - ?int $duration = null + $duration = null ) { $this->setMonitorSlug($monitorSlug); $this->setStatus($status); @@ -105,12 +108,18 @@ public function setEnvironment(string $environment): void $this->environment = $environment; } - public function getDuration(): ?int + /** + * @return int|float|null + */ + public function getDuration() { return $this->duration; } - public function setDuration(?int $duration): void + /** + * @param int|float|null $duration The duration of the check-in in seconds + */ + public function setDuration($duration): void { $this->duration = $duration; } From 884f04548e00a134f6747104a8c2e849cbfc74b1 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 16 Mar 2023 11:36:32 +0100 Subject: [PATCH 0845/1161] Prepare 3.16.0 (#1498) --- CHANGELOG.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d656fc3bd..d6c8a519c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,89 @@ # CHANGELOG +## 3.16.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.16.0. +This release adds initial support for [Cron Monitoring](https://docs.sentry.io/product/crons/). + +> **Warning** +> Cron Monitoring is currently in beta. Beta features are still in-progress and may have bugs. We recognize the irony. +> If you have any questions or feedback, please email us at crons-feedback@sentry.io, reach out via Discord (#cronjobs), or open an issue. + +### Features + +- Add inital support for Cron Monitoring [(#1467)](https://github.com/getsentry/sentry-php/pull/1467) + + You can use Cron Monitoring to monitor your cron jobs. No pun intended. + + Add the code below to your application or script that is invoked by your cron job. + The first Check-In will let Sentry know that your job started, with the second Check-In reporting the outcome. + + ```php + ', + status: CheckInStatus::inProgress(), + ); + + $event = Event::createCheckIn(); + $event->setCheckIn($checkIn); + + $this->hub->captureEvent($event); + + try { + + // do stuff + + $checkIn->setStatus(CheckInStatus::ok()); + } catch (Throwable $e) { + $checkIn->setStatus(CheckInStatus::error()); + } + + $event = Event::createCheckIn(); + $event->setCheckIn($checkIn); + + $this->hub->captureEvent($event); + ``` + + If you only want to check if a cron did run, you may create a "Heartbeat" instead. + Add the code below to your application or script that is invoked by your cron job. + + + ```php + ', + status: CheckInStatus::ok(), // or - CheckInStatus::error() + duration: 10, // optional - duration in seconds + ); + + $event = Event::createCheckIn(); + $event->setCheckIn($checkIn); + + $this->hub->captureEvent($event); + ``` + +- Introduce a new `trace` helper function [(#1490)](https://github.com/getsentry/sentry-php/pull/1490) + + We made it a tad easier to add custom tracing spans to your application. + + ```php + $spanContext = new SpanContext(); + $spanContext->setOp('function'); + $spanContext->setDescription('Soemthing to be traced'); + + trace( + function (Scope $scope) { + // something to be traced + }, + $spanContext, + ); + ``` + ## 3.15.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.15.0. From 5326a8786b8c7c3a51ea0c6d06e6cb6a9dfa6779 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 16 Mar 2023 10:37:16 +0000 Subject: [PATCH 0846/1161] release: 3.16.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 0674cf9ae..3d090cac9 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.15.0'; + public const SDK_VERSION = '3.16.0'; /** * @var Options The client options From 9f3ebe15f599d779f0fff2a95a2ebb8ecddb68c2 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 20 Mar 2023 16:12:46 +0100 Subject: [PATCH 0847/1161] Update README.md (#1501) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edbe5a808..edcc9d461 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ If you need help setting up or configuring the PHP SDK (or anything else in the ## License -Licensed under the BSD license, see [`LICENSE`](LICENSE) +Licensed under the MIT license, see [`LICENSE`](LICENSE) [master Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Amaster [master Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=master From 00a022a815a7785f58c2a6fa907a7121429d69d1 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 23 Mar 2023 15:43:36 +0100 Subject: [PATCH 0848/1161] Use `php-http/discovery": "^1.15` (#1504) --- composer.json | 6 +++--- src/Integration/RequestFetcher.php | 4 ++-- src/Integration/RequestIntegration.php | 17 ++++++++++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 069274dc6..4ff3d68ec 100644 --- a/composer.json +++ b/composer.json @@ -24,21 +24,21 @@ "ext-json": "*", "ext-mbstring": "*", "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.8.4|^2.1.1", "jean85/pretty-package-versions": "^1.5|^2.0.4", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", - "php-http/discovery": "^1.11, <1.15", + "php-http/discovery": "^1.15", "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", "psr/http-factory": "^1.0", - "psr/http-message-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", "symfony/polyfill-php80": "^1.17" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.19|3.4.*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.6|^2.0|^3.0", "nikic/php-parser": "^4.10.3", diff --git a/src/Integration/RequestFetcher.php b/src/Integration/RequestFetcher.php index 333205b5f..f15fef4b3 100644 --- a/src/Integration/RequestFetcher.php +++ b/src/Integration/RequestFetcher.php @@ -4,7 +4,7 @@ namespace Sentry\Integration; -use GuzzleHttp\Psr7\ServerRequest; +use Http\Discovery\Psr17Factory; use Psr\Http\Message\ServerRequestInterface; /** @@ -22,6 +22,6 @@ public function fetchRequest(): ?ServerRequestInterface return null; } - return ServerRequest::fromGlobals(); + return (new Psr17Factory())->createServerRequestFromGlobals(); } } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 792ef8300..6c38d2058 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -4,7 +4,6 @@ namespace Sentry\Integration; -use GuzzleHttp\Psr7\Utils; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; use Sentry\Event; @@ -50,7 +49,7 @@ final class RequestIntegration implements IntegrationInterface 'never' => 0, 'small' => self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH, 'medium' => self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH, - 'always' => -1, + 'always' => \PHP_INT_MAX, ]; /** @@ -224,7 +223,19 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re return $requestData; } - $requestBody = Utils::copyToString($request->getBody(), self::MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP[$maxRequestBodySize]); + $requestBody = ''; + $maxLength = self::MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP[$maxRequestBodySize]; + + if (0 < $maxLength) { + $stream = $request->getBody(); + while (0 < $maxLength && !$stream->eof()) { + if ('' === $buffer = $stream->read(min($maxLength, self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH))) { + break; + } + $requestBody .= $buffer; + $maxLength -= \strlen($buffer); + } + } if ('application/json' === $request->getHeaderLine('Content-Type')) { try { From 48486841080661729caec75176752ca29f0fe9b5 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 23 Mar 2023 15:49:32 +0100 Subject: [PATCH 0849/1161] feat: Add `ignore_exceptions` & `ignore_transactions` (#1503) Co-authored-by: Alex Bouma Co-authored-by: Stefano Arlandini --- phpstan-baseline.neon | 10 +++++ src/Client.php | 47 ++++++++++++++++++++ src/Integration/IgnoreErrorsIntegration.php | 2 + src/Options.php | 48 +++++++++++++++++++++ tests/ClientTest.php | 47 ++++++++++++++++++++ tests/OptionsTest.php | 18 ++++++++ 6 files changed, 172 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c3ddeb163..a4f7cfdc8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -150,6 +150,16 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getIgnoreExceptions\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getIgnoreTransactions\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getInAppExcludedPaths\\(\\) should return array\\ but returns mixed\\.$#" count: 1 diff --git a/src/Client.php b/src/Client.php index 3d090cac9..a324c1153 100644 --- a/src/Client.php +++ b/src/Client.php @@ -281,6 +281,12 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco return null; } + $event = $this->applyIgnoreOptions($event); + + if (null === $event) { + return null; + } + if (null !== $scope) { $beforeEventProcessors = $event; $event = $scope->applyToEvent($event, $hint); @@ -311,6 +317,47 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco return $event; } + private function applyIgnoreOptions(Event $event): ?Event + { + if ($event->getType() === EventType::event()) { + $exceptions = $event->getExceptions(); + + if (empty($exceptions)) { + return $event; + } + + foreach ($exceptions as $exception) { + if (\in_array($exception->getType(), $this->options->getIgnoreExceptions(), true)) { + $this->logger->info( + 'The event will be discarded because it matches an entry in "ignore_exceptions".', + ['event' => $event] + ); + + return null; + } + } + } + + if ($event->getType() === EventType::transaction()) { + $transactionName = $event->getTransaction(); + + if (null === $transactionName) { + return $event; + } + + if (\in_array($transactionName, $this->options->getIgnoreTransactions(), true)) { + $this->logger->info( + 'The event will be discarded because it matches a entry in "ignore_transactions".', + ['event' => $event] + ); + + return null; + } + } + + return $event; + } + private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event { if ($event->getType() === EventType::event()) { diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index aae22e10b..7264be103 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -13,6 +13,8 @@ * This integration decides whether an event should not be captured according * to a series of options that must match with its data. * + * @deprecated since version 3.17, to be removed in 4.0. Use the `ignore_exceptions` option instead + * * @author Stefano Arlandini * * @psalm-type IntegrationOptions array{ diff --git a/src/Options.php b/src/Options.php index dbee7d128..fd216cb76 100644 --- a/src/Options.php +++ b/src/Options.php @@ -416,6 +416,50 @@ public function setServerName(string $serverName): void $this->options = $this->resolver->resolve($options); } + /** + * Gets a list of exceptions to be ignored and not sent to Sentry. + * + * @return string[] + */ + public function getIgnoreExceptions(): array + { + return $this->options['ignore_exceptions']; + } + + /** + * Sets a list of exceptions to be ignored and not sent to Sentry. + * + * @param string[] $ignoreErrors The list of exceptions to be ignored + */ + public function setIgnoreExceptions(array $ignoreErrors): void + { + $options = array_merge($this->options, ['ignore_exceptions' => $ignoreErrors]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets a list of transaction names to be ignored and not sent to Sentry. + * + * @return string[] + */ + public function getIgnoreTransactions(): array + { + return $this->options['ignore_transactions']; + } + + /** + * Sets a list of transaction names to be ignored and not sent to Sentry. + * + * @param string[] $ignoreTransaction The list of transaction names to be ignored + */ + public function setIgnoreTransactions(array $ignoreTransaction): void + { + $options = array_merge($this->options, ['ignore_transactions' => $ignoreTransaction]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets a callback that will be invoked before an event is sent to the server. * If `null` is returned it won't be sent. @@ -872,6 +916,8 @@ private function configureOptions(OptionsResolver $resolver): void 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), + 'ignore_exceptions' => [], + 'ignore_transactions' => [], 'before_send' => static function (Event $event): Event { return $event; }, @@ -916,6 +962,8 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('before_send_transaction', ['callable']); + $resolver->setAllowedTypes('ignore_exceptions', 'string[]'); + $resolver->setAllowedTypes('ignore_transactions', 'string[]'); $resolver->setAllowedTypes('trace_propagation_targets', 'string[]'); $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['null', 'int']); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 078ddc1b1..f5cf5beac 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -587,6 +587,53 @@ public function processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDat ]; } + public function testProcessEventDiscardsEventWhenIgnoreExceptionsMatches(): void + { + $exception = new \Exception('Some foo error'); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because it matches an entry in "ignore_exceptions".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'ignore_exceptions' => [\Exception::class], + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureException($exception); + } + + public function testProcessEventDiscardsEventWhenIgnoreTransactionsMatches(): void + { + $event = Event::createTransaction(); + $event->setTransaction('GET /foo'); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because it matches a entry in "ignore_transactions".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'ignore_transactions' => ['GET /foo'], + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureEvent($event); + } + public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull(): void { /** @var LoggerInterface&MockObject $logger */ diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index c58424878..178165b6c 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -266,6 +266,24 @@ static function (): void {}, null, ]; + yield [ + 'ignore_exceptions', + ['foo', 'bar'], + 'getIgnoreExceptions', + 'setIgnoreExceptions', + null, + null, + ]; + + yield [ + 'ignore_transactions', + ['foo', 'bar'], + 'getIgnoreTransactions', + 'setIgnoreTransactions', + null, + null, + ]; + yield [ 'before_send', static function (): void {}, From e06f3455027b55b2172f5bc29ef113bbf8d84300 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Sun, 26 Mar 2023 12:53:04 +0200 Subject: [PATCH 0850/1161] Prepare 3.17.0 (#1505) --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c8a519c..c1c16fa03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # CHANGELOG +## 3.17.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.16.0. + +### Features + +- Add `ignore_exceptions` & `ignore_transactions` options [(#1503)](https://github.com/getsentry/sentry-php/pull/1503) + + We deprecated the [IgnoreErrorsIntegration](https://docs.sentry.io/platforms/php/integrations/#ignoreerrorsintegration) in favor of this new option. + The option will also take [previous exceptions](https://www.php.net/manual/en/exception.getprevious.php) into account. + + ```php + \Sentry\init([ + 'ignore_exceptions' => [BadThingsHappenedException::class], + ]); + ``` + + To ignore a transaction being sent to Sentry, add its name to the config option. + You can find the transaction name on the [Performance page](https://sentry.io/performance/). + + ```php + \Sentry\init([ + 'ignore_transactions' => ['GET /health'], + ]); + ``` + +### Misc + + - Bump `php-http/discovery` to `^1.15` [(#1504)](https://github.com/getsentry/sentry-php/pull/1504) + + You may need to allow the added composer plugin, introduced in `php-http/discovery v1.15.0`, to execute when running `composer update`. + We previously pinned this package to version `<1.15`. + Due to conflicts with other packages, we decided to lift this restriction. + ## 3.16.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.16.0. From 69b5165eab9f63f885c50820a8c233ed24341702 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Sun, 26 Mar 2023 10:53:37 +0000 Subject: [PATCH 0851/1161] release: 3.17.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index a324c1153..a130a38bf 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.16.0'; + public const SDK_VERSION = '3.17.0'; /** * @var Options The client options From 95d2e932383cf684f77acff0d2a5aef5ad2f1933 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Sun, 26 Mar 2023 23:54:06 +0200 Subject: [PATCH 0852/1161] Update changelog entry message --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c16fa03..688ec7bca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 3.17.0 -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.16.0. +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.17.0. ### Features From 9e53a01d0ee524c2dc48c2e7ca572a5277e76f9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:41:01 +0200 Subject: [PATCH 0853/1161] chore(deps): bump actions/stale from 7.0.0 to 8.0.0 (#1508) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index fbc372132..583feba7a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 with: repo-token: ${{ github.token }} days-before-stale: 21 From 91d8da9b17a1cea24019430e5e4d5ae58c714aa5 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 20 Apr 2023 11:12:04 +0200 Subject: [PATCH 0854/1161] ref: Sent all events to the `/envelope` endpoint if tracing is enabled (#1518) --- src/Serializer/PayloadSerializer.php | 10 +- src/Transport/HttpTransport.php | 1 + tests/Serializer/PayloadSerializerTest.php | 360 ++++++++++++++++++++- 3 files changed, 351 insertions(+), 20 deletions(-) diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 09155fce4..c081b5cd5 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -57,6 +57,10 @@ public function serialize(Event $event): string return $this->serializeAsEnvelope($event); } + if ($this->options->isTracingEnabled()) { + return $this->serializeAsEnvelope($event); + } + return $this->serializeAsEvent($event); } @@ -254,12 +258,10 @@ private function serializeAsEnvelope(Event $event): string 'content_type' => 'application/json', ]; - $seralizedEvent = ''; - if (EventType::transaction() === $event->getType()) { - $seralizedEvent = $this->serializeAsEvent($event); - } if (EventType::checkIn() === $event->getType()) { $seralizedEvent = $this->serializeAsCheckInEvent($event); + } else { + $seralizedEvent = $this->serializeAsEvent($event); } return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $seralizedEvent); diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 74186be85..9aa844edb 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -113,6 +113,7 @@ public function send(Event $event): PromiseInterface } if ( + $this->options->isTracingEnabled() || EventType::transaction() === $eventType || EventType::checkIn() === $eventType ) { diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index afafc33de..5f2523ef7 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -38,31 +38,23 @@ final class PayloadSerializerTest extends TestCase { /** - * @var PayloadSerializer + * @dataProvider serializeAsJsonDataProvider */ - private $serializer; - - protected function setUp(): void + public function testSerializeAsJson(Event $event, string $expectedResult, bool $isOutputJson): void { - $this->serializer = new PayloadSerializer(new Options([ + ClockMock::withClockMock(1597790835); + + $serializer = new PayloadSerializer(new Options([ 'dsn' => 'http://public@example.com/sentry/1', ])); - } - /** - * @dataProvider serializeDataProvider - */ - public function testSerialize(Event $event, string $expectedResult, bool $isOutputJson): void - { - ClockMock::withClockMock(1597790835); - - $result = $this->serializer->serialize($event); + $result = $serializer->serialize($event); if ( EventType::transaction() !== $event->getType() && EventType::checkIn() !== $event->getType() ) { - $resultArray = $this->serializer->toArray($event); + $resultArray = $serializer->toArray($event); $this->assertJsonStringEqualsJsonString($result, json_encode($resultArray)); } @@ -73,7 +65,24 @@ public function testSerialize(Event $event, string $expectedResult, bool $isOutp } } - public function serializeDataProvider(): iterable + /** + * @dataProvider serializeAsEnvelopeDataProvider + */ + public function testSerializeAsEnvelope(Event $event, string $expectedResult): void + { + ClockMock::withClockMock(1597790835); + + $serializer = new PayloadSerializer(new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + 'enable_tracing' => true, + ])); + + $result = $serializer->serialize($event); + + $this->assertSame($expectedResult, $result); + } + + public function serializeAsJsonDataProvider(): iterable { ClockMock::withClockMock(1597790835); @@ -590,4 +599,323 @@ public function serializeDataProvider(): iterable false, ]; } + + public function serializeAsEnvelopeDataProvider(): iterable + { + ClockMock::withClockMock(1597790835); + + $sdkVersion = Client::SDK_VERSION; + + yield [ + Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), + <<setLevel(Severity::error()); + $event->setLogger('app.php'); + $event->setTransaction('/users//'); + $event->setServerName('foo.example.com'); + $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); + $event->setEnvironment('production'); + $event->setFingerprint(['myrpc', 'POST', '/foo.bar']); + $event->setModules(['my.module.name' => '1.0']); + $event->setStartTimestamp(1597790835); + $event->setBreadcrumb([ + new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'log'), + new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_NAVIGATION, 'log', null, ['from' => '/login', 'to' => '/dashboard']), + ]); + + $event->setUser(UserDataBag::createFromArray([ + 'id' => 'unique_id', + 'username' => 'my_user', + 'email' => 'foo@example.com', + 'ip_address' => '127.0.0.1', + 'segment' => 'my_segment', + ])); + + $event->setTags([ + 'ios_version' => '4.0', + 'context' => 'production', + ]); + + $event->setExtra([ + 'my_key' => 1, + 'some_other_value' => 'foo bar', + ]); + + $event->setRequest([ + 'method' => 'POST', + 'url' => 'http://absolute.uri/foo', + 'query_string' => 'query=foobar&page=2', + 'data' => [ + 'foo' => 'bar', + ], + 'cookies' => [ + 'PHPSESSID' => '298zf09hf012fh2', + ], + 'headers' => [ + 'content-type' => 'text/html', + ], + 'env' => [ + 'REMOTE_ADDR' => '127.0.0.1', + ], + ]); + + $event->setOsContext(new OsContext( + 'Linux', + '4.19.104-microsoft-standard', + '#1 SMP Wed Feb 19 06:37:35 UTC 2020', + 'Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64' + )); + + $event->setRuntimeContext(new RuntimeContext( + 'php', + '7.4.3' + )); + + $event->setContext('electron', [ + 'type' => 'runtime', + 'name' => 'Electron', + 'version' => '4.0', + ]); + + $frame1 = new Frame(null, 'file/name.py', 3); + $frame2 = new Frame('myfunction', 'file/name.py', 3, 'raw_function_name', 'absolute/file/name.py', ['my_var' => 'value'], false); + $frame2->setContextLine(' raise ValueError()'); + $frame2->setPreContext([ + 'def foo():', + ' my_var = \'foo\'', + ]); + + $frame2->setPostContext([ + '', + 'def main():', + ]); + + $event->setExceptions([ + new ExceptionDataBag(new \Exception('initial exception')), + new ExceptionDataBag( + new \Exception('chained exception'), + new Stacktrace([ + $frame1, + $frame2, + ]), + new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 123]) + ), + ]); + + yield [ + $event, + <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} +TEXT + ]; + + $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setMessage('My raw message with interpreted strings like this', []); + + yield [ + $event, + <<setMessage('My raw message with interpreted strings like %s', ['this']); + + yield [ + $event, + <<setMessage('My raw message with interpreted strings like %s', ['this'], 'My raw message with interpreted strings like that'); + + yield [ + $event, + <<setSpanId(new SpanId('5dd538dc297544cc')); + $span1->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); + + $span2 = new Span(); + $span2->setSpanId(new SpanId('b01b9f6349558cd1')); + $span2->setParentSpanId(new SpanId('b0e6f15b45c36b12')); + $span2->setTraceId(new TraceId('1e57b752bc6e4544bbaa246cd1d05dee')); + $span2->setOp('http'); + $span2->setDescription('GET /sockjs-node/info'); + $span2->setStatus(SpanStatus::ok()); + $span2->setStartTimestamp(1597790835); + $span2->setTags(['http.status_code' => '200']); + $span2->setData([ + 'url' => 'http://localhost:8080/sockjs-node/info?t=1588601703755', + 'status_code' => 200, + 'type' => 'xhr', + 'method' => 'GET', + ]); + + $span2->finish(1598659060); + + $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setSpans([$span1, $span2]); + $event->setRelease('1.0.0'); + $event->setEnvironment('dev'); + $event->setTransaction('GET /'); + $event->setContext('trace', [ + 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', + 'span_id' => '5dd538dc297544cc', + ]); + $event->setRuntimeContext(new RuntimeContext( + 'php', + '8.2.3' + )); + $event->setOsContext(new OsContext( + 'macOS', + '13.2.1', + '22D68', + 'Darwin Kernel Version 22.2.0', + 'aarch64' + )); + + $excimerLog = [ + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + ], + 'timestamp' => 0.001, + ], + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + [ + 'class' => 'Function', + 'function' => 'doStuff', + 'file' => '/var/www/html/function.php', + 'line' => 84, + ], + ], + 'timestamp' => 0.002, + ], + ]; + + $profile = new Profile(); + // 2022-02-28T09:41:00Z + $profile->setStartTimeStamp(1677573660.0000); + $profile->setExcimerLog($excimerLog); + $profile->setEventId($event->getId()); + + $event->setSdkMetadata('profile', $profile); + + yield [ + $event, + <<setSdkMetadata('dynamic_sampling_context', DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-sample_rate=1')); + $event->setSdkMetadata('transaction_metadata', new TransactionMetadata()); + + yield [ + $event, + <<setStacktrace(new Stacktrace([new Frame(null, '', 0)])); + + yield [ + $event, + <<setCheckIn($checkIn); + + yield [ + $event, + <<setCheckIn($checkIn); + + yield [ + $event, + << Date: Mon, 24 Apr 2023 13:17:48 +0200 Subject: [PATCH 0855/1161] feat: Add `TransactionContext::fromEnvironment` (#1519) --- src/Tracing/TransactionContext.php | 20 ++++++++++++++++++-- tests/Tracing/TransactionContextTest.php | 18 ++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index 13083ed35..dd70441dd 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -136,6 +136,17 @@ public static function fromSentryTrace(string $header): self return $context; } + /** + * Returns a context populated with the data of the given environment variables. + * + * @param string $sentryTrace The sentry-trace value from the environment + * @param string $baggage The baggage header value from the environment + */ + public static function fromEnvironment(string $sentryTrace, string $baggage): self + { + return self::parseTraceAndBaggage($sentryTrace, $baggage); + } + /** * Returns a context populated with the data of the given headers. * @@ -143,11 +154,16 @@ public static function fromSentryTrace(string $header): self * @param string $baggageHeader The baggage header from an incoming request */ public static function fromHeaders(string $sentryTraceHeader, string $baggageHeader): self + { + return self::parseTraceAndBaggage($sentryTraceHeader, $baggageHeader); + } + + private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self { $context = new self(); $hasSentryTrace = false; - if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTraceHeader, $matches)) { + if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) { if (!empty($matches['trace_id'])) { $context->traceId = new TraceId($matches['trace_id']); $hasSentryTrace = true; @@ -164,7 +180,7 @@ public static function fromHeaders(string $sentryTraceHeader, string $baggageHea } } - $samplingContext = DynamicSamplingContext::fromHeader($baggageHeader); + $samplingContext = DynamicSamplingContext::fromHeader($baggage); if ($hasSentryTrace && !$samplingContext->hasEntries()) { // The request comes from an old SDK which does not support Dynamic Sampling. diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index 8fdea8a0b..1899ee360 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -90,7 +90,7 @@ public function fromSentryTraceDataProvider(): iterable } /** - * @dataProvider fromHeadersDataProvider + * @dataProvider tracingDataProvider */ public function testFromHeaders(string $sentryTraceHeader, string $baggageHeader, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedParentSampled, ?string $expectedDynamicSamplingContextClass, ?bool $expectedDynamicSamplingContextFrozen) { @@ -103,7 +103,21 @@ public function testFromHeaders(string $sentryTraceHeader, string $baggageHeader $this->assertSame($expectedDynamicSamplingContextFrozen, $spanContext->getMetadata()->getDynamicSamplingContext()->isFrozen()); } - public function fromHeadersDataProvider(): iterable + /** + * @dataProvider tracingDataProvider + */ + public function testFromEnvironment(string $sentryTrace, string $baggage, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedParentSampled, ?string $expectedDynamicSamplingContextClass, ?bool $expectedDynamicSamplingContextFrozen) + { + $spanContext = TransactionContext::fromEnvironment($sentryTrace, $baggage); + + $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); + $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); + $this->assertSame($expectedParentSampled, $spanContext->getParentSampled()); + $this->assertInstanceOf($expectedDynamicSamplingContextClass, $spanContext->getMetadata()->getDynamicSamplingContext()); + $this->assertSame($expectedDynamicSamplingContextFrozen, $spanContext->getMetadata()->getDynamicSamplingContext()->isFrozen()); + } + + public function tracingDataProvider(): iterable { yield [ '0', From 4e6fae33edf470d096d820556f5be1fcecb6020e Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 3 May 2023 11:42:26 +0200 Subject: [PATCH 0856/1161] ref: Attach the DSC to error events (#1522) --- src/State/Scope.php | 8 +++++++- src/Tracing/DynamicSamplingContext.php | 6 +++++- tests/State/ScopeTest.php | 20 +++++++++++++++++--- tests/Tracing/DynamicSamplingContextTest.php | 1 + 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/State/Scope.php b/src/State/Scope.php index d30257cc0..b4e576f8f 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -360,9 +360,15 @@ public function applyToEvent(Event $event, ?EventHint $hint = null): ?Event $event->setUser($user); } - // We do this here to also apply the trace context to errors if there is a Span on the Scope + // Apply the trace context to errors if there is a Span on the Scope if (null !== $this->span) { $event->setContext('trace', $this->span->getTraceContext()); + + // Apply the dynamic sampling context to errors if there is a Transaction on the Scope + $transaction = $this->span->getTransaction(); + if (null !== $transaction) { + $event->setSdkMetadata('dynamic_sampling_context', $transaction->getDynamicSamplingContext()); + } } foreach (array_merge($this->contexts, $event->getContexts()) as $name => $data) { diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index 179140998..b6e1ee2bc 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -148,7 +148,11 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h { $samplingContext = new self(); $samplingContext->set('trace_id', (string) $transaction->getTraceId()); - $samplingContext->set('sample_rate', (string) $transaction->getMetaData()->getSamplingRate()); + + $sampleRate = $transaction->getMetaData()->getSamplingRate(); + if (null !== $sampleRate) { + $samplingContext->set('sample_rate', (string) $sampleRate); + } // Only include the transaction name if it has good quality if ($transaction->getMetadata()->getSource() !== TransactionSource::url()) { diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 46d8b7bc6..fdd913d7f 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -10,9 +10,12 @@ use Sentry\EventHint; use Sentry\Severity; use Sentry\State\Scope; -use Sentry\Tracing\Span; +use Sentry\Tracing\DynamicSamplingContext; +use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; +use Sentry\Tracing\Transaction; +use Sentry\Tracing\TransactionContext; use Sentry\UserDataBag; final class ScopeTest extends TestCase @@ -357,9 +360,13 @@ public function testApplyToEvent(): void $event = Event::createEvent(); $event->setContext('foocontext', ['foo' => 'foo', 'bar' => 'bar']); - $span = new Span(); + $transactionContext = new TransactionContext('foo'); + $transaction = new Transaction($transactionContext); + $transaction->setSpanId(new SpanId('8c2df92a922b4efe')); + $transaction->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + + $span = $transaction->startChild(new SpanContext()); $span->setSpanId(new SpanId('566e3688a61d4bc8')); - $span->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); $scope = new Scope(); $scope->setLevel(Severity::warning()); @@ -387,10 +394,17 @@ public function testApplyToEvent(): void 'trace' => [ 'span_id' => '566e3688a61d4bc8', 'trace_id' => '566e3688a61d4bc888951642d6f14a19', + 'parent_span_id' => '8c2df92a922b4efe', ], 'barcontext' => [ 'bar' => 'foo', ], ], $event->getContexts()); + + $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); + + $this->assertInstanceOf(DynamicSamplingContext::class, $dynamicSamplingContext); + $this->assertSame('foo', $dynamicSamplingContext->get('transaction')); + $this->assertSame('566e3688a61d4bc888951642d6f14a19', $dynamicSamplingContext->get('trace_id')); } } diff --git a/tests/Tracing/DynamicSamplingContextTest.php b/tests/Tracing/DynamicSamplingContextTest.php index c0d520397..24f59d7c1 100644 --- a/tests/Tracing/DynamicSamplingContextTest.php +++ b/tests/Tracing/DynamicSamplingContextTest.php @@ -100,6 +100,7 @@ public function testFromTransaction(): void $transactionContext->setName('foo'); $transaction = new Transaction($transactionContext, $hub); + $transaction->getMetadata()->setSamplingRate(1.0); $samplingContext = DynamicSamplingContext::fromTransaction($transaction, $hub); From 16ab47e7ac6e11b862c93613aee1c8220a77ad29 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 3 May 2023 12:20:45 +0200 Subject: [PATCH 0857/1161] Prepare 3.18.0 (#1523) --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 688ec7bca..95f7a28da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # CHANGELOG +## 3.18.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.0. + +### Features + +- Add `TransactionContext::fromEnvironment` [(#1519)](https://github.com/getsentry/sentry-php/pull/1519) + +### Misc + +- Sent all events to the `/envelope` endpoint if tracing is enabled [(#1518)](https://github.com/getsentry/sentry-php/pull/1518) +- Attach the Dynamic Sampling Context to error events [(#1522)](https://github.com/getsentry/sentry-php/pull/1522) + ## 3.17.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.17.0. From 2041f2ed3f82a55eaca31079e106c8b17c89dbda Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 3 May 2023 10:21:22 +0000 Subject: [PATCH 0858/1161] release: 3.18.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index a130a38bf..e50d28ca0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.17.0'; + public const SDK_VERSION = '3.18.0'; /** * @var Options The client options From 695d3d1179d333f32a2b9df97c6e860d3b88f190 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 16 May 2023 11:04:59 +0200 Subject: [PATCH 0859/1161] ref: Ignore empty context values (#1529) --- src/Event.php | 6 ++++-- src/State/Scope.php | 6 ++++-- tests/State/ScopeTest.php | 7 +++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Event.php b/src/Event.php index 41d93ef13..215754309 100644 --- a/src/Event.php +++ b/src/Event.php @@ -469,14 +469,16 @@ public function getContexts(): array } /** - * Sets the data of the context with the given name. + * Sets data to the context by a given name. * * @param string $name The name that uniquely identifies the context * @param array $data The data of the context */ public function setContext(string $name, array $data): self { - $this->contexts[$name] = $data; + if (!empty($data)) { + $this->contexts[$name] = $data; + } return $this; } diff --git a/src/State/Scope.php b/src/State/Scope.php index b4e576f8f..3f2be09f6 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -118,7 +118,7 @@ public function removeTag(string $key): self } /** - * Sets context data with the given name. + * Sets data to the context by a given name. * * @param string $name The name that uniquely identifies the context * @param array $value The value @@ -127,7 +127,9 @@ public function removeTag(string $key): self */ public function setContext(string $name, array $value): self { - $this->contexts[$name] = $value; + if (!empty($value)) { + $this->contexts[$name] = $value; + } return $this; } diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index fdd913d7f..5274764bf 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -92,6 +92,13 @@ public function testSetAndRemoveContext(): void $this->assertNotNull($event); $this->assertSame([], $event->getContexts()); + + $scope->setContext('foo', []); + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertNotNull($event); + $this->assertSame([], $event->getContexts()); } public function testSetExtra(): void From b545881ddfcbc2e52368142d8c9f06ff13e2999d Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 16 May 2023 11:11:32 +0200 Subject: [PATCH 0860/1161] fix: Guard against empty profiles (#1528) --- src/Profiling/Profiler.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Profiling/Profiler.php b/src/Profiling/Profiler.php index b2c714a2e..463112fef 100644 --- a/src/Profiling/Profiler.php +++ b/src/Profiling/Profiler.php @@ -52,8 +52,12 @@ public function stop(): void } } - public function getProfile(): Profile + public function getProfile(): ?Profile { + if (null === $this->profiler) { + return null; + } + return $this->profile; } From a0c41826c6174934130eff8a852402a872d93419 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 16 May 2023 11:33:49 +0200 Subject: [PATCH 0861/1161] Prepare 3.18.1 (#1530) --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f7a28da..d257e4125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG +## 3.18.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.1. + +### Bug Fixes + +- Guard against empty profiles [(#1528)](https://github.com/getsentry/sentry-php/pull/1528) +- Ignore empty context values [(#1529)](https://github.com/getsentry/sentry-php/pull/1529) + ## 3.18.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.0. From b10161a08f51df0d12adda0b95cf8412fff3ce47 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 16 May 2023 09:34:42 +0000 Subject: [PATCH 0862/1161] release: 3.18.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index e50d28ca0..9104918ed 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.18.0'; + public const SDK_VERSION = '3.18.1'; /** * @var Options The client options From 122d70658e938e415aa506f70e52af67444b3b0f Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 17 May 2023 12:56:17 +0200 Subject: [PATCH 0863/1161] fix: Require php-http/message-factory (#1534) --- CHANGELOG.md | 8 ++++++++ composer.json | 1 + 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d257e4125..a229fa314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 3.18.2 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.2. + +### Bug Fixes + +- Require php-http/message-factory [(#1534)](https://github.com/getsentry/sentry-php/pull/1534) + ## 3.18.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.1. diff --git a/composer.json b/composer.json index 4ff3d68ec..702809b79 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "php-http/discovery": "^1.15", "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", + "php-http/message-factory": "^1.1", "psr/http-factory": "^1.0", "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", From c634615c09a69bfdc3bb5f6ffeee78db3711167c Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 17 May 2023 10:56:54 +0000 Subject: [PATCH 0864/1161] release: 3.18.2 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 9104918ed..0ffed7b06 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.18.1'; + public const SDK_VERSION = '3.18.2'; /** * @var Options The client options From aad527a935ab705d4f6c5a657f8fe0e67e0cf8bf Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 22 May 2023 17:42:33 +0200 Subject: [PATCH 0865/1161] chore(deps): support guzzlehttp/promises v2 (#1536) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 702809b79..bb119e79b 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "php": "^7.2|^8.0", "ext-json": "*", "ext-mbstring": "*", - "guzzlehttp/promises": "^1.4", + "guzzlehttp/promises": "^1.5.3|^2.0", "jean85/pretty-package-versions": "^1.5|^2.0.4", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", From 81206a4ec88d6d0988d89e1f889cc3918f4f51b6 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 23 May 2023 13:46:41 +0200 Subject: [PATCH 0866/1161] Prepare 3.19.0 (#1540) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a229fa314..ac3a3664a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 3.19.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.19.0. + +### Misc + +- Add support for `guzzlehttp/promises` v2 [(#1536)](https://github.com/getsentry/sentry-php/pull/1536) + ## 3.18.2 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.2. From 83df96628eaf611a7abdb9dc4c04b477101dd3e6 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 23 May 2023 11:47:12 +0000 Subject: [PATCH 0867/1161] release: 3.19.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 0ffed7b06..2d7e70618 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.18.2'; + public const SDK_VERSION = '3.19.0'; /** * @var Options The client options From 6ce65abab960ddbd7b22355afe3088261fab4775 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 25 May 2023 08:18:27 +0200 Subject: [PATCH 0868/1161] Use HTTP/1.1 when compression is enabled (#1542) Co-authored-by: Michael Hoffmann --- CHANGELOG.md | 8 ++++++++ phpstan-baseline.neon | 5 +++++ psalm-baseline.xml | 3 ++- src/HttpClient/HttpClientFactory.php | 13 ++++++++++--- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac3a3664a..898294f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 3.19.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.19.1. + +### Bug Fixes + +- Use HTTP/1.1 when compression is enabled [(#1542)](https://github.com/getsentry/sentry-php/pull/1542) + ## 3.19.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.19.0. diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a4f7cfdc8..8986ef622 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -55,6 +55,11 @@ parameters: count: 1 path: src/HttpClient/HttpClientFactory.php + - + message: "#^Call to static method createWithConfig\\(\\) on an unknown class Http\\\\Adapter\\\\Guzzle6\\\\Client\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + - message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$responseFactory\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 2705d0520..e8bdabe84 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -9,7 +9,8 @@ - + + Guzzle6HttpClient GuzzleHttpClientOptions GuzzleHttpClientOptions GuzzleHttpClientOptions diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 7e9cea0f7..ba12123a4 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -5,7 +5,8 @@ namespace Sentry\HttpClient; use GuzzleHttp\RequestOptions as GuzzleHttpClientOptions; -use Http\Adapter\Guzzle6\Client as GuzzleHttpClient; +use Http\Adapter\Guzzle6\Client as Guzzle6HttpClient; +use Http\Adapter\Guzzle7\Client as Guzzle7HttpClient; use Http\Client\Common\Plugin\AuthenticationPlugin; use Http\Client\Common\Plugin\DecoderPlugin; use Http\Client\Common\Plugin\ErrorPlugin; @@ -114,6 +115,7 @@ private function resolveClient(Options $options) $symfonyConfig = [ 'timeout' => $options->getHttpConnectTimeout(), 'max_duration' => $options->getHttpTimeout(), + 'http_version' => $options->isCompressionEnabled() ? '1.1' : null, ]; if (null !== $options->getHttpProxy()) { @@ -123,7 +125,7 @@ private function resolveClient(Options $options) return new SymfonyHttplugClient(SymfonyHttpClient::create($symfonyConfig)); } - if (class_exists(GuzzleHttpClient::class)) { + if (class_exists(Guzzle7HttpClient::class) || class_exists(Guzzle6HttpClient::class)) { $guzzleConfig = [ GuzzleHttpClientOptions::TIMEOUT => $options->getHttpTimeout(), GuzzleHttpClientOptions::CONNECT_TIMEOUT => $options->getHttpConnectTimeout(), @@ -133,12 +135,17 @@ private function resolveClient(Options $options) $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); } - return GuzzleHttpClient::createWithConfig($guzzleConfig); + if (class_exists(Guzzle7HttpClient::class)) { + return Guzzle7HttpClient::createWithConfig($guzzleConfig); + } + + return Guzzle6HttpClient::createWithConfig($guzzleConfig); } if (class_exists(CurlHttpClient::class)) { $curlConfig = [ \CURLOPT_TIMEOUT => $options->getHttpTimeout(), + \CURLOPT_HTTP_VERSION => $options->isCompressionEnabled() ? \CURL_HTTP_VERSION_1_1 : \CURL_HTTP_VERSION_NONE, \CURLOPT_CONNECTTIMEOUT => $options->getHttpConnectTimeout(), ]; From dd1057fb37d4484ebb2d1bc9b05fa5969c078436 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 25 May 2023 06:19:09 +0000 Subject: [PATCH 0869/1161] release: 3.19.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 2d7e70618..b5b46caba 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.19.0'; + public const SDK_VERSION = '3.19.1'; /** * @var Options The client options From 6b1728902cf985c6a6c9c37860eb75c0916d1659 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 12 Jun 2023 15:41:54 +0200 Subject: [PATCH 0870/1161] feat: Tracing without Performance (#1516) --- phpstan-baseline.neon | 6 +- src/Client.php | 2 +- src/Serializer/PayloadSerializer.php | 26 ++- src/State/Scope.php | 43 ++++- src/Tracing/DynamicSamplingContext.php | 31 ++++ src/Tracing/GuzzleTracingMiddleware.php | 7 + src/Tracing/PropagationContext.php | 182 +++++++++++++++++++ src/functions.php | 88 +++++++++ tests/FunctionsTest.php | 143 +++++++++++++++ tests/Serializer/PayloadSerializerTest.php | 17 +- tests/State/HubTest.php | 43 ++++- tests/State/ScopeTest.php | 27 ++- tests/Tracing/DynamicSamplingContextTest.php | 32 ++++ tests/Tracing/PropagationContextTest.php | 168 +++++++++++++++++ 14 files changed, 793 insertions(+), 22 deletions(-) create mode 100644 src/Tracing/PropagationContext.php create mode 100644 tests/Tracing/PropagationContextTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8986ef622..778dc4eb9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -96,17 +96,17 @@ parameters: path: src/Monolog/BreadcrumbHandler.php - - message: "#^Method Sentry\\\\Options\\:\\:getBeforeBreadcrumbCallback\\(\\) should return callable\\(Sentry\\\\Breadcrumb\\)\\: Sentry\\\\Breadcrumb\\|null but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getBeforeBreadcrumbCallback\\(\\) should return callable\\(Sentry\\\\Breadcrumb\\)\\: \\(Sentry\\\\Breadcrumb\\|null\\) but returns mixed\\.$#" count: 1 path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: Sentry\\\\Event\\|null but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: \\(Sentry\\\\Event\\|null\\) but returns mixed\\.$#" count: 1 path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendTransactionCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: Sentry\\\\Event\\|null but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendTransactionCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: \\(Sentry\\\\Event\\|null\\) but returns mixed\\.$#" count: 1 path: src/Options.php diff --git a/src/Client.php b/src/Client.php index b5b46caba..1a541ebcd 100644 --- a/src/Client.php +++ b/src/Client.php @@ -289,7 +289,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco if (null !== $scope) { $beforeEventProcessors = $event; - $event = $scope->applyToEvent($event, $hint); + $event = $scope->applyToEvent($event, $hint, $this->options); if (null === $event) { $this->logger->info( diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index c081b5cd5..25ed8b299 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -85,6 +85,10 @@ private function serializeAsCheckInEvent(Event $event): string 'release' => $checkIn->getRelease(), 'environment' => $checkIn->getEnvironment(), ]; + + if (!empty($event->getContexts()['trace'])) { + $result['contexts']['trace'] = $event->getContexts()['trace']; + } } return JSON::encode($result); @@ -212,13 +216,33 @@ public function toArray(Event $event): array if (EventType::transaction() === $event->getType()) { $result['spans'] = array_values(array_map([$this, 'serializeSpan'], $event->getSpans())); - $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); + $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); if ($transactionMetadata instanceof TransactionMetadata) { $result['transaction_info']['source'] = (string) $transactionMetadata->getSource(); } } + /** + * In case of error events, with tracing being disabled, we set the Replay ID + * as a context into the payload. + */ + if ( + EventType::event() === $event->getType() && + !$this->options->isTracingEnabled() + ) { + $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); + if ($dynamicSamplingContext instanceof DynamicSamplingContext) { + $replayId = $dynamicSamplingContext->get('replay_id'); + + if (null !== $replayId) { + $result['contexts']['replay'] = [ + 'replay_id' => $replayId, + ]; + } + } + } + $stacktrace = $event->getStacktrace(); if (null !== $stacktrace) { diff --git a/src/State/Scope.php b/src/State/Scope.php index 3f2be09f6..904b6804b 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -7,7 +7,10 @@ use Sentry\Breadcrumb; use Sentry\Event; use Sentry\EventHint; +use Sentry\Options; use Sentry\Severity; +use Sentry\Tracing\DynamicSamplingContext; +use Sentry\Tracing\PropagationContext; use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\UserDataBag; @@ -18,6 +21,11 @@ */ final class Scope { + /** + * @var PropagationContext + */ + private $propagationContext; + /** * @var Breadcrumb[] The list of breadcrumbs recorded in this scope */ @@ -74,6 +82,11 @@ final class Scope */ private static $globalEventProcessors = []; + public function __construct(PropagationContext $propagationContext = null) + { + $this->propagationContext = $propagationContext ?? PropagationContext::fromDefaults(); + } + /** * Sets a new tag in the tags context. * @@ -330,7 +343,7 @@ public function clear(): self * * @param Event $event The event object that will be enriched with scope data */ - public function applyToEvent(Event $event, ?EventHint $hint = null): ?Event + public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $options = null): ?Event { $event->setFingerprint(array_merge($event->getFingerprint(), $this->fingerprint)); @@ -362,7 +375,10 @@ public function applyToEvent(Event $event, ?EventHint $hint = null): ?Event $event->setUser($user); } - // Apply the trace context to errors if there is a Span on the Scope + /** + * Apply the trace context to errors if there is a Span on the Scope. + * Else fallback to the propagation context. + */ if (null !== $this->span) { $event->setContext('trace', $this->span->getTraceContext()); @@ -371,6 +387,14 @@ public function applyToEvent(Event $event, ?EventHint $hint = null): ?Event if (null !== $transaction) { $event->setSdkMetadata('dynamic_sampling_context', $transaction->getDynamicSamplingContext()); } + } else { + $event->setContext('trace', $this->propagationContext->getTraceContext()); + + $dynamicSamplingContext = $this->propagationContext->getDynamicSamplingContext(); + if (null === $dynamicSamplingContext && null !== $options) { + $dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $this); + } + $event->setSdkMetadata('dynamic_sampling_context', $dynamicSamplingContext); } foreach (array_merge($this->contexts, $event->getContexts()) as $name => $data) { @@ -431,10 +455,25 @@ public function getTransaction(): ?Transaction return null; } + public function getPropagationContext(): PropagationContext + { + return $this->propagationContext; + } + + public function setPropagationContext(PropagationContext $propagationContext): self + { + $this->propagationContext = $propagationContext; + + return $this; + } + public function __clone() { if (null !== $this->user) { $this->user = clone $this->user; } + if (null !== $this->propagationContext) { + $this->propagationContext = clone $this->propagationContext; + } } } diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index b6e1ee2bc..ef166ee3c 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -4,6 +4,7 @@ namespace Sentry\Tracing; +use Sentry\Options; use Sentry\State\HubInterface; use Sentry\State\Scope; @@ -188,6 +189,36 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h return $samplingContext; } + public static function fromOptions(Options $options, Scope $scope): self + { + $samplingContext = new self(); + $samplingContext->set('trace_id', (string) $scope->getPropagationContext()->getTraceId()); + + if (null !== $options->getTracesSampleRate()) { + $samplingContext->set('sample_rate', (string) $options->getTracesSampleRate()); + } + + if (null !== $options->getDsn() && null !== $options->getDsn()->getPublicKey()) { + $samplingContext->set('public_key', $options->getDsn()->getPublicKey()); + } + + if (null !== $options->getRelease()) { + $samplingContext->set('release', $options->getRelease()); + } + + if (null !== $options->getEnvironment()) { + $samplingContext->set('environment', $options->getEnvironment()); + } + + if (null !== $scope->getUser() && null !== $scope->getUser()->getSegment()) { + $samplingContext->set('user_segment', $scope->getUser()->getSegment()); + } + + $samplingContext->freeze(); + + return $samplingContext; + } + /** * Serialize the dsc as a string. */ diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 38d8c8082..3a0650e63 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -12,6 +12,8 @@ use Sentry\Breadcrumb; use Sentry\SentrySdk; use Sentry\State\HubInterface; +use function Sentry\baggage; +use function Sentry\traceparent; /** * This handler traces each outgoing HTTP request by recording performance data. @@ -27,6 +29,11 @@ public static function trace(?HubInterface $hub = null): Closure $span = $hub->getSpan(); if (null === $span) { + // TODO(michi) Decide on trace_propagation_targets handling + $request = $request + ->withHeader('sentry-trace', traceparent()) + ->withHeader('baggage', baggage()); + return $handler($request, $options); } diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php new file mode 100644 index 000000000..486cf9417 --- /dev/null +++ b/src/Tracing/PropagationContext.php @@ -0,0 +1,182 @@ +[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + + /** + * @var TraceId The trace id + */ + private $traceId; + + /** + * @var SpanId The span id + */ + private $spanId; + + /** + * @var SpanId|null The parent span id + */ + private $parentSpanId; + + /** + * @var DynamicSamplingContext|null The dynamic sampling context + */ + private $dynamicSamplingContext; + + private function __construct() + { + } + + public static function fromDefaults(): self + { + $context = new self(); + + $context->traceId = TraceId::generate(); + $context->spanId = SpanId::generate(); + $context->parentSpanId = null; + $context->dynamicSamplingContext = null; + + return $context; + } + + public static function fromHeaders(string $sentryTraceHeader, string $baggageHeader): self + { + return self::parseTraceAndBaggage($sentryTraceHeader, $baggageHeader); + } + + public static function fromEnvironment(string $sentryTrace, string $baggage): self + { + return self::parseTraceAndBaggage($sentryTrace, $baggage); + } + + /** + * Returns a string that can be used for the `sentry-trace` header & meta tag. + */ + public function toTraceparent(): string + { + return sprintf('%s-%s', (string) $this->traceId, (string) $this->spanId); + } + + /** + * Returns a string that can be used for the `baggage` header & meta tag. + */ + public function toBaggage(): string + { + if (null === $this->dynamicSamplingContext) { + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + if (null !== $client) { + $options = $client->getOptions(); + + if (null !== $options) { + $hub->configureScope(function (Scope $scope) use ($options) { + $this->dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $scope); + }); + } + } + } + + return (string) $this->dynamicSamplingContext; + } + + /** + * @return array + */ + public function getTraceContext(): array + { + $result = [ + 'trace_id' => (string) $this->traceId, + 'span_id' => (string) $this->spanId, + ]; + + if (null !== $this->parentSpanId) { + $result['parent_span_id'] = (string) $this->parentSpanId; + } + + return $result; + } + + public function getTraceId(): TraceId + { + return $this->traceId; + } + + public function setTraceId(TraceId $traceId): void + { + $this->traceId = $traceId; + } + + public function getParentSpanId(): ?SpanId + { + return $this->parentSpanId; + } + + public function setParentSpanId(?SpanId $parentSpanId): void + { + $this->parentSpanId = $parentSpanId; + } + + public function getSpanId(): SpanId + { + return $this->spanId; + } + + public function setSpanId(SpanId $spanId): void + { + $this->spanId = $spanId; + } + + public function getDynamicSamplingContext(): ?DynamicSamplingContext + { + return $this->dynamicSamplingContext; + } + + public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplingContext): void + { + $this->dynamicSamplingContext = $dynamicSamplingContext; + } + + private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self + { + $context = self::fromDefaults(); + $hasSentryTrace = false; + + if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) { + if (!empty($matches['trace_id'])) { + $context->traceId = new TraceId($matches['trace_id']); + $hasSentryTrace = true; + } + + if (!empty($matches['span_id'])) { + $context->parentSpanId = new SpanId($matches['span_id']); + $hasSentryTrace = true; + } + } + + $samplingContext = DynamicSamplingContext::fromHeader($baggage); + + if ($hasSentryTrace && !$samplingContext->hasEntries()) { + // The request comes from an old SDK which does not support Dynamic Sampling. + // Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries. + $samplingContext->freeze(); + $context->dynamicSamplingContext = $samplingContext; + } + + if ($hasSentryTrace && $samplingContext->hasEntries()) { + // The baggage header contains Dynamic Sampling Context data from an upstream SDK. + // Propagate this Dynamic Sampling Context. + $context->dynamicSamplingContext = $samplingContext; + } + + return $context; + } +} diff --git a/src/functions.php b/src/functions.php index b24a45c22..20707c91b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -5,6 +5,7 @@ namespace Sentry; use Sentry\State\Scope; +use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; @@ -167,3 +168,90 @@ function trace(callable $trace, SpanContext $context) } }); } + +/** + * Creates the current traceparent string, to be used as a HTTP header value + * or HTML meta tag value. + * This function is context aware, as in it either returns the traceparent based + * on the current span, or the scope's propagation context. + */ +function traceparent(): string +{ + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + if (null !== $client) { + $options = $client->getOptions(); + + if (null !== $options && $options->getEnableTracing()) { + $span = SentrySdk::getCurrentHub()->getSpan(); + if (null !== $span) { + return $span->toTraceparent(); + } + } + } + + $traceParent = ''; + $hub->configureScope(function (Scope $scope) use (&$traceParent) { + $traceParent = $scope->getPropagationContext()->toTraceparent(); + }); + + return $traceParent; +} + +/** + * Creates the baggage content string, to be used as a HTTP header value + * or HTML meta tag value. + * This function is context aware, as in it either returns the baggage based + * on the current span or the scope's propagation context. + */ +function baggage(): string +{ + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + if (null !== $client) { + $options = $client->getOptions(); + + if (null !== $options && $options->getEnableTracing()) { + $span = SentrySdk::getCurrentHub()->getSpan(); + if (null !== $span) { + return $span->toBaggage(); + } + } + } + + $baggage = ''; + $hub->configureScope(function (Scope $scope) use (&$baggage) { + $baggage = $scope->getPropagationContext()->toBaggage(); + }); + + return $baggage; +} + +/** + * Continue a trace based on HTTP header values. + * If the SDK is configured with enabled tracing, + * this function returns a populated TransactionContext. + * In any other cases, it populates the propagation context on the scope. + * + * @return mixed + */ +function continueTrace(string $sentryTrace, string $baggage) +{ + $hub = SentrySdk::getCurrentHub(); + $hub->configureScope(function (Scope $scope) use ($sentryTrace, $baggage) { + $propagationContext = PropagationContext::fromHeaders($sentryTrace, $baggage); + $scope->setPropagationContext($propagationContext); + }); + + $client = $hub->getClient(); + + if (null !== $client) { + $options = $client->getOptions(); + + if (null !== $options && $options->getEnableTracing()) { + return TransactionContext::fromHeaders($sentryTrace, $baggage); + } + } +} diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 0bf3ca83e..60a364a94 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -18,18 +18,25 @@ use Sentry\State\Hub; use Sentry\State\HubInterface; use Sentry\State\Scope; +use Sentry\Tracing\PropagationContext; +use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; +use Sentry\Tracing\SpanId; +use Sentry\Tracing\TraceId; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use function Sentry\addBreadcrumb; +use function Sentry\baggage; use function Sentry\captureEvent; use function Sentry\captureException; use function Sentry\captureLastError; use function Sentry\captureMessage; use function Sentry\configureScope; +use function Sentry\continueTrace; use function Sentry\init; use function Sentry\startTransaction; use function Sentry\trace; +use function Sentry\traceparent; use function Sentry\withScope; final class FunctionsTest extends TestCase @@ -273,4 +280,140 @@ public function testTraceCorrectlyReplacesAndRestoresCurrentSpan(): void $this->assertSame($transaction, $hub->getSpan()); } } + + public function testTraceparentWithTracingDisabled(): void + { + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); + + $scope = new Scope($propagationContext); + + $hub = new Hub(null, $scope); + + SentrySdk::setCurrentHub($hub); + + $traceParent = traceparent(); + + $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); + } + + public function testTraceparentWithTracingEnabled(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'enable_tracing' => true, + ])); + + $hub = new Hub($client); + + SentrySdk::setCurrentHub($hub); + + $spanContext = new SpanContext(); + $spanContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $spanContext->setSpanId(new SpanId('566e3688a61d4bc8')); + + $span = new Span($spanContext); + + $hub->setSpan($span); + + $traceParent = traceparent(); + + $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); + } + + public function testBaggageWithTracingDisabled(): void + { + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + + $scope = new Scope($propagationContext); + + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeastOnce()) + ->method('getOptions') + ->willReturn(new Options([ + 'release' => '1.0.0', + 'environment' => 'development', + ])); + + $hub = new Hub($client, $scope); + + SentrySdk::setCurrentHub($hub); + + $baggage = baggage(); + + $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-release=1.0.0,sentry-environment=development', $baggage); + } + + public function testBaggageWithTracingEnabled(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeastOnce()) + ->method('getOptions') + ->willReturn(new Options([ + 'enable_tracing' => true, + 'release' => '1.0.0', + 'environment' => 'development', + ])); + + $hub = new Hub($client); + + SentrySdk::setCurrentHub($hub); + + $transactionContext = new TransactionContext(); + $transactionContext->setName('Test'); + $transactionContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + + $transaction = startTransaction($transactionContext); + + $spanContext = new SpanContext(); + + $span = $transaction->startChild($spanContext); + + $hub->setSpan($span); + + $baggage = baggage(); + + $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1,sentry-transaction=Test,sentry-release=1.0.0,sentry-environment=development', $baggage); + } + + public function testContinueTrace(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeastOnce()) + ->method('getOptions') + ->willReturn(new Options([ + 'enable_tracing' => true, + 'release' => '1.0.0', + 'environment' => 'development', + ])); + + $hub = new Hub($client); + + SentrySdk::setCurrentHub($hub); + + $transactionContext = continueTrace( + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + 'sentry-trace_id=566e3688a61d4bc888951642d6f14a19' + ); + + $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $transactionContext->getTraceId()); + $this->assertSame('566e3688a61d4bc8', (string) $transactionContext->getParentSpanId()); + $this->assertTrue($transactionContext->getParentSampled()); + + configureScope(function (Scope $scope): void { + $propagationContext = $scope->getPropagationContext(); + + $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $propagationContext->getTraceId()); + $this->assertSame('566e3688a61d4bc8', (string) $propagationContext->getParentSpanId()); + + $dynamicSamplingContext = $propagationContext->getDynamicSamplingContext(); + + $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $dynamicSamplingContext->get('trace_id')); + $this->assertTrue($dynamicSamplingContext->isFrozen()); + }); + } } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 5f2523ef7..29f591bfa 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -120,6 +120,8 @@ public function serializeAsJsonDataProvider(): iterable new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_NAVIGATION, 'log', null, ['from' => '/login', 'to' => '/dashboard']), ]); + $event->setSdkMetadata('dynamic_sampling_context', DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-replay_id=12312012123120121231201212312012')); + $event->setUser(UserDataBag::createFromArray([ 'id' => 'unique_id', 'username' => 'my_user', @@ -255,6 +257,9 @@ public function serializeAsJsonDataProvider(): iterable "type": "runtime", "name": "Electron", "version": "4.0" + }, + "replay": { + "replay_id": "12312012123120121231201212312012" } }, "breadcrumbs": { @@ -566,13 +571,17 @@ public function serializeAsJsonDataProvider(): iterable $event = Event::createCheckIn(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setCheckIn($checkIn); + $event->setContext('trace', [ + 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', + 'span_id' => '5dd538dc297544cc', + ]); yield [ $event, <<setCheckIn($checkIn); + $event->setContext('trace', [ + 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', + 'span_id' => '5dd538dc297544cc', + ]); yield [ $event, <<configureScope(function (Scope $scope) use ($propagationContext): void { + $scope->setPropagationContext($propagationContext); + }); + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -248,6 +253,8 @@ public function testCaptureMessage(array $functionCallArgs, array $expectedFunct public function captureMessageDataProvider(): \Generator { + $propagationContext = PropagationContext::fromDefaults(); + yield [ [ 'foo', @@ -256,8 +263,9 @@ public function captureMessageDataProvider(): \Generator [ 'foo', Severity::debug(), - new Scope(), + new Scope($propagationContext), ], + $propagationContext, ]; yield [ @@ -269,20 +277,25 @@ public function captureMessageDataProvider(): \Generator [ 'foo', Severity::debug(), - new Scope(), + new Scope($propagationContext), new EventHint(), ], + $propagationContext, ]; } /** * @dataProvider captureExceptionDataProvider */ - public function testCaptureException(array $functionCallArgs, array $expectedFunctionCallArgs): void + public function testCaptureException(array $functionCallArgs, array $expectedFunctionCallArgs, PropagationContext $propagationContext): void { $eventId = EventId::generate(); $hub = new Hub(); + $hub->configureScope(function (Scope $scope) use ($propagationContext): void { + $scope->setPropagationContext($propagationContext); + }); + $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureException') @@ -298,15 +311,18 @@ public function testCaptureException(array $functionCallArgs, array $expectedFun public function captureExceptionDataProvider(): \Generator { + $propagationContext = PropagationContext::fromDefaults(); + yield [ [ new \Exception('foo'), ], [ new \Exception('foo'), - new Scope(), + new Scope($propagationContext), null, ], + $propagationContext, ]; yield [ @@ -316,20 +332,25 @@ public function captureExceptionDataProvider(): \Generator ], [ new \Exception('foo'), - new Scope(), + new Scope($propagationContext), new EventHint(), ], + $propagationContext, ]; } /** * @dataProvider captureLastErrorDataProvider */ - public function testCaptureLastError(array $functionCallArgs, array $expectedFunctionCallArgs): void + public function testCaptureLastError(array $functionCallArgs, array $expectedFunctionCallArgs, PropagationContext $propagationContext): void { $eventId = EventId::generate(); $hub = new Hub(); + $hub->configureScope(function (Scope $scope) use ($propagationContext): void { + $scope->setPropagationContext($propagationContext); + }); + /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -346,12 +367,15 @@ public function testCaptureLastError(array $functionCallArgs, array $expectedFun public function captureLastErrorDataProvider(): \Generator { + $propagationContext = PropagationContext::fromDefaults(); + yield [ [], [ - new Scope(), + new Scope($propagationContext), null, ], + $propagationContext, ]; yield [ @@ -359,9 +383,10 @@ public function captureLastErrorDataProvider(): \Generator new EventHint(), ], [ - new Scope(), + new Scope($propagationContext), new EventHint(), ], + $propagationContext, ]; } diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index 5274764bf..98029dc68 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -11,6 +11,7 @@ use Sentry\Severity; use Sentry\State\Scope; use Sentry\Tracing\DynamicSamplingContext; +use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; @@ -78,27 +79,45 @@ public function testRemoveTag(): void public function testSetAndRemoveContext(): void { - $scope = new Scope(); + $propgationContext = PropagationContext::fromDefaults(); + + $scope = new Scope($propgationContext); $scope->setContext('foo', ['foo' => 'bar']); $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); - $this->assertSame(['foo' => ['foo' => 'bar']], $event->getContexts()); + $this->assertSame([ + 'trace' => [ + 'trace_id' => (string) $propgationContext->getTraceId(), + 'span_id' => (string) $propgationContext->getSpanId(), + ], + 'foo' => ['foo' => 'bar'], + ], $event->getContexts()); $scope->removeContext('foo'); $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); - $this->assertSame([], $event->getContexts()); + $this->assertSame([ + 'trace' => [ + 'trace_id' => (string) $propgationContext->getTraceId(), + 'span_id' => (string) $propgationContext->getSpanId(), + ], + ], $event->getContexts()); $scope->setContext('foo', []); $event = $scope->applyToEvent(Event::createEvent()); $this->assertNotNull($event); - $this->assertSame([], $event->getContexts()); + $this->assertSame([ + 'trace' => [ + 'trace_id' => (string) $propgationContext->getTraceId(), + 'span_id' => (string) $propgationContext->getSpanId(), + ], + ], $event->getContexts()); } public function testSetExtra(): void diff --git a/tests/Tracing/DynamicSamplingContextTest.php b/tests/Tracing/DynamicSamplingContextTest.php index 24f59d7c1..d578e0f29 100644 --- a/tests/Tracing/DynamicSamplingContextTest.php +++ b/tests/Tracing/DynamicSamplingContextTest.php @@ -10,6 +10,8 @@ use Sentry\State\Hub; use Sentry\State\Scope; use Sentry\Tracing\DynamicSamplingContext; +use Sentry\Tracing\PropagationContext; +use Sentry\Tracing\TraceId; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; @@ -129,6 +131,36 @@ public function testFromTransactionSourceUrl(): void $this->assertNull($samplingContext->get('transaction')); } + public function testFromOptions(): void + { + $options = new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + 'release' => '1.0.0', + 'environment' => 'test', + 'traces_sample_rate' => 0.5, + ]); + + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); + + $user = new UserDataBag(); + $user->setSegment('my_segment'); + + $scope = new Scope(); + $scope->setUser($user); + $scope->setPropagationContext($propagationContext); + + $samplingContext = DynamicSamplingContext::fromOptions($options, $scope); + + $this->assertSame('21160e9b836d479f81611368b2aa3d2c', $samplingContext->get('trace_id')); + $this->assertSame('0.5', $samplingContext->get('sample_rate')); + $this->assertSame('public', $samplingContext->get('public_key')); + $this->assertSame('1.0.0', $samplingContext->get('release')); + $this->assertSame('test', $samplingContext->get('environment')); + $this->assertSame('my_segment', $samplingContext->get('user_segment')); + $this->assertTrue($samplingContext->isFrozen()); + } + /** * @dataProvider getEntriesDataProvider */ diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php new file mode 100644 index 000000000..431ac4238 --- /dev/null +++ b/tests/Tracing/PropagationContextTest.php @@ -0,0 +1,168 @@ +assertInstanceOf(TraceId::class, $propagationContext->getTraceId()); + $this->assertInstanceOf(SpanId::class, $propagationContext->getSpanId()); + $this->assertNull($propagationContext->getParentSpanId()); + $this->assertNull($propagationContext->getDynamicSamplingContext()); + } + + /** + * @dataProvider tracingDataProvider + */ + public function testFromHeaders(string $sentryTraceHeader, string $baggageHeader, ?TraceId $expectedTraceId, ?SpanId $expectedParentSpanId, ?bool $expectedDynamicSamplingContextFrozen) + { + $propagationContext = PropagationContext::fromHeaders($sentryTraceHeader, $baggageHeader); + + $this->assertInstanceOf(TraceId::class, $propagationContext->getTraceId()); + if (null !== $expectedTraceId) { + $this->assertSame((string) $expectedTraceId, (string) $propagationContext->getTraceId()); + } + + $this->assertInstanceOf(SpanId::class, $propagationContext->getParentSpanId()); + if (null !== $expectedParentSpanId) { + $this->assertSame((string) $expectedParentSpanId, (string) $propagationContext->getParentSpanId()); + } + + $this->assertInstanceOf(SpanId::class, $propagationContext->getSpanId()); + $this->assertInstanceOf(DynamicSamplingContext::class, $propagationContext->getDynamicSamplingContext()); + $this->assertSame($expectedDynamicSamplingContextFrozen, $propagationContext->getDynamicSamplingContext()->isFrozen()); + } + + /** + * @dataProvider tracingDataProvider + */ + public function testFromEnvironment(string $sentryTrace, string $baggage, ?TraceId $expectedTraceId, ?SpanId $expectedParentSpanId, ?bool $expectedDynamicSamplingContextFrozen) + { + $propagationContext = PropagationContext::fromEnvironment($sentryTrace, $baggage); + + $this->assertInstanceOf(TraceId::class, $propagationContext->getTraceId()); + if (null !== $expectedTraceId) { + $this->assertSame((string) $expectedTraceId, (string) $propagationContext->getTraceId()); + } + + $this->assertInstanceOf(SpanId::class, $propagationContext->getParentSpanId()); + if (null !== $expectedParentSpanId) { + $this->assertSame((string) $expectedParentSpanId, (string) $propagationContext->getParentSpanId()); + } + + $this->assertInstanceOf(SpanId::class, $propagationContext->getSpanId()); + $this->assertInstanceOf(DynamicSamplingContext::class, $propagationContext->getDynamicSamplingContext()); + $this->assertSame($expectedDynamicSamplingContextFrozen, $propagationContext->getDynamicSamplingContext()->isFrozen()); + } + + public function tracingDataProvider(): iterable + { + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + '', + new TraceId('566e3688a61d4bc888951642d6f14a19'), + new SpanId('566e3688a61d4bc8'), + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', + new TraceId('566e3688a61d4bc888951642d6f14a19'), + new SpanId('566e3688a61d4bc8'), + true, + ]; + + yield [ + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', + 'foo=bar', + new TraceId('566e3688a61d4bc888951642d6f14a19'), + new SpanId('566e3688a61d4bc8'), + true, + ]; + } + + public function testToTraceparent() + { + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); + + $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toTraceparent()); + } + + public function testToBaggage() + { + $dynamicSamplingContext = DynamicSamplingContext::fromHeader('sentry-trace_id=566e3688a61d4bc888951642d6f14a19'); + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setDynamicSamplingContext($dynamicSamplingContext); + + $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19', $propagationContext->toBaggage()); + } + + public function testGetTraceContext() + { + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); + + $this->assertSame([ + 'trace_id' => (string) $propagationContext->getTraceId(), + 'span_id' => (string) $propagationContext->getSpanId(), + ], $propagationContext->getTraceContext()); + + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); + $propagationContext->setParentSpanId(new SpanId('b01b9f6349558cd1')); + + $this->assertSame([ + 'trace_id' => (string) $propagationContext->getTraceId(), + 'span_id' => (string) $propagationContext->getSpanId(), + 'parent_span_id' => (string) $propagationContext->getParentSpanId(), + ], $propagationContext->getTraceContext()); + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters(string $getterMethod, string $setterMethod, $expectedData): void + { + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->$setterMethod($expectedData); + + $this->assertEquals($expectedData, $propagationContext->$getterMethod()); + } + + public function gettersAndSettersDataProvider(): array + { + $scope = new Scope(); + $options = new Options([ + 'dsn' => 'http://public@example.com/sentry/1', + 'release' => '1.0.0', + 'environment' => 'test', + ]); + + $dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $scope); + + return [ + ['getTraceId', 'setTraceId', new TraceId('566e3688a61d4bc888951642d6f14a19')], + ['getParentSpanId', 'setParentSpanId', new SpanId('566e3688a61d4bc8')], + ['getSpanId', 'setSpanId', new SpanId('8c2df92a922b4efe')], + ['getDynamicSamplingContext', 'setDynamicSamplingContext', $dynamicSamplingContext], + ]; + } +} From d907d27f7586e7f848cb8b9704eed0c20296a95e Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 13 Jun 2023 07:13:56 +0200 Subject: [PATCH 0871/1161] feat: Cron Monitor upsert (#1511) Co-authored-by: Alex Bouma --- src/CheckIn.php | 19 +++- src/MonitorConfig.php | 101 ++++++++++++++++++++ src/MonitorSchedule.php | 106 +++++++++++++++++++++ src/MonitorScheduleUnit.php | 67 +++++++++++++ src/Serializer/PayloadSerializer.php | 4 + tests/CheckInTest.php | 3 + tests/MonitorConfigTest.php | 51 ++++++++++ tests/MonitorScheduleTest.php | 65 +++++++++++++ tests/MonitorScheduleUnitTest.php | 53 +++++++++++ tests/Serializer/PayloadSerializerTest.php | 36 +++++++ 10 files changed, 504 insertions(+), 1 deletion(-) create mode 100644 src/MonitorConfig.php create mode 100644 src/MonitorSchedule.php create mode 100644 src/MonitorScheduleUnit.php create mode 100644 tests/MonitorConfigTest.php create mode 100644 tests/MonitorScheduleTest.php create mode 100644 tests/MonitorScheduleUnitTest.php diff --git a/src/CheckIn.php b/src/CheckIn.php index bcc0f3e28..7cc70e756 100644 --- a/src/CheckIn.php +++ b/src/CheckIn.php @@ -38,6 +38,11 @@ final class CheckIn */ private $duration; + /** + * @var \Sentry\MonitorConfig|null The monitor configuration + */ + private $monitorConfig; + /** * @param int|float|null $duration The duration of the check-in in seconds */ @@ -47,7 +52,8 @@ public function __construct( string $id = null, ?string $release = null, ?string $environment = null, - $duration = null + $duration = null, + ?MonitorConfig $monitorConfig = null ) { $this->setMonitorSlug($monitorSlug); $this->setStatus($status); @@ -56,6 +62,7 @@ public function __construct( $this->setRelease($release ?? ''); $this->setEnvironment($environment ?? Event::DEFAULT_ENVIRONMENT); $this->setDuration($duration); + $this->setMonitorConfig($monitorConfig); } public function getId(): string @@ -123,4 +130,14 @@ public function setDuration($duration): void { $this->duration = $duration; } + + public function getMonitorConfig(): ?MonitorConfig + { + return $this->monitorConfig; + } + + public function setMonitorConfig(?MonitorConfig $monitorConfig): void + { + $this->monitorConfig = $monitorConfig; + } } diff --git a/src/MonitorConfig.php b/src/MonitorConfig.php new file mode 100644 index 000000000..c03c82700 --- /dev/null +++ b/src/MonitorConfig.php @@ -0,0 +1,101 @@ +schedule = $schedule; + $this->checkinMargin = $checkinMargin; + $this->maxRuntime = $maxRuntime; + $this->timezone = $timezone; + } + + public function getSchedule(): MonitorSchedule + { + return $this->schedule; + } + + public function setSchedule(MonitorSchedule $schedule): self + { + $this->schedule = $schedule; + + return $this; + } + + public function getCheckinMargin(): ?int + { + return $this->checkinMargin; + } + + public function setCheckinMargin(?int $checkinMargin): self + { + $this->checkinMargin = $checkinMargin; + + return $this; + } + + public function getMaxRuntime(): ?int + { + return $this->maxRuntime; + } + + public function setMaxRuntime(?int $maxRuntime): self + { + $this->maxRuntime = $maxRuntime; + + return $this; + } + + public function getTimezone(): ?string + { + return $this->timezone; + } + + public function setTimezone(?string $timezone): self + { + $this->timezone = $timezone; + + return $this; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'schedule' => $this->schedule->toArray(), + 'checkin_margin' => $this->checkinMargin, + 'max_runtime' => $this->maxRuntime, + 'timezone' => $this->timezone, + ]; + } +} diff --git a/src/MonitorSchedule.php b/src/MonitorSchedule.php new file mode 100644 index 000000000..f9eee21fe --- /dev/null +++ b/src/MonitorSchedule.php @@ -0,0 +1,106 @@ +type = $type; + $this->value = $value; + $this->unit = $unit; + } + + public static function crontab(string $value): self + { + return new self(self::TYPE_CRONTAB, $value); + } + + public static function interval(int $value, MonitorScheduleUnit $unit): self + { + return new self(self::TYPE_INTERVAL, $value, $unit); + } + + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + /** + * @return string|int + */ + public function getValue() + { + return $this->value; + } + + /** + * @param string|int $value + */ + public function setValue($value): self + { + $this->value = $value; + + return $this; + } + + public function getUnit(): ?MonitorScheduleUnit + { + return $this->unit; + } + + public function setUnit(?MonitorScheduleUnit $unit): self + { + $this->unit = $unit; + + return $this; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'value' => $this->value, + 'unit' => (string) $this->unit, + ]; + } +} diff --git a/src/MonitorScheduleUnit.php b/src/MonitorScheduleUnit.php new file mode 100644 index 000000000..950d95ed8 --- /dev/null +++ b/src/MonitorScheduleUnit.php @@ -0,0 +1,67 @@ + A list of cached enum instances + */ + private static $instances = []; + + private function __construct(string $value) + { + $this->value = $value; + } + + public static function minute(): self + { + return self::getInstance('minute'); + } + + public static function hour(): self + { + return self::getInstance('hour'); + } + + public static function day(): self + { + return self::getInstance('day'); + } + + public static function week(): self + { + return self::getInstance('week'); + } + + public static function month(): self + { + return self::getInstance('month'); + } + + public static function year(): self + { + return self::getInstance('year'); + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 25ed8b299..7aec12421 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -86,6 +86,10 @@ private function serializeAsCheckInEvent(Event $event): string 'environment' => $checkIn->getEnvironment(), ]; + if (null !== $checkIn->getMonitorConfig()) { + $result['monitor_config'] = $checkIn->getMonitorConfig()->toArray(); + } + if (!empty($event->getContexts()['trace'])) { $result['contexts']['trace'] = $event->getContexts()['trace']; } diff --git a/tests/CheckInTest.php b/tests/CheckInTest.php index 4380ebd40..dbe191559 100644 --- a/tests/CheckInTest.php +++ b/tests/CheckInTest.php @@ -7,6 +7,8 @@ use PHPUnit\Framework\TestCase; use Sentry\CheckIn; use Sentry\CheckInStatus; +use Sentry\MonitorConfig; +use Sentry\MonitorSchedule; use Sentry\Util\SentryUid; final class CheckInTest extends TestCase @@ -54,6 +56,7 @@ public function gettersAndSettersDataProvider(): array ['getRelease', 'setRelease', '1.0.0'], ['getEnvironment', 'setEnvironment', 'dev'], ['getDuration', 'setDuration', 10], + ['getMonitorConfig', 'setMonitorConfig', new MonitorConfig(MonitorSchedule::crontab('* * * * *'))], ]; } } diff --git a/tests/MonitorConfigTest.php b/tests/MonitorConfigTest.php new file mode 100644 index 000000000..14ca7cea7 --- /dev/null +++ b/tests/MonitorConfigTest.php @@ -0,0 +1,51 @@ +assertEquals($monitorSchedule, $monitorConfig->getSchedule()); + $this->assertEquals(10, $monitorConfig->getCheckinMargin()); + $this->assertEquals(12, $monitorConfig->getMaxRuntime()); + $this->assertEquals('Europe/Amsterdam', $monitorConfig->getTimezone()); + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters(string $getterMethod, string $setterMethod, $expectedData): void + { + $monitorConfig = new MonitorConfig( + MonitorSchedule::crontab('* * * * *') + ); + $monitorConfig->$setterMethod($expectedData); + + $this->assertEquals($expectedData, $monitorConfig->$getterMethod()); + } + + public function gettersAndSettersDataProvider(): array + { + return [ + ['getSchedule', 'setSchedule', MonitorSchedule::crontab('foo')], + ['getCheckinMargin', 'setCheckinMargin', 10], + ['getMaxRuntime', 'setMaxRuntime', 12], + ['getTimezone', 'setTimezone', 'Europe/Amsterdam'], + ]; + } +} diff --git a/tests/MonitorScheduleTest.php b/tests/MonitorScheduleTest.php new file mode 100644 index 000000000..d81d3da73 --- /dev/null +++ b/tests/MonitorScheduleTest.php @@ -0,0 +1,65 @@ +assertEquals(MonitorSchedule::TYPE_CRONTAB, $monitorSchedule->getType()); + $this->assertEquals('* * * * *', $monitorSchedule->getValue()); + $this->assertNull($monitorSchedule->getUnit()); + } + + public function testConvenienceCrontabConstructor(): void + { + $monitorSchedule = MonitorSchedule::crontab('* * * * *'); + + $this->assertEquals(MonitorSchedule::TYPE_CRONTAB, $monitorSchedule->getType()); + $this->assertEquals('* * * * *', $monitorSchedule->getValue()); + $this->assertNull($monitorSchedule->getUnit()); + } + + public function testConvenienceIntervalConstructor(): void + { + $monitorSchedule = MonitorSchedule::interval(10, MonitorScheduleUnit::minute()); + + $this->assertEquals(MonitorSchedule::TYPE_INTERVAL, $monitorSchedule->getType()); + $this->assertEquals(10, $monitorSchedule->getValue()); + $this->assertEquals(MonitorScheduleUnit::minute(), $monitorSchedule->getUnit()); + } + + /** + * @dataProvider gettersAndSettersDataProvider + */ + public function testGettersAndSetters(string $getterMethod, string $setterMethod, $expectedData): void + { + $monitorSchedule = new MonitorSchedule( + MonitorSchedule::TYPE_CRONTAB, + '* * * * *' + ); + $monitorSchedule->$setterMethod($expectedData); + + $this->assertEquals($expectedData, $monitorSchedule->$getterMethod()); + } + + public function gettersAndSettersDataProvider(): array + { + return [ + ['getType', 'setType', MonitorSchedule::TYPE_INTERVAL], + ['getValue', 'setValue', '* * * * *'], + ['getUnit', 'setUnit', MonitorScheduleUnit::hour()], + ]; + } +} diff --git a/tests/MonitorScheduleUnitTest.php b/tests/MonitorScheduleUnitTest.php new file mode 100644 index 000000000..eec63984a --- /dev/null +++ b/tests/MonitorScheduleUnitTest.php @@ -0,0 +1,53 @@ +assertSame('minute', (string) $monitorScheduleUnit); + } + + public function testHour(): void + { + $monitorScheduleUnit = MonitorScheduleUnit::hour(); + + $this->assertSame('hour', (string) $monitorScheduleUnit); + } + + public function testDay(): void + { + $monitorScheduleUnit = MonitorScheduleUnit::day(); + + $this->assertSame('day', (string) $monitorScheduleUnit); + } + + public function testWeek(): void + { + $monitorScheduleUnit = MonitorScheduleUnit::week(); + + $this->assertSame('week', (string) $monitorScheduleUnit); + } + + public function testMonth(): void + { + $monitorScheduleUnit = MonitorScheduleUnit::month(); + + $this->assertSame('month', (string) $monitorScheduleUnit); + } + + public function testYear(): void + { + $monitorScheduleUnit = MonitorScheduleUnit::year(); + + $this->assertSame('year', (string) $monitorScheduleUnit); + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 29f591bfa..2e5f15402 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -17,6 +17,8 @@ use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; +use Sentry\MonitorConfig; +use Sentry\MonitorSchedule; use Sentry\Options; use Sentry\Profiling\Profile; use Sentry\Serializer\PayloadSerializer; @@ -607,6 +609,40 @@ public function serializeAsJsonDataProvider(): iterable {"event_id":"fc9442f5aef34234bb22b9a615e30ccd","sent_at":"2020-08-18T22:47:15Z","dsn":"http:\/\/public@example.com\/sentry\/1","sdk":{"name":"sentry.php","version":"$sdkVersion"}} {"type":"check_in","content_type":"application\/json"} {"check_in_id":"$checkinId","monitor_slug":"my-monitor","status":"in_progress","duration":null,"release":"","environment":"production","contexts":{"trace":{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"5dd538dc297544cc"}}} +TEXT + , + false, + ]; + + $checkinId = SentryUid::generate(); + $checkIn = new CheckIn( + 'my-monitor', + CheckInStatus::ok(), + $checkinId, + '1.0.0', + 'dev', + 10, + new MonitorConfig( + MonitorSchedule::crontab('0 0 * * *'), + 10, + 12, + 'Europe/Amsterdam' + ) + ); + + $event = Event::createCheckIn(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setCheckIn($checkIn); + $event->setContext('trace', [ + 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', + 'span_id' => '5dd538dc297544cc', + ]); + + yield [ + $event, + << Date: Tue, 20 Jun 2023 13:32:18 +0200 Subject: [PATCH 0872/1161] Attach tracing headers (#1547) --- phpstan-baseline.neon | 2 +- src/Options.php | 6 +- src/Tracing/GuzzleTracingMiddleware.php | 46 ++++--- tests/Tracing/GuzzleTracingMiddlewareTest.php | 122 ++++++++++++++++++ 4 files changed, 157 insertions(+), 19 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 778dc4eb9..39aa31fc7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -231,7 +231,7 @@ parameters: path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getTracePropagationTargets\\(\\) should return array\\ but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getTracePropagationTargets\\(\\) should return array\\\\|null but returns mixed\\.$#" count: 1 path: src/Options.php diff --git a/src/Options.php b/src/Options.php index fd216cb76..0ed534160 100644 --- a/src/Options.php +++ b/src/Options.php @@ -515,9 +515,9 @@ public function setBeforeSendTransactionCallback(callable $callback): void /** * Gets an allow list of trace propagation targets. * - * @return string[] + * @return string[]|null */ - public function getTracePropagationTargets(): array + public function getTracePropagationTargets(): ?array { return $this->options['trace_propagation_targets']; } @@ -964,7 +964,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('before_send_transaction', ['callable']); $resolver->setAllowedTypes('ignore_exceptions', 'string[]'); $resolver->setAllowedTypes('ignore_transactions', 'string[]'); - $resolver->setAllowedTypes('trace_propagation_targets', 'string[]'); + $resolver->setAllowedTypes('trace_propagation_targets', ['null', 'string[]']); $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['null', 'int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 3a0650e63..652c6a19e 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -10,6 +10,7 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Sentry\Breadcrumb; +use Sentry\ClientInterface; use Sentry\SentrySdk; use Sentry\State\HubInterface; use function Sentry\baggage; @@ -29,10 +30,11 @@ public static function trace(?HubInterface $hub = null): Closure $span = $hub->getSpan(); if (null === $span) { - // TODO(michi) Decide on trace_propagation_targets handling - $request = $request - ->withHeader('sentry-trace', traceparent()) - ->withHeader('baggage', baggage()); + if (self::shouldAttachTracingHeaders($client, $request)) { + $request = $request + ->withHeader('sentry-trace', traceparent()) + ->withHeader('baggage', baggage()); + } return $handler($request, $options); } @@ -54,17 +56,10 @@ public static function trace(?HubInterface $hub = null): Closure $childSpan = $span->startChild($spanContext); - $request = $request - ->withHeader('sentry-trace', $childSpan->toTraceparent()); - - // Check if the request destination is allow listed in the trace_propagation_targets option. - if (null !== $client) { - $sdkOptions = $client->getOptions(); - - if (\in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets())) { - $request = $request - ->withHeader('baggage', $childSpan->toBaggage()); - } + if (self::shouldAttachTracingHeaders($client, $request)) { + $request = $request + ->withHeader('sentry-trace', $childSpan->toTraceparent()) + ->withHeader('baggage', $childSpan->toBaggage()); } $handlerPromiseCallback = static function ($responseOrException) use ($hub, $request, $childSpan, $partialUri) { @@ -117,4 +112,25 @@ public static function trace(?HubInterface $hub = null): Closure }; }; } + + private static function shouldAttachTracingHeaders(?ClientInterface $client, RequestInterface $request): bool + { + if (null !== $client) { + $sdkOptions = $client->getOptions(); + + // Check if the request destination is allow listed in the trace_propagation_targets option. + if ( + null !== $sdkOptions->getTracePropagationTargets() && + // Due to BC, we treat an empty array (the default) as all hosts are allow listed + ( + [] === $sdkOptions->getTracePropagationTargets() || + \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()) + ) + ) { + return true; + } + } + + return false; + } } diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 2b6587b1c..5c6d8f608 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -26,6 +26,9 @@ final class GuzzleTracingMiddlewareTest extends TestCase public function testTraceDoesNothingIfSpanIsNotSet(): void { $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options()); $hub = new Hub($client); @@ -56,6 +59,125 @@ public function testTraceDoesNothingIfSpanIsNotSet(): void }); } + /** + * @dataProvider traceHeadersDataProvider + */ + public function testTraceHeaders(Request $request, Options $options, bool $headersShouldBePresent): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn($options); + + $hub = new Hub($client); + + $expectedPromiseResult = new Response(); + + $middleware = GuzzleTracingMiddleware::trace($hub); + $function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface { + if ($headersShouldBePresent) { + $this->assertNotEmpty($request->getHeader('sentry-trace')); + $this->assertNotEmpty($request->getHeader('baggage')); + } else { + $this->assertEmpty($request->getHeader('sentry-trace')); + $this->assertEmpty($request->getHeader('baggage')); + } + + return new FulfilledPromise($expectedPromiseResult); + }); + + /** @var PromiseInterface $promise */ + $function($request, []); + } + + /** + * @dataProvider traceHeadersDataProvider + */ + public function testTraceHeadersWithTransacttion(Request $request, Options $options, bool $headersShouldBePresent): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeast(2)) + ->method('getOptions') + ->willReturn($options); + + $hub = new Hub($client); + + $transaction = $hub->startTransaction(new TransactionContext()); + + $hub->setSpan($transaction); + + $expectedPromiseResult = new Response(); + + $middleware = GuzzleTracingMiddleware::trace($hub); + $function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface { + if ($headersShouldBePresent) { + $this->assertNotEmpty($request->getHeader('sentry-trace')); + $this->assertNotEmpty($request->getHeader('baggage')); + } else { + $this->assertEmpty($request->getHeader('sentry-trace')); + $this->assertEmpty($request->getHeader('baggage')); + } + + return new FulfilledPromise($expectedPromiseResult); + }); + + /** @var PromiseInterface $promise */ + $function($request, []); + + $transaction->finish(); + } + + public function traceHeadersDataProvider(): iterable + { + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 1, + ]), + true, + ]; + + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 1, + 'trace_propagation_targets' => [], + ]), + true, + ]; + + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 1, + 'trace_propagation_targets' => [ + 'www.example.com', + ], + ]), + true, + ]; + + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 1, + 'trace_propagation_targets' => null, + ]), + false, + ]; + + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 1, + 'trace_propagation_targets' => [ + 'example.com', + ], + ]), + false, + ]; + } + /** * @dataProvider traceDataProvider */ From 2e7ff8da6fda7d1642f39a74a95e4dd0d0981f10 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 21 Jun 2023 12:54:41 +0200 Subject: [PATCH 0873/1161] Rename traceparent() & baggage() (#1553) --- src/Tracing/GuzzleTracingMiddleware.php | 8 ++++---- src/functions.php | 4 ++-- tests/FunctionsTest.php | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 652c6a19e..8cc9ac345 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -13,8 +13,8 @@ use Sentry\ClientInterface; use Sentry\SentrySdk; use Sentry\State\HubInterface; -use function Sentry\baggage; -use function Sentry\traceparent; +use function Sentry\getBaggage; +use function Sentry\getTraceparent; /** * This handler traces each outgoing HTTP request by recording performance data. @@ -32,8 +32,8 @@ public static function trace(?HubInterface $hub = null): Closure if (null === $span) { if (self::shouldAttachTracingHeaders($client, $request)) { $request = $request - ->withHeader('sentry-trace', traceparent()) - ->withHeader('baggage', baggage()); + ->withHeader('sentry-trace', getTraceparent()) + ->withHeader('baggage', getBaggage()); } return $handler($request, $options); diff --git a/src/functions.php b/src/functions.php index 20707c91b..60518b777 100644 --- a/src/functions.php +++ b/src/functions.php @@ -175,7 +175,7 @@ function trace(callable $trace, SpanContext $context) * This function is context aware, as in it either returns the traceparent based * on the current span, or the scope's propagation context. */ -function traceparent(): string +function getTraceparent(): string { $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); @@ -205,7 +205,7 @@ function traceparent(): string * This function is context aware, as in it either returns the baggage based * on the current span or the scope's propagation context. */ -function baggage(): string +function getBaggage(): string { $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 60a364a94..2cdebf29f 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -26,17 +26,17 @@ use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use function Sentry\addBreadcrumb; -use function Sentry\baggage; use function Sentry\captureEvent; use function Sentry\captureException; use function Sentry\captureLastError; use function Sentry\captureMessage; use function Sentry\configureScope; use function Sentry\continueTrace; +use function Sentry\getBaggage; +use function Sentry\getTraceparent; use function Sentry\init; use function Sentry\startTransaction; use function Sentry\trace; -use function Sentry\traceparent; use function Sentry\withScope; final class FunctionsTest extends TestCase @@ -293,7 +293,7 @@ public function testTraceparentWithTracingDisabled(): void SentrySdk::setCurrentHub($hub); - $traceParent = traceparent(); + $traceParent = getTraceparent(); $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); } @@ -319,7 +319,7 @@ public function testTraceparentWithTracingEnabled(): void $hub->setSpan($span); - $traceParent = traceparent(); + $traceParent = getTraceparent(); $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); } @@ -343,7 +343,7 @@ public function testBaggageWithTracingDisabled(): void SentrySdk::setCurrentHub($hub); - $baggage = baggage(); + $baggage = getBaggage(); $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-release=1.0.0,sentry-environment=development', $baggage); } @@ -375,7 +375,7 @@ public function testBaggageWithTracingEnabled(): void $hub->setSpan($span); - $baggage = baggage(); + $baggage = getBaggage(); $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1,sentry-transaction=Test,sentry-release=1.0.0,sentry-environment=development', $baggage); } From 83a2e3ca43b6915def20b35df603ec4bb7f56eaf Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 22 Jun 2023 14:13:22 +0200 Subject: [PATCH 0874/1161] Prepare 3.20.0 (#1554) --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 898294f7e..f266223c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # CHANGELOG +## 3.20.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.20.0. + +### Features + +- Tracing without Performance [(#1516)](https://github.com/getsentry/sentry-php/pull/1516) + + You can now set up distributed tracing without the need to use the performance APIs. + This allows you to connect your errors that hail from other Sentry instrumented applications to errors in your PHP application. + + To continue a trace, fetch the incoming Sentry tracing headers and call `\Sentry\continueTrace()` as early as possible in the request cycle. + + ```php + $sentryTraceHeader = $request->getHeaderLine('sentry-trace'); + $baggageHeader = $request->getHeaderLine('baggage'); + + continueTrace($sentryTraceHeader, $baggageHeader); + ``` + + To continue a trace outward, you may attach the Sentry tracing headers to any HTTP client request. + You can fetch the required header values by calling `\Sentry\getBaggage()` and `\Sentry\getTraceparent()`. + +- Upserting Cron Monitors [(#1511)](https://github.com/getsentry/sentry-php/pull/1511) + + You can now create and update your Cron Monitors programmatically with code. + Read more about this in our [docs](https://docs.sentry.io/platforms/php/crons/#upserting-cron-monitors). + ## 3.19.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.19.1. From 9b6ca1400001d01460daf1f4945b36dc4bc5baba Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 22 Jun 2023 12:13:56 +0000 Subject: [PATCH 0875/1161] release: 3.20.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 1a541ebcd..235ec349d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.19.1'; + public const SDK_VERSION = '3.20.0'; /** * @var Options The client options From b311b54920adaa53f9c0777da320f7341897b470 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Sat, 24 Jun 2023 00:32:12 +0200 Subject: [PATCH 0876/1161] Use `isTracingEnabled` instead of `getEnableTracing` (#1555) --- src/functions.php | 6 +++--- tests/FunctionsTest.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/functions.php b/src/functions.php index 60518b777..cd96bc47d 100644 --- a/src/functions.php +++ b/src/functions.php @@ -183,7 +183,7 @@ function getTraceparent(): string if (null !== $client) { $options = $client->getOptions(); - if (null !== $options && $options->getEnableTracing()) { + if (null !== $options && $options->isTracingEnabled()) { $span = SentrySdk::getCurrentHub()->getSpan(); if (null !== $span) { return $span->toTraceparent(); @@ -213,7 +213,7 @@ function getBaggage(): string if (null !== $client) { $options = $client->getOptions(); - if (null !== $options && $options->getEnableTracing()) { + if (null !== $options && $options->isTracingEnabled()) { $span = SentrySdk::getCurrentHub()->getSpan(); if (null !== $span) { return $span->toBaggage(); @@ -250,7 +250,7 @@ function continueTrace(string $sentryTrace, string $baggage) if (null !== $client) { $options = $client->getOptions(); - if (null !== $options && $options->getEnableTracing()) { + if (null !== $options && $options->isTracingEnabled()) { return TransactionContext::fromHeaders($sentryTrace, $baggage); } } diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 2cdebf29f..7067429a6 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -304,7 +304,7 @@ public function testTraceparentWithTracingEnabled(): void $client->expects($this->once()) ->method('getOptions') ->willReturn(new Options([ - 'enable_tracing' => true, + 'traces_sample_rate' => 1.0, ])); $hub = new Hub($client); @@ -354,7 +354,7 @@ public function testBaggageWithTracingEnabled(): void $client->expects($this->atLeastOnce()) ->method('getOptions') ->willReturn(new Options([ - 'enable_tracing' => true, + 'traces_sample_rate' => 1.0, 'release' => '1.0.0', 'environment' => 'development', ])); @@ -386,7 +386,7 @@ public function testContinueTrace(): void $client->expects($this->atLeastOnce()) ->method('getOptions') ->willReturn(new Options([ - 'enable_tracing' => true, + 'traces_sample_rate' => 1.0, 'release' => '1.0.0', 'environment' => 'development', ])); From 4e428363e7b7a53dd208b45ad59c71e6be679e58 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 26 Jun 2023 12:57:42 +0200 Subject: [PATCH 0877/1161] Always return a `TransactionContext` from `continueTrace()` (#1556) --- src/functions.php | 14 ++------------ tests/FunctionsTest.php | 11 +---------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/src/functions.php b/src/functions.php index cd96bc47d..158c00b60 100644 --- a/src/functions.php +++ b/src/functions.php @@ -234,10 +234,8 @@ function getBaggage(): string * If the SDK is configured with enabled tracing, * this function returns a populated TransactionContext. * In any other cases, it populates the propagation context on the scope. - * - * @return mixed */ -function continueTrace(string $sentryTrace, string $baggage) +function continueTrace(string $sentryTrace, string $baggage): TransactionContext { $hub = SentrySdk::getCurrentHub(); $hub->configureScope(function (Scope $scope) use ($sentryTrace, $baggage) { @@ -245,13 +243,5 @@ function continueTrace(string $sentryTrace, string $baggage) $scope->setPropagationContext($propagationContext); }); - $client = $hub->getClient(); - - if (null !== $client) { - $options = $client->getOptions(); - - if (null !== $options && $options->isTracingEnabled()) { - return TransactionContext::fromHeaders($sentryTrace, $baggage); - } - } + return TransactionContext::fromHeaders($sentryTrace, $baggage); } diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 7067429a6..e1f3b3f6b 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -382,16 +382,7 @@ public function testBaggageWithTracingEnabled(): void public function testContinueTrace(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->atLeastOnce()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1.0, - 'release' => '1.0.0', - 'environment' => 'development', - ])); - - $hub = new Hub($client); + $hub = new Hub(); SentrySdk::setCurrentHub($hub); From 7df099edb78e7c1ee16aa15d2e16d141ee87d4bf Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 26 Jun 2023 13:00:24 +0200 Subject: [PATCH 0878/1161] Prepare 3.20.1 (#1558) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f266223c4..60df05957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # CHANGELOG +## 3.20.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.20.1. + +### Bug Fixes + +- Use the result of `isTracingEnabled()` to determine the behaviour of `getBaggage()` and `getTraceparent()` [(#1555)](https://github.com/getsentry/sentry-php/pull/1555) + +### Misc + +- Always return a `TransactionContext` from `continueTrace()` [(#1556)](https://github.com/getsentry/sentry-php/pull/1556) + ## 3.20.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.20.0. From 644ad9768c18139a80ac510090fad000d9ffd8a4 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 26 Jun 2023 11:01:40 +0000 Subject: [PATCH 0879/1161] release: 3.20.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 235ec349d..5877a6b9d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.20.0'; + public const SDK_VERSION = '3.20.1'; /** * @var Options The client options From 306a6051bb64e4101ff4cf99d005e3f984959233 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 26 Jun 2023 15:02:25 +0200 Subject: [PATCH 0880/1161] Remove branch-alias (#1557) --- composer.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/composer.json b/composer.json index bb119e79b..32ea2f592 100644 --- a/composer.json +++ b/composer.json @@ -94,10 +94,5 @@ "phpstan/extension-installer": true } }, - "prefer-stable": true, - "extra": { - "branch-alias": { - "dev-master": "3.13.x-dev" - } - } + "prefer-stable": true } From d88b4c901dbeabc969dbc56789b276d7b63d0760 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Mon, 3 Jul 2023 09:43:42 +0200 Subject: [PATCH 0881/1161] Compat support for PHPUnit `10` (#1559) --- .../FrameContextifierIntegration.php | 2 +- tests/BreadcrumbTest.php | 4 +- tests/CheckInTest.php | 2 +- tests/ClientTest.php | 51 ++++++++----------- tests/Context/OsContextTest.php | 2 +- tests/DsnTest.php | 10 ++-- tests/EventHintTest.php | 2 +- tests/EventIdTest.php | 2 +- tests/EventTest.php | 4 +- tests/ExceptionDataBagTest.php | 2 +- tests/ExceptionMechanismTest.php | 2 +- tests/FrameBuilderTest.php | 4 +- tests/FrameTest.php | 2 +- tests/FunctionsTest.php | 6 +-- tests/HttpClient/HttpClientFactoryTest.php | 2 +- .../EnvironmentIntegrationTest.php | 2 +- .../FrameContextifierIntegrationTest.php | 20 +------- .../IgnoreErrorsIntegrationTest.php | 2 +- tests/Integration/IntegrationRegistryTest.php | 4 +- tests/Integration/ModulesIntegrationTest.php | 2 +- tests/Integration/RequestIntegrationTest.php | 2 +- .../TransactionIntegrationTest.php | 2 +- tests/MonitorConfigTest.php | 2 +- tests/MonitorScheduleTest.php | 2 +- tests/Monolog/BreadcrumbHandlerTest.php | 2 +- tests/Monolog/HandlerTest.php | 2 +- tests/OptionsTest.php | 12 ++--- tests/Profiling/ProfileTest.php | 4 +- tests/ResponseStatusTest.php | 4 +- tests/Serializer/AbstractSerializerTest.php | 12 ++--- tests/Serializer/PayloadSerializerTest.php | 4 +- tests/SeverityTest.php | 4 +- tests/StacktraceTest.php | 6 +-- tests/State/HubAdapterTest.php | 6 +-- tests/State/HubTest.php | 8 +-- tests/Tracing/DynamicSamplingContextTest.php | 6 +-- tests/Tracing/GuzzleTracingMiddlewareTest.php | 4 +- tests/Tracing/PropagationContextTest.php | 4 +- tests/Tracing/SpanContextTest.php | 2 +- tests/Tracing/SpanIdTest.php | 2 +- tests/Tracing/SpanStatusTest.php | 4 +- tests/Tracing/SpanTest.php | 6 +-- tests/Tracing/TraceIdTest.php | 2 +- tests/Tracing/TransactionContextTest.php | 4 +- tests/Tracing/TransactionMetadataTest.php | 2 +- tests/Tracing/TransactionTest.php | 2 +- tests/Transport/HttpTransportTest.php | 2 +- tests/Transport/RateLimiterTest.php | 2 +- tests/UserDataBagTest.php | 4 +- tests/Util/JSONTest.php | 8 +-- 50 files changed, 113 insertions(+), 140 deletions(-) diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 129ddfd3a..31104b102 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -27,7 +27,7 @@ final class FrameContextifierIntegration implements IntegrationInterface /** * Creates a new instance of this integration. * - * @param LoggerInterface $logger A PSR-3 logger + * @param LoggerInterface|null $logger A PSR-3 logger */ public function __construct(?LoggerInterface $logger = null) { diff --git a/tests/BreadcrumbTest.php b/tests/BreadcrumbTest.php index a27a8f52b..25bba3ea0 100644 --- a/tests/BreadcrumbTest.php +++ b/tests/BreadcrumbTest.php @@ -44,7 +44,7 @@ public function testConstructor(array $constructorArgs, string $expectedLevel, s $this->assertSame($expectedTimestamp, $breadcrumb->getTimestamp()); } - public function constructorDataProvider(): \Generator + public static function constructorDataProvider(): \Generator { yield [ [ @@ -112,7 +112,7 @@ public function testFromArray(array $requestData, string $expectedLevel, string $this->assertSame($expectedTimestamp, $breadcrumb->getTimestamp()); } - public function fromArrayDataProvider(): iterable + public static function fromArrayDataProvider(): iterable { yield [ [ diff --git a/tests/CheckInTest.php b/tests/CheckInTest.php index dbe191559..e080791d5 100644 --- a/tests/CheckInTest.php +++ b/tests/CheckInTest.php @@ -47,7 +47,7 @@ public function testGettersAndSetters(string $getterMethod, string $setterMethod $this->assertEquals($expectedData, $checkIn->$getterMethod()); } - public function gettersAndSettersDataProvider(): array + public static function gettersAndSettersDataProvider(): array { return [ ['getId', 'setId', SentryUid::generate()], diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f5cf5beac..395c96c82 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -7,7 +7,6 @@ use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Sentry\Client; @@ -182,7 +181,7 @@ public function testCaptureExceptionWithEventHint(EventHint $hint): void $this->assertTrue($beforeSendCallbackCalled); } - public function captureExceptionWithEventHintDataProvider(): \Generator + public static function captureExceptionWithEventHintDataProvider(): \Generator { yield [ EventHint::fromArray([ @@ -223,7 +222,7 @@ public function testCaptureEvent(array $options, Event $event, Event $expectedEv $this->assertSame($event->getId(), $client->captureEvent($event)); } - public function captureEventDataProvider(): \Generator + public static function captureEventDataProvider(): \Generator { $event = Event::createEvent(); $expectedEvent = clone $event; @@ -347,7 +346,7 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt $this->assertNotNull($client->captureEvent(Event::createEvent(), $hint)); } - public function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider(): \Generator + public static function captureEventAttachesStacktraceAccordingToAttachStacktraceOptionDataProvider(): \Generator { yield 'Stacktrace attached when attach_stacktrace = true and both payload.exception and payload.stacktrace are unset' => [ true, @@ -497,7 +496,7 @@ public function testProcessEventChecksBeforeSendOption(Event $event, bool $expec $this->assertSame($expectedBeforeSendCall, $beforeSendCalled); } - public function processEventChecksBeforeSendOptionDataProvider(): \Generator + public static function processEventChecksBeforeSendOptionDataProvider(): \Generator { yield [ Event::createEvent(), @@ -530,7 +529,7 @@ public function testProcessEventChecksBeforeSendTransactionOption(Event $event, $this->assertSame($expectedBeforeSendCall, $beforeSendTransactionCalled); } - public function processEventChecksBeforeSendTransactionOptionDataProvider(): \Generator + public static function processEventChecksBeforeSendTransactionOptionDataProvider(): \Generator { yield [ Event::createEvent(), @@ -543,48 +542,40 @@ public function processEventChecksBeforeSendTransactionOptionDataProvider(): \Ge ]; } - /** - * @dataProvider processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDataProvider - */ - public function testProcessEventDiscardsEventWhenItIsSampledDueToSampleRateOption(float $sampleRate, InvocationOrder $transportCallInvocationMatcher, InvocationOrder $loggerCallInvocationMatcher): void + public function testProcessEventDiscardsEventWhenSampleRateOptionIsZero(): void { - /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); - $transport->expects($transportCallInvocationMatcher) + $transport->expects($this->never()) ->method('send') ->with($this->anything()); - /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); - $logger->expects($loggerCallInvocationMatcher) + $logger->expects($this->once()) ->method('info') ->with('The event will be discarded because it has been sampled.', $this->callback(static function (array $context): bool { return isset($context['event']) && $context['event'] instanceof Event; })); - $client = ClientBuilder::create(['sample_rate' => $sampleRate]) + $client = ClientBuilder::create(['sample_rate' => 0]) ->setTransportFactory($this->createTransportFactory($transport)) ->setLogger($logger) ->getClient(); - for ($i = 0; $i < 10; ++$i) { - $client->captureMessage('foo'); - } + $client->captureEvent(Event::createEvent()); } - public function processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDataProvider(): \Generator + public function testProcessEventCapturesEventWhenSampleRateOptionIsAboveZero(): void { - yield [ - 0, - $this->never(), - $this->exactly(10), - ]; + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->anything()); - yield [ - 1, - $this->exactly(10), - $this->never(), - ]; + $client = ClientBuilder::create(['sample_rate' => 1]) + ->setTransportFactory($this->createTransportFactory($transport)) + ->getClient(); + + $client->captureEvent(Event::createEvent()); } public function testProcessEventDiscardsEventWhenIgnoreExceptionsMatches(): void @@ -967,7 +958,7 @@ public function testGetCspReportUrl(array $options, ?string $expectedUrl): void $this->assertSame($expectedUrl, $client->getCspReportUrl()); } - public function getCspReportUrlDataProvider(): \Generator + public static function getCspReportUrlDataProvider(): \Generator { yield [ ['dsn' => null], diff --git a/tests/Context/OsContextTest.php b/tests/Context/OsContextTest.php index df34275cb..fc22a6b34 100644 --- a/tests/Context/OsContextTest.php +++ b/tests/Context/OsContextTest.php @@ -50,7 +50,7 @@ public function testGettersAndSetters(string $expectedName, ?string $expectedVer $this->assertSame($expectedMachineType, $context->getMachineType()); } - public function valuesDataProvider(): iterable + public static function valuesDataProvider(): iterable { yield [ 'Linux', diff --git a/tests/DsnTest.php b/tests/DsnTest.php index 1b73519ea..0ebf8ef42 100644 --- a/tests/DsnTest.php +++ b/tests/DsnTest.php @@ -36,7 +36,7 @@ public function testCreateFromString( $this->assertSame($expectedPath, $dsn->getPath()); } - public function createFromStringDataProvider(): \Generator + public static function createFromStringDataProvider(): \Generator { yield [ 'http://public@example.com/sentry/1', @@ -138,7 +138,7 @@ public function testCreateFromStringThrowsExceptionIfValueIsInvalid(string $valu Dsn::createFromString($value); } - public function createFromStringThrowsExceptionIfValueIsInvalidDataProvider(): \Generator + public static function createFromStringThrowsExceptionIfValueIsInvalidDataProvider(): \Generator { yield 'invalid DSN' => [ ':', @@ -186,7 +186,7 @@ public function testGetStoreApiEndpointUrl(string $value, string $expectedUrl): $this->assertSame($expectedUrl, $dsn->getStoreApiEndpointUrl()); } - public function getStoreApiEndpointUrlDataProvider(): \Generator + public static function getStoreApiEndpointUrlDataProvider(): \Generator { yield [ 'http://public@example.com/sentry/1', @@ -224,7 +224,7 @@ public function testGetCspReportEndpointUrl(string $value, string $expectedUrl): $this->assertSame($expectedUrl, $dsn->getCspReportEndpointUrl()); } - public function getCspReportEndpointUrlDataProvider(): \Generator + public static function getCspReportEndpointUrlDataProvider(): \Generator { yield [ 'http://public@example.com/sentry/1', @@ -260,7 +260,7 @@ public function testToString(string $value): void $this->assertSame($value, (string) Dsn::createFromString($value)); } - public function toStringDataProvider(): array + public static function toStringDataProvider(): array { return [ ['http://public@example.com/sentry/1'], diff --git a/tests/EventHintTest.php b/tests/EventHintTest.php index 3c8bffb56..0e7955492 100644 --- a/tests/EventHintTest.php +++ b/tests/EventHintTest.php @@ -44,7 +44,7 @@ public function testCreateFromArrayWithInvalidValues(array $hintData, string $ex EventHint::fromArray($hintData); } - public function createFromArrayWithInvalidValuesDataProvider(): \Generator + public static function createFromArrayWithInvalidValuesDataProvider(): \Generator { yield [ ['exception' => 'foo'], diff --git a/tests/EventIdTest.php b/tests/EventIdTest.php index 2e18b0fbf..174219688 100644 --- a/tests/EventIdTest.php +++ b/tests/EventIdTest.php @@ -27,7 +27,7 @@ public function testConstructorThrowsOnInvalidValue(string $value): void new EventId($value); } - public function constructorThrowsOnInvalidValueDataProvider(): \Generator + public static function constructorThrowsOnInvalidValueDataProvider(): \Generator { yield 'Value too long' => ['566e3688a61d4bc888951642d6f14a199']; yield 'Value too short' => ['566e3688a61d4bc888951642d6f14a1']; diff --git a/tests/EventTest.php b/tests/EventTest.php index 116d6d239..bbba308b9 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -35,7 +35,7 @@ public function testGetMessage(array $setMessageArguments, array $expectedValue) $this->assertSame($expectedValue['formatted'], $event->getMessageFormatted()); } - public function getMessageDataProvider(): array + public static function getMessageDataProvider(): array { return [ [ @@ -86,7 +86,7 @@ public function testGettersAndSetters(string $propertyName, $propertyValue): voi $this->assertEquals($event->$getterMethod(), $propertyValue); } - public function gettersAndSettersDataProvider(): array + public static function gettersAndSettersDataProvider(): array { return [ ['sdkIdentifier', 'sentry.sdk.test-identifier'], diff --git a/tests/ExceptionDataBagTest.php b/tests/ExceptionDataBagTest.php index c7ce0eca2..57d6a8a55 100644 --- a/tests/ExceptionDataBagTest.php +++ b/tests/ExceptionDataBagTest.php @@ -25,7 +25,7 @@ public function testConstructor(array $constructorArgs, string $expectedType, st $this->assertSame($expectedExceptionMechansim, $exceptionDataBag->getMechanism()); } - public function constructorDataProvider(): \Generator + public static function constructorDataProvider(): \Generator { yield [ [ diff --git a/tests/ExceptionMechanismTest.php b/tests/ExceptionMechanismTest.php index 60e935355..eee4df83b 100644 --- a/tests/ExceptionMechanismTest.php +++ b/tests/ExceptionMechanismTest.php @@ -25,7 +25,7 @@ public function testConstructor( $this->assertSame($expectedData, $exceptionMechanism->getData()); } - public function constructorDataProvider(): iterable + public static function constructorDataProvider(): iterable { yield [ [ diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php index 2d0e6e1ec..ebaa58df8 100644 --- a/tests/FrameBuilderTest.php +++ b/tests/FrameBuilderTest.php @@ -27,7 +27,7 @@ public function testBuildFromBacktraceFrame(Options $options, array $backtraceFr $this->assertSame($expectedFrame->getAbsoluteFilePath(), $frame->getAbsoluteFilePath()); } - public function buildFromBacktraceFrameDataProvider(): \Generator + public static function buildFromBacktraceFrameDataProvider(): \Generator { yield [ new Options([]), @@ -162,7 +162,7 @@ public function testAddFrameSetsInAppFlagCorrectly(Options $options, string $fil $this->assertSame($expectedResult, $frame->isInApp()); } - public function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator + public static function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator { yield 'No config specified' => [ new Options([ diff --git a/tests/FrameTest.php b/tests/FrameTest.php index a18f44925..a34f4353b 100644 --- a/tests/FrameTest.php +++ b/tests/FrameTest.php @@ -29,7 +29,7 @@ public function testGettersAndSetters(string $getterMethod, string $setterMethod $this->assertEquals($expectedData, $frame->$getterMethod()); } - public function gettersAndSettersDataProvider(): array + public static function gettersAndSettersDataProvider(): array { return [ ['getPreContext', 'setPreContext', ['foo' => 'bar', 'bar' => 'baz']], diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index e1f3b3f6b..685f279e5 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -66,7 +66,7 @@ public function testCaptureMessage(array $functionCallArgs, array $expectedFunct $this->assertSame($eventId, captureMessage(...$functionCallArgs)); } - public function captureMessageDataProvider(): \Generator + public static function captureMessageDataProvider(): \Generator { yield [ [ @@ -112,7 +112,7 @@ public function testCaptureException(array $functionCallArgs, array $expectedFun $this->assertSame($eventId, captureException(...$functionCallArgs)); } - public function captureExceptionDataProvider(): \Generator + public static function captureExceptionDataProvider(): \Generator { yield [ [ @@ -172,7 +172,7 @@ public function testCaptureLastError(array $functionCallArgs, array $expectedFun $this->assertSame($eventId, captureLastError(...$functionCallArgs)); } - public function captureLastErrorDataProvider(): \Generator + public static function captureLastErrorDataProvider(): \Generator { yield [ [], diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php index 182a52a74..b0218c817 100644 --- a/tests/HttpClient/HttpClientFactoryTest.php +++ b/tests/HttpClient/HttpClientFactoryTest.php @@ -51,7 +51,7 @@ public function testCreate(bool $isCompressionEnabled, string $expectedRequestBo $this->assertSame($expectedRequestBody, (string) $httpRequest->getBody()); } - public function createDataProvider(): \Generator + public static function createDataProvider(): \Generator { yield [ false, diff --git a/tests/Integration/EnvironmentIntegrationTest.php b/tests/Integration/EnvironmentIntegrationTest.php index 1aa356627..d530d8900 100644 --- a/tests/Integration/EnvironmentIntegrationTest.php +++ b/tests/Integration/EnvironmentIntegrationTest.php @@ -64,7 +64,7 @@ public function testInvoke(bool $isIntegrationEnabled, ?RuntimeContext $initialR }); } - public function invokeDataProvider(): iterable + public static function invokeDataProvider(): iterable { yield 'Integration disabled => do nothing' => [ false, diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index b54cda285..1a1eea5d1 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -13,30 +13,12 @@ use Sentry\Integration\FrameContextifierIntegration; use Sentry\Options; use Sentry\SentrySdk; -use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\SerializerInterface; use Sentry\Stacktrace; use Sentry\State\Scope; use function Sentry\withScope; final class FrameContextifierIntegrationTest extends TestCase { - /** - * @var MockObject&SerializerInterface - */ - private $serializer; - - /** - * @var MockObject&RepresentationSerializerInterface - */ - private $representationSerializer; - - protected function setUp(): void - { - $this->serializer = $this->createMock(SerializerInterface::class); - $this->representationSerializer = $this->createMock(RepresentationSerializerInterface::class); - } - /** * @dataProvider invokeDataProvider */ @@ -92,7 +74,7 @@ public function testInvoke(string $fixtureFilePath, int $lineNumber, int $contex } } - public function invokeDataProvider(): \Generator + public static function invokeDataProvider(): \Generator { yield 'short file' => [ realpath(__DIR__ . '/../Fixtures/code/ShortFile.php'), diff --git a/tests/Integration/IgnoreErrorsIntegrationTest.php b/tests/Integration/IgnoreErrorsIntegrationTest.php index 0ead916aa..d56f40c50 100644 --- a/tests/Integration/IgnoreErrorsIntegrationTest.php +++ b/tests/Integration/IgnoreErrorsIntegrationTest.php @@ -43,7 +43,7 @@ public function testInvoke(Event $event, bool $isIntegrationEnabled, array $inte }); } - public function invokeDataProvider(): \Generator + public static function invokeDataProvider(): \Generator { $event = Event::createEvent(); $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); diff --git a/tests/Integration/IntegrationRegistryTest.php b/tests/Integration/IntegrationRegistryTest.php index e4ad5d5f2..5b0059ab8 100644 --- a/tests/Integration/IntegrationRegistryTest.php +++ b/tests/Integration/IntegrationRegistryTest.php @@ -49,7 +49,7 @@ static function (string $debugMessage): array { $this->assertEquals($expectedIntegrations, IntegrationRegistry::getInstance()->setupIntegrations($options, $logger)); } - public function setupIntegrationsDataProvider(): iterable + public static function setupIntegrationsDataProvider(): iterable { $integration1 = new class() implements IntegrationInterface { public function setupOnce(): void @@ -319,7 +319,7 @@ public function testSetupIntegrationsThrowsExceptionIfValueReturnedFromOptionIsN ); } - public function setupIntegrationsThrowsExceptionIfValueReturnedFromOptionIsNotValidDataProvider(): iterable + public static function setupIntegrationsThrowsExceptionIfValueReturnedFromOptionIsNotValidDataProvider(): iterable { yield [ 12.34, diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index b428ad46e..864dc738d 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -44,7 +44,7 @@ public function testInvoke(bool $isIntegrationEnabled, bool $expectedEmptyModule }); } - public function invokeDataProvider(): \Generator + public static function invokeDataProvider(): \Generator { yield [ false, diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 3d04e456d..0f904c8ba 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -62,7 +62,7 @@ public function testInvoke(array $options, ServerRequestInterface $request, arra }); } - public function invokeDataProvider(): iterable + public static function invokeDataProvider(): iterable { yield [ [ diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index f991c11cc..fcb99a79b 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -44,7 +44,7 @@ public function testSetupOnce(Event $event, bool $isIntegrationEnabled, ?EventHi }); } - public function setupOnceDataProvider(): \Generator + public static function setupOnceDataProvider(): \Generator { yield [ Event::createEvent(), diff --git a/tests/MonitorConfigTest.php b/tests/MonitorConfigTest.php index 14ca7cea7..e2107165e 100644 --- a/tests/MonitorConfigTest.php +++ b/tests/MonitorConfigTest.php @@ -39,7 +39,7 @@ public function testGettersAndSetters(string $getterMethod, string $setterMethod $this->assertEquals($expectedData, $monitorConfig->$getterMethod()); } - public function gettersAndSettersDataProvider(): array + public static function gettersAndSettersDataProvider(): array { return [ ['getSchedule', 'setSchedule', MonitorSchedule::crontab('foo')], diff --git a/tests/MonitorScheduleTest.php b/tests/MonitorScheduleTest.php index d81d3da73..2a082f7c1 100644 --- a/tests/MonitorScheduleTest.php +++ b/tests/MonitorScheduleTest.php @@ -54,7 +54,7 @@ public function testGettersAndSetters(string $getterMethod, string $setterMethod $this->assertEquals($expectedData, $monitorSchedule->$getterMethod()); } - public function gettersAndSettersDataProvider(): array + public static function gettersAndSettersDataProvider(): array { return [ ['getType', 'setType', MonitorSchedule::TYPE_INTERVAL], diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php index 14802d951..8cd6a4d2f 100644 --- a/tests/Monolog/BreadcrumbHandlerTest.php +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -39,7 +39,7 @@ public function testHandle($record, Breadcrumb $expectedBreadcrumb): void /** * @return iterable, Breadcrumb}> */ - public function handleDataProvider(): iterable + public static function handleDataProvider(): iterable { $defaultBreadcrumb = new Breadcrumb( Breadcrumb::LEVEL_DEBUG, diff --git a/tests/Monolog/HandlerTest.php b/tests/Monolog/HandlerTest.php index c838da0ee..bc1d4dd67 100644 --- a/tests/Monolog/HandlerTest.php +++ b/tests/Monolog/HandlerTest.php @@ -49,7 +49,7 @@ public function testHandle(bool $fillExtraContext, $record, Event $expectedEvent $handler->handle($record); } - public function handleDataProvider(): iterable + public static function handleDataProvider(): iterable { $event = Event::createEvent(); $event->setMessage('foo bar'); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 178165b6c..690691b8e 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -84,7 +84,7 @@ public function testGettersAndSetters( $this->assertEquals($value, $options->$getterMethod()); } - public function optionsDataProvider(): \Generator + public static function optionsDataProvider(): \Generator { yield [ 'send_attempts', @@ -421,7 +421,7 @@ public function testDsnOption($value, ?Dsn $expectedDsnAsObject): void $this->assertEquals($expectedDsnAsObject, $options->getDsn()); } - public function dsnOptionDataProvider(): \Generator + public static function dsnOptionDataProvider(): \Generator { yield [ 'http://public:secret@example.com/sentry/1', @@ -490,7 +490,7 @@ public function testDsnOptionThrowsOnInvalidValue($value, string $expectedExcept new Options(['dsn' => $value]); } - public function dsnOptionThrowsOnInvalidValueDataProvider(): \Generator + public static function dsnOptionThrowsOnInvalidValueDataProvider(): \Generator { yield [ true, @@ -557,7 +557,7 @@ public function testMaxBreadcrumbsOptionIsValidatedCorrectly(bool $isValid, $val $this->assertSame($value, $options->getMaxBreadcrumbs()); } - public function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array + public static function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): array { return [ [false, -1], @@ -585,7 +585,7 @@ public function testContextLinesOptionValidatesInputValue(?int $value, ?string $ new Options(['context_lines' => $value]); } - public function contextLinesOptionValidatesInputValueDataProvider(): \Generator + public static function contextLinesOptionValidatesInputValueDataProvider(): \Generator { yield [ -1, @@ -665,7 +665,7 @@ public function testEnableTracing(?bool $enabledTracing, ?float $tracesSampleRat $this->assertSame($expectedResult, $options->isTracingEnabled()); } - public function enableTracingDataProvider(): array + public static function enableTracingDataProvider(): array { return [ [null, null, false], diff --git a/tests/Profiling/ProfileTest.php b/tests/Profiling/ProfileTest.php index 34f5cd29c..dab6e62d5 100644 --- a/tests/Profiling/ProfileTest.php +++ b/tests/Profiling/ProfileTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Sentry\Tests; +namespace Sentry\Tests\Profiling; use PHPUnit\Framework\TestCase; use Sentry\Context\OsContext; @@ -28,7 +28,7 @@ public function testGetFormattedData(Event $event, array $excimerLog, $expectedD $this->assertSame($expectedData, $profile->getFormattedData($event)); } - public function formattedDataDataProvider(): \Generator + public static function formattedDataDataProvider(): \Generator { $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setRelease('1.0.0'); diff --git a/tests/ResponseStatusTest.php b/tests/ResponseStatusTest.php index 2a9585f75..3edfba746 100644 --- a/tests/ResponseStatusTest.php +++ b/tests/ResponseStatusTest.php @@ -17,7 +17,7 @@ public function testToString(ResponseStatus $responseStatus, string $expectedStr $this->assertSame($expectedStringRepresentation, (string) $responseStatus); } - public function toStringDataProvider(): iterable + public static function toStringDataProvider(): iterable { yield [ ResponseStatus::success(), @@ -58,7 +58,7 @@ public function testCreateFromHttpStatusCode(ResponseStatus $expectedResponseSta $this->assertSame($expectedResponseStatus, ResponseStatus::createFromHttpStatusCode($httpStatusCode)); } - public function createFromHttpStatusCodeDataProvider(): iterable + public static function createFromHttpStatusCodeDataProvider(): iterable { yield [ ResponseStatus::success(), diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 86efed288..10e26f513 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -20,7 +20,7 @@ public static function setUpBeforeClass(): void { } - public function serializeAllObjectsDataProvider(): array + public static function serializeAllObjectsDataProvider(): array { return [ ['serializeAllObjects' => false], @@ -54,7 +54,7 @@ public function testObjectsAreStrings(): void $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObject', $result); } - public function objectsWithIdPropertyDataProvider(): array + public static function objectsWithIdPropertyDataProvider(): array { return [ ['bar', 'Object Sentry\Tests\Serializer\SerializerTestObjectWithIdProperty(#bar)'], @@ -124,7 +124,7 @@ public function testIterablesAreNotConsumed(iterable $iterable, array $input): v $this->assertSame($input, $output); } - public function iterableDataProvider(): \Generator + public static function iterableDataProvider(): \Generator { yield [ 'iterable' => ['value1', 'value2'], @@ -183,7 +183,7 @@ public function testRecursionMaxDepth(bool $serializeAllObjects): void $this->assertSame([[['Array of length 0']]], $result); } - public function dataRecursionInObjectsDataProvider(): \Generator + public static function dataRecursionInObjectsDataProvider(): \Generator { $object = new SerializerTestObject(); $object->key = $object; @@ -285,7 +285,7 @@ public function testRecursionMaxDepthForObject($value, $expectedResult): void $this->assertEquals($expectedResult, $result); } - public function recursionMaxDepthForObjectDataProvider(): array + public static function recursionMaxDepthForObjectDataProvider(): array { return [ [ @@ -575,7 +575,7 @@ public function testSerializationForBadStrings(string $string, string $expected, $this->assertSame($expected, $this->invokeSerialization($serializer, $string)); } - public function serializationForBadStringsDataProvider(): array + public static function serializationForBadStringsDataProvider(): array { $utf8String = 'äöü'; diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 2e5f15402..2a2e106c1 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -84,7 +84,7 @@ public function testSerializeAsEnvelope(Event $event, string $expectedResult): v $this->assertSame($expectedResult, $result); } - public function serializeAsJsonDataProvider(): iterable + public static function serializeAsJsonDataProvider(): iterable { ClockMock::withClockMock(1597790835); @@ -649,7 +649,7 @@ public function serializeAsJsonDataProvider(): iterable ]; } - public function serializeAsEnvelopeDataProvider(): iterable + public static function serializeAsEnvelopeDataProvider(): iterable { ClockMock::withClockMock(1597790835); diff --git a/tests/SeverityTest.php b/tests/SeverityTest.php index 5e8397794..79ade6add 100644 --- a/tests/SeverityTest.php +++ b/tests/SeverityTest.php @@ -33,7 +33,7 @@ public function testToString(Severity $severity, string $expectedStringRepresent $this->assertSame($expectedStringRepresentation, (string) $severity); } - public function constantsDataProvider(): array + public static function constantsDataProvider(): array { return [ [Severity::debug(), 'debug'], @@ -62,7 +62,7 @@ public function testFromError(int $errorLevel, string $expectedSeverity): void $this->assertSame($expectedSeverity, (string) Severity::fromError($errorLevel)); } - public function levelsDataProvider(): array + public static function levelsDataProvider(): array { return [ // Warning diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 5ab505955..873f48fc5 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -32,7 +32,7 @@ public function testConstructorThrowsIfFramesListContainsUnexpectedValue(array $ new Stacktrace($values); } - public function constructorThrowsIfFramesListContainsUnexpectedValueDataProvider(): \Generator + public static function constructorThrowsIfFramesListContainsUnexpectedValueDataProvider(): \Generator { yield [ [ @@ -94,7 +94,7 @@ public function testRemoveFrame(int $index, ?string $expectedExceptionMessage): $this->assertFrameEquals($frames[0], 'test_function_parent', 'path/to/file', 12); } - public function removeFrameDataProvider(): \Generator + public static function removeFrameDataProvider(): \Generator { yield [ -1, @@ -126,7 +126,7 @@ public function testCreateFromBacktrace(Options $options, array $backtrace, arra } } - public function buildFromBacktraceDataProvider(): \Generator + public static function buildFromBacktraceDataProvider(): \Generator { yield 'Plain backtrace' => [ new Options(), diff --git a/tests/State/HubAdapterTest.php b/tests/State/HubAdapterTest.php index 7c8c2cd93..c876fca55 100644 --- a/tests/State/HubAdapterTest.php +++ b/tests/State/HubAdapterTest.php @@ -155,7 +155,7 @@ public function testCaptureMessage(array $expectedFunctionCallArgs): void $this->assertSame($eventId, HubAdapter::getInstance()->captureMessage(...$expectedFunctionCallArgs)); } - public function captureMessageDataProvider(): \Generator + public static function captureMessageDataProvider(): \Generator { yield [ [ @@ -192,7 +192,7 @@ public function testCaptureException(array $expectedFunctionCallArgs): void $this->assertSame($eventId, HubAdapter::getInstance()->captureException(...$expectedFunctionCallArgs)); } - public function captureExceptionDataProvider(): \Generator + public static function captureExceptionDataProvider(): \Generator { yield [ [ @@ -242,7 +242,7 @@ public function testCaptureLastError(array $expectedFunctionCallArgs): void $this->assertSame($eventId, HubAdapter::getInstance()->captureLastError(...$expectedFunctionCallArgs)); } - public function captureLastErrorDataProvider(): \Generator + public static function captureLastErrorDataProvider(): \Generator { yield [ [], diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 6e98aa969..3719d5864 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -251,7 +251,7 @@ public function testCaptureMessage(array $functionCallArgs, array $expectedFunct $this->assertSame($eventId, $hub->captureMessage(...$functionCallArgs)); } - public function captureMessageDataProvider(): \Generator + public static function captureMessageDataProvider(): \Generator { $propagationContext = PropagationContext::fromDefaults(); @@ -309,7 +309,7 @@ public function testCaptureException(array $functionCallArgs, array $expectedFun $this->assertSame($eventId, $hub->captureException(...$functionCallArgs)); } - public function captureExceptionDataProvider(): \Generator + public static function captureExceptionDataProvider(): \Generator { $propagationContext = PropagationContext::fromDefaults(); @@ -365,7 +365,7 @@ public function testCaptureLastError(array $functionCallArgs, array $expectedFun $this->assertSame($eventId, $hub->captureLastError(...$functionCallArgs)); } - public function captureLastErrorDataProvider(): \Generator + public static function captureLastErrorDataProvider(): \Generator { $propagationContext = PropagationContext::fromDefaults(); @@ -586,7 +586,7 @@ public function testStartTransactionWithTracesSampler(Options $options, Transact $this->assertSame($expectedSampled, $transaction->getSampled()); } - public function startTransactionDataProvider(): iterable + public static function startTransactionDataProvider(): iterable { yield 'Acceptable float value returned from traces_sampler' => [ new Options([ diff --git a/tests/Tracing/DynamicSamplingContextTest.php b/tests/Tracing/DynamicSamplingContextTest.php index d578e0f29..137e7030c 100644 --- a/tests/Tracing/DynamicSamplingContextTest.php +++ b/tests/Tracing/DynamicSamplingContextTest.php @@ -43,7 +43,7 @@ public function testFromHeader( $this->assertSame($expectedTransaction, $samplingContext->get('transaction')); } - public function fromHeaderDataProvider(): \Generator + public static function fromHeaderDataProvider(): \Generator { yield [ '', @@ -169,7 +169,7 @@ public function testGetEntries(DynamicSamplingContext $samplingContext, array $e $this->assertSame($expectedDynamicSamplingContext, $samplingContext->getEntries()); } - public function getEntriesDataProvider(): \Generator + public static function getEntriesDataProvider(): \Generator { yield [ DynamicSamplingContext::fromHeader(''), @@ -203,7 +203,7 @@ public function testToString(DynamicSamplingContext $samplingContext, string $ex $this->assertSame($expectedString, (string) $samplingContext); } - public function toStringDataProvider(): \Generator + public static function toStringDataProvider(): \Generator { yield [ DynamicSamplingContext::fromHeader(''), diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 5c6d8f608..ba1a17af4 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -127,7 +127,7 @@ public function testTraceHeadersWithTransacttion(Request $request, Options $opti $transaction->finish(); } - public function traceHeadersDataProvider(): iterable + public static function traceHeadersDataProvider(): iterable { yield [ new Request('GET', 'https://www.example.com'), @@ -263,7 +263,7 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec $transaction->finish(); } - public function traceDataProvider(): iterable + public static function traceDataProvider(): iterable { yield [ new Request('GET', 'https://www.example.com'), diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index 431ac4238..9a3dd1cb9 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -68,7 +68,7 @@ public function testFromEnvironment(string $sentryTrace, string $baggage, ?Trace $this->assertSame($expectedDynamicSamplingContextFrozen, $propagationContext->getDynamicSamplingContext()->isFrozen()); } - public function tracingDataProvider(): iterable + public static function tracingDataProvider(): iterable { yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', @@ -147,7 +147,7 @@ public function testGettersAndSetters(string $getterMethod, string $setterMethod $this->assertEquals($expectedData, $propagationContext->$getterMethod()); } - public function gettersAndSettersDataProvider(): array + public static function gettersAndSettersDataProvider(): array { $scope = new Scope(); $options = new Options([ diff --git a/tests/Tracing/SpanContextTest.php b/tests/Tracing/SpanContextTest.php index e308d9591..0ca04622c 100644 --- a/tests/Tracing/SpanContextTest.php +++ b/tests/Tracing/SpanContextTest.php @@ -36,7 +36,7 @@ public function testFromTraceparent(string $header, ?SpanId $expectedSpanId, ?Tr $this->assertSame($expectedSampled, $spanContext->getSampled()); } - public function fromTraceparentDataProvider(): iterable + public static function fromTraceparentDataProvider(): iterable { yield [ '0', diff --git a/tests/Tracing/SpanIdTest.php b/tests/Tracing/SpanIdTest.php index 6af5f0878..1aa7f5102 100644 --- a/tests/Tracing/SpanIdTest.php +++ b/tests/Tracing/SpanIdTest.php @@ -27,7 +27,7 @@ public function testConstructorThrowsOnInvalidValue(string $value): void new SpanId($value); } - public function constructorThrowsOnInvalidValueDataProvider(): \Generator + public static function constructorThrowsOnInvalidValueDataProvider(): \Generator { yield 'Value too long' => ['566e3688a61d4bc88']; yield 'Value too short' => ['566e3688a61d4b8']; diff --git a/tests/Tracing/SpanStatusTest.php b/tests/Tracing/SpanStatusTest.php index 8856c7bde..7e4969848 100644 --- a/tests/Tracing/SpanStatusTest.php +++ b/tests/Tracing/SpanStatusTest.php @@ -17,7 +17,7 @@ public function testToString(SpanStatus $spanStatus, string $expectedStringRepre $this->assertSame($expectedStringRepresentation, (string) $spanStatus); } - public function toStringDataProvider(): iterable + public static function toStringDataProvider(): iterable { yield [ SpanStatus::unauthenticated(), @@ -93,7 +93,7 @@ public function testCreateFromHttpStatusCode(SpanStatus $expectedSpanStatus, int $this->assertSame($expectedSpanStatus, SpanStatus::createFromHttpStatusCode($httpStatusCode)); } - public function createFromHttpStatusCodeDataProvider(): iterable + public static function createFromHttpStatusCodeDataProvider(): iterable { yield [ SpanStatus::unauthenticated(), diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index 7cfce7c43..8244f18db 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -31,7 +31,7 @@ public function testFinish(?float $currentTimestamp, ?float $endTimestamp, float $this->assertSame($expectedEndTimestamp, $span->getEndTimestamp()); } - public function finishDataProvider(): iterable + public static function finishDataProvider(): iterable { yield [ 1598660006, @@ -86,7 +86,7 @@ public function testToTraceparent(?bool $sampled, string $expectedValue): void $this->assertSame($expectedValue, $span->toTraceparent()); } - public function toTraceparentDataProvider(): iterable + public static function toTraceparentDataProvider(): iterable { yield [ null, @@ -118,7 +118,7 @@ public function testToBaggage(string $baggageHeader, string $expectedValue): voi $this->assertSame($expectedValue, $transaction->toBaggage()); } - public function toBaggageDataProvider(): iterable + public static function toBaggageDataProvider(): iterable { yield [ '', diff --git a/tests/Tracing/TraceIdTest.php b/tests/Tracing/TraceIdTest.php index badd23e03..8ebe0e708 100644 --- a/tests/Tracing/TraceIdTest.php +++ b/tests/Tracing/TraceIdTest.php @@ -27,7 +27,7 @@ public function testConstructorThrowsOnInvalidValue(string $value): void new TraceId($value); } - public function constructorThrowsOnInvalidValueDataProvider(): \Generator + public static function constructorThrowsOnInvalidValueDataProvider(): \Generator { yield 'Value too long' => ['566e3688a61d4bc888951642d6f14a199']; yield 'Value too short' => ['566e3688a61d4bc888951642d6f14a1']; diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index 1899ee360..0e9e3f5d3 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -51,7 +51,7 @@ public function testFromTraceparent(string $header, ?SpanId $expectedSpanId, ?Tr $this->assertSame($expectedParentSampled, $spanContext->getParentSampled()); } - public function fromSentryTraceDataProvider(): iterable + public static function fromSentryTraceDataProvider(): iterable { yield [ '0', @@ -117,7 +117,7 @@ public function testFromEnvironment(string $sentryTrace, string $baggage, ?SpanI $this->assertSame($expectedDynamicSamplingContextFrozen, $spanContext->getMetadata()->getDynamicSamplingContext()->isFrozen()); } - public function tracingDataProvider(): iterable + public static function tracingDataProvider(): iterable { yield [ '0', diff --git a/tests/Tracing/TransactionMetadataTest.php b/tests/Tracing/TransactionMetadataTest.php index 3852b017f..b7ee5862d 100644 --- a/tests/Tracing/TransactionMetadataTest.php +++ b/tests/Tracing/TransactionMetadataTest.php @@ -21,7 +21,7 @@ public function testConstructor(TransactionMetadata $transactionMetadata, $expec $this->assertSame($expectedSource, $transactionMetadata->getSource()); } - public function constructorDataProvider(): \Generator + public static function constructorDataProvider(): \Generator { $samplingContext = DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-sample_rate=1'); $source = TransactionSource::custom(); diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index d1d0b3f89..dc5023632 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -107,7 +107,7 @@ public function testTransactionIsSampledCorrectlyWhenTracingIsSetToZeroInOptions $this->assertSame($expectedSampled, $transaction->getSampled()); } - public function parentTransactionContextDataProvider(): Generator + public static function parentTransactionContextDataProvider(): Generator { yield [ new TransactionContext(TransactionContext::DEFAULT_NAME, true), diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index aad9a44b6..95483aef3 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -181,7 +181,7 @@ public function testSend(int $httpStatusCode, string $expectedPromiseStatus, Res $this->assertSame($event, $promiseResult->getEvent()); } - public function sendDataProvider(): iterable + public static function sendDataProvider(): iterable { yield [ 200, diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index 2fcac521d..22c3e5354 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -53,7 +53,7 @@ public function testHandleResponse(Event $event, ResponseInterface $response, Re $this->assertSame($event, $transportResponse->getEvent()); } - public function handleResponseDataProvider(): \Generator + public static function handleResponseDataProvider(): \Generator { yield 'Rate limits headers missing' => [ Event::createEvent(), diff --git a/tests/UserDataBagTest.php b/tests/UserDataBagTest.php index 1a34bb8e7..8fd8fb0c0 100644 --- a/tests/UserDataBagTest.php +++ b/tests/UserDataBagTest.php @@ -42,7 +42,7 @@ public function testCreateFromArray(array $data, $expectedId, ?string $expectedI $this->assertSame($expectedMetadata, $userDataBag->getMetadata()); } - public function createFromArrayDataProvider(): iterable + public static function createFromArrayDataProvider(): iterable { yield [ ['id' => 1234], @@ -149,7 +149,7 @@ public function testCreateFromUserIdentifierThrowsIfArgumentIsInvalid($value, st UserDataBag::createFromUserIdentifier($value); } - public function unexpectedValueForIdFieldDataProvider(): iterable + public static function unexpectedValueForIdFieldDataProvider(): iterable { yield [ 12.34, diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index a5b382fd9..bbe5c95d8 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -20,7 +20,7 @@ public function testEncode($value, string $expectedResult): void $this->assertSame($expectedResult, JSON::encode($value)); } - public function encodeDataProvider(): \Generator + public static function encodeDataProvider(): \Generator { yield [ [ @@ -78,7 +78,7 @@ public function testEncodeSubstitutesInvalidUtf8Characters($value, string $expec $this->assertSame($expectedResult, JSON::encode($value)); } - public function encodeSubstitutesInvalidUtf8CharactersDataProvider(): \Generator + public static function encodeSubstitutesInvalidUtf8CharactersDataProvider(): \Generator { yield [ "\x61\xb0\x62", @@ -132,7 +132,7 @@ public function testEncodeThrowsExceptionIfMaxDepthArgumentIsInvalid(int $maxDep JSON::encode('foo', 0, $maxDepth); } - public function encodeThrowsExceptionIfMaxDepthArgumentIsInvalidDataProvider(): \Generator + public static function encodeThrowsExceptionIfMaxDepthArgumentIsInvalidDataProvider(): \Generator { yield [0]; yield [-1]; @@ -151,7 +151,7 @@ public function testDecode(string $value, $expectedResult): void $this->assertSame($expectedResult, JSON::decode($value)); } - public function decodeDataProvider(): \Generator + public static function decodeDataProvider(): \Generator { yield [ '{"key":"value"}', From b9856563f0c5b38273eea6f31995f928451eb870 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 6 Jul 2023 10:47:20 +0200 Subject: [PATCH 0882/1161] Undeprecate tags option (#1561) --- src/Client.php | 2 +- src/Options.php | 12 +----------- tests/OptionsTest.php | 4 ++-- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/Client.php b/src/Client.php index 5877a6b9d..15aba1ca7 100644 --- a/src/Client.php +++ b/src/Client.php @@ -254,7 +254,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setSdkIdentifier($this->sdkIdentifier); $event->setSdkVersion($this->sdkVersion); - $event->setTags(array_merge($this->options->getTags(false), $event->getTags())); + $event->setTags(array_merge($this->options->getTags(), $event->getTags())); if (null === $event->getServerName()) { $event->setServerName($this->options->getServerName()); diff --git a/src/Options.php b/src/Options.php index 0ed534160..f17e6955e 100644 --- a/src/Options.php +++ b/src/Options.php @@ -538,15 +538,9 @@ public function setTracePropagationTargets(array $tracePropagationTargets): void * Gets a list of default tags for events. * * @return array - * - * @deprecated since version 3.2, to be removed in 4.0 */ - public function getTags(/*bool $triggerDeprecation = true*/): array + public function getTags(): array { - if (0 === \func_num_args() || false !== func_get_arg(0)) { - @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - } - return $this->options['tags']; } @@ -554,13 +548,9 @@ public function getTags(/*bool $triggerDeprecation = true*/): array * Sets a list of default tags for events. * * @param array $tags A list of tags - * - * @deprecated since version 3.2, to be removed in 4.0 */ public function setTags(array $tags): void { - @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0. Use Sentry\\Scope::setTags() instead.', __METHOD__), \E_USER_DEPRECATED); - $options = array_merge($this->options, ['tags' => $tags]); $this->options = $this->resolver->resolve($options); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 690691b8e..4f47fbbe8 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -244,8 +244,8 @@ static function (): void {}, ['foo', 'bar'], 'getTags', 'setTags', - 'Method Sentry\\Options::getTags() is deprecated since version 3.2 and will be removed in 4.0.', - 'Method Sentry\\Options::setTags() is deprecated since version 3.2 and will be removed in 4.0. Use Sentry\\Scope::setTags() instead.', + null, + null, ]; yield [ From 4bab1dc5173aec3c76ee30544de0244fb1fb46f8 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 10 Jul 2023 13:14:12 +0200 Subject: [PATCH 0883/1161] Add the sampling decision to the trace envelope header (#1562) --- src/Tracing/DynamicSamplingContext.php | 4 ++++ tests/FunctionsTest.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index ef166ee3c..a9ac69ade 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -184,6 +184,10 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h } }); + if (null !== $transaction->getSampled()) { + $samplingContext->set('sampled', $transaction->getSampled() ? 'true' : 'false'); + } + $samplingContext->freeze(); return $samplingContext; diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 685f279e5..473e052d9 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -377,7 +377,7 @@ public function testBaggageWithTracingEnabled(): void $baggage = getBaggage(); - $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1,sentry-transaction=Test,sentry-release=1.0.0,sentry-environment=development', $baggage); + $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1,sentry-transaction=Test,sentry-release=1.0.0,sentry-environment=development,sentry-sampled=true', $baggage); } public function testContinueTrace(): void From 62ab37f1826d58244968c17c86dfb5c806870eaa Mon Sep 17 00:00:00 2001 From: Stephanie Anderson Date: Wed, 12 Jul 2023 16:26:23 +0200 Subject: [PATCH 0884/1161] Update bug report template (#1565) --- .github/ISSUE_TEMPLATE/bug.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 8785950d9..1f0e71f73 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -6,22 +6,22 @@ body: attributes: label: How do you use Sentry? options: - - Sentry Saas (sentry.io) - - Self-hosted/on-premise + - Sentry SaaS (sentry.io) + - Self-hosted / on-premises validations: required: true - type: input id: version attributes: - label: Version - description: Which SDK version? - placeholder: ex. 3.7.0 + label: SDK version + description: Which SDK version do you use? + placeholder: e.g. 3.20.1 validations: required: true - type: textarea id: repro attributes: - label: Steps to Reproduce + label: Steps to reproduce description: How can we see what you're seeing? Specific is terrific. placeholder: |- 1. What @@ -32,13 +32,13 @@ body: - type: textarea id: expected attributes: - label: Expected Result + label: Expected result validations: required: true - type: textarea id: actual attributes: - label: Actual Result + label: Actual result description: Logs? Screenshots? Yes, please. validations: required: true @@ -47,4 +47,4 @@ body: value: |- ## Thanks 🙠validations: - required: false \ No newline at end of file + required: false From c965f552d7365edd2b81170f749aa300ab0707ec Mon Sep 17 00:00:00 2001 From: Hubert Deng Date: Wed, 12 Jul 2023 09:58:50 -0700 Subject: [PATCH 0885/1161] (chore): Delete stale.yml (#1567) --- .github/workflows/stale.yml | 47 ------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 583feba7a..000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: 'close stale issues/PRs' -on: - schedule: - - cron: '0 0 * * *' - workflow_dispatch: -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 - with: - repo-token: ${{ github.token }} - days-before-stale: 21 - days-before-close: 7 - only-labels: "" - operations-per-run: 100 - remove-stale-when-updated: true - debug-only: false - ascending: false - - exempt-issue-labels: "Status: Backlog,Status: In Progress" - stale-issue-label: "Status: Stale" - stale-issue-message: |- - This issue has gone three weeks without activity. In another week, I will close it. - - But! If you comment or otherwise update it, I will reset the clock, and if you label it `Status: Backlog` or `Status: In Progress`, I will leave it alone ... forever! - - ---- - - "A weed is but an unloved flower." ― _Ella Wheeler Wilcox_ 🥀 - skip-stale-issue-message: false - close-issue-label: "" - close-issue-message: "" - - exempt-pr-labels: "Status: Backlog,Status: In Progress" - stale-pr-label: "Status: Stale" - stale-pr-message: |- - This pull request has gone three weeks without activity. In another week, I will close it. - - But! If you comment or otherwise update it, I will reset the clock, and if you label it `Status: Backlog` or `Status: In Progress`, I will leave it alone ... forever! - - ---- - - "A weed is but an unloved flower." ― _Ella Wheeler Wilcox_ 🥀 - skip-stale-pr-message: false - close-pr-label: - close-pr-message: "" From 15a028d4aa3925b06b083cd65c1175828fa6e409 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 26 Jul 2023 11:11:57 +0200 Subject: [PATCH 0886/1161] Strip path prefixes on profile frames (#1568) --- src/FrameBuilder.php | 23 +--- src/Profiling/Profile.php | 17 ++- src/Profiling/Profiler.php | 6 +- src/Tracing/Transaction.php | 5 +- src/Util/PrefixStripper.php | 28 +++++ tests/Profiling/ProfileTest.php | 212 +++++++++++++++++++++++++------- 6 files changed, 223 insertions(+), 68 deletions(-) create mode 100644 src/Util/PrefixStripper.php diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index b2adba6de..5cd61bae6 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -5,6 +5,7 @@ namespace Sentry; use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\Util\PrefixStripper; /** * This class builds a {@see Frame} object out of a backtrace's raw frame. @@ -22,6 +23,8 @@ */ final class FrameBuilder { + use PrefixStripper; + /** * @var Options The SDK client options */ @@ -66,13 +69,13 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac $functionName = null; $rawFunctionName = null; - $strippedFilePath = $this->stripPrefixFromFilePath($file); + $strippedFilePath = $this->stripPrefixFromFilePath($this->options, $file); if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['class']; if (str_starts_with($functionName, Frame::ANONYMOUS_CLASS_PREFIX)) { - $functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath(substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX))); + $functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath($this->options, substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX))); } $rawFunctionName = sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); @@ -92,22 +95,6 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac ); } - /** - * Removes from the given file path the specified prefixes. - * - * @param string $filePath The path to the file - */ - private function stripPrefixFromFilePath(string $filePath): string - { - foreach ($this->options->getPrefixes() as $prefix) { - if (str_starts_with($filePath, $prefix)) { - return mb_substr($filePath, mb_strlen($prefix)); - } - } - - return $filePath; - } - /** * Checks whether a certain frame should be marked as "in app" or not. * diff --git a/src/Profiling/Profile.php b/src/Profiling/Profile.php index b6e974d37..63acbcd04 100644 --- a/src/Profiling/Profile.php +++ b/src/Profiling/Profile.php @@ -8,6 +8,8 @@ use Sentry\Context\RuntimeContext; use Sentry\Event; use Sentry\EventId; +use Sentry\Options; +use Sentry\Util\PrefixStripper; use Sentry\Util\SentryUid; /** @@ -73,6 +75,8 @@ */ final class Profile { + use PrefixStripper; + /** * @var string The version of the profile format */ @@ -108,6 +112,16 @@ final class Profile */ private $eventId; + /** + * @var Options|null + */ + private $options; + + public function __construct(?Options $options = null) + { + $this->options = $options; + } + public function setStartTimeStamp(float $startTimeStamp): void { $this->startTimeStamp = $startTimeStamp; @@ -160,8 +174,7 @@ public function getFormattedData(Event $event): ?array foreach ($loggedStacks as $stackId => $stack) { foreach ($stack['trace'] as $frame) { $absolutePath = (string) $frame['file']; - // TODO(michi) Strip the file path based on the `prefixes` option - $file = $absolutePath; + $file = $this->stripPrefixFromFilePath($this->options, $absolutePath); $module = null; if (isset($frame['class'], $frame['function'])) { diff --git a/src/Profiling/Profiler.php b/src/Profiling/Profiler.php index 463112fef..956c213d0 100644 --- a/src/Profiling/Profiler.php +++ b/src/Profiling/Profiler.php @@ -4,6 +4,8 @@ namespace Sentry\Profiling; +use Sentry\Options; + /** * @internal */ @@ -29,9 +31,9 @@ final class Profiler */ private const MAX_STACK_DEPTH = 128; - public function __construct() + public function __construct(?Options $options = null) { - $this->profile = new Profile(); + $this->profile = new Profile($options); $this->initProfiler(); } diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index ab9bbe485..b4dcc09c1 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -121,7 +121,10 @@ public function detachSpanRecorder(): void public function initProfiler(): void { if (null === $this->profiler) { - $this->profiler = new Profiler(); + $client = $this->hub->getClient(); + $options = null !== $client ? $client->getOptions() : null; + + $this->profiler = new Profiler($options); } } diff --git a/src/Util/PrefixStripper.php b/src/Util/PrefixStripper.php new file mode 100644 index 000000000..35c261312 --- /dev/null +++ b/src/Util/PrefixStripper.php @@ -0,0 +1,28 @@ +getPrefixes() as $prefix) { + if (str_starts_with($filePath, $prefix)) { + return mb_substr($filePath, mb_strlen($prefix)); + } + } + + return $filePath; + } +} diff --git a/tests/Profiling/ProfileTest.php b/tests/Profiling/ProfileTest.php index dab6e62d5..184cecdd6 100644 --- a/tests/Profiling/ProfileTest.php +++ b/tests/Profiling/ProfileTest.php @@ -9,6 +9,7 @@ use Sentry\Context\RuntimeContext; use Sentry\Event; use Sentry\EventId; +use Sentry\Options; use Sentry\Profiling\Profile; final class ProfileTest extends TestCase @@ -16,9 +17,9 @@ final class ProfileTest extends TestCase /** * @dataProvider formattedDataDataProvider */ - public function testGetFormattedData(Event $event, array $excimerLog, $expectedData): void + public function testGetFormattedData(Event $event, array $excimerLog, $expectedData, ?Options $options = null): void { - $profile = new Profile(); + $profile = new Profile($options); // 2022-02-28T09:41:00Z $profile->setStartTimeStamp(1677573660.0000); @@ -50,60 +51,178 @@ public static function formattedDataDataProvider(): \Generator 'aarch64' )); + $excimerLog = [ + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + ], + 'timestamp' => 0.001, + ], + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + [ + 'class' => 'Function', + 'function' => 'doStuff', + 'file' => '/var/www/html/function.php', + 'line' => 84, + ], + ], + 'timestamp' => 0.002, + ], + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + [ + 'class' => 'Function', + 'function' => 'doStuff', + 'file' => '/var/www/html/function.php', + 'line' => 84, + ], + [ + 'class' => 'Class\Something', + 'function' => 'run', + 'file' => '/var/www/html/class.php', + 'line' => 42, + ], + [ + 'function' => '{closure}', + 'file' => '/var/www/html/index.php', + 'line' => 126, + ], + ], + 'timestamp' => 0.003, + ], + ]; + yield [ $event, + $excimerLog, [ - [ - 'trace' => [ + 'device' => [ + 'architecture' => 'aarch64', + ], + 'event_id' => '815e57b4bb134056ab1840919834689d', + 'os' => [ + 'name' => 'macOS', + 'version' => '13.2.1', + 'build_number' => '22D68', + ], + 'platform' => 'php', + 'release' => '1.0.0', + 'environment' => 'dev', + 'runtime' => [ + 'name' => 'php', + 'version' => '8.2.3', + ], + 'timestamp' => '2023-02-28T08:41:00.000+00:00', + 'transaction' => [ + 'id' => 'fc9442f5aef34234bb22b9a615e30ccd', + 'name' => 'GET /', + 'trace_id' => '566e3688a61d4bc888951642d6f14a19', + 'active_thread_id' => '0', + ], + 'version' => '1', + 'profile' => [ + 'frames' => [ [ - 'file' => '/var/www/html/index.php', - 'line' => 42, + 'filename' => '/var/www/html/index.php', + 'abs_path' => '/var/www/html/index.php', + 'module' => null, + 'function' => '/var/www/html/index.php', + 'lineno' => 42, ], - ], - 'timestamp' => 0.001, - ], - [ - 'trace' => [ [ - 'file' => '/var/www/html/index.php', - 'line' => 42, + 'filename' => '/var/www/html/index.php', + 'abs_path' => '/var/www/html/index.php', + 'module' => null, + 'function' => '/var/www/html/index.php', + 'lineno' => 42, ], [ - 'class' => 'Function', - 'function' => 'doStuff', - 'file' => '/var/www/html/function.php', - 'line' => 84, + 'filename' => '/var/www/html/function.php', + 'abs_path' => '/var/www/html/function.php', + 'module' => 'Function', + 'function' => 'Function::doStuff', + 'lineno' => 84, ], - ], - 'timestamp' => 0.002, - ], - [ - 'trace' => [ [ - 'file' => '/var/www/html/index.php', - 'line' => 42, + 'filename' => '/var/www/html/index.php', + 'abs_path' => '/var/www/html/index.php', + 'module' => null, + 'function' => '/var/www/html/index.php', + 'lineno' => 42, ], [ - 'class' => 'Function', - 'function' => 'doStuff', - 'file' => '/var/www/html/function.php', - 'line' => 84, + 'filename' => '/var/www/html/function.php', + 'abs_path' => '/var/www/html/function.php', + 'module' => 'Function', + 'function' => 'Function::doStuff', + 'lineno' => 84, ], [ - 'class' => 'Class\Something', - 'function' => 'run', - 'file' => '/var/www/html/class.php', - 'line' => 42, + 'filename' => '/var/www/html/class.php', + 'abs_path' => '/var/www/html/class.php', + 'module' => 'Class\Something', + 'function' => 'Class\Something::run', + 'lineno' => 42, ], [ + 'filename' => '/var/www/html/index.php', + 'abs_path' => '/var/www/html/index.php', + 'module' => null, 'function' => '{closure}', - 'file' => '/var/www/html/index.php', - 'line' => 126, + 'lineno' => 126, + ], + ], + 'samples' => [ + [ + 'elapsed_since_start_ns' => 1000000, + 'stack_id' => 0, + 'thread_id' => '0', + ], + [ + 'elapsed_since_start_ns' => 2000000, + 'stack_id' => 1, + 'thread_id' => '0', + ], + [ + 'elapsed_since_start_ns' => 3000000, + 'stack_id' => 2, + 'thread_id' => '0', + ], + ], + 'stacks' => [ + [ + 0, + ], + [ + 1, + 2, + ], + [ + 3, + 4, + 5, + 6, ], ], - 'timestamp' => 0.003, ], ], + ]; + + yield [ + $event, + $excimerLog, [ 'device' => [ 'architecture' => 'aarch64', @@ -132,49 +251,49 @@ public static function formattedDataDataProvider(): \Generator 'profile' => [ 'frames' => [ [ - 'filename' => '/var/www/html/index.php', + 'filename' => '/index.php', 'abs_path' => '/var/www/html/index.php', 'module' => null, - 'function' => '/var/www/html/index.php', + 'function' => '/index.php', 'lineno' => 42, ], [ - 'filename' => '/var/www/html/index.php', + 'filename' => '/index.php', 'abs_path' => '/var/www/html/index.php', 'module' => null, - 'function' => '/var/www/html/index.php', + 'function' => '/index.php', 'lineno' => 42, ], [ - 'filename' => '/var/www/html/function.php', + 'filename' => '/function.php', 'abs_path' => '/var/www/html/function.php', 'module' => 'Function', 'function' => 'Function::doStuff', 'lineno' => 84, ], [ - 'filename' => '/var/www/html/index.php', + 'filename' => '/index.php', 'abs_path' => '/var/www/html/index.php', 'module' => null, - 'function' => '/var/www/html/index.php', + 'function' => '/index.php', 'lineno' => 42, ], [ - 'filename' => '/var/www/html/function.php', + 'filename' => '/function.php', 'abs_path' => '/var/www/html/function.php', 'module' => 'Function', 'function' => 'Function::doStuff', 'lineno' => 84, ], [ - 'filename' => '/var/www/html/class.php', + 'filename' => '/class.php', 'abs_path' => '/var/www/html/class.php', 'module' => 'Class\Something', 'function' => 'Class\Something::run', 'lineno' => 42, ], [ - 'filename' => '/var/www/html/index.php', + 'filename' => '/index.php', 'abs_path' => '/var/www/html/index.php', 'module' => null, 'function' => '{closure}', @@ -215,6 +334,9 @@ public static function formattedDataDataProvider(): \Generator ], ], ], + new Options([ + 'prefixes' => ['/var/www/html'], + ]), ]; yield 'Too little samples' => [ From 7f4e12994d9cc588f62dfb3c96a979afc9971b74 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 31 Jul 2023 16:40:57 +0200 Subject: [PATCH 0887/1161] Reuse frames in profile (#1570) --- src/Profiling/Profile.php | 106 +++++++++++++-------- stubs/ExcimerLogEntry.stub | 10 +- tests/Profiling/ProfileTest.php | 89 +++++++---------- tests/Serializer/PayloadSerializerTest.php | 4 +- 4 files changed, 108 insertions(+), 101 deletions(-) diff --git a/src/Profiling/Profile.php b/src/Profiling/Profile.php index 63acbcd04..f92e3de09 100644 --- a/src/Profiling/Profile.php +++ b/src/Profiling/Profile.php @@ -18,6 +18,14 @@ * * @see https://develop.sentry.dev/sdk/sample-format/ * + * @phpstan-type SentryProfileFrame array{ + * abs_path: string, + * filename: string, + * function: string, + * module: string|null, + * lineno: int|null, + * } + * * @phpstan-type SentryProfile array{ * device: array{ * architecture: string, @@ -44,11 +52,7 @@ * }, * version: string, * profile: array{ - * frames: array, + * frames: array, * samples: arrayprepareStacks(); - foreach ($loggedStacks as $stackId => $stack) { + foreach ($loggedStacks as $stack) { + $stackFrames = []; + foreach ($stack['trace'] as $frame) { - $absolutePath = (string) $frame['file']; - $file = $this->stripPrefixFromFilePath($this->options, $absolutePath); - $module = null; - - if (isset($frame['class'], $frame['function'])) { - // Class::method - $function = $frame['class'] . '::' . $frame['function']; - $module = $frame['class']; - } elseif (isset($frame['function'])) { - // {clousre} - $function = $frame['function']; - } else { - // /index.php - $function = $file; + $absolutePath = $frame['file']; + $lineno = $frame['line']; + + $frameKey = "{$absolutePath}:{$lineno}"; + + $frameIndex = $frameHashMap[$frameKey] ?? null; + + if (null === $frameIndex) { + $file = $this->stripPrefixFromFilePath($this->options, $absolutePath); + $module = null; + + if (isset($frame['class'], $frame['function'])) { + // Class::method + $function = $frame['class'] . '::' . $frame['function']; + $module = $frame['class']; + } elseif (isset($frame['function'])) { + // {closure} + $function = $frame['function']; + } else { + // /index.php + $function = $file; + } + + $frameHashMap[$frameKey] = $frameIndex = \count($frames); + $frames[] = [ + 'filename' => $file, + 'abs_path' => $absolutePath, + 'module' => $module, + 'function' => $function, + 'lineno' => $lineno, + ]; } - $frames[] = [ - 'filename' => $file, - 'abs_path' => $absolutePath, - 'module' => $module, - 'function' => $function, - 'lineno' => !empty($frame['line']) ? (int) $frame['line'] : null, - ]; - - $stacks[$stackId][] = $frameIndex; - ++$frameIndex; + $stackFrames[] = $frameIndex; } + $stackId = $registerStack($stackFrames); + $duration = $stack['timestamp']; $samples[] = [ - 'elapsed_since_start_ns' => (int) round($duration * 1e+9), 'stack_id' => $stackId, 'thread_id' => self::THREAD_ID, + 'elapsed_since_start_ns' => (int) round($duration * 1e+9), ]; } diff --git a/stubs/ExcimerLogEntry.stub b/stubs/ExcimerLogEntry.stub index 6bf317e4e..b84bb7b5c 100644 --- a/stubs/ExcimerLogEntry.stub +++ b/stubs/ExcimerLogEntry.stub @@ -45,11 +45,11 @@ namespace { * - closure_line: The line number at which the closure was defined * * @return array */ public function getTrace(): array diff --git a/tests/Profiling/ProfileTest.php b/tests/Profiling/ProfileTest.php index 184cecdd6..a30f68e6d 100644 --- a/tests/Profiling/ProfileTest.php +++ b/tests/Profiling/ProfileTest.php @@ -61,6 +61,15 @@ public static function formattedDataDataProvider(): \Generator ], 'timestamp' => 0.001, ], + [ + 'trace' => [ + [ + 'file' => '/var/www/html/index.php', + 'line' => 42, + ], + ], + 'timestamp' => 0.002, + ], [ 'trace' => [ [ @@ -74,7 +83,7 @@ public static function formattedDataDataProvider(): \Generator 'line' => 84, ], ], - 'timestamp' => 0.002, + 'timestamp' => 0.003, ], [ 'trace' => [ @@ -100,7 +109,7 @@ public static function formattedDataDataProvider(): \Generator 'line' => 126, ], ], - 'timestamp' => 0.003, + 'timestamp' => 0.004, ], ]; @@ -134,27 +143,6 @@ public static function formattedDataDataProvider(): \Generator 'version' => '1', 'profile' => [ 'frames' => [ - [ - 'filename' => '/var/www/html/index.php', - 'abs_path' => '/var/www/html/index.php', - 'module' => null, - 'function' => '/var/www/html/index.php', - 'lineno' => 42, - ], - [ - 'filename' => '/var/www/html/index.php', - 'abs_path' => '/var/www/html/index.php', - 'module' => null, - 'function' => '/var/www/html/index.php', - 'lineno' => 42, - ], - [ - 'filename' => '/var/www/html/function.php', - 'abs_path' => '/var/www/html/function.php', - 'module' => 'Function', - 'function' => 'Function::doStuff', - 'lineno' => 84, - ], [ 'filename' => '/var/www/html/index.php', 'abs_path' => '/var/www/html/index.php', @@ -186,19 +174,24 @@ public static function formattedDataDataProvider(): \Generator ], 'samples' => [ [ - 'elapsed_since_start_ns' => 1000000, 'stack_id' => 0, 'thread_id' => '0', + 'elapsed_since_start_ns' => 1000000, ], [ + 'stack_id' => 0, + 'thread_id' => '0', 'elapsed_since_start_ns' => 2000000, + ], + [ 'stack_id' => 1, 'thread_id' => '0', + 'elapsed_since_start_ns' => 3000000, ], [ - 'elapsed_since_start_ns' => 3000000, 'stack_id' => 2, 'thread_id' => '0', + 'elapsed_since_start_ns' => 4000000, ], ], 'stacks' => [ @@ -206,14 +199,14 @@ public static function formattedDataDataProvider(): \Generator 0, ], [ + 0, 1, - 2, ], [ + 0, + 1, + 2, 3, - 4, - 5, - 6, ], ], ], @@ -250,27 +243,6 @@ public static function formattedDataDataProvider(): \Generator 'version' => '1', 'profile' => [ 'frames' => [ - [ - 'filename' => '/index.php', - 'abs_path' => '/var/www/html/index.php', - 'module' => null, - 'function' => '/index.php', - 'lineno' => 42, - ], - [ - 'filename' => '/index.php', - 'abs_path' => '/var/www/html/index.php', - 'module' => null, - 'function' => '/index.php', - 'lineno' => 42, - ], - [ - 'filename' => '/function.php', - 'abs_path' => '/var/www/html/function.php', - 'module' => 'Function', - 'function' => 'Function::doStuff', - 'lineno' => 84, - ], [ 'filename' => '/index.php', 'abs_path' => '/var/www/html/index.php', @@ -302,19 +274,24 @@ public static function formattedDataDataProvider(): \Generator ], 'samples' => [ [ - 'elapsed_since_start_ns' => 1000000, 'stack_id' => 0, 'thread_id' => '0', + 'elapsed_since_start_ns' => 1000000, ], [ + 'stack_id' => 0, + 'thread_id' => '0', 'elapsed_since_start_ns' => 2000000, + ], + [ 'stack_id' => 1, 'thread_id' => '0', + 'elapsed_since_start_ns' => 3000000, ], [ - 'elapsed_since_start_ns' => 3000000, 'stack_id' => 2, 'thread_id' => '0', + 'elapsed_since_start_ns' => 4000000, ], ], 'stacks' => [ @@ -322,14 +299,14 @@ public static function formattedDataDataProvider(): \Generator 0, ], [ + 0, 1, - 2, ], [ + 0, + 1, + 2, 3, - 4, - 5, - 6, ], ], ], diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 2a2e106c1..292fd9944 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -511,7 +511,7 @@ public static function serializeAsJsonDataProvider(): iterable {"type":"transaction","content_type":"application\/json"} {"event_id":"fc9442f5aef34234bb22b9a615e30ccd","timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion"},"transaction":"GET \/","release":"1.0.0","environment":"dev","contexts":{"os":{"name":"macOS","version":"13.2.1","build":"22D68","kernel_version":"Darwin Kernel Version 22.2.0"},"runtime":{"name":"php","version":"8.2.3"},"trace":{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"5dd538dc297544cc"}},"spans":[{"span_id":"5dd538dc297544cc","trace_id":"21160e9b836d479f81611368b2aa3d2c","start_timestamp":1597790835},{"span_id":"b01b9f6349558cd1","trace_id":"1e57b752bc6e4544bbaa246cd1d05dee","start_timestamp":1597790835,"parent_span_id":"b0e6f15b45c36b12","timestamp":1598659060,"status":"ok","description":"GET \/sockjs-node\/info","op":"http","data":{"url":"http:\/\/localhost:8080\/sockjs-node\/info?t=1588601703755","status_code":200,"type":"xhr","method":"GET"},"tags":{"http.status_code":"200"}}]} {"type":"profile","content_type":"application\/json"} -{"device":{"architecture":"aarch64"},"event_id":"fc9442f5aef34234bb22b9a615e30ccd","os":{"name":"macOS","version":"13.2.1","build_number":"22D68"},"platform":"php","release":"1.0.0","environment":"dev","runtime":{"name":"php","version":"8.2.3"},"timestamp":"2023-02-28T08:41:00.000+00:00","transaction":{"id":"fc9442f5aef34234bb22b9a615e30ccd","name":"GET \/","trace_id":"21160e9b836d479f81611368b2aa3d2c","active_thread_id":"0"},"version":"1","profile":{"frames":[{"filename":"\/var\/www\/html\/index.php","abs_path":"\/var\/www\/html\/index.php","module":null,"function":"\/var\/www\/html\/index.php","lineno":42},{"filename":"\/var\/www\/html\/index.php","abs_path":"\/var\/www\/html\/index.php","module":null,"function":"\/var\/www\/html\/index.php","lineno":42},{"filename":"\/var\/www\/html\/function.php","abs_path":"\/var\/www\/html\/function.php","module":"Function","function":"Function::doStuff","lineno":84}],"samples":[{"elapsed_since_start_ns":1000000,"stack_id":0,"thread_id":"0"},{"elapsed_since_start_ns":2000000,"stack_id":1,"thread_id":"0"}],"stacks":[[0],[1,2]]}} +{"device":{"architecture":"aarch64"},"event_id":"fc9442f5aef34234bb22b9a615e30ccd","os":{"name":"macOS","version":"13.2.1","build_number":"22D68"},"platform":"php","release":"1.0.0","environment":"dev","runtime":{"name":"php","version":"8.2.3"},"timestamp":"2023-02-28T08:41:00.000+00:00","transaction":{"id":"fc9442f5aef34234bb22b9a615e30ccd","name":"GET \/","trace_id":"21160e9b836d479f81611368b2aa3d2c","active_thread_id":"0"},"version":"1","profile":{"frames":[{"filename":"\/var\/www\/html\/index.php","abs_path":"\/var\/www\/html\/index.php","module":null,"function":"\/var\/www\/html\/index.php","lineno":42},{"filename":"\/var\/www\/html\/function.php","abs_path":"\/var\/www\/html\/function.php","module":"Function","function":"Function::doStuff","lineno":84}],"samples":[{"stack_id":0,"thread_id":"0","elapsed_since_start_ns":1000000},{"stack_id":1,"thread_id":"0","elapsed_since_start_ns":2000000}],"stacks":[[0],[0,1]]}} TEXT , false, @@ -891,7 +891,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable {"type":"transaction","content_type":"application\/json"} {"event_id":"fc9442f5aef34234bb22b9a615e30ccd","timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion"},"transaction":"GET \/","release":"1.0.0","environment":"dev","contexts":{"os":{"name":"macOS","version":"13.2.1","build":"22D68","kernel_version":"Darwin Kernel Version 22.2.0"},"runtime":{"name":"php","version":"8.2.3"},"trace":{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"5dd538dc297544cc"}},"spans":[{"span_id":"5dd538dc297544cc","trace_id":"21160e9b836d479f81611368b2aa3d2c","start_timestamp":1597790835},{"span_id":"b01b9f6349558cd1","trace_id":"1e57b752bc6e4544bbaa246cd1d05dee","start_timestamp":1597790835,"parent_span_id":"b0e6f15b45c36b12","timestamp":1598659060,"status":"ok","description":"GET \/sockjs-node\/info","op":"http","data":{"url":"http:\/\/localhost:8080\/sockjs-node\/info?t=1588601703755","status_code":200,"type":"xhr","method":"GET"},"tags":{"http.status_code":"200"}}]} {"type":"profile","content_type":"application\/json"} -{"device":{"architecture":"aarch64"},"event_id":"fc9442f5aef34234bb22b9a615e30ccd","os":{"name":"macOS","version":"13.2.1","build_number":"22D68"},"platform":"php","release":"1.0.0","environment":"dev","runtime":{"name":"php","version":"8.2.3"},"timestamp":"2023-02-28T08:41:00.000+00:00","transaction":{"id":"fc9442f5aef34234bb22b9a615e30ccd","name":"GET \/","trace_id":"21160e9b836d479f81611368b2aa3d2c","active_thread_id":"0"},"version":"1","profile":{"frames":[{"filename":"\/var\/www\/html\/index.php","abs_path":"\/var\/www\/html\/index.php","module":null,"function":"\/var\/www\/html\/index.php","lineno":42},{"filename":"\/var\/www\/html\/index.php","abs_path":"\/var\/www\/html\/index.php","module":null,"function":"\/var\/www\/html\/index.php","lineno":42},{"filename":"\/var\/www\/html\/function.php","abs_path":"\/var\/www\/html\/function.php","module":"Function","function":"Function::doStuff","lineno":84}],"samples":[{"elapsed_since_start_ns":1000000,"stack_id":0,"thread_id":"0"},{"elapsed_since_start_ns":2000000,"stack_id":1,"thread_id":"0"}],"stacks":[[0],[1,2]]}} +{"device":{"architecture":"aarch64"},"event_id":"fc9442f5aef34234bb22b9a615e30ccd","os":{"name":"macOS","version":"13.2.1","build_number":"22D68"},"platform":"php","release":"1.0.0","environment":"dev","runtime":{"name":"php","version":"8.2.3"},"timestamp":"2023-02-28T08:41:00.000+00:00","transaction":{"id":"fc9442f5aef34234bb22b9a615e30ccd","name":"GET \/","trace_id":"21160e9b836d479f81611368b2aa3d2c","active_thread_id":"0"},"version":"1","profile":{"frames":[{"filename":"\/var\/www\/html\/index.php","abs_path":"\/var\/www\/html\/index.php","module":null,"function":"\/var\/www\/html\/index.php","lineno":42},{"filename":"\/var\/www\/html\/function.php","abs_path":"\/var\/www\/html\/function.php","module":"Function","function":"Function::doStuff","lineno":84}],"samples":[{"stack_id":0,"thread_id":"0","elapsed_since_start_ns":1000000},{"stack_id":1,"thread_id":"0","elapsed_since_start_ns":2000000}],"stacks":[[0],[0,1]]}} TEXT , ]; From 153a988f44695bf0f44cab7639997fd26ad93649 Mon Sep 17 00:00:00 2001 From: Will <43466887+will2877@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:06:21 +0200 Subject: [PATCH 0888/1161] Add captureCheckIn() (#1573) Co-authored-by: Michi Hoffmann --- src/State/Hub.php | 35 +++++++++++++++++++++++++++++ src/State/HubAdapter.php | 14 ++++++++++++ src/State/HubInterface.php | 4 ++++ src/functions.php | 14 ++++++++++++ tests/FunctionsTest.php | 37 +++++++++++++++++++++++++++++++ tests/State/HubAdapterTest.php | 40 ++++++++++++++++++++++++++++++++++ tests/State/HubTest.php | 36 ++++++++++++++++++++++++++++++ 7 files changed, 180 insertions(+) diff --git a/src/State/Hub.php b/src/State/Hub.php index 152dec95a..02c0a3390 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -5,11 +5,14 @@ namespace Sentry\State; use Sentry\Breadcrumb; +use Sentry\CheckIn; +use Sentry\CheckInStatus; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; +use Sentry\MonitorConfig; use Sentry\Severity; use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; @@ -169,6 +172,38 @@ public function captureLastError(?EventHint $hint = null): ?EventId return null; } + /** + * @param string $slug Identifier of the Monitor + * @param CheckInStatus $status The status of the check-in + * @param int|float|null $duration The duration of the check-in + * @param MonitorConfig|null $monitorConfig Configuration of the Monitor + * @param string|null $checkInId A check-in ID from the previous check-in + */ + public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string + { + $client = $this->getClient(); + + if (null === $client) { + return null; + } + + $options = $client->getOptions(); + $event = Event::createCheckIn(); + $checkIn = new CheckIn( + $slug, + $status, + $checkInId, + $options->getRelease(), + $options->getEnvironment(), + $duration, + $monitorConfig + ); + $event->setCheckIn($checkIn); + $this->captureEvent($event); + + return $checkIn->getId(); + } + /** * {@inheritdoc} */ diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index eb3da1688..37f4b6395 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -5,11 +5,13 @@ namespace Sentry\State; use Sentry\Breadcrumb; +use Sentry\CheckInStatus; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; +use Sentry\MonitorConfig; use Sentry\SentrySdk; use Sentry\Severity; use Sentry\Tracing\Span; @@ -135,6 +137,18 @@ public function captureLastError(?EventHint $hint = null): ?EventId return SentrySdk::getCurrentHub()->captureLastError($hint); } + /** + * @param string $slug Identifier of the Monitor + * @param CheckInStatus $status The status of the check-in + * @param int|float|null $duration The duration of the check-in + * @param MonitorConfig|null $monitorConfig Configuration of the Monitor + * @param string|null $checkInId A check-in ID from the previous check-in + */ + public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string + { + return SentrySdk::getCurrentHub()->captureCheckIn($slug, $status, $duration, $monitorConfig, $checkInId); + } + /** * {@inheritdoc} */ diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 07ff16879..740686f3f 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -5,11 +5,13 @@ namespace Sentry\State; use Sentry\Breadcrumb; +use Sentry\CheckInStatus; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; +use Sentry\MonitorConfig; use Sentry\Severity; use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; @@ -20,6 +22,8 @@ * This interface represent the class which is responsible for maintaining a * stack of pairs of clients and scopes. It is the main entry point to talk * with the Sentry client. + * + * @method string|null captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $upsertMonitorConfig = null, ?string $checkInId = null) Captures a check-in */ interface HubInterface { diff --git a/src/functions.php b/src/functions.php index 158c00b60..4956b1226 100644 --- a/src/functions.php +++ b/src/functions.php @@ -66,6 +66,20 @@ function captureLastError(?EventHint $hint = null): ?EventId return SentrySdk::getCurrentHub()->captureLastError($hint); } +/** + * Captures a check-in and sends it to Sentry. + * + * @param string $slug Identifier of the Monitor + * @param CheckInStatus $status The status of the check-in + * @param int|float|null $duration The duration of the check-in + * @param MonitorConfig|null $monitorConfig Configuration of the Monitor + * @param string|null $checkInId A check-in ID from the previous check-in + */ +function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string +{ + return SentrySdk::getCurrentHub()->captureCheckIn($slug, $status, $duration, $monitorConfig, $checkInId); +} + /** * Records a new breadcrumb which will be attached to future events. They * will be added to subsequent events to provide more context on user's diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 473e052d9..a01b19104 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -8,10 +8,13 @@ use PHPUnit\Framework\TestCase; use RuntimeException; use Sentry\Breadcrumb; +use Sentry\CheckInStatus; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; use Sentry\EventId; +use Sentry\MonitorConfig; +use Sentry\MonitorSchedule; use Sentry\Options; use Sentry\SentrySdk; use Sentry\Severity; @@ -25,7 +28,9 @@ use Sentry\Tracing\TraceId; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; +use Sentry\Util\SentryUid; use function Sentry\addBreadcrumb; +use function Sentry\captureCheckIn; use function Sentry\captureEvent; use function Sentry\captureException; use function Sentry\captureLastError; @@ -185,6 +190,38 @@ public static function captureLastErrorDataProvider(): \Generator ]; } + public function testCaptureCheckIn() + { + $hub = new Hub(); + $options = new Options([ + 'environment' => Event::DEFAULT_ENVIRONMENT, + 'release' => '1.1.8', + ]); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn($options); + + $hub->bindClient($client); + SentrySdk::setCurrentHub($hub); + + $checkInId = SentryUid::generate(); + + $this->assertSame($checkInId, captureCheckIn( + 'test-crontab', + CheckInStatus::ok(), + 10, + new MonitorConfig( + MonitorSchedule::crontab('*/5 * * * *'), + 5, + 30, + 'UTC' + ), + $checkInId + )); + } + public function testAddBreadcrumb(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); diff --git a/tests/State/HubAdapterTest.php b/tests/State/HubAdapterTest.php index c876fca55..982cfa00e 100644 --- a/tests/State/HubAdapterTest.php +++ b/tests/State/HubAdapterTest.php @@ -4,21 +4,28 @@ namespace Sentry\Tests\State; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\CheckInStatus; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; +use Sentry\MonitorConfig; +use Sentry\MonitorSchedule; +use Sentry\Options; use Sentry\SentrySdk; use Sentry\Severity; +use Sentry\State\Hub; use Sentry\State\HubAdapter; use Sentry\State\HubInterface; use Sentry\State\Scope; use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; +use Sentry\Util\SentryUid; final class HubAdapterTest extends TestCase { @@ -255,6 +262,39 @@ public static function captureLastErrorDataProvider(): \Generator ]; } + public function testCaptureCheckIn() + { + $hub = new Hub(); + + $options = new Options([ + 'environment' => Event::DEFAULT_ENVIRONMENT, + 'release' => '1.1.8', + ]); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn($options); + + $hub->bindClient($client); + SentrySdk::setCurrentHub($hub); + + $checkInId = SentryUid::generate(); + + $this->assertSame($checkInId, HubAdapter::getInstance()->captureCheckIn( + 'test-crontab', + CheckInStatus::ok(), + 10, + new MonitorConfig( + MonitorSchedule::crontab('*/5 * * * *'), + 5, + 30, + 'UTC' + ), + $checkInId + )); + } + public function testAddBreadcrumb(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_DEBUG, Breadcrumb::TYPE_ERROR, 'user'); diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 3719d5864..d2b27e7e5 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -7,11 +7,14 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\CheckInStatus; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; use Sentry\EventId; use Sentry\Integration\IntegrationInterface; +use Sentry\MonitorConfig; +use Sentry\MonitorSchedule; use Sentry\Options; use Sentry\Severity; use Sentry\State\Hub; @@ -19,6 +22,7 @@ use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SamplingContext; use Sentry\Tracing\TransactionContext; +use Sentry\Util\SentryUid; final class HubTest extends TestCase { @@ -390,6 +394,38 @@ public static function captureLastErrorDataProvider(): \Generator ]; } + public function testCaptureCheckIn() + { + $hub = new Hub(); + + $options = new Options([ + 'environment' => Event::DEFAULT_ENVIRONMENT, + 'release' => '1.1.8', + ]); + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn($options); + + $hub->bindClient($client); + + $checkInId = SentryUid::generate(); + + $this->assertSame($checkInId, $hub->captureCheckIn( + 'test-crontab', + CheckInStatus::ok(), + 10, + new MonitorConfig( + MonitorSchedule::crontab('*/5 * * * *'), + 5, + 30, + 'UTC' + ), + $checkInId + )); + } + public function testCaptureEvent(): void { $hub = new Hub(); From abd3a0eb9da298277213e2f51d12b87f17acd5da Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 31 Jul 2023 17:29:50 +0200 Subject: [PATCH 0889/1161] Prepare 3.21.0 (#1575) --- CHANGELOG.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60df05957..bb581d1b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,63 @@ # CHANGELOG +## 3.21.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.21.0. + +### Features + +- Add `Sentry::captureCheckIn()` [(#1573)](https://github.com/getsentry/sentry-php/pull/1573) + + Sending check-ins from the SDK is now simplified. + + ```php + $checkInId = Sentry\captureCheckIn( + slug: 'monitor-slug', + status: CheckInStatus::inProgress() + ); + + + // do something + + Sentry\captureCheckIn( + checkInId: $checkInId, + slug: 'monitor-slug', + status: CheckInStatus::ok() + ); + ``` + + You can also pass in a `monitorConfig` object as well as the `duration`. + +- Undeprecate the `tags` option [(#1561)](https://github.com/getsentry/sentry-php/pull/1561) + + You can now set tags that are applied to each event when calling `Sentry::init()`. + + ```php + Sentry\init([ + 'tags' => [ + 'foo' => 'bar', + ], + ]) + ``` + +- Apply the `prefixes`option to profiling frames [(#1568)](https://github.com/getsentry/sentry-php/pull/1568) + + If you added the `prefixes` option when calling `Sentry::init()`, this option will now also apply to profile frames. + + ```php + Sentry\init([ + 'prefixes' => ['/var/www/html'], + ]) + ``` + +### Misc + +- Deduplicate profile stacks and frames [(#1570)](https://github.com/getsentry/sentry-php/pull/1570) + + This will decrease the payload size of the `profile` event payload. + +- Add the transaction's sampling decision to the trace envelope header [(#1562)](https://github.com/getsentry/sentry-php/pull/1562) + ## 3.20.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.20.1. From 624aafc22b84b089ffa43b71fb01e0096505ec4f Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 31 Jul 2023 15:31:24 +0000 Subject: [PATCH 0890/1161] release: 3.21.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 15aba1ca7..3d0b6d305 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.20.1'; + public const SDK_VERSION = '3.21.0'; /** * @var Options The client options From 7dec1769cc195d8e6032e8a0053f77ca96391c25 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Tue, 1 Aug 2023 16:52:55 +0200 Subject: [PATCH 0891/1161] Improve tests of `captureCheckIn()` (#1577) --- src/State/Hub.php | 8 +++--- src/State/HubAdapter.php | 8 +++--- src/State/HubInterface.php | 2 +- tests/FunctionsTest.php | 40 ++++++++++++++--------------- tests/State/HubTest.php | 52 +++++++++++++++++++++++--------------- 5 files changed, 59 insertions(+), 51 deletions(-) diff --git a/src/State/Hub.php b/src/State/Hub.php index 02c0a3390..5a5ed5ea6 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -173,11 +173,9 @@ public function captureLastError(?EventHint $hint = null): ?EventId } /** - * @param string $slug Identifier of the Monitor - * @param CheckInStatus $status The status of the check-in - * @param int|float|null $duration The duration of the check-in - * @param MonitorConfig|null $monitorConfig Configuration of the Monitor - * @param string|null $checkInId A check-in ID from the previous check-in + * {@inheritdoc} + * + * @param int|float|null $duration */ public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string { diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 37f4b6395..8f637860f 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -138,11 +138,9 @@ public function captureLastError(?EventHint $hint = null): ?EventId } /** - * @param string $slug Identifier of the Monitor - * @param CheckInStatus $status The status of the check-in - * @param int|float|null $duration The duration of the check-in - * @param MonitorConfig|null $monitorConfig Configuration of the Monitor - * @param string|null $checkInId A check-in ID from the previous check-in + * {@inheritdoc} + * + * @param int|float|null $duration */ public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string { diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 740686f3f..9b4c26ceb 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -23,7 +23,7 @@ * stack of pairs of clients and scopes. It is the main entry point to talk * with the Sentry client. * - * @method string|null captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $upsertMonitorConfig = null, ?string $checkInId = null) Captures a check-in + * @method string|null captureCheckIn(string $slug, CheckInStatus $status, int|float|null $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null) Captures a check-in */ interface HubInterface { diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index a01b19104..26d329770 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -190,34 +190,29 @@ public static function captureLastErrorDataProvider(): \Generator ]; } - public function testCaptureCheckIn() + public function testCaptureCheckIn(): void { - $hub = new Hub(); - $options = new Options([ - 'environment' => Event::DEFAULT_ENVIRONMENT, - 'release' => '1.1.8', - ]); - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn($options); + $checkInId = SentryUid::generate(); + $monitorConfig = new MonitorConfig( + MonitorSchedule::crontab('*/5 * * * *'), + 5, + 30, + 'UTC' + ); - $hub->bindClient($client); - SentrySdk::setCurrentHub($hub); + $hub = $this->createMock(StubHubInterface::class); + $hub->expects($this->once()) + ->method('captureCheckIn') + ->with('test-crontab', CheckInStatus::ok(), 10, $monitorConfig, $checkInId) + ->willReturn($checkInId); - $checkInId = SentryUid::generate(); + SentrySdk::setCurrentHub($hub); $this->assertSame($checkInId, captureCheckIn( 'test-crontab', CheckInStatus::ok(), 10, - new MonitorConfig( - MonitorSchedule::crontab('*/5 * * * *'), - 5, - 30, - 'UTC' - ), + $monitorConfig, $checkInId )); } @@ -445,3 +440,8 @@ public function testContinueTrace(): void }); } } + +interface StubHubInterface extends HubInterface +{ + public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string; +} diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index d2b27e7e5..f3949bf04 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\CheckIn; use Sentry\CheckInStatus; use Sentry\ClientInterface; use Sentry\Event; @@ -394,35 +395,46 @@ public static function captureLastErrorDataProvider(): \Generator ]; } - public function testCaptureCheckIn() + public function testCaptureCheckIn(): void { - $hub = new Hub(); - - $options = new Options([ - 'environment' => Event::DEFAULT_ENVIRONMENT, - 'release' => '1.1.8', - ]); - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn($options); - - $hub->bindClient($client); - - $checkInId = SentryUid::generate(); - - $this->assertSame($checkInId, $hub->captureCheckIn( + $expectedCheckIn = new CheckIn( 'test-crontab', CheckInStatus::ok(), + SentryUid::generate(), + '0.0.1-dev', + Event::DEFAULT_ENVIRONMENT, 10, new MonitorConfig( MonitorSchedule::crontab('*/5 * * * *'), 5, 30, 'UTC' - ), - $checkInId + ) + ); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'environment' => Event::DEFAULT_ENVIRONMENT, + 'release' => '0.0.1-dev', + ])); + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(static function (Event $event) use ($expectedCheckIn): bool { + return $event->getCheckIn() == $expectedCheckIn; + })); + + $hub = new Hub($client); + + $this->assertSame($expectedCheckIn->getId(), $hub->captureCheckIn( + $expectedCheckIn->getMonitorSlug(), + $expectedCheckIn->getStatus(), + $expectedCheckIn->getDuration(), + $expectedCheckIn->getMonitorConfig(), + $expectedCheckIn->getId() )); } From 51fb4c714474e18b87753e46d8919f7c59bed1c8 Mon Sep 17 00:00:00 2001 From: mark burdett Date: Tue, 8 Aug 2023 01:58:16 -0700 Subject: [PATCH 0892/1161] Don't add empty HTTP fragment or query to breadcrumb data (#1580) --- src/Tracing/GuzzleTracingMiddleware.php | 8 ++++++-- tests/Tracing/GuzzleTracingMiddlewareTest.php | 6 ------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 8cc9ac345..37990e470 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -80,9 +80,13 @@ public static function trace(?HubInterface $hub = null): Closure 'url' => (string) $partialUri, 'method' => $request->getMethod(), 'request_body_size' => $request->getBody()->getSize(), - 'http.query' => $request->getUri()->getQuery(), - 'http.fragment' => $request->getUri()->getFragment(), ]; + if ('' !== $request->getUri()->getQuery()) { + $breadcrumbData['http.query'] = $request->getUri()->getQuery(); + } + if ('' !== $request->getUri()->getFragment()) { + $breadcrumbData['http.fragment'] = $request->getUri()->getFragment(); + } if (null !== $response) { $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index ba1a17af4..05942a5e0 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -272,8 +272,6 @@ public static function traceDataProvider(): iterable 'url' => 'https://www.example.com', 'method' => 'GET', 'request_body_size' => 0, - 'http.query' => '', - 'http.fragment' => '', 'status_code' => 200, 'response_body_size' => 0, ], @@ -300,8 +298,6 @@ public static function traceDataProvider(): iterable 'url' => 'https://www.example.com', 'method' => 'POST', 'request_body_size' => 10, - 'http.query' => '', - 'http.fragment' => '', 'status_code' => 403, 'response_body_size' => 6, ], @@ -314,8 +310,6 @@ public static function traceDataProvider(): iterable 'url' => 'https://www.example.com', 'method' => 'GET', 'request_body_size' => 0, - 'http.query' => '', - 'http.fragment' => '', ], ]; } From 95d3d3a518010299fc928609ddb05f0067f0b6ac Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 10 Aug 2023 10:05:40 +0200 Subject: [PATCH 0893/1161] Add starfish v1 attributes to span data/breadcrumbs (#1581) --- src/Tracing/GuzzleTracingMiddleware.php | 9 +++--- tests/Tracing/GuzzleTracingMiddlewareTest.php | 28 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 37990e470..fa534b7b6 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -50,6 +50,7 @@ public static function trace(?HubInterface $hub = null): Closure $spanContext->setOp('http.client'); $spanContext->setDescription($request->getMethod() . ' ' . (string) $partialUri); $spanContext->setData([ + 'http.request.method' => $request->getMethod(), 'http.query' => $request->getUri()->getQuery(), 'http.fragment' => $request->getUri()->getFragment(), ]); @@ -78,8 +79,8 @@ public static function trace(?HubInterface $hub = null): Closure $breadcrumbData = [ 'url' => (string) $partialUri, - 'method' => $request->getMethod(), - 'request_body_size' => $request->getBody()->getSize(), + 'http.request.method' => $request->getMethod(), + 'http.request.body.size' => $request->getBody()->getSize(), ]; if ('' !== $request->getUri()->getQuery()) { $breadcrumbData['http.query'] = $request->getUri()->getQuery(); @@ -91,8 +92,8 @@ public static function trace(?HubInterface $hub = null): Closure if (null !== $response) { $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); - $breadcrumbData['status_code'] = $response->getStatusCode(); - $breadcrumbData['response_body_size'] = $response->getBody()->getSize(); + $breadcrumbData['http.response.status_code'] = $response->getStatusCode(); + $breadcrumbData['http.response.body.size'] = $response->getBody()->getSize(); } else { $childSpan->setStatus(SpanStatus::internalError()); } diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 05942a5e0..03010e950 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -270,10 +270,10 @@ public static function traceDataProvider(): iterable new Response(), [ 'url' => 'https://www.example.com', - 'method' => 'GET', - 'request_body_size' => 0, - 'status_code' => 200, - 'response_body_size' => 0, + 'http.request.method' => 'GET', + 'http.request.body.size' => 0, + 'http.response.status_code' => 200, + 'http.response.body.size' => 0, ], ]; @@ -282,12 +282,12 @@ public static function traceDataProvider(): iterable new Response(), [ 'url' => 'https://www.example.com', - 'method' => 'GET', - 'request_body_size' => 0, + 'http.request.method' => 'GET', + 'http.request.body.size' => 0, 'http.query' => 'query=string', 'http.fragment' => 'fragment=1', - 'status_code' => 200, - 'response_body_size' => 0, + 'http.response.status_code' => 200, + 'http.response.body.size' => 0, ], ]; @@ -296,10 +296,10 @@ public static function traceDataProvider(): iterable new Response(403, [], 'sentry'), [ 'url' => 'https://www.example.com', - 'method' => 'POST', - 'request_body_size' => 10, - 'status_code' => 403, - 'response_body_size' => 6, + 'http.request.method' => 'POST', + 'http.request.body.size' => 10, + 'http.response.status_code' => 403, + 'http.response.body.size' => 6, ], ]; @@ -308,8 +308,8 @@ public static function traceDataProvider(): iterable new \Exception(), [ 'url' => 'https://www.example.com', - 'method' => 'GET', - 'request_body_size' => 0, + 'http.request.method' => 'GET', + 'http.request.body.size' => 0, ], ]; } From db1d5a8ddbbbf02c70ae74f57db1112b30e73ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= <8600029+tobias-kuendig@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:56:55 +0200 Subject: [PATCH 0894/1161] Add 3rd party integration for October CMS to the README (#1583) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index edcc9d461..022a392fe 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ The following integrations are available and maintained by members of the Sentry - [Silverstripe](https://github.com/phptek/silverstripe-sentry) - [CakePHP 3.0 - 4.3](https://github.com/Connehito/cake-sentry) - [CakePHP 4.4+](https://github.com/lordsimal/cakephp-sentry) +- [October CMS](https://github.com/OFFLINE-GmbH/oc-sentry-plugin) - ... feel free to be famous, create a port to your favourite platform! ## 3rd party integrations using old SDK 2.x From 238ed086ecba72663f6f3d816fb37cb8c277b989 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Thu, 31 Aug 2023 18:11:32 +0200 Subject: [PATCH 0895/1161] Remove old badges for build and code coverage of `develop` branch from the README (#1584) --- README.md | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 022a392fe..12da4a6b2 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,14 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to he # Official Sentry SDK for PHP +[![CI](https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=master)](https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Amaster) +[![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-php/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-php/branch/master) [![Latest Stable Version](https://poser.pugx.org/sentry/sentry/v/stable)](https://packagist.org/packages/sentry/sentry) [![License](https://poser.pugx.org/sentry/sentry/license)](https://packagist.org/packages/sentry/sentry) [![Total Downloads](https://poser.pugx.org/sentry/sentry/downloads)](https://packagist.org/packages/sentry/sentry) [![Monthly Downloads](https://poser.pugx.org/sentry/sentry/d/monthly)](https://packagist.org/packages/sentry/sentry) [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/cWnMQeA) -| Version | Build Status | Code Coverage | -|:---------:|:-------------:|:-----:| -| `master`| [![CI][master Build Status Image]][master Build Status] | [![Coverage Status][master Code Coverage Image]][master Code Coverage] | -| `develop`| [![CI][develop Build Status Image]][develop Build Status] | [![Coverage Status][develop Code Coverage Image]][develop Code Coverage] | - The Sentry PHP error reporter tracks errors and exceptions that happen during the execution of your application and provides instant notification with detailed information needed to prioritize, identify, reproduce and fix each issue. @@ -131,12 +128,3 @@ If you need help setting up or configuring the PHP SDK (or anything else in the ## License Licensed under the MIT license, see [`LICENSE`](LICENSE) - -[master Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Amaster -[master Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=master -[develop Build Status]: https://github.com/getsentry/sentry-php/actions?query=workflow%3ACI+branch%3Adevelop -[develop Build Status Image]: https://github.com/getsentry/sentry-php/workflows/CI/badge.svg?branch=develop -[master Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/master -[master Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/master?logo=codecov -[develop Code Coverage]: https://codecov.io/gh/getsentry/sentry-php/branch/develop -[develop Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-php/develop?logo=codecov From e39ed0e08c84833ec96ee3e5e9001676595a2264 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 01:06:29 +0200 Subject: [PATCH 0896/1161] chore(deps): bump actions/checkout from 3 to 4 (#1585) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/publish-release.yaml | 2 +- .github/workflows/static-analysis.yaml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b2098430..cdcfb2ec0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 80a6975b0..73f83e74e 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest name: Release version steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.GH_RELEASE_PAT }} fetch-depth: 0 diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index df0356e0e..f61e77a0a 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 # needed by codecov sometimes From faec023aeb27292fb3092545f63ad6b706390364 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 15 Sep 2023 15:51:55 +0200 Subject: [PATCH 0897/1161] Remove obsolete `tags` option depreaction (#1588) --- src/Options.php | 7 ------- tests/ClientTest.php | 4 ---- 2 files changed, 11 deletions(-) diff --git a/src/Options.php b/src/Options.php index f17e6955e..c78416252 100644 --- a/src/Options.php +++ b/src/Options.php @@ -977,13 +977,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedValues('context_lines', \Closure::fromCallable([$this, 'validateContextLinesOption'])); $resolver->setNormalizer('dsn', \Closure::fromCallable([$this, 'normalizeDsnOption'])); - $resolver->setNormalizer('tags', static function (SymfonyOptions $options, array $value): array { - if (!empty($value)) { - @trigger_error('The option "tags" is deprecated since version 3.2 and will be removed in 4.0. Either set the tags on the scope or on the event.', \E_USER_DEPRECATED); - } - - return $value; - }); $resolver->setNormalizer('prefixes', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 395c96c82..749b25836 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -203,10 +203,6 @@ public static function captureExceptionWithEventHintDataProvider(): \Generator */ public function testCaptureEvent(array $options, Event $event, Event $expectedEvent): void { - if (isset($options['tags'])) { - $this->expectDeprecation('The option "tags" is deprecated since version 3.2 and will be removed in 4.0. Either set the tags on the scope or on the event.'); - } - $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) ->method('send') From 0a8b1e7f4eacc5fc2f5c083ee69f52893b11258d Mon Sep 17 00:00:00 2001 From: Jesper Noordsij <45041769+jnoordsij@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:19:33 +0200 Subject: [PATCH 0898/1161] Run CI tests for PHP 8.3 (#1591) --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdcfb2ec0..e7b150bed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ jobs: - '8.0' - '8.1' - '8.2' + - '8.3' dependencies: - lowest - highest @@ -64,10 +65,6 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist if: ${{ matrix.dependencies == 'highest' }} - - name: Restrict lowest Symfony version on PHP 8.1 & 8.2 - run: composer require symfony/options-resolver:^4.4.30 --no-update - if: ${{ matrix.dependencies == 'lowest' && matrix.php == '8.1' || matrix.php == '8.2' }} - - name: Install lowest dependencies run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest if: ${{ matrix.dependencies == 'lowest' }} From b6dc1d204fa40bbef99fd439168165bb31b4509a Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 17 Oct 2023 20:39:01 +0200 Subject: [PATCH 0899/1161] Remove `symfony/polyfill-php80` (#1592) --- composer.json | 3 +-- src/FrameBuilder.php | 8 ++++---- src/Tracing/DynamicSamplingContext.php | 2 +- src/Util/PrefixStripper.php | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 32ea2f592..2fc11051f 100644 --- a/composer.json +++ b/composer.json @@ -34,8 +34,7 @@ "psr/http-factory": "^1.0", "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", - "symfony/polyfill-php80": "^1.17" + "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.19|3.4.*", diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 5cd61bae6..10854dcdb 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -74,7 +74,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['class']; - if (str_starts_with($functionName, Frame::ANONYMOUS_CLASS_PREFIX)) { + if (Frame::ANONYMOUS_CLASS_PREFIX === mb_substr($functionName, 0, mb_strlen(Frame::ANONYMOUS_CLASS_PREFIX))) { $functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath($this->options, substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX))); } @@ -107,7 +107,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool return false; } - if (null !== $functionName && str_starts_with($functionName, 'Sentry\\')) { + if (null !== $functionName && 'Sentry\\' === substr($functionName, 0, \strlen('Sentry\\'))) { return false; } @@ -117,7 +117,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool $isInApp = true; foreach ($excludedAppPaths as $excludedAppPath) { - if (str_starts_with($absoluteFilePath, $excludedAppPath)) { + if (mb_substr($absoluteFilePath, 0, mb_strlen($excludedAppPath)) === $excludedAppPath) { $isInApp = false; break; @@ -125,7 +125,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool } foreach ($includedAppPaths as $includedAppPath) { - if (str_starts_with($absoluteFilePath, $includedAppPath)) { + if (mb_substr($absoluteFilePath, 0, mb_strlen($includedAppPath)) === $includedAppPath) { $isInApp = true; break; diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index a9ac69ade..b737ea960 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -127,7 +127,7 @@ public static function fromHeader(string $header): self [$key, $value] = explode('=', $keyValue, 2); - if (str_starts_with($key, self::SENTRY_ENTRY_PREFIX)) { + if (self::SENTRY_ENTRY_PREFIX === mb_substr($key, 0, mb_strlen(self::SENTRY_ENTRY_PREFIX))) { $samplingContext->set(rawurldecode(mb_substr($key, mb_strlen(self::SENTRY_ENTRY_PREFIX))), rawurldecode($value)); } } diff --git a/src/Util/PrefixStripper.php b/src/Util/PrefixStripper.php index 35c261312..d4f24286a 100644 --- a/src/Util/PrefixStripper.php +++ b/src/Util/PrefixStripper.php @@ -18,7 +18,7 @@ protected function stripPrefixFromFilePath(?Options $options, string $filePath): } foreach ($options->getPrefixes() as $prefix) { - if (str_starts_with($filePath, $prefix)) { + if (mb_substr($filePath, 0, mb_strlen($prefix)) === $prefix) { return mb_substr($filePath, mb_strlen($prefix)); } } From 83d6d017ea68f2ae1be79b59e33a8765052228fa Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 18 Oct 2023 16:53:03 +0200 Subject: [PATCH 0900/1161] Add a default cURL HTTP client (#1589) --- composer.json | 11 +- phpstan-baseline.neon | 65 ++-- src/Client.php | 14 +- src/ClientBuilder.php | 120 +++----- src/ClientBuilderInterface.php | 97 ------ src/ClientInterface.php | 4 +- .../Authentication/SentryAuthentication.php | 78 ----- src/HttpClient/HttpClient.php | 94 ++++++ src/HttpClient/HttpClientFactory.php | 165 ----------- src/HttpClient/HttpClientFactoryInterface.php | 22 -- src/HttpClient/HttpClientInterface.php | 12 + src/HttpClient/Plugin/GzipEncoderPlugin.php | 64 ---- src/HttpClient/Response.php | 94 ++++++ src/Options.php | 57 ++++ src/Transport/DefaultTransportFactory.php | 74 ----- src/Transport/HttpTransport.php | 94 ++---- src/Transport/NullTransport.php | 17 +- src/Transport/RateLimiter.php | 12 +- src/{Response.php => Transport/Result.php} | 12 +- .../ResultStatus.php} | 4 +- src/Transport/TransportFactoryInterface.php | 21 -- src/Transport/TransportInterface.php | 24 +- src/Util/Http.php | 55 ++++ tests/ClientBuilderTest.php | 81 +++-- tests/ClientTest.php | 84 ++---- .../SentryAuthenticationTest.php | 61 ---- tests/HttpClient/HttpClientFactoryTest.php | 104 ------- .../Plugin/GzipEncoderPluginTest.php | 40 --- tests/OptionsTest.php | 39 +++ .../Transport/DefaultTransportFactoryTest.php | 50 ---- tests/Transport/HttpTransportTest.php | 279 +++++------------- tests/Transport/NullTransportTest.php | 16 +- tests/Transport/RateLimiterTest.php | 41 ++- .../ResultStatusTest.php} | 46 +-- tests/Util/HttpTest.php | 43 +++ ...errors_not_silencable_on_php_8_and_up.phpt | 30 +- .../error_handler_captures_fatal_error.phpt | 34 +-- ...spects_capture_silenced_errors_option.phpt | 34 +-- ...espects_current_error_reporting_level.phpt | 30 +- ..._option_regardless_of_error_reporting.phpt | 32 +- ...tegration_respects_error_types_option.phpt | 28 +- ...rror_integration_captures_fatal_error.phpt | 28 +- ...tegration_respects_error_types_option.phpt | 28 +- 43 files changed, 820 insertions(+), 1518 deletions(-) delete mode 100644 src/ClientBuilderInterface.php delete mode 100644 src/HttpClient/Authentication/SentryAuthentication.php create mode 100644 src/HttpClient/HttpClient.php delete mode 100644 src/HttpClient/HttpClientFactory.php delete mode 100644 src/HttpClient/HttpClientFactoryInterface.php create mode 100644 src/HttpClient/HttpClientInterface.php delete mode 100644 src/HttpClient/Plugin/GzipEncoderPlugin.php create mode 100644 src/HttpClient/Response.php delete mode 100644 src/Transport/DefaultTransportFactory.php rename src/{Response.php => Transport/Result.php} (74%) rename src/{ResponseStatus.php => Transport/ResultStatus.php} (97%) delete mode 100644 src/Transport/TransportFactoryInterface.php create mode 100644 src/Util/Http.php delete mode 100644 tests/HttpClient/Authentication/SentryAuthenticationTest.php delete mode 100644 tests/HttpClient/HttpClientFactoryTest.php delete mode 100644 tests/HttpClient/Plugin/GzipEncoderPluginTest.php delete mode 100644 tests/Transport/DefaultTransportFactoryTest.php rename tests/{ResponseStatusTest.php => Transport/ResultStatusTest.php} (53%) create mode 100644 tests/Util/HttpTest.php diff --git a/composer.json b/composer.json index 2fc11051f..34cb61d32 100644 --- a/composer.json +++ b/composer.json @@ -23,21 +23,14 @@ "php": "^7.2|^8.0", "ext-json": "*", "ext-mbstring": "*", - "guzzlehttp/promises": "^1.5.3|^2.0", + "ext-curl": "*", "jean85/pretty-package-versions": "^1.5|^2.0.4", - "php-http/async-client-implementation": "^1.0", - "php-http/client-common": "^1.5|^2.0", - "php-http/discovery": "^1.15", - "php-http/httplug": "^1.1|^2.0", - "php-http/message": "^1.5", - "php-http/message-factory": "^1.1", - "psr/http-factory": "^1.0", - "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.19|3.4.*", + "guzzlehttp/promises": "^1.0|^2.0", "guzzlehttp/psr7": "^1.8.4|^2.1.1", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.6|^2.0|^3.0", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 39aa31fc7..3ca6264b3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -35,51 +35,6 @@ parameters: count: 1 path: src/Dsn.php - - - message: "#^Access to constant CONNECT_TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Access to constant PROXY on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Access to constant TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Call to static method create\\(\\) on an unknown class Symfony\\\\Component\\\\HttpClient\\\\HttpClient\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Call to static method createWithConfig\\(\\) on an unknown class Http\\\\Adapter\\\\Guzzle6\\\\Client\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$responseFactory\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$uriFactory\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Http\\\\Client\\\\Curl\\\\Client\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Symfony\\\\Component\\\\HttpClient\\\\HttplugClient\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - message: "#^Property Sentry\\\\Integration\\\\IgnoreErrorsIntegration\\:\\:\\$options \\(array\\{ignore_exceptions\\: array\\\\>, ignore_tags\\: array\\\\}\\) does not accept array\\.$#" count: 1 @@ -140,6 +95,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpClient\\(\\) should return Sentry\\\\HttpClient\\\\HttpClientInterface\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getHttpConnectTimeout\\(\\) should return float but returns mixed\\.$#" count: 1 @@ -150,6 +110,16 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpProxyAuthentication\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpSslVerifyPeer\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getHttpTimeout\\(\\) should return float but returns mixed\\.$#" count: 1 @@ -245,6 +215,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getTransport\\(\\) should return Sentry\\\\Transport\\\\TransportInterface\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:hasDefaultIntegrations\\(\\) should return bool but returns mixed\\.$#" count: 1 diff --git a/src/Client.php b/src/Client.php index 3d0b6d305..33d74927d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -4,7 +4,6 @@ namespace Sentry; -use GuzzleHttp\Promise\PromiseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Integration\IntegrationInterface; @@ -13,6 +12,7 @@ use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; +use Sentry\Transport\Result; use Sentry\Transport\TransportInterface; /** @@ -173,14 +173,18 @@ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scop } try { - /** @var Response $response */ - $response = $this->transport->send($event)->wait(); - $event = $response->getEvent(); + /** @var Result $result */ + $result = $this->transport->send($event); + $event = $result->getEvent(); if (null !== $event) { return $event->getId(); } } catch (\Throwable $exception) { + $this->logger->error( + sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), + ['exception' => $exception, 'event' => $event] + ); } return null; @@ -216,7 +220,7 @@ public function getIntegration(string $className): ?IntegrationInterface /** * {@inheritdoc} */ - public function flush(?int $timeout = null): PromiseInterface + public function flush(?int $timeout = null): Result { return $this->transport->close($timeout); } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 8cdcc7317..aaefee950 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,21 +4,21 @@ namespace Sentry; -use Http\Discovery\Psr17FactoryDiscovery; use Psr\Log\LoggerInterface; -use Sentry\HttpClient\HttpClientFactory; +use Sentry\HttpClient\HttpClient; +use Sentry\HttpClient\HttpClientInterface; +use Sentry\Serializer\PayloadSerializer; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\SerializerInterface; -use Sentry\Transport\DefaultTransportFactory; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\HttpTransport; use Sentry\Transport\TransportInterface; /** - * The default implementation of {@link ClientBuilderInterface}. + * A configurable builder for Client objects. * - * @author Stefano Arlandini + * @internal */ -final class ClientBuilder implements ClientBuilderInterface +final class ClientBuilder { /** * @var Options The client options @@ -26,14 +26,14 @@ final class ClientBuilder implements ClientBuilderInterface private $options; /** - * @var TransportFactoryInterface|null The transport factory + * @var TransportInterface The transport */ - private $transportFactory; + private $transport; /** - * @var TransportInterface|null The transport + * @var HttpClientInterface The HTTP client */ - private $transport; + private $httpClient; /** * @var SerializerInterface|null The serializer to be injected in the client @@ -68,127 +68,97 @@ final class ClientBuilder implements ClientBuilderInterface public function __construct(Options $options = null) { $this->options = $options ?? new Options(); + + $this->httpClient = $this->options->getHttpClient() ?? new HttpClient($this->sdkIdentifier, $this->sdkVersion); + $this->transport = $this->options->getTransport() ?? new HttpTransport( + $this->options, + $this->httpClient, + new PayloadSerializer($this->options), + $this->logger + ); } /** - * {@inheritdoc} + * @param array $options The client options, in naked array form */ - public static function create(array $options = []): ClientBuilderInterface + public static function create(array $options = []): ClientBuilder { return new self(new Options($options)); } - /** - * {@inheritdoc} - */ public function getOptions(): Options { return $this->options; } - /** - * {@inheritdoc} - */ - public function setSerializer(SerializerInterface $serializer): ClientBuilderInterface + public function setSerializer(SerializerInterface $serializer): ClientBuilder { $this->serializer = $serializer; return $this; } - /** - * {@inheritdoc} - */ - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): ClientBuilderInterface + public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): ClientBuilder { $this->representationSerializer = $representationSerializer; return $this; } - /** - * {@inheritdoc} - */ - public function setLogger(LoggerInterface $logger): ClientBuilderInterface + public function setLogger(LoggerInterface $logger): ClientBuilder { $this->logger = $logger; return $this; } - /** - * {@inheritdoc} - */ - public function setSdkIdentifier(string $sdkIdentifier): ClientBuilderInterface + public function setSdkIdentifier(string $sdkIdentifier): ClientBuilder { $this->sdkIdentifier = $sdkIdentifier; return $this; } - /** - * {@inheritdoc} - */ - public function setSdkVersion(string $sdkVersion): ClientBuilderInterface + public function setSdkVersion(string $sdkVersion): ClientBuilder { $this->sdkVersion = $sdkVersion; return $this; } - /** - * {@inheritdoc} - */ - public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface + public function getTransport(): TransportInterface { - $this->transportFactory = $transportFactory; - - return $this; + return $this->transport; } - /** - * {@inheritdoc} - */ - public function getClient(): ClientInterface + public function setTransport(TransportInterface $transport): ClientBuilder { - $this->transport = $this->transport ?? $this->createTransportInstance(); + $this->transport = $transport; - return new Client($this->options, $this->transport, $this->sdkIdentifier, $this->sdkVersion, $this->serializer, $this->representationSerializer, $this->logger); + return $this; } - /** - * Creates a new instance of the transport mechanism. - */ - private function createTransportInstance(): TransportInterface + public function getHttpClient(): HttpClientInterface { - if (null !== $this->transport) { - return $this->transport; - } + return $this->httpClient; + } - $transportFactory = $this->transportFactory ?? $this->createDefaultTransportFactory(); + public function setHttpClient(HttpClientInterface $httpClient): ClientBuilder + { + $this->httpClient = $httpClient; - return $transportFactory->create($this->options); + return $this; } - /** - * Creates a new instance of the {@see DefaultTransportFactory} factory. - */ - private function createDefaultTransportFactory(): DefaultTransportFactory + public function getClient(): ClientInterface { - $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); - $httpClientFactory = new HttpClientFactory( - null, - null, - $streamFactory, - null, + return new Client( + $this->options, + $this->transport, $this->sdkIdentifier, - $this->sdkVersion - ); - - return new DefaultTransportFactory( - $streamFactory, - Psr17FactoryDiscovery::findRequestFactory(), - $httpClientFactory, + $this->sdkVersion, + $this->serializer, + $this->representationSerializer, $this->logger ); } diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php deleted file mode 100644 index 35d67b12a..000000000 --- a/src/ClientBuilderInterface.php +++ /dev/null @@ -1,97 +0,0 @@ - - */ -interface ClientBuilderInterface -{ - /** - * Creates a new instance of this builder. - * - * @param array $options The client options, in naked array form - * - * @return static - */ - public static function create(array $options = []): self; - - /** - * The options that will be used to create the {@see Client}. - */ - public function getOptions(): Options; - - /** - * Gets the instance of the client built using the configured options. - */ - public function getClient(): ClientInterface; - - /** - * Sets a serializer instance to be injected as a dependency of the client. - * - * @param SerializerInterface $serializer The serializer to be used by the client to fill the events - * - * @return $this - */ - public function setSerializer(SerializerInterface $serializer): self; - - /** - * Sets a representation serializer instance to be injected as a dependency of the client. - * - * @param RepresentationSerializerInterface $representationSerializer The representation serializer, used to serialize function - * arguments in stack traces, to have string representation - * of non-string values - * - * @return $this - */ - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self; - - /** - * Sets a PSR-3 logger to log internal debug messages. - * - * @param LoggerInterface $logger The logger instance - * - * @return $this - */ - public function setLogger(LoggerInterface $logger): ClientBuilderInterface; - - /** - * Sets the transport factory. - * - * @param TransportFactoryInterface $transportFactory The transport factory - * - * @return $this - */ - public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface; - - /** - * Sets the SDK identifier to be passed onto {@see Event} and HTTP User-Agent header. - * - * @param string $sdkIdentifier The SDK identifier to be sent in {@see Event} and HTTP User-Agent headers - * - * @return $this - * - * @internal - */ - public function setSdkIdentifier(string $sdkIdentifier): self; - - /** - * Sets the SDK version to be passed onto {@see Event} and HTTP User-Agent header. - * - * @param string $sdkVersion The version of the SDK in use, to be sent alongside the SDK identifier - * - * @return $this - * - * @internal - */ - public function setSdkVersion(string $sdkVersion): self; -} diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 3f6d06f38..b8774385d 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -4,9 +4,9 @@ namespace Sentry; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; +use Sentry\Transport\Result; /** * This interface must be implemented by all Raven client classes. @@ -78,5 +78,5 @@ public function getIntegration(string $className): ?IntegrationInterface; * * @param int|null $timeout Maximum time in seconds the client should wait */ - public function flush(?int $timeout = null): PromiseInterface; + public function flush(?int $timeout = null): Result; } diff --git a/src/HttpClient/Authentication/SentryAuthentication.php b/src/HttpClient/Authentication/SentryAuthentication.php deleted file mode 100644 index 917487e83..000000000 --- a/src/HttpClient/Authentication/SentryAuthentication.php +++ /dev/null @@ -1,78 +0,0 @@ - - */ -final class SentryAuthentication implements AuthenticationInterface -{ - /** - * @var Options The Sentry client configuration - */ - private $options; - - /** - * @var string The SDK identifier - */ - private $sdkIdentifier; - - /** - * @var string The SDK version - */ - private $sdkVersion; - - /** - * Constructor. - * - * @param Options $options The Sentry client configuration - * @param string $sdkIdentifier The Sentry SDK identifier in use - * @param string $sdkVersion The Sentry SDK version in use - */ - public function __construct(Options $options, string $sdkIdentifier, string $sdkVersion) - { - $this->options = $options; - $this->sdkIdentifier = $sdkIdentifier; - $this->sdkVersion = $sdkVersion; - } - - /** - * {@inheritdoc} - */ - public function authenticate(RequestInterface $request): RequestInterface - { - $dsn = $this->options->getDsn(); - - if (null === $dsn) { - return $request; - } - - $data = [ - 'sentry_version' => Client::PROTOCOL_VERSION, - 'sentry_client' => $this->sdkIdentifier . '/' . $this->sdkVersion, - 'sentry_key' => $dsn->getPublicKey(), - ]; - - if (null !== $dsn->getSecretKey()) { - $data['sentry_secret'] = $dsn->getSecretKey(); - } - - $headers = []; - - foreach ($data as $headerKey => $headerValue) { - $headers[] = $headerKey . '=' . $headerValue; - } - - return $request->withHeader('X-Sentry-Auth', 'Sentry ' . implode(', ', $headers)); - } -} diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php new file mode 100644 index 000000000..d75148757 --- /dev/null +++ b/src/HttpClient/HttpClient.php @@ -0,0 +1,94 @@ +sdkIdentifier = $sdkIdentifier; + $this->sdkVersion = $sdkVersion; + } + + public function sendRequest(string $requestData, Options $options): Response + { + $dsn = $options->getDsn(); + if (null === $dsn) { + throw new \RuntimeException('The DSN option must be set to use the HttpClient.'); + } + + $curlHandle = curl_init(); + + $responseHeaders = []; + $responseHeaderCallback = function ($curlHandle, $headerLine) use (&$responseHeaders): int { + return Http::parseResponseHeaders($headerLine, $responseHeaders); + }; + + curl_setopt($curlHandle, \CURLOPT_URL, $dsn->getEnvelopeApiEndpointUrl()); + curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, Http::getRequestHeaders($dsn, $this->sdkIdentifier, $this->sdkVersion)); + curl_setopt($curlHandle, \CURLOPT_USERAGENT, $this->sdkIdentifier . '/' . $this->sdkVersion); + curl_setopt($curlHandle, \CURLOPT_TIMEOUT, $options->getHttpTimeout()); + curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, $options->getHttpConnectTimeout()); + curl_setopt($curlHandle, \CURLOPT_ENCODING, ''); + curl_setopt($curlHandle, \CURLOPT_POST, true); + curl_setopt($curlHandle, \CURLOPT_POSTFIELDS, $requestData); + curl_setopt($curlHandle, \CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlHandle, \CURLOPT_HEADERFUNCTION, $responseHeaderCallback); + curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1); + + $httpSslVerifyPeer = $options->getHttpSslVerifyPeer(); + if ($httpSslVerifyPeer) { + curl_setopt($curlHandle, \CURLOPT_SSL_VERIFYPEER, true); + } + + $httpProxy = $options->getHttpProxy(); + if (null !== $httpProxy) { + curl_setopt($curlHandle, \CURLOPT_PROXY, $httpProxy); + curl_setopt($curlHandle, \CURLOPT_HEADEROPT, \CURLHEADER_SEPARATE); + } + + $httpProxyAuthentication = $options->getHttpProxyAuthentication(); + if (null !== $httpProxyAuthentication) { + curl_setopt($curlHandle, \CURLOPT_PROXYUSERPWD, $httpProxyAuthentication); + } + + /** + * @TODO(michi) add request compression (gzip/brotli) depending on availiable extensions + */ + $body = curl_exec($curlHandle); + + if (false === $body) { + $errorCode = curl_errno($curlHandle); + $error = curl_error($curlHandle); + curl_close($curlHandle); + + $message = 'cURL Error (' . $errorCode . ') ' . $error; + + return new Response(0, [], $message); + } + + $statusCode = curl_getinfo($curlHandle, \CURLINFO_HTTP_CODE); + + curl_close($curlHandle); + + return new Response($statusCode, $responseHeaders, ''); + } +} diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php deleted file mode 100644 index ba12123a4..000000000 --- a/src/HttpClient/HttpClientFactory.php +++ /dev/null @@ -1,165 +0,0 @@ -streamFactory = $streamFactory; - $this->httpClient = $httpClient; - $this->sdkIdentifier = $sdkIdentifier; - $this->sdkVersion = $sdkVersion; - } - - /** - * {@inheritdoc} - */ - public function create(Options $options): HttpAsyncClientInterface - { - if (null === $options->getDsn()) { - throw new \RuntimeException('Cannot create an HTTP client without the Sentry DSN set in the options.'); - } - - if (null !== $this->httpClient && null !== $options->getHttpProxy()) { - throw new \RuntimeException('The "http_proxy" option does not work together with a custom HTTP client.'); - } - - $httpClient = $this->httpClient ?? $this->resolveClient($options); - - $httpClientPlugins = [ - new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion]), - new AuthenticationPlugin(new SentryAuthentication($options, $this->sdkIdentifier, $this->sdkVersion)), - new RetryPlugin(['retries' => $options->getSendAttempts(false)]), - new ErrorPlugin(['only_server_exception' => true]), - ]; - - if ($options->isCompressionEnabled()) { - $httpClientPlugins[] = new GzipEncoderPlugin($this->streamFactory); - $httpClientPlugins[] = new DecoderPlugin(); - } - - return new PluginClient($httpClient, $httpClientPlugins); - } - - /** - * @return ClientInterface|HttpAsyncClientInterface - */ - private function resolveClient(Options $options) - { - if (class_exists(SymfonyHttplugClient::class)) { - $symfonyConfig = [ - 'timeout' => $options->getHttpConnectTimeout(), - 'max_duration' => $options->getHttpTimeout(), - 'http_version' => $options->isCompressionEnabled() ? '1.1' : null, - ]; - - if (null !== $options->getHttpProxy()) { - $symfonyConfig['proxy'] = $options->getHttpProxy(); - } - - return new SymfonyHttplugClient(SymfonyHttpClient::create($symfonyConfig)); - } - - if (class_exists(Guzzle7HttpClient::class) || class_exists(Guzzle6HttpClient::class)) { - $guzzleConfig = [ - GuzzleHttpClientOptions::TIMEOUT => $options->getHttpTimeout(), - GuzzleHttpClientOptions::CONNECT_TIMEOUT => $options->getHttpConnectTimeout(), - ]; - - if (null !== $options->getHttpProxy()) { - $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); - } - - if (class_exists(Guzzle7HttpClient::class)) { - return Guzzle7HttpClient::createWithConfig($guzzleConfig); - } - - return Guzzle6HttpClient::createWithConfig($guzzleConfig); - } - - if (class_exists(CurlHttpClient::class)) { - $curlConfig = [ - \CURLOPT_TIMEOUT => $options->getHttpTimeout(), - \CURLOPT_HTTP_VERSION => $options->isCompressionEnabled() ? \CURL_HTTP_VERSION_1_1 : \CURL_HTTP_VERSION_NONE, - \CURLOPT_CONNECTTIMEOUT => $options->getHttpConnectTimeout(), - ]; - - if (null !== $options->getHttpProxy()) { - $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); - } - - return new CurlHttpClient(null, null, $curlConfig); - } - - if (null !== $options->getHttpProxy()) { - throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client", the "symfony/http-client" or the "php-http/guzzle6-adapter" package to be installed.'); - } - - return HttpAsyncClientDiscovery::find(); - } -} diff --git a/src/HttpClient/HttpClientFactoryInterface.php b/src/HttpClient/HttpClientFactoryInterface.php deleted file mode 100644 index a0b409d70..000000000 --- a/src/HttpClient/HttpClientFactoryInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - - */ -final class GzipEncoderPlugin implements PluginInterface -{ - /** - * @var StreamFactoryInterface The PSR-17 stream factory - */ - private $streamFactory; - - /** - * Constructor. - * - * @param StreamFactoryInterface $streamFactory The stream factory - * - * @throws \RuntimeException If the zlib extension is not enabled - */ - public function __construct(StreamFactoryInterface $streamFactory) - { - if (!\extension_loaded('zlib')) { - throw new \RuntimeException('The "zlib" extension must be enabled to use this plugin.'); - } - - $this->streamFactory = $streamFactory; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first): PromiseInterface - { - $requestBody = $request->getBody(); - - if ($requestBody->isSeekable()) { - $requestBody->rewind(); - } - - // Instead of using a stream filter we have to compress the whole request - // body in one go to work around a PHP bug. See https://github.com/getsentry/sentry-php/pull/877 - $encodedBody = gzcompress($requestBody->getContents(), -1, \ZLIB_ENCODING_GZIP); - - if (false === $encodedBody) { - throw new \RuntimeException('Failed to GZIP-encode the request body.'); - } - - $request = $request->withHeader('Content-Encoding', 'gzip'); - $request = $request->withBody($this->streamFactory->createStream($encodedBody)); - - return $next($request); - } -} diff --git a/src/HttpClient/Response.php b/src/HttpClient/Response.php new file mode 100644 index 000000000..56d921c07 --- /dev/null +++ b/src/HttpClient/Response.php @@ -0,0 +1,94 @@ +statusCode = $statusCode; + $this->headers = $headers; + $this->error = $error; + + foreach ($headers as $name => $value) { + $this->headerNames[strtolower($name)] = $name; + } + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function isSuccess(): bool + { + return $this->statusCode >= 200 && $this->statusCode <= 299; + } + + public function hasHeader(string $name): bool + { + return isset($this->headerNames[strtolower($name)]); + } + + /** + * @return string[] + */ + public function getHeader(string $header): array + { + if (!$this->hasHeader($header)) { + return []; + } + + $header = $this->headerNames[strtolower($header)]; + + return $this->headers[$header]; + } + + public function getHeaderLine(string $name): string + { + $value = $this->getHeader($name); + if (empty($value)) { + return ''; + } + + return implode(',', $value); + } + + public function getError(): string + { + return $this->error; + } + + public function hasError(): bool + { + return '' !== $this->error; + } +} diff --git a/src/Options.php b/src/Options.php index c78416252..bdcc79e4c 100644 --- a/src/Options.php +++ b/src/Options.php @@ -4,8 +4,10 @@ namespace Sentry; +use Sentry\HttpClient\HttpClientInterface; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\IntegrationInterface; +use Sentry\Transport\TransportInterface; use Symfony\Component\OptionsResolver\Options as SymfonyOptions; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -648,6 +650,30 @@ public function getIntegrations() return $this->options['integrations']; } + public function setTransport(TransportInterface $transport): void + { + $options = array_merge($this->options, ['transport' => $transport]); + + $this->options = $this->resolver->resolve($options); + } + + public function getTransport(): ?TransportInterface + { + return $this->options['transport']; + } + + public function setHttpClient(HttpClientInterface $httpClient): void + { + $options = array_merge($this->options, ['http_client' => $httpClient]); + + $this->options = $this->resolver->resolve($options); + } + + public function getHttpClient(): ?HttpClientInterface + { + return $this->options['http_client']; + } + /** * Should default PII be sent by default. */ @@ -728,6 +754,18 @@ public function setHttpProxy(?string $httpProxy): void $this->options = $this->resolver->resolve($options); } + public function getHttpProxyAuthentication(): ?string + { + return $this->options['http_proxy_authentication']; + } + + public function setHttpProxyAuthentication(?string $httpProxy): void + { + $options = array_merge($this->options, ['http_proxy_authentication' => $httpProxy]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets the maximum number of seconds to wait while trying to connect to a server. */ @@ -770,6 +808,18 @@ public function setHttpTimeout(float $httpTimeout): void $this->options = $this->resolver->resolve($options); } + public function getHttpSslVerifyPeer(): bool + { + return $this->options['http_ssl_verify_peer']; + } + + public function setHttpSslVerifyPeer(bool $httpSslVerifyPeer): void + { + $options = array_merge($this->options, ['http_ssl_verify_peer' => $httpSslVerifyPeer]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets whether the silenced errors should be captured or not. * @@ -925,9 +975,13 @@ private function configureOptions(OptionsResolver $resolver): void 'in_app_include' => [], 'send_default_pii' => false, 'max_value_length' => 1024, + 'transport' => null, + 'http_client' => null, 'http_proxy' => null, + 'http_proxy_authentication' => null, 'http_connect_timeout' => self::DEFAULT_HTTP_CONNECT_TIMEOUT, 'http_timeout' => self::DEFAULT_HTTP_TIMEOUT, + 'http_ssl_verify_peer' => true, 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', 'class_serializers' => [], @@ -963,7 +1017,10 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_default_pii', 'bool'); $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); + $resolver->setAllowedTypes('transport', ['null', TransportInterface::class]); + $resolver->setAllowedTypes('http_client', ['null', HttpCLientInterface::class]); $resolver->setAllowedTypes('http_proxy', ['null', 'string']); + $resolver->setAllowedTypes('http_proxy_authentication', ['null', 'string']); $resolver->setAllowedTypes('http_connect_timeout', ['int', 'float']); $resolver->setAllowedTypes('http_timeout', ['int', 'float']); $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php deleted file mode 100644 index 2d0c1f0b8..000000000 --- a/src/Transport/DefaultTransportFactory.php +++ /dev/null @@ -1,74 +0,0 @@ -streamFactory = $streamFactory; - $this->requestFactory = $requestFactory; - $this->httpClientFactory = $httpClientFactory; - $this->logger = $logger; - } - - /** - * {@inheritdoc} - */ - public function create(Options $options): TransportInterface - { - if (null === $options->getDsn()) { - return new NullTransport(); - } - - return new HttpTransport( - $options, - $this->httpClientFactory->create($options), - $this->streamFactory, - $this->requestFactory, - new PayloadSerializer($options), - $this->logger - ); - } -} diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 9aa844edb..efb9505c4 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -4,50 +4,28 @@ namespace Sentry\Transport; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; -use GuzzleHttp\Promise\RejectedPromise; -use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamFactoryInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Event; -use Sentry\EventType; +use Sentry\HttpClient\HttpClientInterface; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\Serializer\PayloadSerializerInterface; /** - * This transport sends the events using a syncronous HTTP client that will - * delay sending of the requests until the shutdown of the application. - * - * @author Stefano Arlandini + * @internal */ -final class HttpTransport implements TransportInterface +class HttpTransport implements TransportInterface { /** - * @var Options The Sentry client options + * @var Options */ private $options; /** - * @var HttpAsyncClientInterface The HTTP client + * @var HttpClientInterface The HTTP client */ private $httpClient; - /** - * @var StreamFactoryInterface The PSR-7 stream factory - */ - private $streamFactory; - - /** - * @var RequestFactoryInterface The PSR-7 request factory - */ - private $requestFactory; - /** * @var PayloadSerializerInterface The event serializer */ @@ -64,27 +42,19 @@ final class HttpTransport implements TransportInterface private $rateLimiter; /** - * Constructor. - * - * @param Options $options The Sentry client configuration - * @param HttpAsyncClientInterface $httpClient The HTTP client - * @param StreamFactoryInterface $streamFactory The PSR-7 stream factory - * @param RequestFactoryInterface $requestFactory The PSR-7 request factory + * @param Options $options The options + * @param HttpClientInterface $httpClient The HTTP client * @param PayloadSerializerInterface $payloadSerializer The event serializer * @param LoggerInterface|null $logger An instance of a PSR-3 logger */ public function __construct( Options $options, - HttpAsyncClientInterface $httpClient, - StreamFactoryInterface $streamFactory, - RequestFactoryInterface $requestFactory, + HttpClientInterface $httpClient, PayloadSerializerInterface $payloadSerializer, ?LoggerInterface $logger = null ) { $this->options = $options; $this->httpClient = $httpClient; - $this->streamFactory = $streamFactory; - $this->requestFactory = $requestFactory; $this->payloadSerializer = $payloadSerializer; $this->logger = $logger ?? new NullLogger(); $this->rateLimiter = new RateLimiter($this->logger); @@ -93,65 +63,49 @@ public function __construct( /** * {@inheritdoc} */ - public function send(Event $event): PromiseInterface + public function send(Event $event): Result { - $dsn = $this->options->getDsn(); - - if (null === $dsn) { - throw new \RuntimeException(sprintf('The DSN option must be set to use the "%s" transport.', self::class)); - } - $eventType = $event->getType(); - if ($this->rateLimiter->isRateLimited($eventType)) { $this->logger->warning( sprintf('Rate limit exceeded for sending requests of type "%s".', (string) $eventType), ['event' => $event] ); - return new RejectedPromise(new Response(ResponseStatus::rateLimit(), $event)); - } - - if ( - $this->options->isTracingEnabled() || - EventType::transaction() === $eventType || - EventType::checkIn() === $eventType - ) { - $request = $this->requestFactory->createRequest('POST', $dsn->getEnvelopeApiEndpointUrl()) - ->withHeader('Content-Type', 'application/x-sentry-envelope') - ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); - } else { - $request = $this->requestFactory->createRequest('POST', $dsn->getStoreApiEndpointUrl()) - ->withHeader('Content-Type', 'application/json') - ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); + return new Result(ResultStatus::rateLimit()); } try { - /** @var ResponseInterface $response */ - $response = $this->httpClient->sendAsyncRequest($request)->wait(); + $response = $this->httpClient->sendRequest($this->payloadSerializer->serialize($event), $this->options); } catch (\Throwable $exception) { $this->logger->error( sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), ['exception' => $exception, 'event' => $event] ); - return new RejectedPromise(new Response(ResponseStatus::failed(), $event)); + return new Result(ResultStatus::failed()); } - $sendResponse = $this->rateLimiter->handleResponse($event, $response); + $response = $this->rateLimiter->handleResponse($event, $response); + if ($response->isSuccess()) { + return new Result(ResultStatus::success(), $event); + } - if (ResponseStatus::success() === $sendResponse->getStatus()) { - return new FulfilledPromise($sendResponse); + if ($response->hasError()) { + $this->logger->error( + sprintf('Failed to send the event to Sentry. Reason: "%s".', $response->getError()), + ['event' => $event] + ); } - return new RejectedPromise($sendResponse); + return new Result(ResultStatus::createFromHttpStatusCode($response->getStatusCode())); } /** * {@inheritdoc} */ - public function close(?int $timeout = null): PromiseInterface + public function close(?int $timeout = null): Result { - return new FulfilledPromise(true); + return new Result(ResultStatus::success()); } } diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php index 0c067e104..10d275565 100644 --- a/src/Transport/NullTransport.php +++ b/src/Transport/NullTransport.php @@ -4,32 +4,23 @@ namespace Sentry\Transport; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\Event; -use Sentry\Response; -use Sentry\ResponseStatus; -/** - * This transport fakes the sending of events by just ignoring them. - * - * @author Stefano Arlandini - */ final class NullTransport implements TransportInterface { /** * {@inheritdoc} */ - public function send(Event $event): PromiseInterface + public function send(Event $event): Result { - return new FulfilledPromise(new Response(ResponseStatus::skipped(), $event)); + return new Result(ResultStatus::skipped(), $event); } /** * {@inheritdoc} */ - public function close(?int $timeout = null): PromiseInterface + public function close(?int $timeout = null): Result { - return new FulfilledPromise(true); + return new Result(ResultStatus::success()); } } diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index 4f75f234c..e6bcad986 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -4,13 +4,11 @@ namespace Sentry\Transport; -use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Event; use Sentry\EventType; -use Sentry\Response; -use Sentry\ResponseStatus; +use Sentry\HttpClient\Response; final class RateLimiter { @@ -47,10 +45,8 @@ public function __construct(?LoggerInterface $logger = null) $this->logger = $logger ?? new NullLogger(); } - public function handleResponse(Event $event, ResponseInterface $response): Response + public function handleResponse(Event $event, Response $response): Response { - $sendResponse = new Response(ResponseStatus::createFromHttpStatusCode($response->getStatusCode()), $event); - if ($this->handleRateLimit($response)) { $eventType = $event->getType(); $disabledUntil = $this->getDisabledUntil($eventType); @@ -61,7 +57,7 @@ public function handleResponse(Event $event, ResponseInterface $response): Respo ); } - return $sendResponse; + return $response; } public function isRateLimited(EventType $eventType): bool @@ -82,7 +78,7 @@ private function getDisabledUntil(EventType $eventType): int return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); } - private function handleRateLimit(ResponseInterface $response): bool + private function handleRateLimit(Response $response): bool { $now = time(); diff --git a/src/Response.php b/src/Transport/Result.php similarity index 74% rename from src/Response.php rename to src/Transport/Result.php index 3a2d54173..881624f11 100644 --- a/src/Response.php +++ b/src/Transport/Result.php @@ -2,16 +2,18 @@ declare(strict_types=1); -namespace Sentry; +namespace Sentry\Transport; + +use Sentry\Event; /** * This class contains the details of the sending operation of an event, e.g. * if it was sent successfully or if it was skipped because of some reason. */ -final class Response +final class Result { /** - * @var ResponseStatus The status of the sending operation of the event + * @var ResultStatus The status of the sending operation of the event */ private $status; @@ -21,7 +23,7 @@ final class Response */ private $event; - public function __construct(ResponseStatus $status, ?Event $event = null) + public function __construct(ResultStatus $status, ?Event $event = null) { $this->status = $status; $this->event = $event; @@ -30,7 +32,7 @@ public function __construct(ResponseStatus $status, ?Event $event = null) /** * Gets the status of the sending operation of the event. */ - public function getStatus(): ResponseStatus + public function getStatus(): ResultStatus { return $this->status; } diff --git a/src/ResponseStatus.php b/src/Transport/ResultStatus.php similarity index 97% rename from src/ResponseStatus.php rename to src/Transport/ResultStatus.php index 2a2292a4f..e7df98ac0 100644 --- a/src/ResponseStatus.php +++ b/src/Transport/ResultStatus.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Sentry; +namespace Sentry\Transport; /** * This enum represents all possible reasons an event sending operation succeeded * or failed. */ -final class ResponseStatus implements \Stringable +class ResultStatus implements \Stringable { /** * @var string The value of the enum instance diff --git a/src/Transport/TransportFactoryInterface.php b/src/Transport/TransportFactoryInterface.php deleted file mode 100644 index e38f12b1f..000000000 --- a/src/Transport/TransportFactoryInterface.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ interface TransportInterface { - /** - * Sends the given event. - * - * @param Event $event The event - * - * @return PromiseInterface Returns the ID of the event or `null` if it failed to be sent - */ - public function send(Event $event): PromiseInterface; + public function send(Event $event): Result; - /** - * Waits until all pending requests have been sent or the timeout expires. - * - * @param int|null $timeout Maximum time in seconds before the sending - * operation is interrupted - */ - public function close(?int $timeout = null): PromiseInterface; + public function close(?int $timeout = null): Result; } diff --git a/src/Util/Http.php b/src/Util/Http.php new file mode 100644 index 000000000..a82152175 --- /dev/null +++ b/src/Util/Http.php @@ -0,0 +1,55 @@ + Client::PROTOCOL_VERSION, + 'sentry_client' => $sdkIdentifier . '/' . $sdkVersion, + 'sentry_key' => $dsn->getPublicKey(), + ]; + + if (null !== $dsn->getSecretKey()) { + $data['sentry_secret'] = $dsn->getSecretKey(); + } + + $authHeader = []; + foreach ($data as $headerKey => $headerValue) { + $authHeader[] = $headerKey . '=' . $headerValue; + } + + return [ + 'Content-Type' => 'application/x-sentry-envelope', + 'X-Sentry-Auth' => 'Sentry ' . implode(', ', $authHeader), + ]; + } + + /** + * @param string[][] $headers + */ + public static function parseResponseHeaders(string $headerLine, &$headers): int + { + if (false === strpos($headerLine, ':')) { + return \strlen($headerLine); + } + + [$key, $value] = explode(':', trim($headerLine), 2); + $headers[trim($key)] = trim($value); + + return \strlen($headerLine); + } +} diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 153986970..552310d25 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -8,10 +8,14 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\HttpClient\HttpClient; +use Sentry\HttpClient\HttpClientInterface; +use Sentry\HttpClient\Response; use Sentry\Integration\IntegrationInterface; use Sentry\Options; use Sentry\Transport\HttpTransport; -use Sentry\Transport\NullTransport; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; final class ClientBuilderTest extends TestCase @@ -24,24 +28,6 @@ public function testGetOptions() $this->assertSame($options, $clientBuilder->getOptions()); } - public function testHttpTransportIsUsedWhenServerIsConfigured(): void - { - $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); - - $transport = $this->getTransport($clientBuilder->getClient()); - - $this->assertInstanceOf(HttpTransport::class, $transport); - } - - public function testNullTransportIsUsedWhenNoServerIsConfigured(): void - { - $clientBuilder = new ClientBuilder(); - - $transport = $this->getTransport($clientBuilder->getClient()); - - $this->assertInstanceOf(NullTransport::class, $transport); - } - public function testClientBuilderFallbacksToDefaultSdkIdentifierAndVersion(): void { $callbackCalled = false; @@ -92,15 +78,39 @@ public function testCreateWithNoOptionsIsTheSameAsDefaultOptions(): void ); } - private function getTransport(Client $client): TransportInterface + public function testDefaultHttpClientAndTransport() + { + $options = new Options(); + $clientBuilder = new ClientBuilder($options); + + $this->assertInstanceOf(HttpClient::class, $clientBuilder->getHttpClient()); + $this->assertInstanceOf(HttpTransport::class, $clientBuilder->getTransport()); + } + + public function testSettingCustomHttpClinet() + { + $httpClient = new CustomHttpClient(); + + $options = new Options([ + 'http_client' => $httpClient, + ]); + $clientBuilder = new ClientBuilder($options); + + $this->assertSame($httpClient, $clientBuilder->getHttpClient()); + $this->assertInstanceOf(HttpTransport::class, $clientBuilder->getTransport()); + } + + public function testSettingCustomTransport() { - $property = new \ReflectionProperty(Client::class, 'transport'); + $transport = new CustomTransport(); - $property->setAccessible(true); - $value = $property->getValue($client); - $property->setAccessible(false); + $options = new Options([ + 'transport' => $transport, + ]); + $clientBuilder = new ClientBuilder($options); - return $value; + $this->assertInstanceOf(HttpClient::class, $clientBuilder->getHttpClient()); + $this->assertSame($transport, $clientBuilder->getTransport()); } } @@ -110,3 +120,24 @@ public function setupOnce(): void { } } + +final class CustomHttpClient implements HttpClientInterface +{ + public function sendRequest(string $requestData, Options $options): Response + { + return new Response(0, [], ''); + } +} + +final class CustomTransport implements TransportInterface +{ + public function send(Event $event): Result + { + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 749b25836..b76c1c6db 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -4,8 +4,6 @@ namespace Sentry\Tests; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; @@ -18,15 +16,14 @@ use Sentry\Frame; use Sentry\Integration\IntegrationInterface; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; @@ -94,12 +91,12 @@ public function testCaptureMessage(): void return true; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create() - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureMessage('foo', Severity::fatal())); @@ -145,12 +142,12 @@ public function testCaptureException(): void return true; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create() - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureException($exception)); @@ -207,12 +204,12 @@ public function testCaptureEvent(array $options, Event $event, Event $expectedEv $transport->expects($this->once()) ->method('send') ->with($expectedEvent) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create($options) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertSame($event->getId(), $client->captureEvent($event)); @@ -331,12 +328,12 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt return true; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create(['attach_stacktrace' => $attachStacktraceOption]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureEvent(Event::createEvent(), $hint)); @@ -386,12 +383,12 @@ public function testCaptureEventPrefersExplicitStacktrace(): void ->with($this->callback(static function (Event $event) use ($stacktrace): bool { return $stacktrace === $event->getStacktrace(); })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureEvent(Event::createEvent(), EventHint::fromArray([ @@ -413,12 +410,12 @@ public function testCaptureLastError(): void return true; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); @trigger_error('foo', \E_USER_NOTICE); @@ -464,7 +461,7 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void ->with($this->anything()); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); error_clear_last(); @@ -553,7 +550,7 @@ public function testProcessEventDiscardsEventWhenSampleRateOptionIsZero(): void })); $client = ClientBuilder::create(['sample_rate' => 0]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->setLogger($logger) ->getClient(); @@ -568,7 +565,7 @@ public function testProcessEventCapturesEventWhenSampleRateOptionIsAboveZero(): ->with($this->anything()); $client = ClientBuilder::create(['sample_rate' => 1]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $client->captureEvent(Event::createEvent()); @@ -700,12 +697,12 @@ public function testAttachStacktrace(): void return null !== $result; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureMessage('test')); @@ -718,16 +715,15 @@ public function testFlush(): void $transport->expects($this->once()) ->method('close') ->with(10) - ->willReturn(new FulfilledPromise(true)); + ->willReturn(new Result(ResultStatus::success())); $client = ClientBuilder::create() - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); - $promise = $client->flush(10); + $response = $client->flush(10); - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertTrue($promise->wait()); + $this->assertSame(ResultStatus::success(), $response->getStatus()); } public function testBuildEventInCLIDoesntSetTransaction(): void @@ -992,24 +988,4 @@ public static function getCspReportUrlDataProvider(): \Generator 'https://example.com/api/1/security/?sentry_key=public&sentry_release=dev-release&sentry_environment=development', ]; } - - private function createTransportFactory(TransportInterface $transport): TransportFactoryInterface - { - return new class($transport) implements TransportFactoryInterface { - /** - * @var TransportInterface - */ - private $transport; - - public function __construct(TransportInterface $transport) - { - $this->transport = $transport; - } - - public function create(Options $options): TransportInterface - { - return $this->transport; - } - }; - } } diff --git a/tests/HttpClient/Authentication/SentryAuthenticationTest.php b/tests/HttpClient/Authentication/SentryAuthenticationTest.php deleted file mode 100644 index 726903a10..000000000 --- a/tests/HttpClient/Authentication/SentryAuthenticationTest.php +++ /dev/null @@ -1,61 +0,0 @@ - 'http://public:secret@example.com/sentry/1']); - $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '1.2.3'); - $request = new Request('POST', 'http://www.example.com', []); - $expectedHeader = sprintf( - 'Sentry sentry_version=%s, sentry_client=%s, sentry_key=public, sentry_secret=secret', - Client::PROTOCOL_VERSION, - 'sentry.php.test/1.2.3' - ); - - $this->assertFalse($request->hasHeader('X-Sentry-Auth')); - - $request = $authentication->authenticate($request); - - $this->assertTrue($request->hasHeader('X-Sentry-Auth')); - $this->assertSame($expectedHeader, $request->getHeaderLine('X-Sentry-Auth')); - } - - public function testAuthenticateWithoutSecretKey(): void - { - $configuration = new Options(['dsn' => 'http://public@example.com/sentry/1']); - $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '1.2.3'); - $request = new Request('POST', 'http://www.example.com', []); - $expectedHeader = sprintf( - 'Sentry sentry_version=%s, sentry_client=%s, sentry_key=public', - Client::PROTOCOL_VERSION, - 'sentry.php.test/1.2.3' - ); - - $this->assertFalse($request->hasHeader('X-Sentry-Auth')); - - $request = $authentication->authenticate($request); - - $this->assertTrue($request->hasHeader('X-Sentry-Auth')); - $this->assertSame($expectedHeader, $request->getHeaderLine('X-Sentry-Auth')); - } - - public function testAuthenticateWithoutDsnOptionSet(): void - { - $authentication = new SentryAuthentication(new Options(), 'sentry.php.test', '1.2.3'); - $request = new Request('POST', 'http://www.example.com', []); - $request = $authentication->authenticate($request); - - $this->assertFalse($request->hasHeader('X-Sentry-Auth')); - } -} diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php deleted file mode 100644 index b0218c817..000000000 --- a/tests/HttpClient/HttpClientFactoryTest.php +++ /dev/null @@ -1,104 +0,0 @@ -create(new Options([ - 'dsn' => 'http://public@example.com/sentry/1', - 'default_integrations' => false, - 'enable_compression' => $isCompressionEnabled, - ])); - - $request = Psr17FactoryDiscovery::findRequestFactory() - ->createRequest('POST', 'http://example.com/sentry/foo') - ->withBody($streamFactory->createStream('foo bar')); - - $httpClient->sendAsyncRequest($request); - - $httpRequest = $mockHttpClient->getLastRequest(); - - $this->assertSame('http://example.com/sentry/foo', (string) $httpRequest->getUri()); - $this->assertSame('sentry.php.test/1.2.3', $httpRequest->getHeaderLine('User-Agent')); - $this->assertSame('Sentry sentry_version=7, sentry_client=sentry.php.test/1.2.3, sentry_key=public', $httpRequest->getHeaderLine('X-Sentry-Auth')); - $this->assertSame($expectedRequestBody, (string) $httpRequest->getBody()); - } - - public static function createDataProvider(): \Generator - { - yield [ - false, - 'foo bar', - ]; - - yield [ - true, - gzcompress('foo bar', -1, \ZLIB_ENCODING_GZIP), - ]; - } - - public function testCreateThrowsIfDsnOptionIsNotConfigured(): void - { - $httpClientFactory = new HttpClientFactory( - null, - null, - Psr17FactoryDiscovery::findStreamFactory(), - null, - 'sentry.php.test', - '1.2.3' - ); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Cannot create an HTTP client without the Sentry DSN set in the options.'); - - $httpClientFactory->create(new Options(['default_integrations' => false])); - } - - public function testCreateThrowsIfHttpProxyOptionIsUsedWithCustomHttpClient(): void - { - $httpClientFactory = new HttpClientFactory( - null, - null, - Psr17FactoryDiscovery::findStreamFactory(), - $this->createMock(HttpAsyncClientInterface::class), - 'sentry.php.test', - '1.2.3' - ); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('The "http_proxy" option does not work together with a custom HTTP client.'); - - $httpClientFactory->create(new Options([ - 'dsn' => 'http://public@example.com/sentry/1', - 'default_integrations' => false, - 'http_proxy' => 'http://example.com', - ])); - } -} diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php deleted file mode 100644 index 0e5423015..000000000 --- a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php +++ /dev/null @@ -1,40 +0,0 @@ -createMock(PromiseInterface::class); - $request = Psr17FactoryDiscovery::findRequestFactory() - ->createRequest('POST', 'http://www.example.com') - ->withBody($streamFactory->createStream('foo')); - - $this->assertSame('foo', (string) $request->getBody()); - $this->assertSame($expectedPromise, $plugin->handleRequest( - $request, - function (RequestInterface $requestArg) use ($expectedPromise): PromiseInterface { - $this->assertSame('gzip', $requestArg->getHeaderLine('Content-Encoding')); - $this->assertSame(gzcompress('foo', -1, \ZLIB_ENCODING_GZIP), (string) $requestArg->getBody()); - - return $expectedPromise; - }, - static function (): void {} - )); - } -} diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 4f47fbbe8..b27dc02f7 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -6,7 +6,10 @@ use PHPUnit\Framework\TestCase; use Sentry\Dsn; +use Sentry\HttpClient\HttpClient; use Sentry\Options; +use Sentry\Serializer\PayloadSerializer; +use Sentry\Transport\HttpTransport; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; @@ -347,6 +350,24 @@ static function (): void {}, null, ]; + yield [ + 'transport', + new HttpTransport(new Options(), new HttpClient('foo', 'bar'), new PayloadSerializer(new Options())), + 'getTransport', + 'setTransport', + null, + null, + ]; + + yield [ + 'http_client', + new HttpClient('foo', 'bar'), + 'getHttpClient', + 'setHttpClient', + null, + null, + ]; + yield [ 'http_proxy', '127.0.0.1', @@ -356,6 +377,15 @@ static function (): void {}, null, ]; + yield [ + 'http_proxy_authentication', + 'username:password', + 'getHttpProxyAuthentication', + 'setHttpProxyAuthentication', + null, + null, + ]; + yield [ 'http_timeout', 1, @@ -392,6 +422,15 @@ static function (): void {}, null, ]; + yield [ + 'http_ssl_verify_peer', + false, + 'getHttpSslVerifyPeer', + 'setHttpSslVerifyPeer', + null, + null, + ]; + yield [ 'capture_silenced_errors', true, diff --git a/tests/Transport/DefaultTransportFactoryTest.php b/tests/Transport/DefaultTransportFactoryTest.php deleted file mode 100644 index 8ef47ddd4..000000000 --- a/tests/Transport/DefaultTransportFactoryTest.php +++ /dev/null @@ -1,50 +0,0 @@ -createMock(StreamFactoryInterface::class), - $this->createMock(RequestFactoryInterface::class), - $this->createMock(HttpClientFactoryInterface::class) - ); - - $this->assertInstanceOf(NullTransport::class, $factory->create(new Options())); - } - - public function testCreateReturnsHttpTransportWhenDsnOptionIsConfigured(): void - { - $options = new Options(['dsn' => 'http://public@example.com/sentry/1']); - - /** @var HttpClientFactoryInterface&MockObject $httpClientFactory */ - $httpClientFactory = $this->createMock(HttpClientFactoryInterface::class); - $httpClientFactory->expects($this->once()) - ->method('create') - ->with($options) - ->willReturn($this->createMock(HttpAsyncClientInterface::class)); - - $factory = new DefaultTransportFactory( - $this->createMock(StreamFactoryInterface::class), - $this->createMock(RequestFactoryInterface::class), - $httpClientFactory - ); - - $this->assertInstanceOf(HttpTransport::class, $factory->create($options)); - } -} diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 95483aef3..6efa78e42 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -4,26 +4,16 @@ namespace Sentry\Tests\Transport; -use GuzzleHttp\Promise\PromiseInterface; -use GuzzleHttp\Promise\RejectionException; -use GuzzleHttp\Psr7\Request; -use GuzzleHttp\Psr7\Response; -use GuzzleHttp\Psr7\Utils; -use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Promise\FulfilledPromise as HttpFullfilledPromise; -use Http\Promise\RejectedPromise as HttpRejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\StreamInterface; use Psr\Log\LoggerInterface; -use Sentry\Dsn; use Sentry\Event; +use Sentry\HttpClient\HttpClientInterface; +use Sentry\HttpClient\Response; use Sentry\Options; -use Sentry\ResponseStatus; use Sentry\Serializer\PayloadSerializerInterface; use Sentry\Transport\HttpTransport; +use Sentry\Transport\ResultStatus; use Symfony\Bridge\PhpUnit\ClockMock; final class HttpTransportTest extends TestCase @@ -33,16 +23,6 @@ final class HttpTransportTest extends TestCase */ private $httpClient; - /** - * @var MockObject&StreamFactoryInterface - */ - private $streamFactory; - - /** - * @var MockObject&RequestFactoryInterface - */ - private $requestFactory; - /** * @var MockObject&PayloadSerializerInterface */ @@ -50,82 +30,15 @@ final class HttpTransportTest extends TestCase protected function setUp(): void { - $this->httpClient = $this->createMock(HttpAsyncClientInterface::class); - $this->streamFactory = $this->createMock(StreamFactoryInterface::class); - $this->requestFactory = $this->createMock(RequestFactoryInterface::class); + $this->httpClient = $this->createMock(HttpClientInterface::class); $this->payloadSerializer = $this->createMock(PayloadSerializerInterface::class); } - public function testSendThrowsIfDsnOptionIsNotSet(): void - { - $transport = new HttpTransport( - new Options(), - $this->httpClient, - $this->streamFactory, - $this->requestFactory, - $this->payloadSerializer - ); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('The DSN option must be set to use the "Sentry\Transport\HttpTransport" transport.'); - - $transport->send(Event::createEvent()); - } - - public function testSendTransactionAsEnvelope(): void - { - $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); - $event = Event::createTransaction(); - - $this->payloadSerializer->expects($this->once()) - ->method('serialize') - ->with($event) - ->willReturn('{"foo":"bar"}'); - - $this->requestFactory->expects($this->once()) - ->method('createRequest') - ->with('POST', $dsn->getEnvelopeApiEndpointUrl()) - ->willReturn(new Request('POST', 'http://www.example.com')); - - $this->streamFactory->expects($this->once()) - ->method('createStream') - ->with('{"foo":"bar"}') - ->willReturnCallback(static function (string $content): StreamInterface { - return Utils::streamFor($content); - }); - - $this->httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->with($this->callback(function (Request $requestArg): bool { - if ('application/x-sentry-envelope' !== $requestArg->getHeaderLine('Content-Type')) { - return false; - } - - if ('{"foo":"bar"}' !== $requestArg->getBody()->getContents()) { - return false; - } - - return true; - })) - ->willReturn(new HttpFullfilledPromise(new Response())); - - $transport = new HttpTransport( - new Options(['dsn' => $dsn]), - $this->httpClient, - $this->streamFactory, - $this->requestFactory, - $this->payloadSerializer - ); - - $transport->send($event); - } - /** * @dataProvider sendDataProvider */ - public function testSend(int $httpStatusCode, string $expectedPromiseStatus, ResponseStatus $expectedResponseStatus): void + public function testSend(int $httpStatusCode, ResultStatus $expectedResultStatus, bool $expectEventReturned): void { - $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $event = Event::createEvent(); $this->payloadSerializer->expects($this->once()) @@ -133,72 +46,53 @@ public function testSend(int $httpStatusCode, string $expectedPromiseStatus, Res ->with($event) ->willReturn('{"foo":"bar"}'); - $this->streamFactory->expects($this->once()) - ->method('createStream') - ->with('{"foo":"bar"}') - ->willReturnCallback(static function (string $content): StreamInterface { - return Utils::streamFor($content); - }); - - $this->requestFactory->expects($this->once()) - ->method('createRequest') - ->with('POST', $dsn->getStoreApiEndpointUrl()) - ->willReturn(new Request('POST', 'http://www.example.com')); - $this->httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->with($this->callback(function (Request $requestArg): bool { - if ('application/json' !== $requestArg->getHeaderLine('Content-Type')) { - return false; - } - - if ('{"foo":"bar"}' !== $requestArg->getBody()->getContents()) { - return false; - } - - return true; - })) - ->willReturn(new HttpFullfilledPromise(new Response($httpStatusCode))); + ->method('sendRequest') + ->willReturn(new Response($httpStatusCode, [], '')); $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), + new Options(), $this->httpClient, - $this->streamFactory, - $this->requestFactory, $this->payloadSerializer ); - $promise = $transport->send($event); + $result = $transport->send($event); - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); + $this->assertSame($expectedResultStatus, $result->getStatus()); + if ($expectEventReturned) { + $this->assertSame($event, $result->getEvent()); } - - $this->assertSame($expectedPromiseStatus, $promise->getState()); - $this->assertSame($expectedResponseStatus, $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); } public static function sendDataProvider(): iterable { yield [ 200, - PromiseInterface::FULFILLED, - ResponseStatus::success(), + ResultStatus::success(), + true, + ]; + + yield [ + 401, + ResultStatus::invalid(), + false, + ]; + + yield [ + 429, + ResultStatus::rateLimit(), + false, ]; yield [ 500, - PromiseInterface::REJECTED, - ResponseStatus::failed(), + ResultStatus::failed(), + false, ]; } - public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientException(): void + public function testSendFailsDueToHttpClientException(): void { - $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $exception = new \Exception('foo'); $event = Event::createEvent(); @@ -213,52 +107,60 @@ public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientExce ->with($event) ->willReturn('{"foo":"bar"}'); - $this->requestFactory->expects($this->once()) - ->method('createRequest') - ->with('POST', $dsn->getStoreApiEndpointUrl()) - ->willReturn(new Request('POST', 'http://www.example.com')); + $this->httpClient->expects($this->once()) + ->method('sendRequest') + ->will($this->throwException($exception)); - $this->streamFactory->expects($this->once()) - ->method('createStream') - ->with('{"foo":"bar"}') - ->willReturnCallback(static function (string $content): StreamInterface { - return Utils::streamFor($content); - }); + $transport = new HttpTransport( + new Options(), + $this->httpClient, + $this->payloadSerializer, + $logger + ); + + $result = $transport->send($event); + + $this->assertSame(ResultStatus::failed(), $result->getStatus()); + } + + public function testSendFailsDueToCulrError(): void + { + $event = Event::createEvent(); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('error') + ->with('Failed to send the event to Sentry. Reason: "cURL Error (6) Could not resolve host: example.com".', ['event' => $event]); + + $this->payloadSerializer->expects($this->once()) + ->method('serialize') + ->with($event) + ->willReturn('{"foo":"bar"}'); $this->httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn(new HttpRejectedPromise($exception)); + ->method('sendRequest') + ->willReturn(new Response(0, [], 'cURL Error (6) Could not resolve host: example.com')); $transport = new HttpTransport( - new Options(['dsn' => $dsn]), + new Options(), $this->httpClient, - $this->streamFactory, - $this->requestFactory, $this->payloadSerializer, $logger ); - $promise = $transport->send($event); + $result = $transport->send($event); - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); - } - - $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); - $this->assertSame(ResponseStatus::failed(), $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); + $this->assertSame(ResultStatus::unknown(), $result->getStatus()); } /** * @group time-sensitive */ - public function testSendReturnsRejectedPromiseIfExceedingRateLimits(): void + public function testSendFailsDueToExceedingRateLimits(): void { ClockMock::withClockMock(1644105600); - $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $event = Event::createEvent(); /** @var LoggerInterface&MockObject $logger */ @@ -275,69 +177,38 @@ public function testSendReturnsRejectedPromiseIfExceedingRateLimits(): void ->with($event) ->willReturn('{"foo":"bar"}'); - $this->requestFactory->expects($this->once()) - ->method('createRequest') - ->with('POST', $dsn->getStoreApiEndpointUrl()) - ->willReturn(new Request('POST', 'http://www.example.com')); - - $this->streamFactory->expects($this->once()) - ->method('createStream') - ->with('{"foo":"bar"}') - ->willReturnCallback([Utils::class, 'streamFor']); - $this->httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn(new HttpFullfilledPromise(new Response(429, ['Retry-After' => '60']))); + ->method('sendRequest') + ->willReturn(new Response(429, ['Retry-After' => ['60']], '')); $transport = new HttpTransport( - new Options(['dsn' => $dsn]), + new Options(), $this->httpClient, - $this->streamFactory, - $this->requestFactory, $this->payloadSerializer, $logger ); // Event should be sent, but the server should reply with a HTTP 429 - $promise = $transport->send($event); + $result = $transport->send($event); - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); - } - - $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); - $this->assertSame(ResponseStatus::rateLimit(), $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); + $this->assertSame(ResultStatus::rateLimit(), $result->getStatus()); // Event should not be sent at all because rate-limit is in effect - $promise = $transport->send($event); + $result = $transport->send($event); - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); - } - - $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); - $this->assertSame(ResponseStatus::rateLimit(), $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); + $this->assertSame(ResultStatus::rateLimit(), $result->getStatus()); } public function testClose(): void { $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $this->createMock(HttpAsyncClientInterface::class), - $this->createMock(StreamFactoryInterface::class), - $this->createMock(RequestFactoryInterface::class), + new Options(), + $this->createMock(HttpClientInterface::class), $this->createMock(PayloadSerializerInterface::class) ); - $promise = $transport->close(); + $result = $transport->close(); - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertTrue($promise->wait()); + $this->assertSame(ResultStatus::success(), $result->getStatus()); } } diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php index 954e3ee0b..dbefc9add 100644 --- a/tests/Transport/NullTransportTest.php +++ b/tests/Transport/NullTransportTest.php @@ -4,11 +4,10 @@ namespace Sentry\Tests\Transport; -use GuzzleHttp\Promise\PromiseInterface; use PHPUnit\Framework\TestCase; use Sentry\Event; -use Sentry\ResponseStatus; use Sentry\Transport\NullTransport; +use Sentry\Transport\ResultStatus; final class NullTransportTest extends TestCase { @@ -26,19 +25,16 @@ public function testSend(): void { $event = Event::createEvent(); - $promise = $this->transport->send($event); - $promiseResult = $promise->wait(); + $result = $this->transport->send($event); - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertSame(ResponseStatus::skipped(), $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); + $this->assertSame(ResultStatus::skipped(), $result->getStatus()); + $this->assertSame($event, $result->getEvent()); } public function testClose(): void { - $promise = $this->transport->close(); + $response = $this->transport->close(); - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertTrue($promise->wait()); + $this->assertSame(ResultStatus::success(), $response->getStatus()); } } diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index 22c3e5354..14315d401 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -4,14 +4,12 @@ namespace Sentry\Tests\Transport; -use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; use Sentry\Event; use Sentry\EventType; -use Sentry\ResponseStatus; +use Sentry\HttpClient\Response; use Sentry\Transport\RateLimiter; use Symfony\Bridge\PhpUnit\ClockMock; @@ -39,56 +37,55 @@ protected function setUp(): void /** * @dataProvider handleResponseDataProvider */ - public function testHandleResponse(Event $event, ResponseInterface $response, ResponseStatus $responseStatus): void + public function testHandleResponse(Event $event, Response $response, int $responseStatusCode): void { ClockMock::withClockMock(1644105600); - $this->logger->expects($responseStatus === ResponseStatus::success() ? $this->never() : $this->once()) + $this->logger->expects($response->isSuccess() ? $this->never() : $this->once()) ->method('warning') ->with('Rate limited exceeded for requests of type "event", backing off until "2022-02-06T00:01:00+00:00".', ['event' => $event]); - $transportResponse = $this->rateLimiter->handleResponse($event, $response); + $rateLimiterResponse = $this->rateLimiter->handleResponse($event, $response); - $this->assertSame($responseStatus, $transportResponse->getStatus()); - $this->assertSame($event, $transportResponse->getEvent()); + $this->assertSame($responseStatusCode, $rateLimiterResponse->getStatusCode()); } public static function handleResponseDataProvider(): \Generator { yield 'Rate limits headers missing' => [ Event::createEvent(), - new Response(), - ResponseStatus::success(), + new Response(200, [], ''), + 200, ]; yield 'Back-off using X-Sentry-Rate-Limits header with single category' => [ Event::createEvent(), - new Response(429, ['X-Sentry-Rate-Limits' => '60:error:org']), - ResponseStatus::rateLimit(), + new Response(429, ['X-Sentry-Rate-Limits' => ['60:error:org']], ''), + 429, ]; yield 'Back-off using X-Sentry-Rate-Limits header with multiple categories' => [ Event::createEvent(), - new Response(429, ['X-Sentry-Rate-Limits' => '60:error;transaction:org']), - ResponseStatus::rateLimit(), + new Response(429, ['X-Sentry-Rate-Limits' => ['60:error;transaction:org']], ''), + 429, ]; yield 'Back-off using X-Sentry-Rate-Limits header with missing categories should lock them all' => [ Event::createEvent(), - new Response(429, ['X-Sentry-Rate-Limits' => '60::org']), - ResponseStatus::rateLimit(), + new Response(429, ['X-Sentry-Rate-Limits' => ['60::org']], ''), + 429, ]; yield 'Back-off using Retry-After header with number-based value' => [ Event::createEvent(), - new Response(429, ['Retry-After' => '60']), - ResponseStatus::rateLimit(), + new Response(429, ['Retry-After' => ['60']], ''), + 429, ]; yield 'Back-off using Retry-After header with date-based value' => [ Event::createEvent(), - new Response(429, ['Retry-After' => 'Sun, 02 February 2022 00:01:00 GMT']), - ResponseStatus::rateLimit(), + new Response(429, ['Retry-After' => ['Sun, 02 February 2022 00:01:00 GMT']], ''), + 429, ]; } @@ -102,7 +99,7 @@ public function testIsRateLimited(): void // Events should be rate-limited for 60 seconds, but transactions should // still be allowed to be sent - $this->rateLimiter->handleResponse(Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => '60:error:org'])); + $this->rateLimiter->handleResponse(Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => ['60:error:org']], '')); $this->assertTrue($this->rateLimiter->isRateLimited(EventType::event())); $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); @@ -115,7 +112,7 @@ public function testIsRateLimited(): void // Both events and transactions should be rate-limited if all categories // are - $this->rateLimiter->handleResponse(Event::createTransaction(), new Response(429, ['X-Sentry-Rate-Limits' => '60:all:org'])); + $this->rateLimiter->handleResponse(Event::createTransaction(), new Response(429, ['X-Sentry-Rate-Limits' => ['60:all:org']], '')); $this->assertTrue($this->rateLimiter->isRateLimited(EventType::event())); $this->assertTrue($this->rateLimiter->isRateLimited(EventType::transaction())); diff --git a/tests/ResponseStatusTest.php b/tests/Transport/ResultStatusTest.php similarity index 53% rename from tests/ResponseStatusTest.php rename to tests/Transport/ResultStatusTest.php index 3edfba746..cea73dc00 100644 --- a/tests/ResponseStatusTest.php +++ b/tests/Transport/ResultStatusTest.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace Sentry\Tests; +namespace Sentry\Tests\Transport; use PHPUnit\Framework\TestCase; -use Sentry\ResponseStatus; +use Sentry\Transport\ResultStatus; -final class ResponseStatusTest extends TestCase +final class ResultStatusTest extends TestCase { /** * @dataProvider toStringDataProvider */ - public function testToString(ResponseStatus $responseStatus, string $expectedStringRepresentation): void + public function testToString(ResultStatus $responseStatus, string $expectedStringRepresentation): void { $this->assertSame($expectedStringRepresentation, (string) $responseStatus); } @@ -20,32 +20,32 @@ public function testToString(ResponseStatus $responseStatus, string $expectedStr public static function toStringDataProvider(): iterable { yield [ - ResponseStatus::success(), + ResultStatus::success(), 'SUCCESS', ]; yield [ - ResponseStatus::failed(), + ResultStatus::failed(), 'FAILED', ]; yield [ - ResponseStatus::invalid(), + ResultStatus::invalid(), 'INVALID', ]; yield [ - ResponseStatus::skipped(), + ResultStatus::skipped(), 'SKIPPED', ]; yield [ - ResponseStatus::rateLimit(), + ResultStatus::rateLimit(), 'RATE_LIMIT', ]; yield [ - ResponseStatus::unknown(), + ResultStatus::unknown(), 'UNKNOWN', ]; } @@ -53,59 +53,59 @@ public static function toStringDataProvider(): iterable /** * @dataProvider createFromHttpStatusCodeDataProvider */ - public function testCreateFromHttpStatusCode(ResponseStatus $expectedResponseStatus, int $httpStatusCode): void + public function testCreateFromHttpStatusCode(ResultStatus $expectedResultStatus, int $httpStatusCode): void { - $this->assertSame($expectedResponseStatus, ResponseStatus::createFromHttpStatusCode($httpStatusCode)); + $this->assertSame($expectedResultStatus, ResultStatus::createFromHttpStatusCode($httpStatusCode)); } public static function createFromHttpStatusCodeDataProvider(): iterable { yield [ - ResponseStatus::success(), + ResultStatus::success(), 200, ]; yield [ - ResponseStatus::success(), + ResultStatus::success(), 299, ]; yield [ - ResponseStatus::rateLimit(), + ResultStatus::rateLimit(), 429, ]; yield [ - ResponseStatus::invalid(), + ResultStatus::invalid(), 400, ]; yield [ - ResponseStatus::invalid(), + ResultStatus::invalid(), 499, ]; yield [ - ResponseStatus::failed(), + ResultStatus::failed(), 500, ]; yield [ - ResponseStatus::failed(), + ResultStatus::failed(), 501, ]; yield [ - ResponseStatus::unknown(), + ResultStatus::unknown(), 199, ]; } public function testStrictComparison(): void { - $responseStatus1 = ResponseStatus::unknown(); - $responseStatus2 = ResponseStatus::unknown(); - $responseStatus3 = ResponseStatus::skipped(); + $responseStatus1 = ResultStatus::unknown(); + $responseStatus2 = ResultStatus::unknown(); + $responseStatus3 = ResultStatus::skipped(); $this->assertSame($responseStatus1, $responseStatus2); $this->assertNotSame($responseStatus1, $responseStatus3); diff --git a/tests/Util/HttpTest.php b/tests/Util/HttpTest.php new file mode 100644 index 000000000..df00fd049 --- /dev/null +++ b/tests/Util/HttpTest.php @@ -0,0 +1,43 @@ +assertSame($expectedResult, Http::getRequestHeaders($dsn, $sdkIdentifier, $sdkVersion)); + } + + public static function getRequestHeadersDataProvider(): \Generator + { + yield [ + Dsn::createFromString('http://public@example.com/1'), + 'sentry.sdk.identifier', + '1.2.3', + [ + 'Content-Type' => 'application/x-sentry-envelope', + 'X-Sentry-Auth' => 'Sentry sentry_version=7, sentry_client=sentry.sdk.identifier/1.2.3, sentry_key=public', + ], + ]; + + yield [ + Dsn::createFromString('http://public:secret@example.com/1'), + 'sentry.sdk.identifier', + '1.2.3', + [ + 'Content-Type' => 'application/x-sentry-envelope', + 'X-Sentry-Auth' => 'Sentry sentry_version=7, sentry_client=sentry.sdk.identifier/1.2.3, sentry_key=public, sentry_secret=secret', + ], + ]; + } +} diff --git a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt index facb159b9..f52c12932 100644 --- a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt +++ b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt @@ -15,15 +15,12 @@ declare(strict_types=1); namespace Sentry\Tests; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -34,22 +31,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; + echo 'Transport called' . PHP_EOL; - return new FulfilledPromise(new Response(ResponseStatus::success())); - } + return new Result(ResultStatus::success()); + } - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -62,7 +54,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index 2f684a12a..e7b457d2b 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -7,16 +7,13 @@ declare(strict_types=1); namespace Sentry\Tests; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\ErrorHandler; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -27,22 +24,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; - - return new FulfilledPromise(new Response(ResponseStatus::success())); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + echo 'Transport called' . PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -51,7 +43,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt index ac2c4e223..0063489b7 100644 --- a/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt +++ b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt @@ -9,15 +9,12 @@ declare(strict_types=1); namespace Sentry\Tests; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -28,22 +25,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; - - return new FulfilledPromise(new Response(ResponseStatus::success())); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + echo 'Transport called' . PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -53,7 +45,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt index 6b4d8e93c..f6017b8a5 100644 --- a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt +++ b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt @@ -15,10 +15,9 @@ use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -29,20 +28,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - return new FulfilledPromise(new Response(ResponseStatus::success())); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + echo 'Transport called' . PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -57,7 +53,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt index e47750b5c..2ab9a423e 100644 --- a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt @@ -12,10 +12,9 @@ use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -26,22 +25,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; - - return new FulfilledPromise(new Response(ResponseStatus::success())); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + echo 'Transport called' . PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -53,7 +47,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt index 870a0696f..4c66a5f9a 100644 --- a/tests/phpt/error_listener_integration_respects_error_types_option.phpt +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -15,10 +15,9 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -29,22 +28,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; + echo 'Transport called' . PHP_EOL; - return new FulfilledPromise(new Response(ResponseStatus::success())); - } + return new Result(ResultStatus::success()); + } - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -57,7 +51,7 @@ $options = new Options([ ]); $client = (new ClientBuilder($options)) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt index d264e9810..ba82e9bca 100644 --- a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt @@ -13,10 +13,9 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -27,22 +26,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; + echo 'Transport called' . PHP_EOL; - return new FulfilledPromise(new Response(ResponseStatus::success())); - } + return new Result(ResultStatus::success()); + } - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -54,7 +48,7 @@ $options = new Options([ ]); $client = (new ClientBuilder($options)) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index 00db34a07..8010b35b2 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -13,10 +13,9 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -27,22 +26,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called (it should not have been)' . PHP_EOL; + echo 'Transport called' . PHP_EOL; - return new FulfilledPromise(new Response(ResponseStatus::success())); - } + return new Result(ResultStatus::success()); + } - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -55,7 +49,7 @@ $options = new Options([ ]); $client = (new ClientBuilder($options)) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); From e717d2135c0d46407860c128754d87e952dde2f3 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 18 Oct 2023 16:56:13 +0200 Subject: [PATCH 0901/1161] Remove `symfony/options-resolver: ^3.4.43` (#1593) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 34cb61d32..a011a72b1 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ext-curl": "*", "jean85/pretty-package-versions": "^1.5|^2.0.4", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0" + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.19|3.4.*", From 9240c305b76790f15099aa7b1873df996af6202c Mon Sep 17 00:00:00 2001 From: fmata Date: Wed, 18 Oct 2023 17:31:19 +0200 Subject: [PATCH 0902/1161] [4.x] Rollback to is_a() to ignore exceptions instead of in_array() (#1587) Co-authored-by: Florent Mata Co-authored-by: Michi Hoffmann --- src/Client.php | 16 +++++++++------- tests/ClientTest.php | 24 ++++++++++++++++++++++++ tests/Fixtures/code/CustomException.php | 9 +++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 tests/Fixtures/code/CustomException.php diff --git a/src/Client.php b/src/Client.php index 33d74927d..4966c7039 100644 --- a/src/Client.php +++ b/src/Client.php @@ -331,13 +331,15 @@ private function applyIgnoreOptions(Event $event): ?Event } foreach ($exceptions as $exception) { - if (\in_array($exception->getType(), $this->options->getIgnoreExceptions(), true)) { - $this->logger->info( - 'The event will be discarded because it matches an entry in "ignore_exceptions".', - ['event' => $event] - ); - - return null; + foreach ($this->options->getIgnoreExceptions() as $ignoredException) { + if (is_a($exception->getType(), $ignoredException, true)) { + $this->logger->info( + 'The event will be discarded because it matches an entry in "ignore_exceptions".', + ['event' => $event] + ); + + return null; + } } } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index b76c1c6db..806458037 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -22,6 +22,7 @@ use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; +use Sentry\Tests\Fixtures\code\CustomException; use Sentry\Transport\Result; use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; @@ -594,6 +595,29 @@ public function testProcessEventDiscardsEventWhenIgnoreExceptionsMatches(): void $client->captureException($exception); } + public function testProcessEventDiscardsEventWhenParentHierarchyOfIgnoreExceptionsMatches(): void + { + $exception = new CustomException('Some foo error'); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because it matches an entry in "ignore_exceptions".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'ignore_exceptions' => [\RuntimeException::class], + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureException($exception); + } + public function testProcessEventDiscardsEventWhenIgnoreTransactionsMatches(): void { $event = Event::createTransaction(); diff --git a/tests/Fixtures/code/CustomException.php b/tests/Fixtures/code/CustomException.php new file mode 100644 index 000000000..f47296725 --- /dev/null +++ b/tests/Fixtures/code/CustomException.php @@ -0,0 +1,9 @@ + Date: Mon, 23 Oct 2023 13:12:33 +0200 Subject: [PATCH 0903/1161] Add symfony/options-resolver 7.0 (#1597) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 32ea2f592..81e9f19bd 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "psr/http-factory": "^1.0", "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", + "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0|^7.0", "symfony/polyfill-php80": "^1.17" }, "require-dev": { From 0d0752dc8fe94e0f1f0c37e8db49fa60a466d585 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 23 Oct 2023 22:34:15 +0200 Subject: [PATCH 0904/1161] Prepare 3.22.0 (#1598) --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb581d1b0..28d402977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # CHANGELOG +## 3.22.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.22.0. + +### Features + +- Adopt Starfish HTTP attributes in spans and breadcrumbs [(#1581)](https://github.com/getsentry/sentry-php/pull/1581) + +### Bug Fixes + +- Don't add empty HTTP fragment or query strings to breadcrumb data [(#1588)](https://github.com/getsentry/sentry-php/pull/1588) + +### Misc + +- Remove obsolete `tags` option depreaction [(#1588)](https://github.com/getsentry/sentry-php/pull/1588) +- Run CI on PHP 8.3 [(1591)](https://github.com/getsentry/sentry-php/pull/1591) +- Add support for `symfony/options-resolver: ^7.0` [(1597)](https://github.com/getsentry/sentry-php/pull/1597) + ## 3.21.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.21.0. From c0e3df5a5c1d133cd9461e7672568ff07042c19d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 23 Oct 2023 20:34:53 +0000 Subject: [PATCH 0905/1161] release: 3.22.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 3d0b6d305..5f1cef5fc 100644 --- a/src/Client.php +++ b/src/Client.php @@ -35,7 +35,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.21.0'; + public const SDK_VERSION = '3.22.0'; /** * @var Options The client options From 088b95f934ad03527638ed9ff2f665e23c8b6c11 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 24 Oct 2023 12:32:17 +0200 Subject: [PATCH 0906/1161] Remove deprecations (#1594) --- UPGRADE-4.0.md | 12 ++ composer.json | 2 +- phpstan-baseline.neon | 22 +-- src/Breadcrumb.php | 6 +- src/Client.php | 3 - src/ClientBuilder.php | 14 -- src/Dsn.php | 12 +- src/Exception/EventCreationException.php | 2 +- src/Exception/ExceptionInterface.php | 16 -- src/Exception/InvalidArgumentException.php | 17 -- src/Integration/IgnoreErrorsIntegration.php | 148 ------------------ src/Integration/RequestIntegration.php | 3 - src/Options.php | 33 +--- src/Serializer/AbstractSerializer.php | 3 +- src/Tracing/SpanContext.php | 39 ----- src/Tracing/TransactionContext.php | 30 ---- tests/Benchmark/SpanBench.php | 5 +- tests/ClientTest.php | 10 -- tests/DsnTest.php | 12 -- .../IgnoreErrorsIntegrationTest.php | 128 --------------- tests/OptionsTest.php | 9 -- tests/Tracing/SpanContextTest.php | 76 --------- tests/Tracing/TransactionContextTest.php | 52 ------ 23 files changed, 24 insertions(+), 630 deletions(-) create mode 100644 UPGRADE-4.0.md delete mode 100644 src/Exception/ExceptionInterface.php delete mode 100644 src/Exception/InvalidArgumentException.php delete mode 100644 src/Integration/IgnoreErrorsIntegration.php delete mode 100644 tests/Integration/IgnoreErrorsIntegrationTest.php delete mode 100644 tests/Tracing/SpanContextTest.php diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md new file mode 100644 index 000000000..45604b0aa --- /dev/null +++ b/UPGRADE-4.0.md @@ -0,0 +1,12 @@ +# Upgrade 3.x to 4.0 + +- The `send_attempts` option was removed. You may implement a custom transport if you rely on this behaviour. +- `SpanContext::fromTraceparent()` was removed. Use `Sentry\continueTrace()` instead. +- `TransactionContext::fromSentryTrace()` was removed. Use `Sentry\continueTrace()` instead. +- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_errors` option instead. +- `Sentry\Exception\InvalidArgumentException` was removed. Use `\InvalidArgumentException` instead. +- `Sentry\Exception/ExceptionInterface` was removed. +- Removed `ClientBuilderInterface::setSerializer()` +- Removed `ClientBuilder::setSerializer()` +- Removed `Client::__construct()` param SerializerInterface $serializer. +- Change return type of `Dsn:: getProjectId()` to string diff --git a/composer.json b/composer.json index a011a72b1..0e0d05890 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ext-curl": "*", "jean85/pretty-package-versions": "^1.5|^2.0.4", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0" + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.19|3.4.*", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3ca6264b3..bc3e59bf6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Constructor of class Sentry\\\\Client has an unused parameter \\$serializer\\.$#" - count: 1 - path: src/Client.php - - message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return \\(T of Sentry\\\\Integration\\\\IntegrationInterface\\)\\|null but returns \\(T of Sentry\\\\Integration\\\\IntegrationInterface\\)\\|null\\.$#" count: 1 @@ -35,11 +30,6 @@ parameters: count: 1 path: src/Dsn.php - - - message: "#^Property Sentry\\\\Integration\\\\IgnoreErrorsIntegration\\:\\:\\$options \\(array\\{ignore_exceptions\\: array\\\\>, ignore_tags\\: array\\\\}\\) does not accept array\\.$#" - count: 1 - path: src/Integration/IgnoreErrorsIntegration.php - - message: "#^Property Sentry\\\\Integration\\\\RequestIntegration\\:\\:\\$options \\(array\\{pii_sanitize_headers\\: array\\\\}\\) does not accept array\\.$#" count: 1 @@ -126,7 +116,7 @@ parameters: path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getIgnoreExceptions\\(\\) should return array\\ but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getIgnoreExceptions\\(\\) should return array\\\\> but returns mixed\\.$#" count: 1 path: src/Options.php @@ -185,11 +175,6 @@ parameters: count: 1 path: src/Options.php - - - message: "#^Method Sentry\\\\Options\\:\\:getSendAttempts\\(\\) should return int but returns mixed\\.$#" - count: 1 - path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getServerName\\(\\) should return string but returns mixed\\.$#" count: 1 @@ -310,11 +295,6 @@ parameters: count: 1 path: src/Tracing/GuzzleTracingMiddleware.php - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: src/Tracing/SpanContext.php - - message: "#^Parameter \\#1 \\$email of method Sentry\\\\UserDataBag\\:\\:setEmail\\(\\) expects string\\|null, mixed given\\.$#" count: 1 diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index 7229fddc4..a5e3116dc 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -4,8 +4,6 @@ namespace Sentry; -use Sentry\Exception\InvalidArgumentException; - /** * This class stores all the information about a breadcrumb. * @@ -118,7 +116,7 @@ final class Breadcrumb public function __construct(string $level, string $type, string $category, ?string $message = null, array $metadata = [], ?float $timestamp = null) { if (!\in_array($level, self::ALLOWED_LEVELS, true)) { - throw new InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); + throw new \InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); } $this->type = $type; @@ -174,7 +172,7 @@ public function getLevel(): string public function withLevel(string $level): self { if (!\in_array($level, self::ALLOWED_LEVELS, true)) { - throw new InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); + throw new \InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); } if ($level === $this->level) { diff --git a/src/Client.php b/src/Client.php index 4966c7039..fbb5e96cb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -10,7 +10,6 @@ use Sentry\Integration\IntegrationRegistry; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; use Sentry\Transport\Result; use Sentry\Transport\TransportInterface; @@ -81,7 +80,6 @@ final class Client implements ClientInterface * @param TransportInterface $transport The transport * @param string|null $sdkIdentifier The Sentry SDK identifier * @param string|null $sdkVersion The Sentry SDK version - * @param SerializerInterface|null $serializer The serializer argument is deprecated since version 3.3 and will be removed in 4.0. It's currently unused. * @param RepresentationSerializerInterface|null $representationSerializer The serializer for function arguments * @param LoggerInterface|null $logger The PSR-3 logger */ @@ -90,7 +88,6 @@ public function __construct( TransportInterface $transport, ?string $sdkIdentifier = null, ?string $sdkVersion = null, - ?SerializerInterface $serializer = null, ?RepresentationSerializerInterface $representationSerializer = null, ?LoggerInterface $logger = null ) { diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index aaefee950..1131d0c29 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -9,7 +9,6 @@ use Sentry\HttpClient\HttpClientInterface; use Sentry\Serializer\PayloadSerializer; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\SerializerInterface; use Sentry\Transport\HttpTransport; use Sentry\Transport\TransportInterface; @@ -35,11 +34,6 @@ final class ClientBuilder */ private $httpClient; - /** - * @var SerializerInterface|null The serializer to be injected in the client - */ - private $serializer; - /** * @var RepresentationSerializerInterface|null The representation serializer to be injected in the client */ @@ -91,13 +85,6 @@ public function getOptions(): Options return $this->options; } - public function setSerializer(SerializerInterface $serializer): ClientBuilder - { - $this->serializer = $serializer; - - return $this; - } - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): ClientBuilder { $this->representationSerializer = $representationSerializer; @@ -157,7 +144,6 @@ public function getClient(): ClientInterface $this->transport, $this->sdkIdentifier, $this->sdkVersion, - $this->serializer, $this->representationSerializer, $this->logger ); diff --git a/src/Dsn.php b/src/Dsn.php index ee09d68d0..054289bc3 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -150,18 +150,10 @@ public function getPath(): string /** * Gets the ID of the resource to access. - * - * @return int|string */ - public function getProjectId(bool $returnAsString = false) + public function getProjectId(): string { - if ($returnAsString) { - return $this->projectId; - } - - @trigger_error(sprintf('Calling the method %s() and expecting it to return an integer is deprecated since version 3.4 and will stop working in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - return (int) $this->projectId; + return $this->projectId; } /** diff --git a/src/Exception/EventCreationException.php b/src/Exception/EventCreationException.php index 94dae2a2c..666515193 100644 --- a/src/Exception/EventCreationException.php +++ b/src/Exception/EventCreationException.php @@ -7,7 +7,7 @@ /** * This exception is thrown when an issue is preventing the creation of an {@see Event}. */ -class EventCreationException extends \RuntimeException implements ExceptionInterface +class EventCreationException extends \RuntimeException { /** * EventCreationException constructor. diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php deleted file mode 100644 index d4be5c56d..000000000 --- a/src/Exception/ExceptionInterface.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * @deprecated since version 3.1, to be removed in 4.0 - */ -interface ExceptionInterface -{ -} diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php deleted file mode 100644 index 745f0e82b..000000000 --- a/src/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * @deprecated since version 3.1, to be removed in 4.0 - */ -class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface -{ -} diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php deleted file mode 100644 index 7264be103..000000000 --- a/src/Integration/IgnoreErrorsIntegration.php +++ /dev/null @@ -1,148 +0,0 @@ - - * - * @psalm-type IntegrationOptions array{ - * ignore_exceptions: list>, - * ignore_tags: array - * } - */ -final class IgnoreErrorsIntegration implements IntegrationInterface -{ - /** - * @var array The options - * - * @psalm-var IntegrationOptions - */ - private $options; - - /** - * Creates a new instance of this integration and configures it with the - * given options. - * - * @param array $options The options - * - * @psalm-param array{ - * ignore_exceptions?: list>, - * ignore_tags?: array - * } $options - */ - public function __construct(array $options = []) - { - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - 'ignore_exceptions' => [], - 'ignore_tags' => [], - ]); - - $resolver->setAllowedTypes('ignore_exceptions', ['array']); - $resolver->setAllowedTypes('ignore_tags', ['array']); - - $this->options = $resolver->resolve($options); - } - - /** - * {@inheritdoc} - */ - public function setupOnce(): void - { - Scope::addGlobalEventProcessor(static function (Event $event): ?Event { - $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); - - if (null !== $integration && $integration->shouldDropEvent($event, $integration->options)) { - return null; - } - - return $event; - }); - } - - /** - * Checks whether the given event should be dropped according to the options - * that configures the current instance of this integration. - * - * @param Event $event The event to check - * @param array $options The options of the integration - * - * @psalm-param IntegrationOptions $options - */ - private function shouldDropEvent(Event $event, array $options): bool - { - if ($this->isIgnoredException($event, $options)) { - return true; - } - - if ($this->isIgnoredTag($event, $options)) { - return true; - } - - return false; - } - - /** - * Checks whether the given event should be dropped or not according to the - * criteria defined in the integration's options. - * - * @param Event $event The event instance - * @param array $options The options of the integration - * - * @psalm-param IntegrationOptions $options - */ - private function isIgnoredException(Event $event, array $options): bool - { - $exceptions = $event->getExceptions(); - - if (empty($exceptions)) { - return false; - } - - foreach ($options['ignore_exceptions'] as $ignoredException) { - if (is_a($exceptions[0]->getType(), $ignoredException, true)) { - return true; - } - } - - return false; - } - - /** - * Checks whether the given event should be dropped or not according to the - * criteria defined in the integration's options. - * - * @param Event $event The event instance - * @param array $options The options of the integration - * - * @psalm-param IntegrationOptions $options - */ - private function isIgnoredTag(Event $event, array $options): bool - { - $tags = $event->getTags(); - - if (empty($tags)) { - return false; - } - - foreach ($options['ignore_tags'] as $key => $value) { - if (isset($tags[$key]) && $tags[$key] === $value) { - return true; - } - } - - return false; - } -} diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 6c38d2058..b35007bcd 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -41,11 +41,8 @@ final class RequestIntegration implements IntegrationInterface /** * This constant is a map of maximum allowed sizes for each value of the * `max_request_body_size` option. - * - * @deprecated The 'none' option is deprecated since version 3.10, to be removed in 4.0 */ private const MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP = [ - 'none' => 0, 'never' => 0, 'small' => self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH, 'medium' => self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH, diff --git a/src/Options.php b/src/Options.php index bdcc79e4c..4616e4174 100644 --- a/src/Options.php +++ b/src/Options.php @@ -64,36 +64,6 @@ public function __construct(array $options = []) } } - /** - * Gets the number of attempts to resend an event that failed to be sent. - * - * @deprecated since version 3.5, to be removed in 4.0 - */ - public function getSendAttempts(/*bool $triggerDeprecation = true*/): int - { - if (0 === \func_num_args() || false !== func_get_arg(0)) { - @trigger_error(sprintf('Method %s() is deprecated since version 3.5 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - } - - return $this->options['send_attempts']; - } - - /** - * Sets the number of attempts to resend an event that failed to be sent. - * - * @param int $attemptsCount The number of attempts - * - * @deprecated since version 3.5, to be removed in 4.0 - */ - public function setSendAttempts(int $attemptsCount): void - { - @trigger_error(sprintf('Method %s() is deprecated since version 3.5 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - $options = array_merge($this->options, ['send_attempts' => $attemptsCount]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the prefixes which should be stripped from filenames to create * relative paths. @@ -422,6 +392,7 @@ public function setServerName(string $serverName): void * Gets a list of exceptions to be ignored and not sent to Sentry. * * @return string[] + * @psalm-return list> */ public function getIgnoreExceptions(): array { @@ -941,7 +912,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'integrations' => [], 'default_integrations' => true, - 'send_attempts' => 0, 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, 'enable_tracing' => null, @@ -987,7 +957,6 @@ private function configureOptions(OptionsResolver $resolver): void 'class_serializers' => [], ]); - $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'string[]'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('enable_tracing', ['null', 'bool']); diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 1e2a72b25..4e20b9175 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -20,7 +20,6 @@ namespace Sentry\Serializer; -use Sentry\Exception\InvalidArgumentException; use Sentry\Options; /** @@ -286,7 +285,7 @@ protected function serializeCallable($callable): string } if (!\is_callable($callable)) { - throw new InvalidArgumentException(sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); + throw new \InvalidArgumentException(sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); } try { diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 0aa336ee2..59e80745b 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -6,11 +6,6 @@ class SpanContext { - /** - * @deprecated since version 3.1, to be removed in 4.0 - */ - private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; - /** * @var string|null Description of the Span */ @@ -187,38 +182,4 @@ public function setEndTimestamp(?float $endTimestamp): void { $this->endTimestamp = $endTimestamp; } - - /** - * Returns a context populated with the data of the given header. - * - * @param string $header The sentry-trace header from the request - * - * @return static - * - * @deprecated since version 3.1, to be removed in 4.0 - */ - public static function fromTraceparent(string $header) - { - @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromHeaders() instead.', __METHOD__), \E_USER_DEPRECATED); - - $context = new static(); - - if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { - return $context; - } - - if (!empty($matches['trace_id'])) { - $context->traceId = new TraceId($matches['trace_id']); - } - - if (!empty($matches['span_id'])) { - $context->parentSpanId = new SpanId($matches['span_id']); - } - - if (isset($matches['sampled'])) { - $context->sampled = '1' === $matches['sampled']; - } - - return $context; - } } diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index dd70441dd..863dd28e2 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -106,36 +106,6 @@ public function setSource(TransactionSource $transactionSource): void $this->metadata->setSource($transactionSource); } - /** - * Returns a context populated with the data of the given header. - * - * @param string $header The sentry-trace header from the request - * - * @deprecated since version 3.9, to be removed in 4.0 - */ - public static function fromSentryTrace(string $header): self - { - $context = new self(); - - if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { - return $context; - } - - if (!empty($matches['trace_id'])) { - $context->traceId = new TraceId($matches['trace_id']); - } - - if (!empty($matches['span_id'])) { - $context->parentSpanId = new SpanId($matches['span_id']); - } - - if (isset($matches['sampled'])) { - $context->parentSampled = '1' === $matches['sampled']; - } - - return $context; - } - /** * Returns a context populated with the data of the given environment variables. * diff --git a/tests/Benchmark/SpanBench.php b/tests/Benchmark/SpanBench.php index a4414373b..78e1d1ca6 100644 --- a/tests/Benchmark/SpanBench.php +++ b/tests/Benchmark/SpanBench.php @@ -8,6 +8,7 @@ use PhpBench\Benchmark\Metadata\Annotations\Revs; use Sentry\Tracing\Span; use Sentry\Tracing\TransactionContext; +use function Sentry\continueTrace; final class SpanBench { @@ -23,8 +24,8 @@ final class SpanBench public function __construct() { - $this->context = TransactionContext::fromSentryTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0'); - $this->contextWithTimestamp = TransactionContext::fromSentryTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0'); + $this->context = continueTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', ''); + $this->contextWithTimestamp = continueTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', ''); $this->contextWithTimestamp->setStartTimestamp(microtime(true)); } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 806458037..bc1b35cb6 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -17,8 +17,6 @@ use Sentry\Integration\IntegrationInterface; use Sentry\Options; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; -use Sentry\Serializer\SerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; @@ -71,7 +69,6 @@ public function setupOnce(): void null, null, null, - null, $logger ); @@ -767,7 +764,6 @@ public function testBuildEventInCLIDoesntSetTransaction(): void $transport, 'sentry.sdk.identifier', '1.2.3', - $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class) ); @@ -806,7 +802,6 @@ public function testBuildEventWithException(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -841,7 +836,6 @@ public function testBuildEventWithExceptionAndMechansim(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -870,7 +864,6 @@ public function testBuildWithErrorException(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -909,7 +902,6 @@ public function testBuildWithStacktrace(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -945,7 +937,6 @@ public function testBuildWithCustomStacktrace(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -967,7 +958,6 @@ public function testGetCspReportUrl(array $options, ?string $expectedUrl): void $this->createMock(TransportInterface::class), 'sentry.sdk.identifier', '1.2.3', - $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class) ); diff --git a/tests/DsnTest.php b/tests/DsnTest.php index 0ebf8ef42..3ea095356 100644 --- a/tests/DsnTest.php +++ b/tests/DsnTest.php @@ -271,16 +271,4 @@ public static function toStringDataProvider(): array ['https://public@example.com:4343/sentry/1'], ]; } - - /** - * @group legacy - */ - public function testGetProjectIdTriggersDeprecationErrorIfReturningInteger(): void - { - $dsn = Dsn::createFromString('https://public@example.com/sentry/1'); - - $this->expectDeprecation('Calling the method Sentry\\Dsn::getProjectId() and expecting it to return an integer is deprecated since version 3.4 and will stop working in 4.0.'); - - $this->assertSame(1, $dsn->getProjectId()); - } } diff --git a/tests/Integration/IgnoreErrorsIntegrationTest.php b/tests/Integration/IgnoreErrorsIntegrationTest.php deleted file mode 100644 index d56f40c50..000000000 --- a/tests/Integration/IgnoreErrorsIntegrationTest.php +++ /dev/null @@ -1,128 +0,0 @@ -setupOnce(); - - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getIntegration') - ->willReturn($isIntegrationEnabled ? $integration : null); - - SentrySdk::getCurrentHub()->bindClient($client); - - withScope(function (Scope $scope) use ($event, $expectedEventToBeDropped): void { - $event = $scope->applyToEvent($event); - - if ($expectedEventToBeDropped) { - $this->assertNull($event); - } else { - $this->assertNotNull($event); - } - }); - } - - public static function invokeDataProvider(): \Generator - { - $event = Event::createEvent(); - $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); - - yield 'Integration disabled' => [ - Event::createEvent(), - false, - [ - 'ignore_exceptions' => [], - ], - false, - ]; - - $event = Event::createEvent(); - $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); - - yield 'No exceptions to check' => [ - Event::createEvent(), - true, - [ - 'ignore_exceptions' => [], - ], - false, - ]; - - $event = Event::createEvent(); - $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); - - yield 'The exception is matching exactly the "ignore_exceptions" option' => [ - $event, - true, - [ - 'ignore_exceptions' => [ - \RuntimeException::class, - ], - ], - true, - ]; - - $event = Event::createEvent(); - $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); - - yield 'The exception is matching the "ignore_exceptions" option' => [ - $event, - true, - [ - 'ignore_exceptions' => [ - \Exception::class, - ], - ], - true, - ]; - - $event = Event::createEvent(); - $event->setTags(['route' => 'foo']); - - yield 'The tag is matching the "ignore_tags" option' => [ - $event, - true, - [ - 'ignore_tags' => [ - 'route' => 'foo', - ], - ], - true, - ]; - - $event = Event::createEvent(); - $event->setTags(['route' => 'bar']); - - yield 'The tag is not matching the "ignore_tags" option' => [ - $event, - true, - [ - 'ignore_tags' => [ - 'route' => 'foo', - ], - ], - false, - ]; - } -} diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index b27dc02f7..317225e46 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -89,15 +89,6 @@ public function testGettersAndSetters( public static function optionsDataProvider(): \Generator { - yield [ - 'send_attempts', - 1, - 'getSendAttempts', - 'setSendAttempts', - 'Method Sentry\\Options::getSendAttempts() is deprecated since version 3.5 and will be removed in 4.0.', - 'Method Sentry\\Options::setSendAttempts() is deprecated since version 3.5 and will be removed in 4.0.', - ]; - yield [ 'prefixes', ['foo', 'bar'], diff --git a/tests/Tracing/SpanContextTest.php b/tests/Tracing/SpanContextTest.php deleted file mode 100644 index 0ca04622c..000000000 --- a/tests/Tracing/SpanContextTest.php +++ /dev/null @@ -1,76 +0,0 @@ -expectDeprecation('The Sentry\\Tracing\\SpanContext::fromTraceparent() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromHeaders() instead.'); - - $spanContext = SpanContext::fromTraceparent($header); - - if (null !== $expectedSpanId) { - $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); - } - - if (null !== $expectedTraceId) { - $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); - } - - $this->assertSame($expectedSampled, $spanContext->getSampled()); - } - - public static function fromTraceparentDataProvider(): iterable - { - yield [ - '0', - null, - null, - false, - ]; - - yield [ - '1', - null, - null, - true, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - false, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - true, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - null, - ]; - } -} diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index 0e9e3f5d3..c4c1176e2 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -37,58 +37,6 @@ public function testGettersAndSetters(): void $this->assertSame($transactionSource, $transactionContext->getMetadata()->getSource()); } - /** - * @dataProvider fromSentryTraceDataProvider - * - * @group legacy - */ - public function testFromTraceparent(string $header, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedParentSampled): void - { - $spanContext = TransactionContext::fromSentryTrace($header); - - $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); - $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); - $this->assertSame($expectedParentSampled, $spanContext->getParentSampled()); - } - - public static function fromSentryTraceDataProvider(): iterable - { - yield [ - '0', - null, - null, - false, - ]; - - yield [ - '1', - null, - null, - true, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - false, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - true, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - null, - ]; - } - /** * @dataProvider tracingDataProvider */ From a8901b2a4872091ac5d542f10c67ea0a9b33f97d Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 24 Oct 2023 14:58:20 +0200 Subject: [PATCH 0907/1161] Improve header parsing and add tests (#1600) --- src/Util/Http.php | 16 +++++++++++++--- tests/Util/HttpTest.php | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/Util/Http.php b/src/Util/Http.php index a82152175..214033fcd 100644 --- a/src/Util/Http.php +++ b/src/Util/Http.php @@ -40,15 +40,25 @@ public static function getRequestHeaders(Dsn $dsn, string $sdkIdentifier, string /** * @param string[][] $headers + * + * @param-out string[][] $headers */ - public static function parseResponseHeaders(string $headerLine, &$headers): int + public static function parseResponseHeaders(string $headerLine, array &$headers): int { if (false === strpos($headerLine, ':')) { return \strlen($headerLine); } - [$key, $value] = explode(':', trim($headerLine), 2); - $headers[trim($key)] = trim($value); + [$name, $value] = explode(':', trim($headerLine), 2); + + $name = trim($name); + $value = trim($value); + + if (isset($headers[$name])) { + $headers[$name][] = $value; + } else { + $headers[$name] = (array) $value; + } return \strlen($headerLine); } diff --git a/tests/Util/HttpTest.php b/tests/Util/HttpTest.php index df00fd049..e5566d6e2 100644 --- a/tests/Util/HttpTest.php +++ b/tests/Util/HttpTest.php @@ -40,4 +40,42 @@ public static function getRequestHeadersDataProvider(): \Generator ], ]; } + + /** + * @dataProvider parseResponseHeadersDataProvider + */ + public function testParseResponseHeaders(string $headerline, $expectedResult): void + { + $responseHeaders = []; + + Http::parseResponseHeaders($headerline, $responseHeaders); + + $this->assertSame($expectedResult, $responseHeaders); + } + + public static function parseResponseHeadersDataProvider(): \Generator + { + yield [ + 'Content-Type: application/json', + [ + 'Content-Type' => [ + 'application/json', + ], + ], + ]; + + yield [ + 'X-Sentry-Rate-Limits: 60:transaction:key,2700:default;error;security:organization', + [ + 'X-Sentry-Rate-Limits' => [ + '60:transaction:key,2700:default;error;security:organization', + ], + ], + ]; + + yield [ + 'Invalid', + [], + ]; + } } From f8b64d330d03cbf809795beb5846b2c48175cd62 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 24 Oct 2023 16:03:22 +0200 Subject: [PATCH 0908/1161] Update PHPCS (#1599) --- .github/workflows/static-analysis.yaml | 2 +- .php-cs-fixer.dist.php | 15 +++- composer.json | 2 +- phpstan-baseline.neon | 70 ------------------- src/CheckIn.php | 2 +- src/Client.php | 38 +++++----- src/ClientBuilder.php | 2 +- src/ClientInterface.php | 6 +- src/Context/OsContext.php | 4 +- src/Context/RuntimeContext.php | 4 +- src/Dsn.php | 16 ++--- src/ErrorHandler.php | 16 ++--- src/Event.php | 6 +- src/EventHint.php | 6 +- src/Frame.php | 2 +- src/FrameBuilder.php | 12 ++-- src/HttpClient/HttpClient.php | 8 +-- src/HttpClient/Response.php | 2 +- .../AbstractErrorListenerIntegration.php | 2 +- src/Integration/EnvironmentIntegration.php | 16 ++--- src/Integration/ErrorListenerIntegration.php | 2 +- .../ExceptionListenerIntegration.php | 2 +- .../FatalErrorListenerIntegration.php | 2 +- .../FrameContextifierIntegration.php | 12 ++-- src/Integration/IntegrationRegistry.php | 4 +- src/Integration/ModulesIntegration.php | 2 +- src/Integration/RequestIntegration.php | 20 +++--- src/Integration/TransactionIntegration.php | 4 +- src/Monolog/Handler.php | 6 +- src/Options.php | 25 ++++--- src/Profiling/Profile.php | 25 +++---- src/Profiling/Profiler.php | 6 +- src/SentrySdk.php | 2 +- src/Serializer/AbstractSerializer.php | 6 +- src/Serializer/PayloadSerializer.php | 66 ++++++++--------- src/Serializer/RepresentationSerializer.php | 6 +- src/Severity.php | 46 ++++++------ src/Stacktrace.php | 2 +- src/State/Hub.php | 42 +++++------ src/State/HubAdapter.php | 4 +- src/State/HubInterface.php | 8 +-- src/State/Scope.php | 26 +++---- src/Tracing/DynamicSamplingContext.php | 26 +++---- src/Tracing/GuzzleTracingMiddleware.php | 24 +++---- src/Tracing/PropagationContext.php | 8 +-- src/Tracing/Span.php | 16 ++--- src/Tracing/SpanStatus.php | 18 ++--- src/Tracing/Transaction.php | 24 +++---- src/Tracing/TransactionContext.php | 2 +- src/Transport/RateLimiter.php | 4 +- src/Transport/ResultStatus.php | 2 +- src/UserDataBag.php | 4 +- src/Util/Http.php | 4 +- src/Util/JSON.php | 6 +- src/Util/PrefixStripper.php | 2 +- src/Util/SentryUid.php | 2 +- src/functions.php | 14 ++-- tests/Benchmark/SpanBench.php | 4 ++ tests/ClientTest.php | 6 +- tests/FunctionsTest.php | 6 +- .../EnvironmentIntegrationTest.php | 5 +- .../FrameContextifierIntegrationTest.php | 3 +- tests/Integration/ModulesIntegrationTest.php | 1 + tests/Integration/RequestIntegrationTest.php | 7 +- .../TransactionIntegrationTest.php | 1 + tests/Monolog/RecordFactory.php | 3 +- tests/OptionsTest.php | 10 +-- tests/Profiling/ProfileTest.php | 2 +- tests/Serializer/AbstractSerializerTest.php | 6 +- tests/Serializer/PayloadSerializerTest.php | 4 +- tests/StacktraceTest.php | 2 +- tests/Tracing/PropagationContextTest.php | 8 +-- tests/Tracing/TransactionTest.php | 5 +- 73 files changed, 358 insertions(+), 420 deletions(-) diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index f61e77a0a..7ac104a8e 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -18,7 +18,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 8f4027a98..3975a591f 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -10,9 +10,20 @@ 'imports_order' => ['class', 'function', 'const'], ], 'declare_strict_types' => true, - 'yoda_style' => true, + 'get_class_to_class_keyword' => false, + 'yoda_style' => [ + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, + ], 'self_accessor' => false, - 'phpdoc_no_useless_inheritdoc' => false, + 'modernize_strpos' => false, + 'nullable_type_declaration_for_default_null_value' => [ + 'use_nullable_type_declaration' => true, + ], + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => true, + ], 'phpdoc_to_comment' => false, 'phpdoc_align' => [ 'tags' => ['param', 'return', 'throws', 'type', 'var'], diff --git a/composer.json b/composer.json index 0e0d05890..19f4f89ba 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.19|3.4.*", + "friendsofphp/php-cs-fixer": "^3.4", "guzzlehttp/promises": "^1.0|^2.0", "guzzlehttp/psr7": "^1.8.4|^2.1.1", "http-interop/http-factory-guzzle": "^1.0", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index bc3e59bf6..e008f9ef9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5,11 +5,6 @@ parameters: count: 1 path: src/Client.php - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$hint$#" - count: 3 - path: src/ClientInterface.php - - message: "#^Offset 'host' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 @@ -240,51 +235,6 @@ parameters: count: 1 path: src/Serializer/AbstractSerializer.php - - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" - count: 1 - path: src/State/Hub.php - - - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureLastError\\(\\) invoked with 2 parameters, 0\\-1 required\\.$#" - count: 1 - path: src/State/Hub.php - - - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureMessage\\(\\) invoked with 4 parameters, 1\\-3 required\\.$#" - count: 1 - path: src/State/Hub.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 1 - path: src/State/HubAdapter.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureLastError\\(\\) invoked with 1 parameter, 0 required\\.$#" - count: 1 - path: src/State/HubAdapter.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureMessage\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" - count: 1 - path: src/State/HubAdapter.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 1 - path: src/State/HubAdapter.php - - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$customSamplingContext$#" - count: 1 - path: src/State/HubInterface.php - - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$hint$#" - count: 3 - path: src/State/HubInterface.php - - message: "#^Call to method getResponse\\(\\) on an unknown class GuzzleHttp\\\\Exception\\\\RequestException\\.$#" count: 1 @@ -324,23 +274,3 @@ parameters: message: "#^Method Sentry\\\\Util\\\\JSON\\:\\:encode\\(\\) should return string but returns string\\|false\\.$#" count: 1 path: src/Util/JSON.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 1 - path: src/functions.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureLastError\\(\\) invoked with 1 parameter, 0 required\\.$#" - count: 1 - path: src/functions.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureMessage\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" - count: 1 - path: src/functions.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 1 - path: src/functions.php diff --git a/src/CheckIn.php b/src/CheckIn.php index 7cc70e756..f49e83a7d 100644 --- a/src/CheckIn.php +++ b/src/CheckIn.php @@ -49,7 +49,7 @@ final class CheckIn public function __construct( string $monitorSlug, CheckInStatus $status, - string $id = null, + ?string $id = null, ?string $release = null, ?string $environment = null, $duration = null, diff --git a/src/Client.php b/src/Client.php index fbb5e96cb..2f43cff0f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -115,7 +115,7 @@ public function getCspReportUrl(): ?string { $dsn = $this->options->getDsn(); - if (null === $dsn) { + if ($dsn === null) { return null; } @@ -151,7 +151,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null, ?E { $hint = $hint ?? new EventHint(); - if (null === $hint->exception) { + if ($hint->exception === null) { $hint->exception = $exception; } @@ -165,7 +165,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scop { $event = $this->prepareEvent($event, $hint, $scope); - if (null === $event) { + if ($event === null) { return null; } @@ -174,7 +174,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scop $result = $this->transport->send($event); $event = $result->getEvent(); - if (null !== $event) { + if ($event !== null) { return $event->getId(); } } catch (\Throwable $exception) { @@ -194,7 +194,7 @@ public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): { $error = error_get_last(); - if (null === $error || !isset($error['message'][0])) { + if ($error === null || !isset($error['message'][0])) { return null; } @@ -241,12 +241,12 @@ public function getStacktraceBuilder(): StacktraceBuilder */ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?Event { - if (null !== $hint) { - if (null !== $hint->exception && empty($event->getExceptions())) { + if ($hint !== null) { + if ($hint->exception !== null && empty($event->getExceptions())) { $this->addThrowableToEvent($event, $hint->exception, $hint); } - if (null !== $hint->stacktrace && null === $event->getStacktrace()) { + if ($hint->stacktrace !== null && $event->getStacktrace() === null) { $event->setStacktrace($hint->stacktrace); } } @@ -257,19 +257,19 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setSdkVersion($this->sdkVersion); $event->setTags(array_merge($this->options->getTags(), $event->getTags())); - if (null === $event->getServerName()) { + if ($event->getServerName() === null) { $event->setServerName($this->options->getServerName()); } - if (null === $event->getRelease()) { + if ($event->getRelease() === null) { $event->setRelease($this->options->getRelease()); } - if (null === $event->getEnvironment()) { + if ($event->getEnvironment() === null) { $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT); } - if (null === $event->getLogger()) { + if ($event->getLogger() === null) { $event->setLogger($this->options->getLogger(false)); } @@ -284,15 +284,15 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event = $this->applyIgnoreOptions($event); - if (null === $event) { + if ($event === null) { return null; } - if (null !== $scope) { + if ($scope !== null) { $beforeEventProcessors = $event; $event = $scope->applyToEvent($event, $hint, $this->options); - if (null === $event) { + if ($event === null) { $this->logger->info( 'The event will be discarded because one of the event processors returned "null".', ['event' => $beforeEventProcessors] @@ -305,7 +305,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $beforeSendCallback = $event; $event = $this->applyBeforeSendCallback($event, $hint); - if (null === $event) { + if ($event === null) { $this->logger->info( sprintf( 'The event will be discarded because the "%s" callback returned "null".', @@ -344,7 +344,7 @@ private function applyIgnoreOptions(Event $event): ?Event if ($event->getType() === EventType::transaction()) { $transactionName = $event->getTransaction(); - if (null === $transactionName) { + if ($transactionName === null) { return $event; } @@ -397,7 +397,7 @@ private function addMissingStacktraceToEvent(Event $event): void } // We should not add a stacktrace when the event already has one or contains exceptions - if (null !== $event->getStacktrace() || !empty($event->getExceptions())) { + if ($event->getStacktrace() !== null || !empty($event->getExceptions())) { return; } @@ -417,7 +417,7 @@ private function addMissingStacktraceToEvent(Event $event): void */ private function addThrowableToEvent(Event $event, \Throwable $exception, EventHint $hint): void { - if ($exception instanceof \ErrorException && null === $event->getLevel()) { + if ($exception instanceof \ErrorException && $event->getLevel() === null) { $event->setLevel(Severity::fromError($exception->getSeverity())); } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 1131d0c29..680ff73c6 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -59,7 +59,7 @@ final class ClientBuilder * * @param Options|null $options The client options */ - public function __construct(Options $options = null) + public function __construct(?Options $options = null) { $this->options = $options ?? new Options(); diff --git a/src/ClientInterface.php b/src/ClientInterface.php index b8774385d..33af1ff4e 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -31,7 +31,7 @@ public function getOptions(): Options; * @param Scope|null $scope An optional scope keeping the state * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId; /** * Logs an exception. @@ -40,7 +40,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope * @param Scope|null $scope An optional scope keeping the state * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureException(\Throwable $exception, ?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; + public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId; /** * Logs the most recent error (obtained with {@link error_get_last}). @@ -48,7 +48,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null/*, * @param Scope|null $scope An optional scope keeping the state * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureLastError(?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; + public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId; /** * Captures a new event using the provided data. diff --git a/src/Context/OsContext.php b/src/Context/OsContext.php index 5578dc4be..c8424f912 100644 --- a/src/Context/OsContext.php +++ b/src/Context/OsContext.php @@ -52,7 +52,7 @@ public function __construct( ?string $kernelVersion = null, ?string $machineType = null ) { - if ('' === trim($name)) { + if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -78,7 +78,7 @@ public function getName(): string */ public function setName(string $name): void { - if ('' === trim($name)) { + if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index d0a114e48..65ea1fa36 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -29,7 +29,7 @@ final class RuntimeContext */ public function __construct(string $name, ?string $version = null) { - if ('' === trim($name)) { + if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -52,7 +52,7 @@ public function getName(): string */ public function setName(string $name): void { - if ('' === trim($name)) { + if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Dsn.php b/src/Dsn.php index 054289bc3..d3dde5044 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -78,7 +78,7 @@ public static function createFromString(string $value): self { $parsedDsn = parse_url($value); - if (false === $parsedDsn) { + if ($parsedDsn === false) { throw new \InvalidArgumentException(sprintf('The "%s" DSN is invalid.', $value)); } @@ -101,14 +101,14 @@ public static function createFromString(string $value): self $lastSlashPosition = strrpos($parsedDsn['path'], '/'); $path = $parsedDsn['path']; - if (false !== $lastSlashPosition) { + if ($lastSlashPosition !== false) { $path = substr($parsedDsn['path'], 0, $lastSlashPosition); } return new self( $parsedDsn['scheme'], $parsedDsn['host'], - $parsedDsn['port'] ?? ('http' === $parsedDsn['scheme'] ? 80 : 443), + $parsedDsn['port'] ?? ($parsedDsn['scheme'] === 'http' ? 80 : 443), $projectId, $path, $parsedDsn['user'], @@ -203,17 +203,17 @@ public function __toString(): string { $url = $this->scheme . '://' . $this->publicKey; - if (null !== $this->secretKey) { + if ($this->secretKey !== null) { $url .= ':' . $this->secretKey; } $url .= '@' . $this->host; - if (('http' === $this->scheme && 80 !== $this->port) || ('https' === $this->scheme && 443 !== $this->port)) { + if (($this->scheme === 'http' && $this->port !== 80) || ($this->scheme === 'https' && $this->port !== 443)) { $url .= ':' . $this->port; } - if (null !== $this->path) { + if ($this->path !== null) { $url .= $this->path; } @@ -229,11 +229,11 @@ private function getBaseEndpointUrl(): string { $url = $this->scheme . '://' . $this->host; - if (('http' === $this->scheme && 80 !== $this->port) || ('https' === $this->scheme && 443 !== $this->port)) { + if (($this->scheme === 'http' && $this->port !== 80) || ($this->scheme === 'https' && $this->port !== 443)) { $url .= ':' . $this->port; } - if (null !== $this->path) { + if ($this->path !== null) { $url .= $this->path; } diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 1fee7d01c..fc6ff3253 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -132,7 +132,7 @@ private function __construct() */ public static function registerOnceErrorHandler(): self { - if (null === self::$handlerInstance) { + if (self::$handlerInstance === null) { self::$handlerInstance = new self(); } @@ -145,7 +145,7 @@ public static function registerOnceErrorHandler(): self self::$handlerInstance->isErrorHandlerRegistered = true; self::$handlerInstance->previousErrorHandler = set_error_handler($errorHandlerCallback); - if (null === self::$handlerInstance->previousErrorHandler) { + if (self::$handlerInstance->previousErrorHandler === null) { restore_error_handler(); // Specifying the error types caught by the error handler with the @@ -172,7 +172,7 @@ public static function registerOnceFatalErrorHandler(int $reservedMemorySize = s throw new \InvalidArgumentException('The $reservedMemorySize argument must be greater than 0.'); } - if (null === self::$handlerInstance) { + if (self::$handlerInstance === null) { self::$handlerInstance = new self(); } @@ -195,7 +195,7 @@ public static function registerOnceFatalErrorHandler(int $reservedMemorySize = s */ public static function registerOnceExceptionHandler(): self { - if (null === self::$handlerInstance) { + if (self::$handlerInstance === null) { self::$handlerInstance = new self(); } @@ -272,7 +272,7 @@ public function addExceptionHandlerListener(callable $listener): void */ private function handleError(int $level, string $message, string $file, int $line, ?array $errcontext = []): bool { - $isSilencedError = 0 === error_reporting(); + $isSilencedError = error_reporting() === 0; if (\PHP_MAJOR_VERSION >= 8) { // Starting from PHP8, when a silenced error occurs the `error_reporting()` @@ -301,7 +301,7 @@ private function handleError(int $level, string $message, string $file, int $lin $this->invokeListeners($this->errorListeners, $errorAsException); - if (null !== $this->previousErrorHandler) { + if ($this->previousErrorHandler !== null) { return false !== ($this->previousErrorHandler)($level, $message, $file, $line, $errcontext); } @@ -317,7 +317,7 @@ private function handleFatalError(): void { // If there is not enough memory that can be used to handle the error // do nothing - if (null === self::$reservedMemory) { + if (self::$reservedMemory === null) { return; } @@ -353,7 +353,7 @@ private function handleException(\Throwable $exception): void $this->previousExceptionHandler = null; try { - if (null !== $previousExceptionHandler) { + if ($previousExceptionHandler !== null) { $previousExceptionHandler($exception); return; diff --git a/src/Event.php b/src/Event.php index 215754309..57d7d0991 100644 --- a/src/Event.php +++ b/src/Event.php @@ -200,7 +200,7 @@ public static function createEvent(?EventId $eventId = null): self * * @param EventId|null $eventId The ID of the event */ - public static function createTransaction(EventId $eventId = null): self + public static function createTransaction(?EventId $eventId = null): self { return new self($eventId, EventType::transaction()); } @@ -260,8 +260,6 @@ public function setSdkVersion(string $sdkVersion): void /** * Gets the timestamp of when this event was generated. - * - * @return float */ public function getTimestamp(): ?float { @@ -731,7 +729,7 @@ public function setSdkMetadata(string $name, $data): void */ public function getSdkMetadata(?string $name = null) { - if (null !== $name) { + if ($name !== null) { return $this->sdkMetadata[$name] ?? null; } diff --git a/src/EventHint.php b/src/EventHint.php index ed3b0b9f1..ceff03315 100644 --- a/src/EventHint.php +++ b/src/EventHint.php @@ -55,15 +55,15 @@ public static function fromArray(array $hintData): self $stacktrace = $hintData['stacktrace'] ?? null; $extra = $hintData['extra'] ?? []; - if (null !== $exception && !$exception instanceof \Throwable) { + if ($exception !== null && !$exception instanceof \Throwable) { throw new \InvalidArgumentException(sprintf('The value of the "exception" field must be an instance of a class implementing the "%s" interface. Got: "%s".', \Throwable::class, get_debug_type($exception))); } - if (null !== $mechanism && !$mechanism instanceof ExceptionMechanism) { + if ($mechanism !== null && !$mechanism instanceof ExceptionMechanism) { throw new \InvalidArgumentException(sprintf('The value of the "mechanism" field must be an instance of the "%s" class. Got: "%s".', ExceptionMechanism::class, get_debug_type($mechanism))); } - if (null !== $stacktrace && !$stacktrace instanceof Stacktrace) { + if ($stacktrace !== null && !$stacktrace instanceof Stacktrace) { throw new \InvalidArgumentException(sprintf('The value of the "stacktrace" field must be an instance of the "%s" class. Got: "%s".', Stacktrace::class, get_debug_type($stacktrace))); } diff --git a/src/Frame.php b/src/Frame.php index 4d6a067af..4b2c34c0d 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -245,6 +245,6 @@ public function setVars(array $vars): void */ public function isInternal(): bool { - return self::INTERNAL_FRAME_FILENAME === $this->file; + return $this->file === self::INTERNAL_FRAME_FILENAME; } } diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 10854dcdb..143252d57 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -74,7 +74,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['class']; - if (Frame::ANONYMOUS_CLASS_PREFIX === mb_substr($functionName, 0, mb_strlen(Frame::ANONYMOUS_CLASS_PREFIX))) { + if (mb_substr($functionName, 0, mb_strlen(Frame::ANONYMOUS_CLASS_PREFIX)) === Frame::ANONYMOUS_CLASS_PREFIX) { $functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath($this->options, substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX))); } @@ -89,7 +89,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac $strippedFilePath, $line, $rawFunctionName, - Frame::INTERNAL_FRAME_FILENAME !== $file ? $file : null, + $file !== Frame::INTERNAL_FRAME_FILENAME ? $file : null, $this->getFunctionArguments($backtraceFrame), $this->isFrameInApp($file, $functionName) ); @@ -103,11 +103,11 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac */ private function isFrameInApp(string $file, ?string $functionName): bool { - if (Frame::INTERNAL_FRAME_FILENAME === $file) { + if ($file === Frame::INTERNAL_FRAME_FILENAME) { return false; } - if (null !== $functionName && 'Sentry\\' === substr($functionName, 0, \strlen('Sentry\\'))) { + if ($functionName !== null && substr($functionName, 0, \strlen('Sentry\\')) === 'Sentry\\') { return false; } @@ -156,7 +156,7 @@ private function getFunctionArguments(array $backtraceFrame): array if (isset($backtraceFrame['class'])) { if (method_exists($backtraceFrame['class'], $backtraceFrame['function'])) { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], $backtraceFrame['function']); - } elseif (isset($backtraceFrame['type']) && '::' === $backtraceFrame['type']) { + } elseif (isset($backtraceFrame['type']) && $backtraceFrame['type'] === '::') { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__callStatic'); } else { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); @@ -170,7 +170,7 @@ private function getFunctionArguments(array $backtraceFrame): array $argumentValues = []; - if (null !== $reflectionFunction) { + if ($reflectionFunction !== null) { $argumentValues = $this->getFunctionArgumentValues($reflectionFunction, $backtraceFrame['args']); } else { foreach ($backtraceFrame['args'] as $parameterPosition => $parameterValue) { diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index d75148757..8ea8ebc48 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -31,7 +31,7 @@ public function __construct(string $sdkIdentifier, string $sdkVersion) public function sendRequest(string $requestData, Options $options): Response { $dsn = $options->getDsn(); - if (null === $dsn) { + if ($dsn === null) { throw new \RuntimeException('The DSN option must be set to use the HttpClient.'); } @@ -60,13 +60,13 @@ public function sendRequest(string $requestData, Options $options): Response } $httpProxy = $options->getHttpProxy(); - if (null !== $httpProxy) { + if ($httpProxy !== null) { curl_setopt($curlHandle, \CURLOPT_PROXY, $httpProxy); curl_setopt($curlHandle, \CURLOPT_HEADEROPT, \CURLHEADER_SEPARATE); } $httpProxyAuthentication = $options->getHttpProxyAuthentication(); - if (null !== $httpProxyAuthentication) { + if ($httpProxyAuthentication !== null) { curl_setopt($curlHandle, \CURLOPT_PROXYUSERPWD, $httpProxyAuthentication); } @@ -75,7 +75,7 @@ public function sendRequest(string $requestData, Options $options): Response */ $body = curl_exec($curlHandle); - if (false === $body) { + if ($body === false) { $errorCode = curl_errno($curlHandle); $error = curl_error($curlHandle); curl_close($curlHandle); diff --git a/src/HttpClient/Response.php b/src/HttpClient/Response.php index 56d921c07..76748271a 100644 --- a/src/HttpClient/Response.php +++ b/src/HttpClient/Response.php @@ -89,6 +89,6 @@ public function getError(): string public function hasError(): bool { - return '' !== $this->error; + return $this->error !== ''; } } diff --git a/src/Integration/AbstractErrorListenerIntegration.php b/src/Integration/AbstractErrorListenerIntegration.php index 530dc86af..a8894a7e2 100644 --- a/src/Integration/AbstractErrorListenerIntegration.php +++ b/src/Integration/AbstractErrorListenerIntegration.php @@ -38,7 +38,7 @@ protected function addExceptionMechanismToEvent(Event $event): Event foreach ($exceptions as $exception) { $data = []; $mechanism = $exception->getMechanism(); - if (null !== $mechanism) { + if ($mechanism !== null) { $data = $mechanism->getData(); } diff --git a/src/Integration/EnvironmentIntegration.php b/src/Integration/EnvironmentIntegration.php index 9552fafc6..fb0f591e3 100644 --- a/src/Integration/EnvironmentIntegration.php +++ b/src/Integration/EnvironmentIntegration.php @@ -26,7 +26,7 @@ public function setupOnce(): void Scope::addGlobalEventProcessor(static function (Event $event): Event { $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); - if (null !== $integration) { + if ($integration !== null) { $event->setRuntimeContext($integration->updateRuntimeContext($event->getRuntimeContext())); $event->setOsContext($integration->updateServerOsContext($event->getOsContext())); } @@ -37,11 +37,11 @@ public function setupOnce(): void private function updateRuntimeContext(?RuntimeContext $runtimeContext): RuntimeContext { - if (null === $runtimeContext) { + if ($runtimeContext === null) { $runtimeContext = new RuntimeContext('php'); } - if (null === $runtimeContext->getVersion()) { + if ($runtimeContext->getVersion() === null) { $runtimeContext->setVersion(PHPVersion::parseVersion()); } @@ -54,23 +54,23 @@ private function updateServerOsContext(?OsContext $osContext): ?OsContext return $osContext; } - if (null === $osContext) { + if ($osContext === null) { $osContext = new OsContext(php_uname('s')); } - if (null === $osContext->getVersion()) { + if ($osContext->getVersion() === null) { $osContext->setVersion(php_uname('r')); } - if (null === $osContext->getBuild()) { + if ($osContext->getBuild() === null) { $osContext->setBuild(php_uname('v')); } - if (null === $osContext->getKernelVersion()) { + if ($osContext->getKernelVersion() === null) { $osContext->setKernelVersion(php_uname('a')); } - if (null === $osContext->getMachineType()) { + if ($osContext->getMachineType() === null) { $osContext->setMachineType(php_uname('m')); } diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index 1d7539e27..b56d1c00e 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -27,7 +27,7 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration || null === $client) { + if ($integration === null || $client === null) { return; } diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index 4c3d2c386..18e7afc75 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -25,7 +25,7 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration) { + if ($integration === null) { return; } diff --git a/src/Integration/FatalErrorListenerIntegration.php b/src/Integration/FatalErrorListenerIntegration.php index 5c72341e3..688103cd4 100644 --- a/src/Integration/FatalErrorListenerIntegration.php +++ b/src/Integration/FatalErrorListenerIntegration.php @@ -28,7 +28,7 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration || null === $client) { + if ($integration === null || $client === null) { return; } diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 31104b102..d0ed04f99 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -42,25 +42,25 @@ public function setupOnce(): void Scope::addGlobalEventProcessor(static function (Event $event): Event { $client = SentrySdk::getCurrentHub()->getClient(); - if (null === $client) { + if ($client === null) { return $event; } $maxContextLines = $client->getOptions()->getContextLines(); $integration = $client->getIntegration(self::class); - if (null === $integration || null === $maxContextLines) { + if ($integration === null || $maxContextLines === null) { return $event; } $stacktrace = $event->getStacktrace(); - if (null !== $stacktrace) { + if ($stacktrace !== null) { $integration->addContextToStacktraceFrames($maxContextLines, $stacktrace); } foreach ($event->getExceptions() as $exception) { - if (null !== $exception->getStacktrace()) { + if ($exception->getStacktrace() !== null) { $integration->addContextToStacktraceFrames($maxContextLines, $exception->getStacktrace()); } } @@ -78,7 +78,7 @@ public function setupOnce(): void private function addContextToStacktraceFrames(int $maxContextLines, Stacktrace $stacktrace): void { foreach ($stacktrace->getFrames() as $frame) { - if ($frame->isInternal() || null === $frame->getAbsoluteFilePath()) { + if ($frame->isInternal() || $frame->getAbsoluteFilePath() === null) { continue; } @@ -113,7 +113,7 @@ private function getSourceCodeExcerpt(int $maxContextLines, string $filePath, in 'post_context' => [], ]; - $target = max(0, ($lineNumber - ($maxContextLines + 1))); + $target = max(0, $lineNumber - ($maxContextLines + 1)); $currentLineNumber = $target + 1; try { diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index 2c33c83ad..e7209f4c7 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -32,7 +32,7 @@ private function __construct() */ public static function getInstance(): self { - if (null === self::$instance) { + if (self::$instance === null) { self::$instance = new self(); } @@ -134,7 +134,7 @@ private function getDefaultIntegrations(Options $options): array new ModulesIntegration(), ]; - if (null !== $options->getDsn()) { + if ($options->getDsn() !== null) { array_unshift($integrations, new ExceptionListenerIntegration(), new ErrorListenerIntegration(), new FatalErrorListenerIntegration()); } diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index dc47bd89a..860d90151 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -32,7 +32,7 @@ public function setupOnce(): void // The integration could be bound to a client that is not the one // attached to the current hub. If this is the case, bail out - if (null !== $integration) { + if ($integration !== null) { $event->setModules(self::getComposerPackages()); } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index b35007bcd..4238b457c 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -107,7 +107,7 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration || null === $client) { + if ($integration === null || $client === null) { return $event; } @@ -121,7 +121,7 @@ private function processEvent(Event $event, Options $options): void { $request = $this->requestFetcher->fetchRequest(); - if (null === $request) { + if ($request === null) { return; } @@ -141,9 +141,9 @@ private function processEvent(Event $event, Options $options): void $user = $event->getUser(); $requestData['env']['REMOTE_ADDR'] = $serverParams['REMOTE_ADDR']; - if (null === $user) { + if ($user === null) { $user = UserDataBag::createFromUserIpAddress($serverParams['REMOTE_ADDR']); - } elseif (null === $user->getIpAddress()) { + } elseif ($user->getIpAddress() === null) { $user->setIpAddress($serverParams['REMOTE_ADDR']); } @@ -223,9 +223,9 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re $requestBody = ''; $maxLength = self::MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP[$maxRequestBodySize]; - if (0 < $maxLength) { + if ($maxLength > 0) { $stream = $request->getBody(); - while (0 < $maxLength && !$stream->eof()) { + while ($maxLength > 0 && !$stream->eof()) { if ('' === $buffer = $stream->read(min($maxLength, self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH))) { break; } @@ -234,7 +234,7 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re } } - if ('application/json' === $request->getHeaderLine('Content-Type')) { + if ($request->getHeaderLine('Content-Type') === 'application/json') { try { return JSON::decode($requestBody); } catch (JsonException $exception) { @@ -280,15 +280,15 @@ private function isRequestBodySizeWithinReadBounds(int $requestBodySize, string return false; } - if ('none' === $maxRequestBodySize || 'never' === $maxRequestBodySize) { + if ($maxRequestBodySize === 'none' || $maxRequestBodySize === 'never') { return false; } - if ('small' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) { + if ($maxRequestBodySize === 'small' && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) { return false; } - if ('medium' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) { + if ($maxRequestBodySize === 'medium' && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) { return false; } diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index d7c775fce..1ed22d753 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -28,11 +28,11 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration) { + if ($integration === null) { return $event; } - if (null !== $event->getTransaction()) { + if ($event->getTransaction() !== null) { return $event; } diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index 3cba4c341..ddce8f243 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -69,13 +69,13 @@ protected function doWrite($record): void $monologContextData = $this->getMonologContextData($record['context']); - if ([] !== $monologContextData) { + if ($monologContextData !== []) { $scope->setExtra('monolog.context', $monologContextData); } $monologExtraData = $this->getMonologExtraData($record['extra']); - if ([] !== $monologExtraData) { + if ($monologExtraData !== []) { $scope->setExtra('monolog.extra', $monologExtraData); } @@ -98,7 +98,7 @@ private function getMonologContextData(array $context): array foreach ($context as $key => $value) { // We skip the `exception` field because it goes in its own context - if (self::CONTEXT_EXCEPTION_KEY === $key) { + if ($key === self::CONTEXT_EXCEPTION_KEY) { continue; } diff --git a/src/Options.php b/src/Options.php index 4616e4174..40e59ec04 100644 --- a/src/Options.php +++ b/src/Options.php @@ -59,7 +59,7 @@ public function __construct(array $options = []) $this->options = $this->resolver->resolve($options); - if (true === $this->options['enable_tracing'] && null === $this->options['traces_sample_rate']) { + if ($this->options['enable_tracing'] === true && $this->options['traces_sample_rate'] === null) { $this->options = array_merge($this->options, ['traces_sample_rate' => 1]); } } @@ -177,11 +177,11 @@ public function setProfilesSampleRate(?float $sampleRate): void */ public function isTracingEnabled(): bool { - if (null !== $this->getEnableTracing() && false === $this->getEnableTracing()) { + if ($this->getEnableTracing() !== null && $this->getEnableTracing() === false) { return false; } - return null !== $this->getTracesSampleRate() || null !== $this->getTracesSampler(); + return $this->getTracesSampleRate() !== null || $this->getTracesSampler() !== null; } /** @@ -313,9 +313,9 @@ public function setInAppIncludedPaths(array $paths): void * * @deprecated since version 3.2, to be removed in 4.0 */ - public function getLogger(/*bool $triggerDeprecation = true*/): string + public function getLogger(/* bool $triggerDeprecation = true */): string { - if (0 === \func_num_args() || false !== func_get_arg(0)) { + if (\func_num_args() === 0 || func_get_arg(0) !== false) { @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); } @@ -340,8 +340,6 @@ public function setLogger(string $logger): void /** * Gets the release tag to be passed with every event sent to Sentry. - * - * @return string */ public function getRelease(): ?string { @@ -392,6 +390,7 @@ public function setServerName(string $serverName): void * Gets a list of exceptions to be ignored and not sent to Sentry. * * @return string[] + * * @psalm-return list> */ public function getIgnoreExceptions(): array @@ -1017,7 +1016,7 @@ private function configureOptions(OptionsResolver $resolver): void }); $resolver->setNormalizer('logger', function (SymfonyOptions $options, ?string $value): ?string { - if ('php' !== $value) { + if ($value !== 'php') { @trigger_error('The option "logger" is deprecated.', \E_USER_DEPRECATED); } @@ -1034,7 +1033,7 @@ private function normalizeAbsolutePath(string $value): string { $path = @realpath($value); - if (false === $path) { + if ($path === false) { $path = $value; } @@ -1050,7 +1049,7 @@ private function normalizeAbsolutePath(string $value): string */ private function normalizeDsnOption(SymfonyOptions $options, $value): ?Dsn { - if (null === $value || \is_bool($value)) { + if ($value === null || \is_bool($value)) { return null; } @@ -1080,12 +1079,12 @@ private function normalizeDsnOption(SymfonyOptions $options, $value): ?Dsn */ private function validateDsnOption($dsn): bool { - if (null === $dsn || $dsn instanceof Dsn) { + if ($dsn === null || $dsn instanceof Dsn) { return true; } if (\is_bool($dsn)) { - return false === $dsn; + return $dsn === false; } switch (strtolower($dsn)) { @@ -1141,6 +1140,6 @@ private function validateClassSerializersOption(array $serializers): bool */ private function validateContextLinesOption(?int $contextLines): bool { - return null === $contextLines || $contextLines >= 0; + return $contextLines === null || $contextLines >= 0; } } diff --git a/src/Profiling/Profile.php b/src/Profiling/Profile.php index f92e3de09..cd1da1373 100644 --- a/src/Profiling/Profile.php +++ b/src/Profiling/Profile.php @@ -25,7 +25,6 @@ * module: string|null, * lineno: int|null, * } - * * @phpstan-type SentryProfile array{ * device: array{ * architecture: string, @@ -61,7 +60,6 @@ * stacks: array>, * }, * } - * * @phpstan-type ExcimerLogStackEntryTrace array{ * file: string, * line: int, @@ -69,7 +67,6 @@ * function?: string, * closure_line?: int, * } - * * @phpstan-type ExcimerLogStackEntry array{ * trace: array, * timestamp: float @@ -176,7 +173,7 @@ public function getFormattedData(Event $event): ?array $registerStack = static function (array $stack) use (&$stacks, &$stackHashMap): int { $stackHash = md5(serialize($stack)); - if (false === \array_key_exists($stackHash, $stackHashMap)) { + if (\array_key_exists($stackHash, $stackHashMap) === false) { $stackHashMap[$stackHash] = \count($stacks); $stacks[] = $stack; } @@ -200,7 +197,7 @@ public function getFormattedData(Event $event): ?array $frameIndex = $frameHashMap[$frameKey] ?? null; - if (null === $frameIndex) { + if ($frameIndex === null) { $file = $this->stripPrefixFromFilePath($this->options, $absolutePath); $module = null; @@ -245,7 +242,7 @@ public function getFormattedData(Event $event): ?array } $startTime = \DateTime::createFromFormat('U.u', number_format($this->startTimeStamp, 4, '.', ''), new \DateTimeZone('UTC')); - if (false === $startTime) { + if ($startTime === false) { return null; } @@ -314,7 +311,7 @@ private function validateExcimerLog(): bool $sampleCount = $this->excimerLog->count(); } - return self::MIN_SAMPLE_COUNT <= $sampleCount; + return $sampleCount >= self::MIN_SAMPLE_COUNT; } private function validateMaxDuration(float $duration): bool @@ -333,15 +330,15 @@ private function validateMaxDuration(float $duration): bool */ private function validateOsContext(?OsContext $osContext): bool { - if (null === $osContext) { + if ($osContext === null) { return false; } - if (null === $osContext->getVersion()) { + if ($osContext->getVersion() === null) { return false; } - if (null === $osContext->getMachineType()) { + if ($osContext->getMachineType() === null) { return false; } @@ -354,11 +351,11 @@ private function validateOsContext(?OsContext $osContext): bool */ private function validateRuntimeContext(?RuntimeContext $runtimeContext): bool { - if (null === $runtimeContext) { + if ($runtimeContext === null) { return false; } - if (null === $runtimeContext->getVersion()) { + if ($runtimeContext->getVersion() === null) { return false; } @@ -371,11 +368,11 @@ private function validateRuntimeContext(?RuntimeContext $runtimeContext): bool */ private function validateEvent(Event $event): bool { - if (null === $event->getTransaction()) { + if ($event->getTransaction() === null) { return false; } - if (null === $event->getTraceId()) { + if ($event->getTraceId() === null) { return false; } diff --git a/src/Profiling/Profiler.php b/src/Profiling/Profiler.php index 956c213d0..dca039c7d 100644 --- a/src/Profiling/Profiler.php +++ b/src/Profiling/Profiler.php @@ -40,14 +40,14 @@ public function __construct(?Options $options = null) public function start(): void { - if (null !== $this->profiler) { + if ($this->profiler !== null) { $this->profiler->start(); } } public function stop(): void { - if (null !== $this->profiler) { + if ($this->profiler !== null) { $this->profiler->stop(); $this->profile->setExcimerLog($this->profiler->flush()); @@ -56,7 +56,7 @@ public function stop(): void public function getProfile(): ?Profile { - if (null === $this->profiler) { + if ($this->profiler === null) { return null; } diff --git a/src/SentrySdk.php b/src/SentrySdk.php index a8773f185..4b41fc30a 100644 --- a/src/SentrySdk.php +++ b/src/SentrySdk.php @@ -43,7 +43,7 @@ public static function init(): HubInterface */ public static function getCurrentHub(): HubInterface { - if (null === self::$currentHub) { + if (self::$currentHub === null) { self::$currentHub = new Hub(); } diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 4e20b9175..ac6741a52 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -76,7 +76,7 @@ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDete { $this->maxDepth = $maxDepth; - if (null != $mbDetectOrder) { + if ($mbDetectOrder != null) { $this->mbDetectOrder = $mbDetectOrder; } @@ -235,7 +235,7 @@ protected function serializeString($value): string */ protected function serializeValue($value) { - if ((null === $value) || \is_bool($value) || is_numeric($value)) { + if (($value === null) || \is_bool($value) || is_numeric($value)) { return $value; } @@ -253,7 +253,7 @@ protected function serializeValue($value) } } - return 'Object ' . $reflection->getName() . (is_scalar($objectId) ? '(#' . $objectId . ')' : ''); + return 'Object ' . $reflection->getName() . (\is_scalar($objectId) ? '(#' . $objectId . ')' : ''); } if (\is_resource($value)) { diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 7aec12421..5c8d444b2 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -43,9 +43,9 @@ public function serialize(Event $event): string $transactionEnvelope = $this->serializeAsEnvelope($event); // Attach a new envelope item containing the profile data - if (null !== $event->getSdkMetadata('profile')) { + if ($event->getSdkMetadata('profile') !== null) { $profileEnvelope = $this->seralizeProfileAsEnvelope($event); - if (null !== $profileEnvelope) { + if ($profileEnvelope !== null) { return sprintf("%s\n%s", $transactionEnvelope, $profileEnvelope); } } @@ -76,7 +76,7 @@ private function serializeAsCheckInEvent(Event $event): string $result = []; $checkIn = $event->getCheckIn(); - if (null !== $checkIn) { + if ($checkIn !== null) { $result = [ 'check_in_id' => $checkIn->getId(), 'monitor_slug' => $checkIn->getMonitorSlug(), @@ -86,7 +86,7 @@ private function serializeAsCheckInEvent(Event $event): string 'environment' => $checkIn->getEnvironment(), ]; - if (null !== $checkIn->getMonitorConfig()) { + if ($checkIn->getMonitorConfig() !== null) { $result['monitor_config'] = $checkIn->getMonitorConfig()->toArray(); } @@ -113,31 +113,31 @@ public function toArray(Event $event): array ], ]; - if (null !== $event->getStartTimestamp()) { + if ($event->getStartTimestamp() !== null) { $result['start_timestamp'] = $event->getStartTimestamp(); } - if (null !== $event->getLevel()) { + if ($event->getLevel() !== null) { $result['level'] = (string) $event->getLevel(); } - if (null !== $event->getLogger()) { + if ($event->getLogger() !== null) { $result['logger'] = $event->getLogger(); } - if (null !== $event->getTransaction()) { + if ($event->getTransaction() !== null) { $result['transaction'] = $event->getTransaction(); } - if (null !== $event->getServerName()) { + if ($event->getServerName() !== null) { $result['server_name'] = $event->getServerName(); } - if (null !== $event->getRelease()) { + if ($event->getRelease() !== null) { $result['release'] = $event->getRelease(); } - if (null !== $event->getEnvironment()) { + if ($event->getEnvironment() !== null) { $result['environment'] = $event->getEnvironment(); } @@ -159,7 +159,7 @@ public function toArray(Event $event): array $user = $event->getUser(); - if (null !== $user) { + if ($user !== null) { $result['user'] = array_merge($user->getMetadata(), [ 'id' => $user->getId(), 'username' => $user->getUsername(), @@ -172,7 +172,7 @@ public function toArray(Event $event): array $osContext = $event->getOsContext(); $runtimeContext = $event->getRuntimeContext(); - if (null !== $osContext) { + if ($osContext !== null) { $result['contexts']['os'] = [ 'name' => $osContext->getName(), 'version' => $osContext->getVersion(), @@ -181,7 +181,7 @@ public function toArray(Event $event): array ]; } - if (null !== $runtimeContext) { + if ($runtimeContext !== null) { $result['contexts']['runtime'] = [ 'name' => $runtimeContext->getName(), 'version' => $runtimeContext->getVersion(), @@ -200,7 +200,7 @@ public function toArray(Event $event): array $result['request'] = $event->getRequest(); } - if (null !== $event->getMessage()) { + if ($event->getMessage() !== null) { if (empty($event->getMessageParams())) { $result['message'] = $event->getMessage(); } else { @@ -232,14 +232,14 @@ public function toArray(Event $event): array * as a context into the payload. */ if ( - EventType::event() === $event->getType() && - !$this->options->isTracingEnabled() + EventType::event() === $event->getType() + && !$this->options->isTracingEnabled() ) { $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); if ($dynamicSamplingContext instanceof DynamicSamplingContext) { $replayId = $dynamicSamplingContext->get('replay_id'); - if (null !== $replayId) { + if ($replayId !== null) { $result['contexts']['replay'] = [ 'replay_id' => $replayId, ]; @@ -249,7 +249,7 @@ public function toArray(Event $event): array $stacktrace = $event->getStacktrace(); - if (null !== $stacktrace) { + if ($stacktrace !== null) { $result['stacktrace'] = [ 'frames' => array_map([$this, 'serializeStacktraceFrame'], $stacktrace->getFrames()), ]; @@ -308,7 +308,7 @@ private function seralizeProfileAsEnvelope(Event $event): ?string } $profileData = $profile->getFormattedData($event); - if (null === $profileData) { + if ($profileData === null) { return null; } @@ -336,7 +336,7 @@ private function serializeBreadcrumb(Breadcrumb $breadcrumb): array 'timestamp' => $breadcrumb->getTimestamp(), ]; - if (null !== $breadcrumb->getMessage()) { + if ($breadcrumb->getMessage() !== null) { $result['message'] = $breadcrumb->getMessage(); } @@ -372,19 +372,19 @@ private function serializeException(ExceptionDataBag $exception): array 'value' => $exception->getValue(), ]; - if (null !== $exceptionStacktrace) { + if ($exceptionStacktrace !== null) { $result['stacktrace'] = [ 'frames' => array_map([$this, 'serializeStacktraceFrame'], $exceptionStacktrace->getFrames()), ]; } - if (null !== $exceptionMechanism) { + if ($exceptionMechanism !== null) { $result['mechanism'] = [ 'type' => $exceptionMechanism->getType(), 'handled' => $exceptionMechanism->isHandled(), ]; - if ([] !== $exceptionMechanism->getData()) { + if ($exceptionMechanism->getData() !== []) { $result['mechanism']['data'] = $exceptionMechanism->getData(); } } @@ -416,15 +416,15 @@ private function serializeStacktraceFrame(Frame $frame): array 'in_app' => $frame->isInApp(), ]; - if (null !== $frame->getAbsoluteFilePath()) { + if ($frame->getAbsoluteFilePath() !== null) { $result['abs_path'] = $frame->getAbsoluteFilePath(); } - if (null !== $frame->getFunctionName()) { + if ($frame->getFunctionName() !== null) { $result['function'] = $frame->getFunctionName(); } - if (null !== $frame->getRawFunctionName()) { + if ($frame->getRawFunctionName() !== null) { $result['raw_function'] = $frame->getRawFunctionName(); } @@ -432,7 +432,7 @@ private function serializeStacktraceFrame(Frame $frame): array $result['pre_context'] = $frame->getPreContext(); } - if (null !== $frame->getContextLine()) { + if ($frame->getContextLine() !== null) { $result['context_line'] = $frame->getContextLine(); } @@ -471,23 +471,23 @@ private function serializeSpan(Span $span): array 'start_timestamp' => $span->getStartTimestamp(), ]; - if (null !== $span->getParentSpanId()) { + if ($span->getParentSpanId() !== null) { $result['parent_span_id'] = (string) $span->getParentSpanId(); } - if (null !== $span->getEndTimestamp()) { + if ($span->getEndTimestamp() !== null) { $result['timestamp'] = $span->getEndTimestamp(); } - if (null !== $span->getStatus()) { + if ($span->getStatus() !== null) { $result['status'] = (string) $span->getStatus(); } - if (null !== $span->getDescription()) { + if ($span->getDescription() !== null) { $result['description'] = $span->getDescription(); } - if (null !== $span->getOp()) { + if ($span->getOp() !== null) { $result['op'] = $span->getOp(); } diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index f8b9e07e2..d7fc2d5ec 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -37,15 +37,15 @@ public function representationSerialize($value) */ protected function serializeValue($value) { - if (null === $value) { + if ($value === null) { return 'null'; } - if (false === $value) { + if ($value === false) { return 'false'; } - if (true === $value) { + if ($value === true) { return 'true'; } diff --git a/src/Severity.php b/src/Severity.php index 4e065d62f..8fdf161b3 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -83,34 +83,32 @@ public function __construct(string $value = self::INFO) * Translate a PHP Error constant into a Sentry log level group. * * @param int $severity PHP E_* error constant - * - * @return Severity */ public static function fromError(int $severity): self { switch ($severity) { - case \E_DEPRECATED: - case \E_USER_DEPRECATED: - case \E_WARNING: - case \E_USER_WARNING: - return self::warning(); - case \E_ERROR: - case \E_PARSE: - case \E_CORE_ERROR: - case \E_CORE_WARNING: - case \E_COMPILE_ERROR: - case \E_COMPILE_WARNING: - return self::fatal(); - case \E_RECOVERABLE_ERROR: - case \E_USER_ERROR: - return self::error(); - case \E_NOTICE: - case \E_USER_NOTICE: - case \E_STRICT: - return self::info(); - default: - return self::error(); - } + case \E_DEPRECATED: + case \E_USER_DEPRECATED: + case \E_WARNING: + case \E_USER_WARNING: + return self::warning(); + case \E_ERROR: + case \E_PARSE: + case \E_CORE_ERROR: + case \E_CORE_WARNING: + case \E_COMPILE_ERROR: + case \E_COMPILE_WARNING: + return self::fatal(); + case \E_RECOVERABLE_ERROR: + case \E_USER_ERROR: + return self::error(); + case \E_NOTICE: + case \E_USER_NOTICE: + case \E_STRICT: + return self::info(); + default: + return self::error(); + } } /** diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 5750ced85..3b532a214 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -87,7 +87,7 @@ public function removeFrame(int $index): void throw new \OutOfBoundsException(sprintf('Cannot remove the frame at index %d.', $index)); } - if (1 === \count($this->frames)) { + if (\count($this->frames) === 1) { throw new \RuntimeException('Cannot remove all frames from the stacktrace.'); } diff --git a/src/State/Hub.php b/src/State/Hub.php index 5a5ed5ea6..9ac3ff0e4 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -78,11 +78,11 @@ public function pushScope(): Scope */ public function popScope(): bool { - if (1 === \count($this->stack)) { + if (\count($this->stack) === 1) { return false; } - return null !== array_pop($this->stack); + return array_pop($this->stack) !== null; } /** @@ -123,7 +123,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?EventH { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope(), $hint); } @@ -137,7 +137,7 @@ public function captureException(\Throwable $exception, ?EventHint $hint = null) { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $this->lastEventId = $client->captureException($exception, $this->getScope(), $hint); } @@ -151,7 +151,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $this->lastEventId = $client->captureEvent($event, $hint, $this->getScope()); } @@ -165,7 +165,7 @@ public function captureLastError(?EventHint $hint = null): ?EventId { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $this->lastEventId = $client->captureLastError($this->getScope(), $hint); } @@ -181,7 +181,7 @@ public function captureCheckIn(string $slug, CheckInStatus $status, $duration = { $client = $this->getClient(); - if (null === $client) { + if ($client === null) { return null; } @@ -209,7 +209,7 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool { $client = $this->getClient(); - if (null === $client) { + if ($client === null) { return false; } @@ -223,11 +223,11 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool $breadcrumb = $beforeBreadcrumbCallback($breadcrumb); - if (null !== $breadcrumb) { + if ($breadcrumb !== null) { $this->getScope()->addBreadcrumb($breadcrumb, $maxBreadcrumbs); } - return null !== $breadcrumb; + return $breadcrumb !== null; } /** @@ -237,7 +237,7 @@ public function getIntegration(string $className): ?IntegrationInterface { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $client->getIntegration($className); } @@ -253,9 +253,9 @@ public function startTransaction(TransactionContext $context, array $customSampl { $transaction = new Transaction($context, $this); $client = $this->getClient(); - $options = null !== $client ? $client->getOptions() : null; + $options = $client !== null ? $client->getOptions() : null; - if (null === $options || !$options->isTracingEnabled()) { + if ($options === null || !$options->isTracingEnabled()) { $transaction->setSampled(false); return $transaction; @@ -266,8 +266,8 @@ public function startTransaction(TransactionContext $context, array $customSampl $tracesSampler = $options->getTracesSampler(); - if (null === $transaction->getSampled()) { - if (null !== $tracesSampler) { + if ($transaction->getSampled() === null) { + if ($tracesSampler !== null) { $sampleRate = $tracesSampler($samplingContext); } else { $sampleRate = $this->getSampleRate( @@ -284,7 +284,7 @@ public function startTransaction(TransactionContext $context, array $customSampl $transaction->getMetadata()->setSamplingRate($sampleRate); - if (0.0 === $sampleRate) { + if ($sampleRate === 0.0) { $transaction->setSampled(false); return $transaction; @@ -303,7 +303,7 @@ public function startTransaction(TransactionContext $context, array $customSampl if ($this->sample($profilesSampleRate)) { $transaction->initProfiler(); $profiler = $transaction->getProfiler(); - if (null !== $profiler) { + if ($profiler !== null) { $profiler->start(); } } @@ -355,11 +355,11 @@ private function getStackTop(): Layer private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float { - if (true === $hasParentBeenSampled) { + if ($hasParentBeenSampled === true) { return 1; } - if (false === $hasParentBeenSampled) { + if ($hasParentBeenSampled === false) { return 0; } @@ -371,11 +371,11 @@ private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampl */ private function sample($sampleRate): bool { - if (0.0 === $sampleRate) { + if ($sampleRate === 0.0) { return false; } - if (1.0 === $sampleRate) { + if ($sampleRate === 1.0) { return true; } diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 8f637860f..a2a5c79e7 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -42,7 +42,7 @@ private function __construct() */ public static function getInstance(): self { - if (null === self::$instance) { + if (self::$instance === null) { self::$instance = new self(); } @@ -165,8 +165,6 @@ public function getIntegration(string $className): ?IntegrationInterface /** * {@inheritdoc} - * - * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 9b4c26ceb..ad5b8aa63 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -91,7 +91,7 @@ public function bindClient(ClientInterface $client): void; * @param Severity|null $level The severity level of the message * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureMessage(string $message, ?Severity $level = null/*, ?EventHint $hint = null*/): ?EventId; + public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId; /** * Captures an exception event and sends it to Sentry. @@ -99,7 +99,7 @@ public function captureMessage(string $message, ?Severity $level = null/*, ?Even * @param \Throwable $exception The exception * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureException(\Throwable $exception/*, ?EventHint $hint = null*/): ?EventId; + public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId; /** * Captures a new event using the provided data. @@ -114,7 +114,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId; * * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureLastError(/*?EventHint $hint = null*/): ?EventId; + public function captureLastError(?EventHint $hint = null): ?EventId; /** * Records a new breadcrumb which will be attached to future events. They @@ -158,7 +158,7 @@ public function getIntegration(string $className): ?IntegrationInterface; * @param TransactionContext $context Properties of the new transaction * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ - public function startTransaction(TransactionContext $context/*, array $customSamplingContext = []*/): Transaction; + public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction; /** * Returns the transaction that is on the Hub. diff --git a/src/State/Scope.php b/src/State/Scope.php index 904b6804b..0f3984aea 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -82,7 +82,7 @@ final class Scope */ private static $globalEventProcessors = []; - public function __construct(PropagationContext $propagationContext = null) + public function __construct(?PropagationContext $propagationContext = null) { $this->propagationContext = $propagationContext ?? PropagationContext::fromDefaults(); } @@ -215,7 +215,7 @@ public function setUser($user): self $user = UserDataBag::createFromArray($user); } - if (null === $this->user) { + if ($this->user === null) { $this->user = $user; } else { $this->user = $this->user->merge($user); @@ -351,7 +351,7 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op $event->setBreadcrumb($this->breadcrumbs); } - if (null !== $this->level) { + if ($this->level !== null) { $event->setLevel($this->level); } @@ -363,10 +363,10 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op $event->setExtra(array_merge($this->extra, $event->getExtra())); } - if (null !== $this->user) { + if ($this->user !== null) { $user = $event->getUser(); - if (null === $user) { + if ($user === null) { $user = $this->user; } else { $user = $this->user->merge($user); @@ -379,19 +379,19 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op * Apply the trace context to errors if there is a Span on the Scope. * Else fallback to the propagation context. */ - if (null !== $this->span) { + if ($this->span !== null) { $event->setContext('trace', $this->span->getTraceContext()); // Apply the dynamic sampling context to errors if there is a Transaction on the Scope $transaction = $this->span->getTransaction(); - if (null !== $transaction) { + if ($transaction !== null) { $event->setSdkMetadata('dynamic_sampling_context', $transaction->getDynamicSamplingContext()); } } else { $event->setContext('trace', $this->propagationContext->getTraceContext()); $dynamicSamplingContext = $this->propagationContext->getDynamicSamplingContext(); - if (null === $dynamicSamplingContext && null !== $options) { + if ($dynamicSamplingContext === null && $options !== null) { $dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $this); } $event->setSdkMetadata('dynamic_sampling_context', $dynamicSamplingContext); @@ -402,14 +402,14 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op } // We create a empty `EventHint` instance to allow processors to always receive a `EventHint` instance even if there wasn't one - if (null === $hint) { + if ($hint === null) { $hint = new EventHint(); } foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) { $event = $processor($event, $hint); - if (null === $event) { + if ($event === null) { return null; } @@ -448,7 +448,7 @@ public function setSpan(?Span $span): self */ public function getTransaction(): ?Transaction { - if (null !== $this->span) { + if ($this->span !== null) { return $this->span->getTransaction(); } @@ -469,10 +469,10 @@ public function setPropagationContext(PropagationContext $propagationContext): s public function __clone() { - if (null !== $this->user) { + if ($this->user !== null) { $this->user = clone $this->user; } - if (null !== $this->propagationContext) { + if ($this->propagationContext !== null) { $this->propagationContext = clone $this->propagationContext; } } diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index b737ea960..75c4c9d29 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -127,7 +127,7 @@ public static function fromHeader(string $header): self [$key, $value] = explode('=', $keyValue, 2); - if (self::SENTRY_ENTRY_PREFIX === mb_substr($key, 0, mb_strlen(self::SENTRY_ENTRY_PREFIX))) { + if (mb_substr($key, 0, mb_strlen(self::SENTRY_ENTRY_PREFIX)) === self::SENTRY_ENTRY_PREFIX) { $samplingContext->set(rawurldecode(mb_substr($key, mb_strlen(self::SENTRY_ENTRY_PREFIX))), rawurldecode($value)); } } @@ -151,7 +151,7 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h $samplingContext->set('trace_id', (string) $transaction->getTraceId()); $sampleRate = $transaction->getMetaData()->getSamplingRate(); - if (null !== $sampleRate) { + if ($sampleRate !== null) { $samplingContext->set('sample_rate', (string) $sampleRate); } @@ -162,29 +162,29 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h $client = $hub->getClient(); - if (null !== $client) { + if ($client !== null) { $options = $client->getOptions(); - if (null !== $options->getDsn() && null !== $options->getDsn()->getPublicKey()) { + if ($options->getDsn() !== null && $options->getDsn()->getPublicKey() !== null) { $samplingContext->set('public_key', $options->getDsn()->getPublicKey()); } - if (null !== $options->getRelease()) { + if ($options->getRelease() !== null) { $samplingContext->set('release', $options->getRelease()); } - if (null !== $options->getEnvironment()) { + if ($options->getEnvironment() !== null) { $samplingContext->set('environment', $options->getEnvironment()); } } $hub->configureScope(static function (Scope $scope) use ($samplingContext): void { - if (null !== $scope->getUser() && null !== $scope->getUser()->getSegment()) { + if ($scope->getUser() !== null && $scope->getUser()->getSegment() !== null) { $samplingContext->set('user_segment', $scope->getUser()->getSegment()); } }); - if (null !== $transaction->getSampled()) { + if ($transaction->getSampled() !== null) { $samplingContext->set('sampled', $transaction->getSampled() ? 'true' : 'false'); } @@ -198,23 +198,23 @@ public static function fromOptions(Options $options, Scope $scope): self $samplingContext = new self(); $samplingContext->set('trace_id', (string) $scope->getPropagationContext()->getTraceId()); - if (null !== $options->getTracesSampleRate()) { + if ($options->getTracesSampleRate() !== null) { $samplingContext->set('sample_rate', (string) $options->getTracesSampleRate()); } - if (null !== $options->getDsn() && null !== $options->getDsn()->getPublicKey()) { + if ($options->getDsn() !== null && $options->getDsn()->getPublicKey() !== null) { $samplingContext->set('public_key', $options->getDsn()->getPublicKey()); } - if (null !== $options->getRelease()) { + if ($options->getRelease() !== null) { $samplingContext->set('release', $options->getRelease()); } - if (null !== $options->getEnvironment()) { + if ($options->getEnvironment() !== null) { $samplingContext->set('environment', $options->getEnvironment()); } - if (null !== $scope->getUser() && null !== $scope->getUser()->getSegment()) { + if ($scope->getUser() !== null && $scope->getUser()->getSegment() !== null) { $samplingContext->set('user_segment', $scope->getUser()->getSegment()); } diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index fa534b7b6..132276747 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -4,7 +4,6 @@ namespace Sentry\Tracing; -use Closure; use GuzzleHttp\Exception\RequestException as GuzzleRequestException; use GuzzleHttp\Psr7\Uri; use Psr\Http\Message\RequestInterface; @@ -13,6 +12,7 @@ use Sentry\ClientInterface; use Sentry\SentrySdk; use Sentry\State\HubInterface; + use function Sentry\getBaggage; use function Sentry\getTraceparent; @@ -21,15 +21,15 @@ */ final class GuzzleTracingMiddleware { - public static function trace(?HubInterface $hub = null): Closure + public static function trace(?HubInterface $hub = null): \Closure { - return static function (callable $handler) use ($hub): Closure { + return static function (callable $handler) use ($hub): \Closure { return static function (RequestInterface $request, array $options) use ($hub, $handler) { $hub = $hub ?? SentrySdk::getCurrentHub(); $client = $hub->getClient(); $span = $hub->getSpan(); - if (null === $span) { + if ($span === null) { if (self::shouldAttachTracingHeaders($client, $request)) { $request = $request ->withHeader('sentry-trace', getTraceparent()) @@ -82,14 +82,14 @@ public static function trace(?HubInterface $hub = null): Closure 'http.request.method' => $request->getMethod(), 'http.request.body.size' => $request->getBody()->getSize(), ]; - if ('' !== $request->getUri()->getQuery()) { + if ($request->getUri()->getQuery() !== '') { $breadcrumbData['http.query'] = $request->getUri()->getQuery(); } - if ('' !== $request->getUri()->getFragment()) { + if ($request->getUri()->getFragment() !== '') { $breadcrumbData['http.fragment'] = $request->getUri()->getFragment(); } - if (null !== $response) { + if ($response !== null) { $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); $breadcrumbData['http.response.status_code'] = $response->getStatusCode(); @@ -120,16 +120,16 @@ public static function trace(?HubInterface $hub = null): Closure private static function shouldAttachTracingHeaders(?ClientInterface $client, RequestInterface $request): bool { - if (null !== $client) { + if ($client !== null) { $sdkOptions = $client->getOptions(); // Check if the request destination is allow listed in the trace_propagation_targets option. if ( - null !== $sdkOptions->getTracePropagationTargets() && + $sdkOptions->getTracePropagationTargets() !== null // Due to BC, we treat an empty array (the default) as all hosts are allow listed - ( - [] === $sdkOptions->getTracePropagationTargets() || - \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()) + && ( + $sdkOptions->getTracePropagationTargets() === [] + || \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()) ) ) { return true; diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index 486cf9417..f05f00667 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -70,14 +70,14 @@ public function toTraceparent(): string */ public function toBaggage(): string { - if (null === $this->dynamicSamplingContext) { + if ($this->dynamicSamplingContext === null) { $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); - if (null !== $client) { + if ($client !== null) { $options = $client->getOptions(); - if (null !== $options) { + if ($options !== null) { $hub->configureScope(function (Scope $scope) use ($options) { $this->dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $scope); }); @@ -98,7 +98,7 @@ public function getTraceContext(): array 'span_id' => (string) $this->spanId, ]; - if (null !== $this->parentSpanId) { + if ($this->parentSpanId !== null) { $result['parent_span_id'] = (string) $this->parentSpanId; } diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 1b503482f..ec0a016a8 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -85,7 +85,7 @@ class Span */ public function __construct(?SpanContext $context = null) { - if (null === $context) { + if ($context === null) { $this->traceId = TraceId::generate(); $this->spanId = SpanId::generate(); $this->startTimestamp = microtime(true); @@ -340,19 +340,19 @@ public function getTraceContext(): array 'trace_id' => (string) $this->traceId, ]; - if (null !== $this->parentSpanId) { + if ($this->parentSpanId !== null) { $result['parent_span_id'] = (string) $this->parentSpanId; } - if (null !== $this->description) { + if ($this->description !== null) { $result['description'] = $this->description; } - if (null !== $this->op) { + if ($this->op !== null) { $result['op'] = $this->op; } - if (null !== $this->status) { + if ($this->status !== null) { $result['status'] = (string) $this->status; } @@ -398,7 +398,7 @@ public function startChild(SpanContext $context): self $span->transaction = $this->transaction; $span->spanRecorder = $this->spanRecorder; - if (null != $span->spanRecorder) { + if ($span->spanRecorder != null) { $span->spanRecorder->add($span); } @@ -438,7 +438,7 @@ public function toTraceparent(): string { $sampled = ''; - if (null !== $this->sampled) { + if ($this->sampled !== null) { $sampled = $this->sampled ? '-1' : '-0'; } @@ -452,7 +452,7 @@ public function toBaggage(): string { $transaction = $this->getTransaction(); - if (null !== $transaction) { + if ($transaction !== null) { return (string) $transaction->getDynamicSamplingContext(); } diff --git a/src/Tracing/SpanStatus.php b/src/Tracing/SpanStatus.php index cd0e878fc..7cad0f216 100644 --- a/src/Tracing/SpanStatus.php +++ b/src/Tracing/SpanStatus.php @@ -152,23 +152,23 @@ public static function unknownError(): self public static function createFromHttpStatusCode(int $statusCode): self { switch (true) { - case 401 === $statusCode: + case $statusCode === 401: return self::unauthenticated(); - case 403 === $statusCode: + case $statusCode === 403: return self::permissionDenied(); - case 404 === $statusCode: + case $statusCode === 404: return self::notFound(); - case 409 === $statusCode: + case $statusCode === 409: return self::alreadyExists(); - case 413 === $statusCode: + case $statusCode === 413: return self::failedPrecondition(); - case 429 === $statusCode: + case $statusCode === 429: return self::resourceExchausted(); - case 501 === $statusCode: + case $statusCode === 501: return self::unimplemented(); - case 503 === $statusCode: + case $statusCode === 503: return self::unavailable(); - case 504 === $statusCode: + case $statusCode === 504: return self::deadlineExceeded(); case $statusCode < 400: return self::ok(); diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index b4dcc09c1..4df809015 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -38,7 +38,7 @@ final class Transaction extends Span /** * @var Profiler|null Reference instance to the {@see Profiler} */ - protected $profiler = null; + protected $profiler; /** * Span constructor. @@ -89,7 +89,7 @@ public function getMetadata(): TransactionMetadata */ public function getDynamicSamplingContext(): DynamicSamplingContext { - if (null !== $this->metadata->getDynamicSamplingContext()) { + if ($this->metadata->getDynamicSamplingContext() !== null) { return $this->metadata->getDynamicSamplingContext(); } @@ -106,7 +106,7 @@ public function getDynamicSamplingContext(): DynamicSamplingContext */ public function initSpanRecorder(int $maxSpans = 1000): void { - if (null === $this->spanRecorder) { + if ($this->spanRecorder === null) { $this->spanRecorder = new SpanRecorder($maxSpans); } @@ -120,9 +120,9 @@ public function detachSpanRecorder(): void public function initProfiler(): void { - if (null === $this->profiler) { + if ($this->profiler === null) { $client = $this->hub->getClient(); - $options = null !== $client ? $client->getOptions() : null; + $options = $client !== null ? $client->getOptions() : null; $this->profiler = new Profiler($options); } @@ -143,26 +143,26 @@ public function detachProfiler(): void */ public function finish(?float $endTimestamp = null): ?EventId { - if (null !== $this->profiler) { + if ($this->profiler !== null) { $this->profiler->stop(); } - if (null !== $this->endTimestamp) { + if ($this->endTimestamp !== null) { // Transaction was already finished once and we don't want to re-flush it return null; } parent::finish($endTimestamp); - if (true !== $this->sampled) { + if ($this->sampled !== true) { return null; } $finishedSpans = []; - if (null !== $this->spanRecorder) { + if ($this->spanRecorder !== null) { foreach ($this->spanRecorder->getSpans() as $span) { - if ($span->getSpanId() !== $this->getSpanId() && null !== $span->getEndTimestamp()) { + if ($span->getSpanId() !== $this->getSpanId() && $span->getEndTimestamp() !== null) { $finishedSpans[] = $span; } } @@ -178,9 +178,9 @@ public function finish(?float $endTimestamp = null): ?EventId $event->setSdkMetadata('dynamic_sampling_context', $this->getDynamicSamplingContext()); $event->setSdkMetadata('transaction_metadata', $this->getMetadata()); - if (null !== $this->profiler) { + if ($this->profiler !== null) { $profile = $this->profiler->getProfile(); - if (null !== $profile) { + if ($profile !== null) { $event->setSdkMetadata('profile', $profile); } } diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index 863dd28e2..4f28037fb 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -145,7 +145,7 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag } if (isset($matches['sampled'])) { - $context->parentSampled = '1' === $matches['sampled']; + $context->parentSampled = $matches['sampled'] === '1'; $hasSentryTrace = true; } } diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index e6bcad986..f21d1e83b 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -109,13 +109,13 @@ private function handleRateLimit(Response $response): bool private function parseRetryAfterHeader(int $currentTime, string $header): int { - if (1 === preg_match('/^\d+$/', $header)) { + if (preg_match('/^\d+$/', $header) === 1) { return (int) $header; } $headerDate = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC1123, $header); - if (false !== $headerDate && $headerDate->getTimestamp() >= $currentTime) { + if ($headerDate !== false && $headerDate->getTimestamp() >= $currentTime) { return $headerDate->getTimestamp() - $currentTime; } diff --git a/src/Transport/ResultStatus.php b/src/Transport/ResultStatus.php index e7df98ac0..130bc56a5 100644 --- a/src/Transport/ResultStatus.php +++ b/src/Transport/ResultStatus.php @@ -94,7 +94,7 @@ public static function createFromHttpStatusCode(int $statusCode): self switch (true) { case $statusCode >= 200 && $statusCode < 300: return self::success(); - case 429 === $statusCode: + case $statusCode === 429: return self::rateLimit(); case $statusCode >= 400 && $statusCode < 500: return self::invalid(); diff --git a/src/UserDataBag.php b/src/UserDataBag.php index fa043b571..6e7444be4 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -132,7 +132,7 @@ public function getId() */ public function setId($id): void { - if (null !== $id && !\is_string($id) && !\is_int($id)) { + if ($id !== null && !\is_string($id) && !\is_int($id)) { throw new \UnexpectedValueException(sprintf('Expected an integer or string value for the $id argument. Got: "%s".', get_debug_type($id))); } @@ -208,7 +208,7 @@ public function getIpAddress(): ?string */ public function setIpAddress(?string $ipAddress): void { - if (null !== $ipAddress && false === filter_var($ipAddress, \FILTER_VALIDATE_IP)) { + if ($ipAddress !== null && filter_var($ipAddress, \FILTER_VALIDATE_IP) === false) { throw new \InvalidArgumentException(sprintf('The "%s" value is not a valid IP address.', $ipAddress)); } diff --git a/src/Util/Http.php b/src/Util/Http.php index 214033fcd..a9ffc27ab 100644 --- a/src/Util/Http.php +++ b/src/Util/Http.php @@ -23,7 +23,7 @@ public static function getRequestHeaders(Dsn $dsn, string $sdkIdentifier, string 'sentry_key' => $dsn->getPublicKey(), ]; - if (null !== $dsn->getSecretKey()) { + if ($dsn->getSecretKey() !== null) { $data['sentry_secret'] = $dsn->getSecretKey(); } @@ -45,7 +45,7 @@ public static function getRequestHeaders(Dsn $dsn, string $sdkIdentifier, string */ public static function parseResponseHeaders(string $headerLine, array &$headers): int { - if (false === strpos($headerLine, ':')) { + if (strpos($headerLine, ':') === false) { return \strlen($headerLine); } diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 3c9360c69..9c568c52f 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -36,9 +36,9 @@ public static function encode($data, int $options = 0, int $maxDepth = 512): str $allowedErrors = [\JSON_ERROR_NONE, \JSON_ERROR_RECURSION, \JSON_ERROR_INF_OR_NAN, \JSON_ERROR_UNSUPPORTED_TYPE]; - $encounteredAnyError = \JSON_ERROR_NONE !== json_last_error(); + $encounteredAnyError = json_last_error() !== \JSON_ERROR_NONE; - if (($encounteredAnyError && ('null' === $encodedData || false === $encodedData)) || !\in_array(json_last_error(), $allowedErrors, true)) { + if (($encounteredAnyError && ($encodedData === 'null' || $encodedData === false)) || !\in_array(json_last_error(), $allowedErrors, true)) { throw new JsonException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); } @@ -58,7 +58,7 @@ public static function decode(string $data) { $decodedData = json_decode($data, true); - if (\JSON_ERROR_NONE !== json_last_error()) { + if (json_last_error() !== \JSON_ERROR_NONE) { throw new JsonException(sprintf('Could not decode value from JSON format. Error was: "%s".', json_last_error_msg())); } diff --git a/src/Util/PrefixStripper.php b/src/Util/PrefixStripper.php index d4f24286a..eccee711b 100644 --- a/src/Util/PrefixStripper.php +++ b/src/Util/PrefixStripper.php @@ -13,7 +13,7 @@ trait PrefixStripper */ protected function stripPrefixFromFilePath(?Options $options, string $filePath): string { - if (null === $options) { + if ($options === null) { return $filePath; } diff --git a/src/Util/SentryUid.php b/src/Util/SentryUid.php index 50c117f69..7adbc1cca 100644 --- a/src/Util/SentryUid.php +++ b/src/Util/SentryUid.php @@ -17,7 +17,7 @@ final class SentryUid public static function generate(): string { if (\function_exists('uuid_create')) { - return strtolower(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM))); + return strtolower(str_replace('-', '', uuid_create(\UUID_TYPE_RANDOM))); } $uuid = bin2hex(random_bytes(16)); diff --git a/src/functions.php b/src/functions.php index 4956b1226..b33863965 100644 --- a/src/functions.php +++ b/src/functions.php @@ -165,7 +165,7 @@ function trace(callable $trace, SpanContext $context) // If there's a span set on the scope there is a transaction // active currently. If that is the case we create a child span // and set it on the scope. Otherwise we only execute the callable - if (null !== $parentSpan) { + if ($parentSpan !== null) { $span = $parentSpan->startChild($context); $scope->setSpan($span); @@ -194,12 +194,12 @@ function getTraceparent(): string $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); - if (null !== $client) { + if ($client !== null) { $options = $client->getOptions(); - if (null !== $options && $options->isTracingEnabled()) { + if ($options !== null && $options->isTracingEnabled()) { $span = SentrySdk::getCurrentHub()->getSpan(); - if (null !== $span) { + if ($span !== null) { return $span->toTraceparent(); } } @@ -224,12 +224,12 @@ function getBaggage(): string $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); - if (null !== $client) { + if ($client !== null) { $options = $client->getOptions(); - if (null !== $options && $options->isTracingEnabled()) { + if ($options !== null && $options->isTracingEnabled()) { $span = SentrySdk::getCurrentHub()->getSpan(); - if (null !== $span) { + if ($span !== null) { return $span->toBaggage(); } } diff --git a/tests/Benchmark/SpanBench.php b/tests/Benchmark/SpanBench.php index 78e1d1ca6..0e0878d92 100644 --- a/tests/Benchmark/SpanBench.php +++ b/tests/Benchmark/SpanBench.php @@ -8,6 +8,7 @@ use PhpBench\Benchmark\Metadata\Annotations\Revs; use Sentry\Tracing\Span; use Sentry\Tracing\TransactionContext; + use function Sentry\continueTrace; final class SpanBench @@ -31,6 +32,7 @@ public function __construct() /** * @Revs(100000) + * * @Iterations(10) */ public function benchConstructor(): void @@ -40,6 +42,7 @@ public function benchConstructor(): void /** * @Revs(100000) + * * @Iterations(10) */ public function benchConstructorWithInjectedContext(): void @@ -49,6 +52,7 @@ public function benchConstructorWithInjectedContext(): void /** * @Revs(100000) + * * @Iterations(10) */ public function benchConstructorWithInjectedContextAndStartTimestamp(): void diff --git a/tests/ClientTest.php b/tests/ClientTest.php index bc1b35cb6..3b5011ddf 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -316,11 +316,11 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt $transport->expects($this->once()) ->method('send') ->with($this->callback(static function (Event $event) use ($shouldAttachStacktrace): bool { - if ($shouldAttachStacktrace && null === $event->getStacktrace()) { + if ($shouldAttachStacktrace && $event->getStacktrace() === null) { return false; } - if (!$shouldAttachStacktrace && null !== $event->getStacktrace()) { + if (!$shouldAttachStacktrace && $event->getStacktrace() !== null) { return false; } @@ -716,7 +716,7 @@ public function testAttachStacktrace(): void ->with($this->callback(function (Event $event): bool { $result = $event->getStacktrace(); - return null !== $result; + return $result !== null; })) ->willReturnCallback(static function (Event $event): Result { return new Result(ResultStatus::success(), $event); diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 26d329770..f343f941e 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -6,7 +6,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use RuntimeException; use Sentry\Breadcrumb; use Sentry\CheckInStatus; use Sentry\ClientInterface; @@ -29,6 +28,7 @@ use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Util\SentryUid; + use function Sentry\addBreadcrumb; use function Sentry\captureCheckIn; use function Sentry\captureEvent; @@ -306,9 +306,9 @@ public function testTraceCorrectlyReplacesAndRestoresCurrentSpan(): void try { trace(function () { - throw new RuntimeException('Throwing should still restore the previous span'); + throw new \RuntimeException('Throwing should still restore the previous span'); }, new SpanContext()); - } catch (RuntimeException $e) { + } catch (\RuntimeException $e) { $this->assertSame($transaction, $hub->getSpan()); } } diff --git a/tests/Integration/EnvironmentIntegrationTest.php b/tests/Integration/EnvironmentIntegrationTest.php index d530d8900..02c195170 100644 --- a/tests/Integration/EnvironmentIntegrationTest.php +++ b/tests/Integration/EnvironmentIntegrationTest.php @@ -14,6 +14,7 @@ use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\Util\PHPVersion; + use function Sentry\withScope; final class EnvironmentIntegrationTest extends TestCase @@ -46,14 +47,14 @@ public function testInvoke(bool $isIntegrationEnabled, ?RuntimeContext $initialR $runtimeContext = $event->getRuntimeContext(); $osContext = $event->getOsContext(); - if (null === $expectedRuntimeContext) { + if ($expectedRuntimeContext === null) { $this->assertNull($runtimeContext); } else { $this->assertSame($expectedRuntimeContext->getName(), $runtimeContext->getName()); $this->assertSame($expectedRuntimeContext->getVersion(), $runtimeContext->getVersion()); } - if (null === $expectedOsContext) { + if ($expectedOsContext === null) { $this->assertNull($expectedOsContext); } else { $this->assertSame($expectedOsContext->getName(), $osContext->getName()); diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index 1a1eea5d1..e4458be4c 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -15,6 +15,7 @@ use Sentry\SentrySdk; use Sentry\Stacktrace; use Sentry\State\Scope; + use function Sentry\withScope; final class FrameContextifierIntegrationTest extends TestCase @@ -159,7 +160,7 @@ private function getFixtureFileContent(string $file): string { $fileContent = file_get_contents($file); - if (false === $fileContent) { + if ($fileContent === false) { throw new \RuntimeException(sprintf('The fixture file at path "%s" could not be read.', $file)); } diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index 864dc738d..4fcd4dd02 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -11,6 +11,7 @@ use Sentry\Integration\ModulesIntegration; use Sentry\SentrySdk; use Sentry\State\Scope; + use function Sentry\withScope; final class ModulesIntegrationTest extends TestCase diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 0f904c8ba..266405d90 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -18,6 +18,7 @@ use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\UserDataBag; + use function Sentry\withScope; final class RequestIntegrationTest extends TestCase @@ -53,7 +54,7 @@ public function testInvoke(array $options, ServerRequestInterface $request, arra $user = $event->getUser(); - if (null !== $expectedUser) { + if ($expectedUser !== null) { $this->assertNotNull($user); $this->assertEquals($expectedUser, $user); } else { @@ -105,7 +106,7 @@ public static function invokeDataProvider(): iterable [ 'send_default_pii' => true, ], - (new ServerRequest('GET', 'http://www.example.com:1234/foo')), + new ServerRequest('GET', 'http://www.example.com:1234/foo'), [ 'url' => 'http://www.example.com:1234/foo', 'method' => 'GET', @@ -122,7 +123,7 @@ public static function invokeDataProvider(): iterable [ 'send_default_pii' => false, ], - (new ServerRequest('GET', 'http://www.example.com:1234/foo')), + new ServerRequest('GET', 'http://www.example.com:1234/foo'), [ 'url' => 'http://www.example.com:1234/foo', 'method' => 'GET', diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index fcb99a79b..ab46c6a1b 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -12,6 +12,7 @@ use Sentry\Integration\TransactionIntegration; use Sentry\SentrySdk; use Sentry\State\Scope; + use function Sentry\withScope; final class TransactionIntegrationTest extends TestCase diff --git a/tests/Monolog/RecordFactory.php b/tests/Monolog/RecordFactory.php index b302c509b..39e2b67d6 100644 --- a/tests/Monolog/RecordFactory.php +++ b/tests/Monolog/RecordFactory.php @@ -4,7 +4,6 @@ namespace Sentry\Tests\Monolog; -use DateTimeImmutable; use Monolog\Logger; use Monolog\LogRecord; @@ -24,7 +23,7 @@ public static function create(string $message, int $level, string $channel, arra { if (Logger::API >= 3) { return new LogRecord( - new DateTimeImmutable(), + new \DateTimeImmutable(), $channel, Logger::toMonologLevel($level), $message, diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 317225e46..a43337195 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -48,7 +48,7 @@ public function testConstructor( ?string $setterMethod, ?string $expectedGetterDeprecationMessage ): void { - if (null !== $expectedGetterDeprecationMessage) { + if ($expectedGetterDeprecationMessage !== null) { $this->expectDeprecation($expectedGetterDeprecationMessage); } @@ -70,17 +70,17 @@ public function testGettersAndSetters( ?string $expectedGetterDeprecationMessage, ?string $expectedSetterDeprecationMessage ): void { - if (null !== $expectedSetterDeprecationMessage) { + if ($expectedSetterDeprecationMessage !== null) { $this->expectDeprecation($expectedSetterDeprecationMessage); } - if (null !== $expectedGetterDeprecationMessage) { + if ($expectedGetterDeprecationMessage !== null) { $this->expectDeprecation($expectedGetterDeprecationMessage); } $options = new Options(); - if (null !== $setterMethod) { + if ($setterMethod !== null) { $options->$setterMethod($value); } @@ -605,7 +605,7 @@ public static function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): a */ public function testContextLinesOptionValidatesInputValue(?int $value, ?string $expectedExceptionMessage): void { - if (null !== $expectedExceptionMessage) { + if ($expectedExceptionMessage !== null) { $this->expectException(InvalidOptionsException::class); $this->expectExceptionMessage($expectedExceptionMessage); } else { diff --git a/tests/Profiling/ProfileTest.php b/tests/Profiling/ProfileTest.php index a30f68e6d..513210c23 100644 --- a/tests/Profiling/ProfileTest.php +++ b/tests/Profiling/ProfileTest.php @@ -24,7 +24,7 @@ public function testGetFormattedData(Event $event, array $excimerLog, $expectedD $profile->setStartTimeStamp(1677573660.0000); $profile->setExcimerLog($excimerLog); - $profile->setEventId((new EventId('815e57b4bb134056ab1840919834689d'))); + $profile->setEventId(new EventId('815e57b4bb134056ab1840919834689d')); $this->assertSame($expectedData, $profile->getFormattedData($event)); } diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 10e26f513..43fadc722 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -468,7 +468,7 @@ public function serializableCallableProvider(): array 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass param1d]', ], [ - 'callable' => function (\stdClass $param1e = null) { + 'callable' => function (?\stdClass $param1e = null) { throw new \Exception('Don\'t even think about invoke me'); }, 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass|null [param1e]]', @@ -480,7 +480,7 @@ public function serializableCallableProvider(): array 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array ¶m1f]', ], [ - 'callable' => function (array &$param1g = null) { + 'callable' => function (?array &$param1g = null) { throw new \Exception('Don\'t even think about invoke me'); }, 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array|null [¶m1g]]', @@ -564,7 +564,7 @@ public function testSerializeCallable($callable, string $expected): void /** * @dataProvider serializationForBadStringsDataProvider */ - public function testSerializationForBadStrings(string $string, string $expected, string $mbDetectOrder = null): void + public function testSerializationForBadStrings(string $string, string $expected, ?string $mbDetectOrder = null): void { $serializer = $this->createSerializer(); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 292fd9944..ccf7da096 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -53,8 +53,8 @@ public function testSerializeAsJson(Event $event, string $expectedResult, bool $ $result = $serializer->serialize($event); if ( - EventType::transaction() !== $event->getType() && - EventType::checkIn() !== $event->getType() + EventType::transaction() !== $event->getType() + && EventType::checkIn() !== $event->getType() ) { $resultArray = $serializer->toArray($event); $this->assertJsonStringEqualsJsonString($result, json_encode($resultArray)); diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 873f48fc5..27704821c 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -74,7 +74,7 @@ public function testAddFrame(): void */ public function testRemoveFrame(int $index, ?string $expectedExceptionMessage): void { - if (null !== $expectedExceptionMessage) { + if ($expectedExceptionMessage !== null) { $this->expectException(\OutOfBoundsException::class); $this->expectExceptionMessage($expectedExceptionMessage); } diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index 9a3dd1cb9..a80d9d17f 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -32,12 +32,12 @@ public function testFromHeaders(string $sentryTraceHeader, string $baggageHeader $propagationContext = PropagationContext::fromHeaders($sentryTraceHeader, $baggageHeader); $this->assertInstanceOf(TraceId::class, $propagationContext->getTraceId()); - if (null !== $expectedTraceId) { + if ($expectedTraceId !== null) { $this->assertSame((string) $expectedTraceId, (string) $propagationContext->getTraceId()); } $this->assertInstanceOf(SpanId::class, $propagationContext->getParentSpanId()); - if (null !== $expectedParentSpanId) { + if ($expectedParentSpanId !== null) { $this->assertSame((string) $expectedParentSpanId, (string) $propagationContext->getParentSpanId()); } @@ -54,12 +54,12 @@ public function testFromEnvironment(string $sentryTrace, string $baggage, ?Trace $propagationContext = PropagationContext::fromEnvironment($sentryTrace, $baggage); $this->assertInstanceOf(TraceId::class, $propagationContext->getTraceId()); - if (null !== $expectedTraceId) { + if ($expectedTraceId !== null) { $this->assertSame((string) $expectedTraceId, (string) $propagationContext->getTraceId()); } $this->assertInstanceOf(SpanId::class, $propagationContext->getParentSpanId()); - if (null !== $expectedParentSpanId) { + if ($expectedParentSpanId !== null) { $this->assertSame((string) $expectedParentSpanId, (string) $propagationContext->getParentSpanId()); } diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index dc5023632..d66f198d7 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -4,7 +4,6 @@ namespace Sentry\Tests\Tracing; -use Generator; use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; use Sentry\Event; @@ -107,7 +106,7 @@ public function testTransactionIsSampledCorrectlyWhenTracingIsSetToZeroInOptions $this->assertSame($expectedSampled, $transaction->getSampled()); } - public static function parentTransactionContextDataProvider(): Generator + public static function parentTransactionContextDataProvider(): \Generator { yield [ new TransactionContext(TransactionContext::DEFAULT_NAME, true), @@ -150,7 +149,7 @@ public function testTransactionIsNotSampledWhenTracingIsDisabledInOptions(Transa $this->assertSame($expectedSampled, $transaction->getSampled()); } - public function parentTransactionContextDataProviderDisabled(): Generator + public function parentTransactionContextDataProviderDisabled(): \Generator { yield [ new TransactionContext(TransactionContext::DEFAULT_NAME, true), From e0e78ea49928f1ff5d1ae0105cde44e9a24ee0f3 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 24 Oct 2023 17:12:23 +0200 Subject: [PATCH 0909/1161] [4.x] Ignore #1599 (PHPCS update) in blame view (#1604) --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..251ced3f6 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Update PHPCS (#1599) +f8b64d330d03cbf809795beb5846b2c48175cd62 From a12232522ca5127d5a3a82917679eacd5220f9c2 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 25 Oct 2023 13:06:17 +0200 Subject: [PATCH 0910/1161] Fix request fetcher (#1605) --- composer.json | 2 +- src/Integration/RequestFetcher.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 19f4f89ba..3a5bcca33 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "ext-json": "*", "ext-mbstring": "*", "ext-curl": "*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", "jean85/pretty-package-versions": "^1.5|^2.0.4", "psr/log": "^1.0|^2.0|^3.0", "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" @@ -31,7 +32,6 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.4", "guzzlehttp/promises": "^1.0|^2.0", - "guzzlehttp/psr7": "^1.8.4|^2.1.1", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.6|^2.0|^3.0", "nikic/php-parser": "^4.10.3", diff --git a/src/Integration/RequestFetcher.php b/src/Integration/RequestFetcher.php index f15fef4b3..333205b5f 100644 --- a/src/Integration/RequestFetcher.php +++ b/src/Integration/RequestFetcher.php @@ -4,7 +4,7 @@ namespace Sentry\Integration; -use Http\Discovery\Psr17Factory; +use GuzzleHttp\Psr7\ServerRequest; use Psr\Http\Message\ServerRequestInterface; /** @@ -22,6 +22,6 @@ public function fetchRequest(): ?ServerRequestInterface return null; } - return (new Psr17Factory())->createServerRequestFromGlobals(); + return ServerRequest::fromGlobals(); } } From ab2e11f45cf6e8e187ec60393d8032920e0c487a Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 30 Oct 2023 14:36:45 +0100 Subject: [PATCH 0911/1161] Cleanup some leftover dependecies (#1602) --- .github/workflows/static-analysis.yaml | 6 +++--- Makefile | 30 -------------------------- composer.json | 19 ++++++---------- tests/bootstrap.php | 4 ---- 4 files changed, 9 insertions(+), 50 deletions(-) delete mode 100644 Makefile diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 7ac104a8e..3c95813ec 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -24,7 +24,7 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist - name: Run script - run: composer phpcs + run: vendor/bin/php-cs-fixer fix --verbose --diff --dry-run phpstan: name: PHPStan @@ -42,7 +42,7 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist - name: Run script - run: composer phpstan + run: vendor/bin/phpstan analyse psalm: name: Psalm @@ -62,4 +62,4 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist - name: Run script - run: composer psalm + run: vendor/bin/psalm diff --git a/Makefile b/Makefile deleted file mode 100644 index 11d78096c..000000000 --- a/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -gc -am .PHONY: test - -develop: update-submodules - composer install - make setup-git - -update-submodules: - git submodule init - git submodule update - -cs: - vendor/bin/php-cs-fixer fix --verbose --diff - -cs-dry-run: - vendor/bin/php-cs-fixer fix --verbose --diff --dry-run - -cs-fix: - vendor/bin/php-cs-fixer fix - -psalm: - vendor/bin/psalm - -phpstan: - vendor/bin/phpstan analyse - -test: cs-fix phpstan psalm - vendor/bin/phpunit --verbose - -setup-git: - git config branch.autosetuprebase always diff --git a/composer.json b/composer.json index 3a5bcca33..dc7197367 100644 --- a/composer.json +++ b/composer.json @@ -32,14 +32,10 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.4", "guzzlehttp/promises": "^1.0|^2.0", - "http-interop/http-factory-guzzle": "^1.0", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", "monolog/monolog": "^1.6|^2.0|^3.0", - "nikic/php-parser": "^4.10.3", - "php-http/mock-client": "^1.3", "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.3", - "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^8.5.14|^9.4", "symfony/phpunit-bridge": "^5.2|^6.0", "vimeo/psalm": "^4.17" @@ -48,7 +44,6 @@ "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." }, "conflict": { - "php-http/client-common": "1.8.0", "raven/raven": "*" }, "autoload": { @@ -68,9 +63,12 @@ "tests": [ "vendor/bin/phpunit --verbose" ], - "phpcs": [ + "cs-check": [ "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run" ], + "cs-fix": [ + "vendor/bin/php-cs-fixer fix --verbose --diff" + ], "phpstan": [ "vendor/bin/phpstan analyse" ], @@ -79,12 +77,7 @@ ] }, "config": { - "sort-packages": true, - "allow-plugins": { - "composer/package-versions-deprecated": true, - "php-http/discovery": false, - "phpstan/extension-installer": true - } + "sort-packages": true }, "prefer-stable": true } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 0632c8aa0..4766219f0 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,8 +2,6 @@ declare(strict_types=1); -use Http\Discovery\ClassDiscovery; -use Http\Discovery\Strategy\MockClientStrategy; use Sentry\Breadcrumb; use Sentry\Event; use Sentry\Tracing\Span; @@ -12,8 +10,6 @@ require_once __DIR__ . '/../vendor/autoload.php'; -ClassDiscovery::appendStrategy(MockClientStrategy::class); - // According to the Symfony documentation the proper way to register the mocked // functions for a certain class would be to configure the listener in the // phpunit.xml file, however in our case it doesn't work because PHPUnit loads From fc5853fb42d11c95b47bf917a43caa0150ca3fd6 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 30 Oct 2023 14:41:09 +0100 Subject: [PATCH 0912/1161] Simplify breadcrumb API (#1603) Co-authored-by: Michi Hoffmann --- src/functions.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/functions.php b/src/functions.php index b33863965..9dba70ab1 100644 --- a/src/functions.php +++ b/src/functions.php @@ -85,11 +85,20 @@ function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ? * will be added to subsequent events to provide more context on user's * actions prior to an error or crash. * - * @param Breadcrumb $breadcrumb The breadcrumb to record + * @param Breadcrumb|string $category The category of the breadcrumb, can be a Breadcrumb instance as well (in which case the other parameters are ignored) + * @param string|null $message Breadcrumb message + * @param array $metadata Additional information about the breadcrumb + * @param string $level The error level of the breadcrumb + * @param string $type The type of the breadcrumb + * @param float|null $timestamp Optional timestamp of the breadcrumb */ -function addBreadcrumb(Breadcrumb $breadcrumb): void +function addBreadcrumb($category, ?string $message = null, array $metadata = [], string $level = Breadcrumb::LEVEL_INFO, string $type = Breadcrumb::TYPE_DEFAULT, ?float $timestamp = null): void { - SentrySdk::getCurrentHub()->addBreadcrumb($breadcrumb); + SentrySdk::getCurrentHub()->addBreadcrumb( + $category instanceof Breadcrumb + ? $category + : new Breadcrumb($level, $type, $category, $message, $metadata, $timestamp) + ); } /** From 5cc49b3e33a3e042cde6b3ac25539f1c2e6179a0 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 30 Oct 2023 15:33:41 +0100 Subject: [PATCH 0913/1161] Fluent methods (#1601) Co-authored-by: Michi Hoffmann --- UPGRADE-4.0.md | 7 +- src/CheckIn.php | 24 +++- src/ClientBuilder.php | 14 +-- src/Event.php | 124 ++++++++++++++------ src/ExceptionDataBag.php | 16 ++- src/ExceptionMechanism.php | 4 +- src/Frame.php | 20 +++- src/Options.php | 154 ++++++++++++++++++------- src/Stacktrace.php | 8 +- src/Tracing/DynamicSamplingContext.php | 10 +- src/Tracing/PropagationContext.php | 8 +- src/Tracing/SamplingContext.php | 8 +- src/Tracing/Span.php | 53 ++++++--- src/Tracing/SpanContext.php | 44 +++++-- src/Tracing/SpanRecorder.php | 4 +- src/Tracing/Transaction.php | 15 +-- src/Tracing/TransactionContext.php | 16 ++- src/Tracing/TransactionMetadata.php | 12 +- src/UserDataBag.php | 28 +++-- tests/FunctionsTest.php | 6 +- tests/Tracing/SpanTest.php | 18 +-- tests/Tracing/TransactionTest.php | 8 +- 22 files changed, 430 insertions(+), 171 deletions(-) diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 45604b0aa..34a12f5a9 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -6,7 +6,8 @@ - The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_errors` option instead. - `Sentry\Exception\InvalidArgumentException` was removed. Use `\InvalidArgumentException` instead. - `Sentry\Exception/ExceptionInterface` was removed. -- Removed `ClientBuilderInterface::setSerializer()` -- Removed `ClientBuilder::setSerializer()` +- Removed `ClientBuilderInterface::setSerializer()`. +- Removed `ClientBuilder::setSerializer()`. - Removed `Client::__construct()` param SerializerInterface $serializer. -- Change return type of `Dsn:: getProjectId()` to string +- Change return type of `Dsn:: getProjectId()` to string. +- Most setters now return `$this` instead of `void`. diff --git a/src/CheckIn.php b/src/CheckIn.php index f49e83a7d..1a0eafbe5 100644 --- a/src/CheckIn.php +++ b/src/CheckIn.php @@ -80,9 +80,11 @@ public function getMonitorSlug(): string return $this->monitorSlug; } - public function setMonitorSlug(string $monitorSlug): void + public function setMonitorSlug(string $monitorSlug): self { $this->monitorSlug = $monitorSlug; + + return $this; } public function getStatus(): CheckInStatus @@ -90,9 +92,11 @@ public function getStatus(): CheckInStatus return $this->status; } - public function setStatus(CheckInStatus $status): void + public function setStatus(CheckInStatus $status): self { $this->status = $status; + + return $this; } public function getRelease(): ?string @@ -100,9 +104,11 @@ public function getRelease(): ?string return $this->release; } - public function setRelease(string $release): void + public function setRelease(string $release): self { $this->release = $release; + + return $this; } public function getEnvironment(): ?string @@ -110,9 +116,11 @@ public function getEnvironment(): ?string return $this->environment; } - public function setEnvironment(string $environment): void + public function setEnvironment(string $environment): self { $this->environment = $environment; + + return $this; } /** @@ -126,9 +134,11 @@ public function getDuration() /** * @param int|float|null $duration The duration of the check-in in seconds */ - public function setDuration($duration): void + public function setDuration($duration): self { $this->duration = $duration; + + return $this; } public function getMonitorConfig(): ?MonitorConfig @@ -136,8 +146,10 @@ public function getMonitorConfig(): ?MonitorConfig return $this->monitorConfig; } - public function setMonitorConfig(?MonitorConfig $monitorConfig): void + public function setMonitorConfig(?MonitorConfig $monitorConfig): self { $this->monitorConfig = $monitorConfig; + + return $this; } } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 680ff73c6..0594ba08e 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -75,7 +75,7 @@ public function __construct(?Options $options = null) /** * @param array $options The client options, in naked array form */ - public static function create(array $options = []): ClientBuilder + public static function create(array $options = []): self { return new self(new Options($options)); } @@ -85,28 +85,28 @@ public function getOptions(): Options return $this->options; } - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): ClientBuilder + public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self { $this->representationSerializer = $representationSerializer; return $this; } - public function setLogger(LoggerInterface $logger): ClientBuilder + public function setLogger(LoggerInterface $logger): self { $this->logger = $logger; return $this; } - public function setSdkIdentifier(string $sdkIdentifier): ClientBuilder + public function setSdkIdentifier(string $sdkIdentifier): self { $this->sdkIdentifier = $sdkIdentifier; return $this; } - public function setSdkVersion(string $sdkVersion): ClientBuilder + public function setSdkVersion(string $sdkVersion): self { $this->sdkVersion = $sdkVersion; @@ -118,7 +118,7 @@ public function getTransport(): TransportInterface return $this->transport; } - public function setTransport(TransportInterface $transport): ClientBuilder + public function setTransport(TransportInterface $transport): self { $this->transport = $transport; @@ -130,7 +130,7 @@ public function getHttpClient(): HttpClientInterface return $this->httpClient; } - public function setHttpClient(HttpClientInterface $httpClient): ClientBuilder + public function setHttpClient(HttpClientInterface $httpClient): self { $this->httpClient = $httpClient; diff --git a/src/Event.php b/src/Event.php index 57d7d0991..b9fd06e27 100644 --- a/src/Event.php +++ b/src/Event.php @@ -233,9 +233,11 @@ public function getSdkIdentifier(): string * * @internal */ - public function setSdkIdentifier(string $sdkIdentifier): void + public function setSdkIdentifier(string $sdkIdentifier): self { $this->sdkIdentifier = $sdkIdentifier; + + return $this; } /** @@ -253,9 +255,11 @@ public function getSdkVersion(): string * * @internal */ - public function setSdkVersion(string $sdkVersion): void + public function setSdkVersion(string $sdkVersion): self { $this->sdkVersion = $sdkVersion; + + return $this; } /** @@ -269,9 +273,11 @@ public function getTimestamp(): ?float /** * Sets the timestamp of when the Event was created. */ - public function setTimestamp(?float $timestamp): void + public function setTimestamp(?float $timestamp): self { $this->timestamp = $timestamp; + + return $this; } /** @@ -287,9 +293,11 @@ public function getLevel(): ?Severity * * @param Severity|null $level The severity */ - public function setLevel(?Severity $level): void + public function setLevel(?Severity $level): self { $this->level = $level; + + return $this; } /** @@ -305,9 +313,11 @@ public function getLogger(): ?string * * @param string|null $logger The logger name */ - public function setLogger(?string $logger): void + public function setLogger(?string $logger): self { $this->logger = $logger; + + return $this; } /** @@ -325,14 +335,11 @@ public function getTransaction(): ?string * * @param string|null $transaction The transaction name */ - public function setTransaction(?string $transaction): void + public function setTransaction(?string $transaction): self { $this->transaction = $transaction; - } - public function setCheckIn(?CheckIn $checkIn): void - { - $this->checkIn = $checkIn; + return $this; } public function getCheckIn(): ?CheckIn @@ -340,6 +347,13 @@ public function getCheckIn(): ?CheckIn return $this->checkIn; } + public function setCheckIn(?CheckIn $checkIn): self + { + $this->checkIn = $checkIn; + + return $this; + } + /** * Gets the name of the server. */ @@ -353,9 +367,11 @@ public function getServerName(): ?string * * @param string|null $serverName The server name */ - public function setServerName(?string $serverName): void + public function setServerName(?string $serverName): self { $this->serverName = $serverName; + + return $this; } /** @@ -371,9 +387,11 @@ public function getRelease(): ?string * * @param string|null $release The release */ - public function setRelease(?string $release): void + public function setRelease(?string $release): self { $this->release = $release; + + return $this; } /** @@ -409,11 +427,13 @@ public function getMessageParams(): array * @param string[] $params The parameters to use to format the message * @param string|null $formatted The formatted message */ - public function setMessage(string $message, array $params = [], ?string $formatted = null): void + public function setMessage(string $message, array $params = [], ?string $formatted = null): self { $this->message = $message; $this->messageParams = $params; $this->messageFormatted = $formatted; + + return $this; } /** @@ -431,9 +451,11 @@ public function getModules(): array * * @param array $modules */ - public function setModules(array $modules): void + public function setModules(array $modules): self { $this->modules = $modules; + + return $this; } /** @@ -451,9 +473,11 @@ public function getRequest(): array * * @param array $request The request data */ - public function setRequest(array $request): void + public function setRequest(array $request): self { $this->request = $request; + + return $this; } /** @@ -496,9 +520,11 @@ public function getExtra(): array * * @param array $extra The context object */ - public function setExtra(array $extra): void + public function setExtra(array $extra): self { $this->extra = $extra; + + return $this; } /** @@ -516,9 +542,11 @@ public function getTags(): array * * @param array $tags The tags to set */ - public function setTags(array $tags): void + public function setTags(array $tags): self { $this->tags = $tags; + + return $this; } /** @@ -527,9 +555,11 @@ public function setTags(array $tags): void * @param string $key The key that uniquely identifies the tag * @param string $value The value */ - public function setTag(string $key, string $value): void + public function setTag(string $key, string $value): self { $this->tags[$key] = $value; + + return $this; } /** @@ -537,9 +567,11 @@ public function setTag(string $key, string $value): void * * @param string $key The key that uniquely identifies the tag */ - public function removeTag(string $key): void + public function removeTag(string $key): self { unset($this->tags[$key]); + + return $this; } /** @@ -555,9 +587,11 @@ public function getUser(): ?UserDataBag * * @param UserDataBag|null $user The context object */ - public function setUser(?UserDataBag $user): void + public function setUser(?UserDataBag $user): self { $this->user = $user; + + return $this; } /** @@ -573,9 +607,11 @@ public function getOsContext(): ?OsContext * * @param OsContext|null $osContext The context object */ - public function setOsContext(?OsContext $osContext): void + public function setOsContext(?OsContext $osContext): self { $this->osContext = $osContext; + + return $this; } /** @@ -591,9 +627,11 @@ public function getRuntimeContext(): ?RuntimeContext * * @param RuntimeContext|null $runtimeContext The context object */ - public function setRuntimeContext(?RuntimeContext $runtimeContext): void + public function setRuntimeContext(?RuntimeContext $runtimeContext): self { $this->runtimeContext = $runtimeContext; + + return $this; } /** @@ -613,9 +651,11 @@ public function getFingerprint(): array * * @param string[] $fingerprint The strings */ - public function setFingerprint(array $fingerprint): void + public function setFingerprint(array $fingerprint): self { $this->fingerprint = $fingerprint; + + return $this; } /** @@ -631,9 +671,11 @@ public function getEnvironment(): ?string * * @param string|null $environment The name of the environment */ - public function setEnvironment(?string $environment): void + public function setEnvironment(?string $environment): self { $this->environment = $environment; + + return $this; } /** @@ -651,9 +693,11 @@ public function getBreadcrumbs(): array * * @param Breadcrumb[] $breadcrumbs The breadcrumb array */ - public function setBreadcrumb(array $breadcrumbs): void + public function setBreadcrumb(array $breadcrumbs): self { $this->breadcrumbs = $breadcrumbs; + + return $this; } /** @@ -671,7 +715,7 @@ public function getExceptions(): array * * @param ExceptionDataBag[] $exceptions The exceptions */ - public function setExceptions(array $exceptions): void + public function setExceptions(array $exceptions): self { foreach ($exceptions as $exception) { if (!$exception instanceof ExceptionDataBag) { @@ -680,6 +724,8 @@ public function setExceptions(array $exceptions): void } $this->exceptions = $exceptions; + + return $this; } /** @@ -695,9 +741,11 @@ public function getStacktrace(): ?Stacktrace * * @param Stacktrace|null $stacktrace The stacktrace instance */ - public function setStacktrace(?Stacktrace $stacktrace): void + public function setStacktrace(?Stacktrace $stacktrace): self { $this->stacktrace = $stacktrace; + + return $this; } public function getType(): EventType @@ -711,9 +759,11 @@ public function getType(): EventType * @param string $name The name that uniquely identifies the SDK metadata * @param mixed $data The data of the SDK metadata */ - public function setSdkMetadata(string $name, $data): void + public function setSdkMetadata(string $name, $data): self { $this->sdkMetadata[$name] = $data; + + return $this; } /** @@ -749,9 +799,11 @@ public function getStartTimestamp(): ?float * * @param float|null $startTimestamp The start time of the measurement */ - public function setStartTimestamp(?float $startTimestamp): void + public function setStartTimestamp(?float $startTimestamp): self { $this->startTimestamp = $startTimestamp; + + return $this; } /** @@ -769,14 +821,11 @@ public function getSpans(): array * * @param Span[] $spans The list of spans */ - public function setSpans(array $spans): void + public function setSpans(array $spans): self { $this->spans = $spans; - } - public function setProfile(?Profile $profile): void - { - $this->profile = $profile; + return $this; } public function getProfile(): ?Profile @@ -784,6 +833,13 @@ public function getProfile(): ?Profile return $this->profile; } + public function setProfile(?Profile $profile): self + { + $this->profile = $profile; + + return $this; + } + public function getTraceId(): ?string { $traceId = $this->getContexts()['trace']['trace_id']; diff --git a/src/ExceptionDataBag.php b/src/ExceptionDataBag.php index e93b6c617..3bdd36d4d 100644 --- a/src/ExceptionDataBag.php +++ b/src/ExceptionDataBag.php @@ -53,9 +53,11 @@ public function getType(): string * * @param string $type The exception type */ - public function setType(string $type): void + public function setType(string $type): self { $this->type = $type; + + return $this; } /** @@ -69,9 +71,11 @@ public function getValue(): string /** * Sets the value of the exception. */ - public function setValue(string $value): void + public function setValue(string $value): self { $this->value = $value; + + return $this; } /** @@ -87,9 +91,11 @@ public function getStacktrace(): ?Stacktrace * * @param Stacktrace $stacktrace The stacktrace */ - public function setStacktrace(Stacktrace $stacktrace): void + public function setStacktrace(Stacktrace $stacktrace): self { $this->stacktrace = $stacktrace; + + return $this; } /** @@ -105,8 +111,10 @@ public function getMechanism(): ?ExceptionMechanism * * @param ExceptionMechanism|null $mechanism The mechanism that created this exception */ - public function setMechanism(?ExceptionMechanism $mechanism): void + public function setMechanism(?ExceptionMechanism $mechanism): self { $this->mechanism = $mechanism; + + return $this; } } diff --git a/src/ExceptionMechanism.php b/src/ExceptionMechanism.php index 9a3cf23ee..fed2081b1 100644 --- a/src/ExceptionMechanism.php +++ b/src/ExceptionMechanism.php @@ -83,8 +83,10 @@ public function getData(): array * * @param array $data */ - public function setData(array $data): void + public function setData(array $data): self { $this->data = $data; + + return $this; } } diff --git a/src/Frame.php b/src/Frame.php index 4b2c34c0d..fa5b3e169 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -153,9 +153,11 @@ public function getPreContext(): array * * @param string[] $preContext The source code lines */ - public function setPreContext(array $preContext): void + public function setPreContext(array $preContext): self { $this->preContext = $preContext; + + return $this; } /** @@ -173,9 +175,11 @@ public function getContextLine(): ?string * * @param string|null $contextLine The source code line */ - public function setContextLine(?string $contextLine): void + public function setContextLine(?string $contextLine): self { $this->contextLine = $contextLine; + + return $this; } /** @@ -193,9 +197,11 @@ public function getPostContext(): array * * @param string[] $postContext The source code lines */ - public function setPostContext(array $postContext): void + public function setPostContext(array $postContext): self { $this->postContext = $postContext; + + return $this; } /** @@ -213,9 +219,11 @@ public function isInApp(): bool * * @param bool $inApp flag indicating whether the frame is application-related */ - public function setIsInApp(bool $inApp): void + public function setIsInApp(bool $inApp): self { $this->inApp = $inApp; + + return $this; } /** @@ -235,9 +243,11 @@ public function getVars(): array * * @param array $vars The variables */ - public function setVars(array $vars): void + public function setVars(array $vars): self { $this->vars = $vars; + + return $this; } /** diff --git a/src/Options.php b/src/Options.php index 40e59ec04..e520e1a2f 100644 --- a/src/Options.php +++ b/src/Options.php @@ -81,11 +81,13 @@ public function getPrefixes(): array * * @param string[] $prefixes The prefixes */ - public function setPrefixes(array $prefixes): void + public function setPrefixes(array $prefixes): self { $options = array_merge($this->options, ['prefixes' => $prefixes]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -103,11 +105,13 @@ public function getSampleRate(): float * * @param float $sampleRate The sampling factor */ - public function setSampleRate(float $sampleRate): void + public function setSampleRate(float $sampleRate): self { $options = array_merge($this->options, ['sample_rate' => $sampleRate]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -125,11 +129,13 @@ public function getTracesSampleRate(): ?float * * @param bool|null $enableTracing Boolean if tracing should be enabled or not */ - public function setEnableTracing(?bool $enableTracing): void + public function setEnableTracing(?bool $enableTracing): self { $options = array_merge($this->options, ['enable_tracing' => $enableTracing]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -148,11 +154,13 @@ public function getEnableTracing(): ?bool * * @param ?float $sampleRate The sampling factor */ - public function setTracesSampleRate(?float $sampleRate): void + public function setTracesSampleRate(?float $sampleRate): self { $options = array_merge($this->options, ['traces_sample_rate' => $sampleRate]); $this->options = $this->resolver->resolve($options); + + return $this; } public function getProfilesSampleRate(): ?float @@ -163,11 +171,13 @@ public function getProfilesSampleRate(): ?float return $value ?? null; } - public function setProfilesSampleRate(?float $sampleRate): void + public function setProfilesSampleRate(?float $sampleRate): self { $options = array_merge($this->options, ['profiles_sample_rate' => $sampleRate]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -197,11 +207,13 @@ public function shouldAttachStacktrace(): bool * * @param bool $enable Flag indicating if the stacktrace will be attached to captureMessage calls */ - public function setAttachStacktrace(bool $enable): void + public function setAttachStacktrace(bool $enable): self { $options = array_merge($this->options, ['attach_stacktrace' => $enable]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -217,11 +229,13 @@ public function getContextLines(): ?int * * @param int|null $contextLines The number of lines of code */ - public function setContextLines(?int $contextLines): void + public function setContextLines(?int $contextLines): self { $options = array_merge($this->options, ['context_lines' => $contextLines]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -237,11 +251,13 @@ public function isCompressionEnabled(): bool * * @param bool $enabled Flag indicating whether the request should be compressed */ - public function setEnableCompression(bool $enabled): void + public function setEnableCompression(bool $enabled): self { $options = array_merge($this->options, ['enable_compression' => $enabled]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -257,11 +273,13 @@ public function getEnvironment(): ?string * * @param string|null $environment The environment */ - public function setEnvironment(?string $environment): void + public function setEnvironment(?string $environment): self { $options = array_merge($this->options, ['environment' => $environment]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -279,11 +297,13 @@ public function getInAppExcludedPaths(): array * * @param string[] $paths The list of paths */ - public function setInAppExcludedPaths(array $paths): void + public function setInAppExcludedPaths(array $paths): self { $options = array_merge($this->options, ['in_app_exclude' => $paths]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -301,11 +321,13 @@ public function getInAppIncludedPaths(): array * * @param string[] $paths The list of paths */ - public function setInAppIncludedPaths(array $paths): void + public function setInAppIncludedPaths(array $paths): self { $options = array_merge($this->options, ['in_app_include' => $paths]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -329,13 +351,15 @@ public function getLogger(/* bool $triggerDeprecation = true */): string * * @deprecated since version 3.2, to be removed in 4.0 */ - public function setLogger(string $logger): void + public function setLogger(string $logger): self { @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); $options = array_merge($this->options, ['logger' => $logger]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -351,11 +375,13 @@ public function getRelease(): ?string * * @param string|null $release The release */ - public function setRelease(?string $release): void + public function setRelease(?string $release): self { $options = array_merge($this->options, ['release' => $release]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -379,11 +405,13 @@ public function getServerName(): string * * @param string $serverName The server name */ - public function setServerName(string $serverName): void + public function setServerName(string $serverName): self { $options = array_merge($this->options, ['server_name' => $serverName]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -403,11 +431,13 @@ public function getIgnoreExceptions(): array * * @param string[] $ignoreErrors The list of exceptions to be ignored */ - public function setIgnoreExceptions(array $ignoreErrors): void + public function setIgnoreExceptions(array $ignoreErrors): self { $options = array_merge($this->options, ['ignore_exceptions' => $ignoreErrors]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -425,11 +455,13 @@ public function getIgnoreTransactions(): array * * @param string[] $ignoreTransaction The list of transaction names to be ignored */ - public function setIgnoreTransactions(array $ignoreTransaction): void + public function setIgnoreTransactions(array $ignoreTransaction): self { $options = array_merge($this->options, ['ignore_transactions' => $ignoreTransaction]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -451,11 +483,13 @@ public function getBeforeSendCallback(): callable * * @psalm-param callable(Event, ?EventHint): ?Event $callback */ - public function setBeforeSendCallback(callable $callback): void + public function setBeforeSendCallback(callable $callback): self { $options = array_merge($this->options, ['before_send' => $callback]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -477,11 +511,13 @@ public function getBeforeSendTransactionCallback(): callable * * @psalm-param callable(Event, ?EventHint): ?Event $callback */ - public function setBeforeSendTransactionCallback(callable $callback): void + public function setBeforeSendTransactionCallback(callable $callback): self { $options = array_merge($this->options, ['before_send_transaction' => $callback]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -499,11 +535,13 @@ public function getTracePropagationTargets(): ?array * * @param string[] $tracePropagationTargets Trace propagation targets */ - public function setTracePropagationTargets(array $tracePropagationTargets): void + public function setTracePropagationTargets(array $tracePropagationTargets): self { $options = array_merge($this->options, ['trace_propagation_targets' => $tracePropagationTargets]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -521,11 +559,13 @@ public function getTags(): array * * @param array $tags A list of tags */ - public function setTags(array $tags): void + public function setTags(array $tags): self { $options = array_merge($this->options, ['tags' => $tags]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -541,11 +581,13 @@ public function getErrorTypes(): int * * @param int $errorTypes The bit mask */ - public function setErrorTypes(int $errorTypes): void + public function setErrorTypes(int $errorTypes): self { $options = array_merge($this->options, ['error_types' => $errorTypes]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -561,11 +603,13 @@ public function getMaxBreadcrumbs(): int * * @param int $maxBreadcrumbs The maximum number of breadcrumbs */ - public function setMaxBreadcrumbs(int $maxBreadcrumbs): void + public function setMaxBreadcrumbs(int $maxBreadcrumbs): self { $options = array_merge($this->options, ['max_breadcrumbs' => $maxBreadcrumbs]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -589,11 +633,13 @@ public function getBeforeBreadcrumbCallback(): callable * * @psalm-param callable(Breadcrumb): ?Breadcrumb $callback */ - public function setBeforeBreadcrumbCallback(callable $callback): void + public function setBeforeBreadcrumbCallback(callable $callback): self { $options = array_merge($this->options, ['before_breadcrumb' => $callback]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -603,11 +649,13 @@ public function setBeforeBreadcrumbCallback(callable $callback): void * * @param IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[] $integrations The list or callable */ - public function setIntegrations($integrations): void + public function setIntegrations($integrations): self { $options = array_merge($this->options, ['integrations' => $integrations]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -620,11 +668,13 @@ public function getIntegrations() return $this->options['integrations']; } - public function setTransport(TransportInterface $transport): void + public function setTransport(TransportInterface $transport): self { $options = array_merge($this->options, ['transport' => $transport]); $this->options = $this->resolver->resolve($options); + + return $this; } public function getTransport(): ?TransportInterface @@ -632,11 +682,13 @@ public function getTransport(): ?TransportInterface return $this->options['transport']; } - public function setHttpClient(HttpClientInterface $httpClient): void + public function setHttpClient(HttpClientInterface $httpClient): self { $options = array_merge($this->options, ['http_client' => $httpClient]); $this->options = $this->resolver->resolve($options); + + return $this; } public function getHttpClient(): ?HttpClientInterface @@ -657,11 +709,13 @@ public function shouldSendDefaultPii(): bool * * @param bool $enable Flag indicating if default PII will be sent */ - public function setSendDefaultPii(bool $enable): void + public function setSendDefaultPii(bool $enable): self { $options = array_merge($this->options, ['send_default_pii' => $enable]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -677,11 +731,13 @@ public function hasDefaultIntegrations(): bool * * @param bool $enable Flag indicating whether the default integrations should be enabled */ - public function setDefaultIntegrations(bool $enable): void + public function setDefaultIntegrations(bool $enable): self { $options = array_merge($this->options, ['default_integrations' => $enable]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -697,11 +753,13 @@ public function getMaxValueLength(): int * * @param int $maxValueLength The number of characters after which the values containing text will be truncated */ - public function setMaxValueLength(int $maxValueLength): void + public function setMaxValueLength(int $maxValueLength): self { $options = array_merge($this->options, ['max_value_length' => $maxValueLength]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -717,11 +775,13 @@ public function getHttpProxy(): ?string * * @param string|null $httpProxy The http proxy */ - public function setHttpProxy(?string $httpProxy): void + public function setHttpProxy(?string $httpProxy): self { $options = array_merge($this->options, ['http_proxy' => $httpProxy]); $this->options = $this->resolver->resolve($options); + + return $this; } public function getHttpProxyAuthentication(): ?string @@ -729,11 +789,13 @@ public function getHttpProxyAuthentication(): ?string return $this->options['http_proxy_authentication']; } - public function setHttpProxyAuthentication(?string $httpProxy): void + public function setHttpProxyAuthentication(?string $httpProxy): self { $options = array_merge($this->options, ['http_proxy_authentication' => $httpProxy]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -749,11 +811,13 @@ public function getHttpConnectTimeout(): float * * @param float $httpConnectTimeout The amount of time in seconds */ - public function setHttpConnectTimeout(float $httpConnectTimeout): void + public function setHttpConnectTimeout(float $httpConnectTimeout): self { $options = array_merge($this->options, ['http_connect_timeout' => $httpConnectTimeout]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -771,11 +835,13 @@ public function getHttpTimeout(): float * * @param float $httpTimeout The amount of time in seconds */ - public function setHttpTimeout(float $httpTimeout): void + public function setHttpTimeout(float $httpTimeout): self { $options = array_merge($this->options, ['http_timeout' => $httpTimeout]); $this->options = $this->resolver->resolve($options); + + return $this; } public function getHttpSslVerifyPeer(): bool @@ -783,11 +849,13 @@ public function getHttpSslVerifyPeer(): bool return $this->options['http_ssl_verify_peer']; } - public function setHttpSslVerifyPeer(bool $httpSslVerifyPeer): void + public function setHttpSslVerifyPeer(bool $httpSslVerifyPeer): self { $options = array_merge($this->options, ['http_ssl_verify_peer' => $httpSslVerifyPeer]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -807,11 +875,13 @@ public function shouldCaptureSilencedErrors(): bool * @param bool $shouldCapture If set to true, errors silenced through the @ * operator will be reported, ignored otherwise */ - public function setCaptureSilencedErrors(bool $shouldCapture): void + public function setCaptureSilencedErrors(bool $shouldCapture): self { $options = array_merge($this->options, ['capture_silenced_errors' => $shouldCapture]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -841,11 +911,13 @@ public function getMaxRequestBodySize(): string * request body for as long as sentry can * make sense of it */ - public function setMaxRequestBodySize(string $maxRequestBodySize): void + public function setMaxRequestBodySize(string $maxRequestBodySize): self { $options = array_merge($this->options, ['max_request_body_size' => $maxRequestBodySize]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -866,11 +938,13 @@ public function getClassSerializers(): array * * @param array $serializers The list of serializer callbacks */ - public function setClassSerializers(array $serializers): void + public function setClassSerializers(array $serializers): self { $options = array_merge($this->options, ['class_serializers' => $serializers]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -891,11 +965,13 @@ public function getTracesSampler(): ?callable * * @psalm-param null|callable(\Sentry\Tracing\SamplingContext): float $sampler */ - public function setTracesSampler(?callable $sampler): void + public function setTracesSampler(?callable $sampler): self { $options = array_merge($this->options, ['traces_sampler' => $sampler]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -986,7 +1062,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); $resolver->setAllowedTypes('transport', ['null', TransportInterface::class]); - $resolver->setAllowedTypes('http_client', ['null', HttpCLientInterface::class]); + $resolver->setAllowedTypes('http_client', ['null', HttpClientInterface::class]); $resolver->setAllowedTypes('http_proxy', ['null', 'string']); $resolver->setAllowedTypes('http_proxy_authentication', ['null', 'string']); $resolver->setAllowedTypes('http_connect_timeout', ['int', 'float']); diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 3b532a214..52633602c 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -69,9 +69,11 @@ public function getFrame(int $index): Frame * * @param Frame $frame The frame */ - public function addFrame(Frame $frame): void + public function addFrame(Frame $frame): self { array_unshift($this->frames, $frame); + + return $this; } /** @@ -81,7 +83,7 @@ public function addFrame(Frame $frame): void * * @throws \OutOfBoundsException If the index is out of range */ - public function removeFrame(int $index): void + public function removeFrame(int $index): self { if (!isset($this->frames[$index])) { throw new \OutOfBoundsException(sprintf('Cannot remove the frame at index %d.', $index)); @@ -92,5 +94,7 @@ public function removeFrame(int $index): void } array_splice($this->frames, $index, 1); + + return $this; } } diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index 75c4c9d29..c3a704a53 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -40,13 +40,15 @@ private function __construct() * @param string $key the list member key * @param string $value the list member value */ - public function set(string $key, string $value): void + public function set(string $key, string $value): self { if ($this->isFrozen) { - return; + return $this; } $this->entries[$key] = $value; + + return $this; } /** @@ -73,9 +75,11 @@ public function get(string $key, ?string $default = null): ?string /** * Mark the dsc as frozen. */ - public function freeze(): void + public function freeze(): self { $this->isFrozen = true; + + return $this; } /** diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index f05f00667..851ecc527 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -130,9 +130,11 @@ public function getSpanId(): SpanId return $this->spanId; } - public function setSpanId(SpanId $spanId): void + public function setSpanId(SpanId $spanId): self { $this->spanId = $spanId; + + return $this; } public function getDynamicSamplingContext(): ?DynamicSamplingContext @@ -140,9 +142,11 @@ public function getDynamicSamplingContext(): ?DynamicSamplingContext return $this->dynamicSamplingContext; } - public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplingContext): void + public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplingContext): self { $this->dynamicSamplingContext = $dynamicSamplingContext; + + return $this; } private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self diff --git a/src/Tracing/SamplingContext.php b/src/Tracing/SamplingContext.php index f698286a7..bee540bfb 100644 --- a/src/Tracing/SamplingContext.php +++ b/src/Tracing/SamplingContext.php @@ -49,9 +49,11 @@ public function getParentSampled(): ?bool /** * Sets the sampling decision from the parent transaction, if any. */ - public function setParentSampled(?bool $parentSampled): void + public function setParentSampled(?bool $parentSampled): self { $this->parentSampled = $parentSampled; + + return $this; } /** @@ -59,9 +61,11 @@ public function setParentSampled(?bool $parentSampled): void * * @param array|null $additionalContext */ - public function setAdditionalContext(?array $additionalContext): void + public function setAdditionalContext(?array $additionalContext): self { $this->additionalContext = $additionalContext; + + return $this; } /** diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index ec0a016a8..496efb2d4 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -111,9 +111,11 @@ public function __construct(?SpanContext $context = null) * * @param SpanId $spanId The ID */ - public function setSpanId(SpanId $spanId): void + public function setSpanId(SpanId $spanId): self { $this->spanId = $spanId; + + return $this; } /** @@ -129,9 +131,11 @@ public function getTraceId(): TraceId * * @param TraceId $traceId The ID */ - public function setTraceId(TraceId $traceId): void + public function setTraceId(TraceId $traceId): self { $this->traceId = $traceId; + + return $this; } /** @@ -147,9 +151,11 @@ public function getParentSpanId(): ?SpanId * * @param SpanId|null $parentSpanId The ID */ - public function setParentSpanId(?SpanId $parentSpanId): void + public function setParentSpanId(?SpanId $parentSpanId): self { $this->parentSpanId = $parentSpanId; + + return $this; } /** @@ -165,9 +171,11 @@ public function getStartTimestamp(): float * * @param float $startTimestamp The timestamp */ - public function setStartTimestamp(float $startTimestamp): void + public function setStartTimestamp(float $startTimestamp): self { $this->startTimestamp = $startTimestamp; + + return $this; } /** @@ -193,9 +201,11 @@ public function getDescription(): ?string * * @param string|null $description The description */ - public function setDescription(?string $description): void + public function setDescription(?string $description): self { $this->description = $description; + + return $this; } /** @@ -211,9 +221,11 @@ public function getOp(): ?string * * @param string|null $op The short code */ - public function setOp(?string $op): void + public function setOp(?string $op): self { $this->op = $op; + + return $this; } /** @@ -229,9 +241,11 @@ public function getStatus(): ?SpanStatus * * @param SpanStatus|null $status The status */ - public function setStatus(?SpanStatus $status): void + public function setStatus(?SpanStatus $status): self { $this->status = $status; + + return $this; } /** @@ -239,7 +253,7 @@ public function setStatus(?SpanStatus $status): void * * @param int $statusCode The HTTP status code */ - public function setHttpStatus(int $statusCode): void + public function setHttpStatus(int $statusCode): self { $this->tags['http.status_code'] = (string) $statusCode; @@ -248,6 +262,8 @@ public function setHttpStatus(int $statusCode): void if ($status !== SpanStatus::unknownError()) { $this->status = $status; } + + return $this; } /** @@ -261,13 +277,16 @@ public function getTags(): array } /** - * Sets a map of tags for this event. + * Sets a map of tags for this event. This method will merge the given tags with + * the existing ones. * * @param array $tags The tags */ - public function setTags(array $tags): void + public function setTags(array $tags): self { $this->tags = array_merge($this->tags, $tags); + + return $this; } /** @@ -291,9 +310,11 @@ public function getSampled(): ?bool * * @param bool $sampled Whether to sample or not this span */ - public function setSampled(?bool $sampled): void + public function setSampled(?bool $sampled): self { $this->sampled = $sampled; + + return $this; } /** @@ -312,9 +333,11 @@ public function getData(): array * * @param array $data The data */ - public function setData(array $data): void + public function setData(array $data): self { $this->data = array_merge($this->data, $data); + + return $this; } /** @@ -398,7 +421,7 @@ public function startChild(SpanContext $context): self $span->transaction = $this->transaction; $span->spanRecorder = $this->spanRecorder; - if ($span->spanRecorder != null) { + if ($span->spanRecorder !== null) { $span->spanRecorder->add($span); } @@ -418,9 +441,11 @@ public function getSpanRecorder(): ?SpanRecorder /** * Detaches the span recorder from this instance. */ - public function detachSpanRecorder(): void + public function detachSpanRecorder(): self { $this->spanRecorder = null; + + return $this; } /** diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 59e80745b..a1afa3f77 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -66,9 +66,11 @@ public function getDescription(): ?string return $this->description; } - public function setDescription(?string $description): void + public function setDescription(?string $description): self { $this->description = $description; + + return $this; } public function getOp(): ?string @@ -76,9 +78,11 @@ public function getOp(): ?string return $this->op; } - public function setOp(?string $op): void + public function setOp(?string $op): self { $this->op = $op; + + return $this; } public function getStatus(): ?SpanStatus @@ -86,9 +90,11 @@ public function getStatus(): ?SpanStatus return $this->status; } - public function setStatus(?SpanStatus $status): void + public function setStatus(?SpanStatus $status): self { $this->status = $status; + + return $this; } public function getParentSpanId(): ?SpanId @@ -96,9 +102,11 @@ public function getParentSpanId(): ?SpanId return $this->parentSpanId; } - public function setParentSpanId(?SpanId $parentSpanId): void + public function setParentSpanId(?SpanId $parentSpanId): self { $this->parentSpanId = $parentSpanId; + + return $this; } public function getSampled(): ?bool @@ -106,9 +114,11 @@ public function getSampled(): ?bool return $this->sampled; } - public function setSampled(?bool $sampled): void + public function setSampled(?bool $sampled): self { $this->sampled = $sampled; + + return $this; } public function getSpanId(): ?SpanId @@ -116,9 +126,11 @@ public function getSpanId(): ?SpanId return $this->spanId; } - public function setSpanId(?SpanId $spanId): void + public function setSpanId(?SpanId $spanId): self { $this->spanId = $spanId; + + return $this; } public function getTraceId(): ?TraceId @@ -126,9 +138,11 @@ public function getTraceId(): ?TraceId return $this->traceId; } - public function setTraceId(?TraceId $traceId): void + public function setTraceId(?TraceId $traceId): self { $this->traceId = $traceId; + + return $this; } /** @@ -142,9 +156,11 @@ public function getTags(): array /** * @param array $tags */ - public function setTags(array $tags): void + public function setTags(array $tags): self { $this->tags = $tags; + + return $this; } /** @@ -158,9 +174,11 @@ public function getData(): array /** * @param array $data */ - public function setData(array $data): void + public function setData(array $data): self { $this->data = $data; + + return $this; } public function getStartTimestamp(): ?float @@ -168,9 +186,11 @@ public function getStartTimestamp(): ?float return $this->startTimestamp; } - public function setStartTimestamp(?float $startTimestamp): void + public function setStartTimestamp(?float $startTimestamp): self { $this->startTimestamp = $startTimestamp; + + return $this; } public function getEndTimestamp(): ?float @@ -178,8 +198,10 @@ public function getEndTimestamp(): ?float return $this->endTimestamp; } - public function setEndTimestamp(?float $endTimestamp): void + public function setEndTimestamp(?float $endTimestamp): self { $this->endTimestamp = $endTimestamp; + + return $this; } } diff --git a/src/Tracing/SpanRecorder.php b/src/Tracing/SpanRecorder.php index 560763044..7f9f70b88 100644 --- a/src/Tracing/SpanRecorder.php +++ b/src/Tracing/SpanRecorder.php @@ -31,13 +31,15 @@ public function __construct(int $maxSpans = 1000) * Adds a span to the list of recorded spans or detaches the recorder if the * maximum number of spans to store has been reached. */ - public function add(Span $span): void + public function add(Span $span): self { if (\count($this->spans) > $this->maxSpans) { $span->detachSpanRecorder(); } else { $this->spans[] = $span; } + + return $this; } /** diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index 4df809015..4c832d369 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -104,21 +104,18 @@ public function getDynamicSamplingContext(): DynamicSamplingContext * * @param int $maxSpans The maximum number of spans that can be recorded */ - public function initSpanRecorder(int $maxSpans = 1000): void + public function initSpanRecorder(int $maxSpans = 1000): self { if ($this->spanRecorder === null) { $this->spanRecorder = new SpanRecorder($maxSpans); } $this->spanRecorder->add($this); - } - public function detachSpanRecorder(): void - { - $this->spanRecorder = null; + return $this; } - public function initProfiler(): void + public function initProfiler(): self { if ($this->profiler === null) { $client = $this->hub->getClient(); @@ -126,6 +123,8 @@ public function initProfiler(): void $this->profiler = new Profiler($options); } + + return $this; } public function getProfiler(): ?Profiler @@ -133,9 +132,11 @@ public function getProfiler(): ?Profiler return $this->profiler; } - public function detachProfiler(): void + public function detachProfiler(): self { $this->profiler = null; + + return $this; } /** diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index 4f28037fb..e452de72a 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -55,9 +55,11 @@ public function getName(): string * * @param string $name The name */ - public function setName(string $name): void + public function setName(string $name): self { $this->name = $name; + + return $this; } /** @@ -73,9 +75,11 @@ public function getParentSampled(): ?bool * * @param bool|null $parentSampled The decision */ - public function setParentSampled(?bool $parentSampled): void + public function setParentSampled(?bool $parentSampled): self { $this->parentSampled = $parentSampled; + + return $this; } /** @@ -91,9 +95,11 @@ public function getMetadata(): TransactionMetadata * * @param TransactionMetadata $metadata The transaction metadata */ - public function setMetadata(TransactionMetadata $metadata): void + public function setMetadata(TransactionMetadata $metadata): self { $this->metadata = $metadata; + + return $this; } /** @@ -101,9 +107,11 @@ public function setMetadata(TransactionMetadata $metadata): void * * @param TransactionSource $transactionSource The transaction source */ - public function setSource(TransactionSource $transactionSource): void + public function setSource(TransactionSource $transactionSource): self { $this->metadata->setSource($transactionSource); + + return $this; } /** diff --git a/src/Tracing/TransactionMetadata.php b/src/Tracing/TransactionMetadata.php index a217fc831..2a10923bc 100644 --- a/src/Tracing/TransactionMetadata.php +++ b/src/Tracing/TransactionMetadata.php @@ -49,9 +49,11 @@ public function getSamplingRate() /** * @param float|int|null $samplingRate */ - public function setSamplingRate($samplingRate): void + public function setSamplingRate($samplingRate): self { $this->samplingRate = $samplingRate; + + return $this; } public function getDynamicSamplingContext(): ?DynamicSamplingContext @@ -59,9 +61,11 @@ public function getDynamicSamplingContext(): ?DynamicSamplingContext return $this->dynamicSamplingContext; } - public function setDynamicSamplingContext(?DynamicSamplingContext $dynamicSamplingContext): void + public function setDynamicSamplingContext(?DynamicSamplingContext $dynamicSamplingContext): self { $this->dynamicSamplingContext = $dynamicSamplingContext; + + return $this; } public function getSource(): ?TransactionSource @@ -69,8 +73,10 @@ public function getSource(): ?TransactionSource return $this->source; } - public function setSource(?TransactionSource $source): void + public function setSource(?TransactionSource $source): self { $this->source = $source; + + return $this; } } diff --git a/src/UserDataBag.php b/src/UserDataBag.php index 6e7444be4..5e08dbb5b 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -130,13 +130,15 @@ public function getId() * * @param string|int|null $id The ID */ - public function setId($id): void + public function setId($id): self { if ($id !== null && !\is_string($id) && !\is_int($id)) { throw new \UnexpectedValueException(sprintf('Expected an integer or string value for the $id argument. Got: "%s".', get_debug_type($id))); } $this->id = $id; + + return $this; } /** @@ -152,9 +154,11 @@ public function getUsername(): ?string * * @param string|null $username The username */ - public function setUsername(?string $username): void + public function setUsername(?string $username): self { $this->username = $username; + + return $this; } /** @@ -170,9 +174,11 @@ public function getEmail(): ?string * * @param string|null $email The email */ - public function setEmail(?string $email): void + public function setEmail(?string $email): self { $this->email = $email; + + return $this; } /** @@ -188,9 +194,11 @@ public function getSegment(): ?string * * @param string|null $segment The segment */ - public function setSegment(?string $segment): void + public function setSegment(?string $segment): self { $this->segment = $segment; + + return $this; } /** @@ -206,13 +214,15 @@ public function getIpAddress(): ?string * * @param string|null $ipAddress The ip address */ - public function setIpAddress(?string $ipAddress): void + public function setIpAddress(?string $ipAddress): self { if ($ipAddress !== null && filter_var($ipAddress, \FILTER_VALIDATE_IP) === false) { throw new \InvalidArgumentException(sprintf('The "%s" value is not a valid IP address.', $ipAddress)); } $this->ipAddress = $ipAddress; + + return $this; } /** @@ -231,9 +241,11 @@ public function getMetadata(): array * @param string $name The name of the field * @param mixed $value The value */ - public function setMetadata(string $name, $value): void + public function setMetadata(string $name, $value): self { $this->metadata[$name] = $value; + + return $this; } /** @@ -241,9 +253,11 @@ public function setMetadata(string $name, $value): void * * @param string $name The name of the field */ - public function removeMetadata(string $name): void + public function removeMetadata(string $name): self { unset($this->metadata[$name]); + + return $this; } /** diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index f343f941e..53218169d 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -343,9 +343,9 @@ public function testTraceparentWithTracingEnabled(): void SentrySdk::setCurrentHub($hub); - $spanContext = new SpanContext(); - $spanContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); - $spanContext->setSpanId(new SpanId('566e3688a61d4bc8')); + $spanContext = (new SpanContext()) + ->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')) + ->setSpanId(new SpanId('566e3688a61d4bc8')); $span = new Span($spanContext); diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index 8244f18db..9c2387415 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -51,15 +51,15 @@ public function testStartChild(): void $spanContext2ParentSpanId = SpanId::generate(); $spanContext2TraceId = TraceId::generate(); - $spanContext1 = new SpanContext(); - $spanContext1->setSampled(false); - $spanContext1->setSpanId(SpanId::generate()); - $spanContext1->setTraceId(TraceId::generate()); - - $spanContext2 = new SpanContext(); - $spanContext2->setSampled(true); - $spanContext2->setParentSpanId($spanContext2ParentSpanId); - $spanContext2->setTraceId($spanContext2TraceId); + $spanContext1 = (new SpanContext()) + ->setSampled(false) + ->setSpanId(SpanId::generate()) + ->setTraceId(TraceId::generate()); + + $spanContext2 = (new SpanContext()) + ->setSampled(true) + ->setParentSpanId($spanContext2ParentSpanId) + ->setTraceId($spanContext2TraceId); $span1 = new Span($spanContext1); $span2 = $span1->startChild($spanContext2); diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index d66f198d7..34cfe2b0e 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -28,10 +28,10 @@ public function testFinish(): void ClockMock::withClockMock(1600640877); $expectedEventId = null; - $transactionContext = new TransactionContext(); - $transactionContext->setTags(['ios_version' => '4.0']); - $transactionContext->setSampled(true); - $transactionContext->setStartTimestamp(1600640865); + $transactionContext = (new TransactionContext()) + ->setTags(['ios_version' => '4.0']) + ->setSampled(true) + ->setStartTimestamp(1600640865); $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) From 0ed7fd9f1f9de201d5e4e1f3bd22b4d5146d71dd Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 30 Oct 2023 15:39:26 +0100 Subject: [PATCH 0914/1161] Merge master (#1608) --- CHANGELOG.md | 18 ++++++++++++++++++ src/Client.php | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb581d1b0..28d402977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # CHANGELOG +## 3.22.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.22.0. + +### Features + +- Adopt Starfish HTTP attributes in spans and breadcrumbs [(#1581)](https://github.com/getsentry/sentry-php/pull/1581) + +### Bug Fixes + +- Don't add empty HTTP fragment or query strings to breadcrumb data [(#1588)](https://github.com/getsentry/sentry-php/pull/1588) + +### Misc + +- Remove obsolete `tags` option depreaction [(#1588)](https://github.com/getsentry/sentry-php/pull/1588) +- Run CI on PHP 8.3 [(1591)](https://github.com/getsentry/sentry-php/pull/1591) +- Add support for `symfony/options-resolver: ^7.0` [(1597)](https://github.com/getsentry/sentry-php/pull/1597) + ## 3.21.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.21.0. diff --git a/src/Client.php b/src/Client.php index 2f43cff0f..f93e6210f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -34,7 +34,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.21.0'; + public const SDK_VERSION = '3.22.0'; /** * @var Options The client options From 9ed638ffb8e35ca2a10c3e0f09047082f608478b Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 30 Oct 2023 16:40:53 +0100 Subject: [PATCH 0915/1161] Cleanup the DSN (#1609) --- src/Dsn.php | 50 ++++++++--------------------------------- src/Util/Http.php | 17 ++++---------- tests/DsnTest.php | 26 --------------------- tests/Util/HttpTest.php | 10 --------- 4 files changed, 13 insertions(+), 90 deletions(-) diff --git a/src/Dsn.php b/src/Dsn.php index d3dde5044..071a6292f 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -32,11 +32,6 @@ final class Dsn implements \Stringable */ private $publicKey; - /** - * @var string|null The secret key to authenticate the SDK - */ - private $secretKey; - /** * @var string The ID of the resource to access */ @@ -50,21 +45,19 @@ final class Dsn implements \Stringable /** * Class constructor. * - * @param string $scheme The protocol to be used to access the resource - * @param string $host The host that holds the resource - * @param int $port The port on which the resource is exposed - * @param string $projectId The ID of the resource to access - * @param string $path The specific resource that the web client wants to access - * @param string $publicKey The public key to authenticate the SDK - * @param string|null $secretKey The secret key to authenticate the SDK - */ - private function __construct(string $scheme, string $host, int $port, string $projectId, string $path, string $publicKey, ?string $secretKey) + * @param string $scheme The protocol to be used to access the resource + * @param string $host The host that holds the resource + * @param int $port The port on which the resource is exposed + * @param string $projectId The ID of the resource to access + * @param string $path The specific resource that the web client wants to access + * @param string $publicKey The public key to authenticate the SDK + */ + private function __construct(string $scheme, string $host, int $port, string $projectId, string $path, string $publicKey) { $this->scheme = $scheme; $this->host = $host; $this->port = $port; $this->publicKey = $publicKey; - $this->secretKey = $secretKey; $this->path = $path; $this->projectId = $projectId; } @@ -88,10 +81,6 @@ public static function createFromString(string $value): self } } - if (isset($parsedDsn['pass']) && empty($parsedDsn['pass'])) { - throw new \InvalidArgumentException(sprintf('The "%s" DSN must contain a valid secret key.', $value)); - } - if (!\in_array($parsedDsn['scheme'], ['http', 'https'], true)) { throw new \InvalidArgumentException(sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value)); } @@ -111,8 +100,7 @@ public static function createFromString(string $value): self $parsedDsn['port'] ?? ($parsedDsn['scheme'] === 'http' ? 80 : 443), $projectId, $path, - $parsedDsn['user'], - $parsedDsn['pass'] ?? null + $parsedDsn['user'] ); } @@ -164,22 +152,6 @@ public function getPublicKey(): string return $this->publicKey; } - /** - * Gets the secret key to authenticate the SDK. - */ - public function getSecretKey(): ?string - { - return $this->secretKey; - } - - /** - * Returns the URL of the API for the store endpoint. - */ - public function getStoreApiEndpointUrl(): string - { - return $this->getBaseEndpointUrl() . '/store/'; - } - /** * Returns the URL of the API for the envelope endpoint. */ @@ -203,10 +175,6 @@ public function __toString(): string { $url = $this->scheme . '://' . $this->publicKey; - if ($this->secretKey !== null) { - $url .= ':' . $this->secretKey; - } - $url .= '@' . $this->host; if (($this->scheme === 'http' && $this->port !== 80) || ($this->scheme === 'https' && $this->port !== 443)) { diff --git a/src/Util/Http.php b/src/Util/Http.php index a9ffc27ab..9bfdd5d1e 100644 --- a/src/Util/Http.php +++ b/src/Util/Http.php @@ -17,21 +17,12 @@ final class Http */ public static function getRequestHeaders(Dsn $dsn, string $sdkIdentifier, string $sdkVersion): array { - $data = [ - 'sentry_version' => Client::PROTOCOL_VERSION, - 'sentry_client' => $sdkIdentifier . '/' . $sdkVersion, - 'sentry_key' => $dsn->getPublicKey(), + $authHeader = [ + 'sentry_version=' . Client::PROTOCOL_VERSION, + 'sentry_client=' . $sdkIdentifier . '/' . $sdkVersion, + 'sentry_key=' . $dsn->getPublicKey(), ]; - if ($dsn->getSecretKey() !== null) { - $data['sentry_secret'] = $dsn->getSecretKey(); - } - - $authHeader = []; - foreach ($data as $headerKey => $headerValue) { - $authHeader[] = $headerKey . '=' . $headerValue; - } - return [ 'Content-Type' => 'application/x-sentry-envelope', 'X-Sentry-Auth' => 'Sentry ' . implode(', ', $authHeader), diff --git a/tests/DsnTest.php b/tests/DsnTest.php index 3ea095356..724d00538 100644 --- a/tests/DsnTest.php +++ b/tests/DsnTest.php @@ -21,7 +21,6 @@ public function testCreateFromString( string $expectedHost, int $expectedPort, string $expectedPublicKey, - ?string $expectedSecretKey, string $expectedProjectId, string $expectedPath ): void { @@ -31,7 +30,6 @@ public function testCreateFromString( $this->assertSame($expectedHost, $dsn->getHost()); $this->assertSame($expectedPort, $dsn->getPort()); $this->assertSame($expectedPublicKey, $dsn->getPublicKey()); - $this->assertSame($expectedSecretKey, $dsn->getSecretKey()); $this->assertSame($expectedProjectId, $dsn->getProjectId(true)); $this->assertSame($expectedPath, $dsn->getPath()); } @@ -44,7 +42,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 80, 'public', - null, '1', '/sentry', ]; @@ -55,7 +52,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 80, 'public', - null, '1', '', ]; @@ -66,7 +62,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 80, 'public', - 'secret', '1', '', ]; @@ -77,7 +72,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 80, 'public', - null, '1', '', ]; @@ -88,7 +82,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 8080, 'public', - null, '1', '', ]; @@ -99,7 +92,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 443, 'public', - null, '1', '', ]; @@ -110,7 +102,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 443, 'public', - null, '1', '', ]; @@ -121,7 +112,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 4343, 'public', - null, '1', '', ]; @@ -155,11 +145,6 @@ public static function createFromStringThrowsExceptionIfValueIsInvalidDataProvid 'The "http://:secret@example.com/sentry/1" DSN must contain a scheme, a host, a user and a path component.', ]; - yield 'missing secret key' => [ - 'http://public:@example.com/sentry/1', - 'The "http://public:@example.com/sentry/1" DSN must contain a valid secret key.', - ]; - yield 'missing host' => [ '/sentry/1', 'The "/sentry/1" DSN must contain a scheme, a host, a user and a path component.', @@ -176,16 +161,6 @@ public static function createFromStringThrowsExceptionIfValueIsInvalidDataProvid ]; } - /** - * @dataProvider getStoreApiEndpointUrlDataProvider - */ - public function testGetStoreApiEndpointUrl(string $value, string $expectedUrl): void - { - $dsn = Dsn::createFromString($value); - - $this->assertSame($expectedUrl, $dsn->getStoreApiEndpointUrl()); - } - public static function getStoreApiEndpointUrlDataProvider(): \Generator { yield [ @@ -264,7 +239,6 @@ public static function toStringDataProvider(): array { return [ ['http://public@example.com/sentry/1'], - ['http://public:secret@example.com/sentry/1'], ['http://public@example.com/1'], ['http://public@example.com:8080/sentry/1'], ['https://public@example.com/sentry/1'], diff --git a/tests/Util/HttpTest.php b/tests/Util/HttpTest.php index e5566d6e2..51a6a650a 100644 --- a/tests/Util/HttpTest.php +++ b/tests/Util/HttpTest.php @@ -29,16 +29,6 @@ public static function getRequestHeadersDataProvider(): \Generator 'X-Sentry-Auth' => 'Sentry sentry_version=7, sentry_client=sentry.sdk.identifier/1.2.3, sentry_key=public', ], ]; - - yield [ - Dsn::createFromString('http://public:secret@example.com/1'), - 'sentry.sdk.identifier', - '1.2.3', - [ - 'Content-Type' => 'application/x-sentry-envelope', - 'X-Sentry-Auth' => 'Sentry sentry_version=7, sentry_client=sentry.sdk.identifier/1.2.3, sentry_key=public, sentry_secret=secret', - ], - ]; } /** From 7a6f7ca89c23422b9c243a83aded50834737c267 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Oct 2023 10:54:57 +0100 Subject: [PATCH 0916/1161] Update interfaces (#1611) --- src/ClientInterface.php | 18 ++++++++++-------- src/State/HubInterface.php | 38 +++++++------------------------------- 2 files changed, 17 insertions(+), 39 deletions(-) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 33af1ff4e..0aab383a4 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -8,14 +8,6 @@ use Sentry\State\Scope; use Sentry\Transport\Result; -/** - * This interface must be implemented by all Raven client classes. - * - * @method StacktraceBuilder getStacktraceBuilder() Returns the stacktrace builder of the client. - * @method string|null getCspReportUrl() Returns an URL for security policy reporting that's generated from the given DSN - * - * @author Stefano Arlandini - */ interface ClientInterface { /** @@ -23,6 +15,11 @@ interface ClientInterface */ public function getOptions(): Options; + /** + * Returns an URL for security policy reporting that's generated from the configured DSN. + */ + public function getCspReportUrl(): ?string; + /** * Logs a message. * @@ -79,4 +76,9 @@ public function getIntegration(string $className): ?IntegrationInterface; * @param int|null $timeout Maximum time in seconds the client should wait */ public function flush(?int $timeout = null): Result; + + /** + * Returns the stacktrace builder of the client. + */ + public function getStacktraceBuilder(): StacktraceBuilder; } diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index ad5b8aa63..44d9e70e3 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -13,18 +13,10 @@ use Sentry\Integration\IntegrationInterface; use Sentry\MonitorConfig; use Sentry\Severity; -use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; -/** - * This interface represent the class which is responsible for maintaining a - * stack of pairs of clients and scopes. It is the main entry point to talk - * with the Sentry client. - * - * @method string|null captureCheckIn(string $slug, CheckInStatus $status, int|float|null $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null) Captures a check-in - */ interface HubInterface { /** @@ -72,47 +64,31 @@ public function withScope(callable $callback); /** * Calls the given callback passing to it the current scope so that any * operation can be run within its context. - * - * @param callable $callback The callback to be executed */ public function configureScope(callable $callback): void; /** * Binds the given client to the current scope. - * - * @param ClientInterface $client The client */ public function bindClient(ClientInterface $client): void; /** * Captures a message event and sends it to Sentry. - * - * @param string $message The message - * @param Severity|null $level The severity level of the message - * @param EventHint|null $hint Object that can contain additional information about the event */ public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId; /** * Captures an exception event and sends it to Sentry. - * - * @param \Throwable $exception The exception - * @param EventHint|null $hint Object that can contain additional information about the event */ public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId; /** * Captures a new event using the provided data. - * - * @param Event $event The event being captured - * @param EventHint|null $hint May contain additional information about the event */ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId; /** * Captures an event that logs the last occurred error. - * - * @param EventHint|null $hint Object that can contain additional information about the event */ public function captureLastError(?EventHint $hint = null): ?EventId; @@ -120,13 +96,16 @@ public function captureLastError(?EventHint $hint = null): ?EventId; * Records a new breadcrumb which will be attached to future events. They * will be added to subsequent events to provide more context on user's * actions prior to an error or crash. - * - * @param Breadcrumb $breadcrumb The breadcrumb to record - * - * @return bool Whether the breadcrumb was actually added to the current scope */ public function addBreadcrumb(Breadcrumb $breadcrumb): bool; + /** + * Captures a check-in. + * + * @param int|float|null $duration + */ + public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string; + /** * Gets the integration whose FQCN matches the given one if it's available on the current client. * @@ -155,7 +134,6 @@ public function getIntegration(string $className): ?IntegrationInterface; * which point the transaction with all its finished child spans will be sent to * Sentry. * - * @param TransactionContext $context Properties of the new transaction * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction; @@ -172,8 +150,6 @@ public function getSpan(): ?Span; /** * Sets the span on the Hub. - * - * @param Span|null $span The span */ public function setSpan(?Span $span): HubInterface; } From 1c7b485dc936af99848fe8156f03a60a9c8891d2 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Oct 2023 10:55:13 +0100 Subject: [PATCH 0917/1161] Refactor the Payloadseralizer (#1612) --- src/Serializer/PayloadSerializer.php | 30 +- tests/Serializer/PayloadSerializerTest.php | 627 ++------------------- 2 files changed, 34 insertions(+), 623 deletions(-) diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 5c8d444b2..0fdb2a320 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -53,15 +53,7 @@ public function serialize(Event $event): string return $transactionEnvelope; } - if (EventType::checkIn() === $event->getType()) { - return $this->serializeAsEnvelope($event); - } - - if ($this->options->isTracingEnabled()) { - return $this->serializeAsEnvelope($event); - } - - return $this->serializeAsEvent($event); + return $this->serializeAsEnvelope($event); } private function serializeAsEvent(Event $event): string @@ -227,26 +219,6 @@ public function toArray(Event $event): array } } - /** - * In case of error events, with tracing being disabled, we set the Replay ID - * as a context into the payload. - */ - if ( - EventType::event() === $event->getType() - && !$this->options->isTracingEnabled() - ) { - $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); - if ($dynamicSamplingContext instanceof DynamicSamplingContext) { - $replayId = $dynamicSamplingContext->get('replay_id'); - - if ($replayId !== null) { - $result['contexts']['replay'] = [ - 'replay_id' => $replayId, - ]; - } - } - } - $stacktrace = $event->getStacktrace(); if ($stacktrace !== null) { diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index ccf7da096..5e149f7b2 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -13,7 +13,6 @@ use Sentry\Context\RuntimeContext; use Sentry\Event; use Sentry\EventId; -use Sentry\EventType; use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; @@ -39,34 +38,6 @@ */ final class PayloadSerializerTest extends TestCase { - /** - * @dataProvider serializeAsJsonDataProvider - */ - public function testSerializeAsJson(Event $event, string $expectedResult, bool $isOutputJson): void - { - ClockMock::withClockMock(1597790835); - - $serializer = new PayloadSerializer(new Options([ - 'dsn' => 'http://public@example.com/sentry/1', - ])); - - $result = $serializer->serialize($event); - - if ( - EventType::transaction() !== $event->getType() - && EventType::checkIn() !== $event->getType() - ) { - $resultArray = $serializer->toArray($event); - $this->assertJsonStringEqualsJsonString($result, json_encode($resultArray)); - } - - if ($isOutputJson) { - $this->assertJsonStringEqualsJsonString($expectedResult, $result); - } else { - $this->assertSame($expectedResult, $result); - } - } - /** * @dataProvider serializeAsEnvelopeDataProvider */ @@ -76,7 +47,6 @@ public function testSerializeAsEnvelope(Event $event, string $expectedResult): v $serializer = new PayloadSerializer(new Options([ 'dsn' => 'http://public@example.com/sentry/1', - 'enable_tracing' => true, ])); $result = $serializer->serialize($event); @@ -84,7 +54,7 @@ public function testSerializeAsEnvelope(Event $event, string $expectedResult): v $this->assertSame($expectedResult, $result); } - public static function serializeAsJsonDataProvider(): iterable + public static function serializeAsEnvelopeDataProvider(): iterable { ClockMock::withClockMock(1597790835); @@ -92,19 +62,12 @@ public static function serializeAsJsonDataProvider(): iterable yield [ Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), - << '/login', 'to' => '/dashboard']), ]); - $event->setSdkMetadata('dynamic_sampling_context', DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-replay_id=12312012123120121231201212312012')); - $event->setUser(UserDataBag::createFromArray([ 'id' => 'unique_id', 'username' => 'my_user', @@ -205,154 +166,11 @@ public static function serializeAsJsonDataProvider(): iterable yield [ $event, - <</", - "server_name": "foo.example.com", - "release": "721e41770371db95eee98ca2707686226b993eda", - "environment": "production", - "fingerprint": [ - "myrpc", - "POST", - "/foo.bar" - ], - "modules": { - "my.module.name": "1.0" - }, - "extra": { - "my_key": 1, - "some_other_value": "foo bar" - }, - "tags": { - "ios_version": "4.0", - "context": "production" - }, - "user": { - "id": "unique_id", - "username": "my_user", - "email": "foo@example.com", - "ip_address": "127.0.0.1", - "segment": "my_segment" - }, - "contexts": { - "os": { - "name": "Linux", - "version": "4.19.104-microsoft-standard", - "build": "#1 SMP Wed Feb 19 06:37:35 UTC 2020", - "kernel_version": "Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64" - }, - "runtime": { - "name": "php", - "version": "7.4.3" - }, - "electron": { - "type": "runtime", - "name": "Electron", - "version": "4.0" - }, - "replay": { - "replay_id": "12312012123120121231201212312012" - } - }, - "breadcrumbs": { - "values": [ - { - "type": "user", - "category": "log", - "level": "info", - "timestamp": 1597790835 - }, - { - "type": "navigation", - "category": "log", - "level": "info", - "timestamp": 1597790835, - "data": { - "from": "/login", - "to": "/dashboard" - } - } - ] - }, - "request": { - "method": "POST", - "url": "http://absolute.uri/foo", - "query_string": "query=foobar&page=2", - "data": { - "foo": "bar" - }, - "cookies": { - "PHPSESSID": "298zf09hf012fh2" - }, - "headers": { - "content-type": "text/html" - }, - "env": { - "REMOTE_ADDR": "127.0.0.1" - } - }, - "exception": { - "values": [ - { - "type": "Exception", - "value": "chained exception", - "stacktrace": { - "frames": [ - { - "filename": "file/name.py", - "lineno": 3, - "in_app": true - }, - { - "filename": "file/name.py", - "lineno": 3, - "in_app": false, - "abs_path": "absolute/file/name.py", - "function": "myfunction", - "raw_function": "raw_function_name", - "pre_context": [ - "def foo():", - " my_var = 'foo'" - ], - "context_line": " raise ValueError()", - "post_context": [ - "", - "def main():" - ], - "vars": { - "my_var": "value" - } - } - ] - }, - "mechanism": { - "type": "generic", - "handled": true, - "data": { - "code": 123 - } - } - }, - { - "type": "Exception", - "value": "initial exception" - } - ] - } -} -JSON - , - true, + <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} +TEXT ]; $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); @@ -360,20 +178,12 @@ public static function serializeAsJsonDataProvider(): iterable yield [ $event, - <<setCheckIn($checkIn); - $event->setContext('trace', [ - 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', - 'span_id' => '5dd538dc297544cc', - ]); yield [ $event, <<setCheckIn($checkIn); - $event->setContext('trace', [ - 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', - 'span_id' => '5dd538dc297544cc', - ]); yield [ $event, <<setLevel(Severity::error()); - $event->setLogger('app.php'); - $event->setTransaction('/users//'); - $event->setServerName('foo.example.com'); - $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); - $event->setEnvironment('production'); - $event->setFingerprint(['myrpc', 'POST', '/foo.bar']); - $event->setModules(['my.module.name' => '1.0']); - $event->setStartTimestamp(1597790835); - $event->setBreadcrumb([ - new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'log'), - new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_NAVIGATION, 'log', null, ['from' => '/login', 'to' => '/dashboard']), - ]); - - $event->setUser(UserDataBag::createFromArray([ - 'id' => 'unique_id', - 'username' => 'my_user', - 'email' => 'foo@example.com', - 'ip_address' => '127.0.0.1', - 'segment' => 'my_segment', - ])); - - $event->setTags([ - 'ios_version' => '4.0', - 'context' => 'production', - ]); - - $event->setExtra([ - 'my_key' => 1, - 'some_other_value' => 'foo bar', - ]); - - $event->setRequest([ - 'method' => 'POST', - 'url' => 'http://absolute.uri/foo', - 'query_string' => 'query=foobar&page=2', - 'data' => [ - 'foo' => 'bar', - ], - 'cookies' => [ - 'PHPSESSID' => '298zf09hf012fh2', - ], - 'headers' => [ - 'content-type' => 'text/html', - ], - 'env' => [ - 'REMOTE_ADDR' => '127.0.0.1', - ], - ]); - - $event->setOsContext(new OsContext( - 'Linux', - '4.19.104-microsoft-standard', - '#1 SMP Wed Feb 19 06:37:35 UTC 2020', - 'Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64' - )); - - $event->setRuntimeContext(new RuntimeContext( - 'php', - '7.4.3' - )); - - $event->setContext('electron', [ - 'type' => 'runtime', - 'name' => 'Electron', - 'version' => '4.0', - ]); - - $frame1 = new Frame(null, 'file/name.py', 3); - $frame2 = new Frame('myfunction', 'file/name.py', 3, 'raw_function_name', 'absolute/file/name.py', ['my_var' => 'value'], false); - $frame2->setContextLine(' raise ValueError()'); - $frame2->setPreContext([ - 'def foo():', - ' my_var = \'foo\'', - ]); - - $frame2->setPostContext([ - '', - 'def main():', - ]); - - $event->setExceptions([ - new ExceptionDataBag(new \Exception('initial exception')), - new ExceptionDataBag( - new \Exception('chained exception'), - new Stacktrace([ - $frame1, - $frame2, - ]), - new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 123]) - ), - ]); - - yield [ - $event, - <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} -TEXT - ]; - - $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); - $event->setMessage('My raw message with interpreted strings like this', []); - - yield [ - $event, - <<setMessage('My raw message with interpreted strings like %s', ['this']); - - yield [ - $event, - <<setMessage('My raw message with interpreted strings like %s', ['this'], 'My raw message with interpreted strings like that'); - - yield [ - $event, - <<setSpanId(new SpanId('5dd538dc297544cc')); - $span1->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); - - $span2 = new Span(); - $span2->setSpanId(new SpanId('b01b9f6349558cd1')); - $span2->setParentSpanId(new SpanId('b0e6f15b45c36b12')); - $span2->setTraceId(new TraceId('1e57b752bc6e4544bbaa246cd1d05dee')); - $span2->setOp('http'); - $span2->setDescription('GET /sockjs-node/info'); - $span2->setStatus(SpanStatus::ok()); - $span2->setStartTimestamp(1597790835); - $span2->setTags(['http.status_code' => '200']); - $span2->setData([ - 'url' => 'http://localhost:8080/sockjs-node/info?t=1588601703755', - 'status_code' => 200, - 'type' => 'xhr', - 'method' => 'GET', - ]); - - $span2->finish(1598659060); - - $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); - $event->setSpans([$span1, $span2]); - $event->setRelease('1.0.0'); - $event->setEnvironment('dev'); - $event->setTransaction('GET /'); - $event->setContext('trace', [ - 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', - 'span_id' => '5dd538dc297544cc', - ]); - $event->setRuntimeContext(new RuntimeContext( - 'php', - '8.2.3' - )); - $event->setOsContext(new OsContext( - 'macOS', - '13.2.1', - '22D68', - 'Darwin Kernel Version 22.2.0', - 'aarch64' - )); - - $excimerLog = [ - [ - 'trace' => [ - [ - 'file' => '/var/www/html/index.php', - 'line' => 42, - ], - ], - 'timestamp' => 0.001, - ], - [ - 'trace' => [ - [ - 'file' => '/var/www/html/index.php', - 'line' => 42, - ], - [ - 'class' => 'Function', - 'function' => 'doStuff', - 'file' => '/var/www/html/function.php', - 'line' => 84, - ], - ], - 'timestamp' => 0.002, - ], - ]; - - $profile = new Profile(); - // 2022-02-28T09:41:00Z - $profile->setStartTimeStamp(1677573660.0000); - $profile->setExcimerLog($excimerLog); - $profile->setEventId($event->getId()); - - $event->setSdkMetadata('profile', $profile); - - yield [ - $event, - <<setSdkMetadata('dynamic_sampling_context', DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-sample_rate=1')); - $event->setSdkMetadata('transaction_metadata', new TransactionMetadata()); - - yield [ - $event, - <<setStacktrace(new Stacktrace([new Frame(null, '', 0)])); - - yield [ - $event, - <<setCheckIn($checkIn); - - yield [ - $event, - <<setCheckIn($checkIn); - - yield [ - $event, - << Date: Tue, 31 Oct 2023 12:28:40 +0100 Subject: [PATCH 0918/1161] Refactor `tracePropagationTargets` (#1614) --- src/Options.php | 2 +- src/Tracing/GuzzleTracingMiddleware.php | 8 ++------ tests/Tracing/GuzzleTracingMiddlewareTest.php | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Options.php b/src/Options.php index e520e1a2f..dda20c051 100644 --- a/src/Options.php +++ b/src/Options.php @@ -1009,7 +1009,7 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send_transaction' => static function (Event $transaction): Event { return $transaction; }, - 'trace_propagation_targets' => [], + 'trace_propagation_targets' => null, 'tags' => [], 'error_types' => null, 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 132276747..10690abb0 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -125,12 +125,8 @@ private static function shouldAttachTracingHeaders(?ClientInterface $client, Req // Check if the request destination is allow listed in the trace_propagation_targets option. if ( - $sdkOptions->getTracePropagationTargets() !== null - // Due to BC, we treat an empty array (the default) as all hosts are allow listed - && ( - $sdkOptions->getTracePropagationTargets() === [] - || \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()) - ) + $sdkOptions->getTracePropagationTargets() === null + || \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()) ) { return true; } diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 03010e950..54934cb43 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -141,7 +141,7 @@ public static function traceHeadersDataProvider(): iterable new Request('GET', 'https://www.example.com'), new Options([ 'traces_sample_rate' => 1, - 'trace_propagation_targets' => [], + 'trace_propagation_targets' => null, ]), true, ]; @@ -161,7 +161,7 @@ public static function traceHeadersDataProvider(): iterable new Request('GET', 'https://www.example.com'), new Options([ 'traces_sample_rate' => 1, - 'trace_propagation_targets' => null, + 'trace_propagation_targets' => [], ]), false, ]; From 6ba2a2c228c689c7d87b4490f006c0ab05151f77 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Oct 2023 12:36:00 +0100 Subject: [PATCH 0919/1161] New seralizer logic (#1613) --- src/Serializer/EnvelopItems/CheckInItem.php | 46 ++ .../EnvelopItems/EnvelopeItemInterface.php | 15 + src/Serializer/EnvelopItems/EventItem.php | 243 ++++++++++ src/Serializer/EnvelopItems/ProfileItem.php | 35 ++ .../EnvelopItems/TransactionItem.php | 181 +++++++ src/Serializer/PayloadSerializer.php | 442 +----------------- .../Traits/BreadcrumbSeralizerTrait.php | 45 ++ tests/Serializer/PayloadSerializerTest.php | 17 +- 8 files changed, 598 insertions(+), 426 deletions(-) create mode 100644 src/Serializer/EnvelopItems/CheckInItem.php create mode 100644 src/Serializer/EnvelopItems/EnvelopeItemInterface.php create mode 100644 src/Serializer/EnvelopItems/EventItem.php create mode 100644 src/Serializer/EnvelopItems/ProfileItem.php create mode 100644 src/Serializer/EnvelopItems/TransactionItem.php create mode 100644 src/Serializer/Traits/BreadcrumbSeralizerTrait.php diff --git a/src/Serializer/EnvelopItems/CheckInItem.php b/src/Serializer/EnvelopItems/CheckInItem.php new file mode 100644 index 000000000..e3e2778bf --- /dev/null +++ b/src/Serializer/EnvelopItems/CheckInItem.php @@ -0,0 +1,46 @@ + (string) $event->getType(), + 'content_type' => 'application/json', + ]; + + $payload = []; + + $checkIn = $event->getCheckIn(); + if ($checkIn !== null) { + $payload = [ + 'check_in_id' => $checkIn->getId(), + 'monitor_slug' => $checkIn->getMonitorSlug(), + 'status' => (string) $checkIn->getStatus(), + 'duration' => $checkIn->getDuration(), + 'release' => $checkIn->getRelease(), + 'environment' => $checkIn->getEnvironment(), + ]; + + if ($checkIn->getMonitorConfig() !== null) { + $payload['monitor_config'] = $checkIn->getMonitorConfig()->toArray(); + } + + if (!empty($event->getContexts()['trace'])) { + $payload['contexts']['trace'] = $event->getContexts()['trace']; + } + } + + return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + } +} diff --git a/src/Serializer/EnvelopItems/EnvelopeItemInterface.php b/src/Serializer/EnvelopItems/EnvelopeItemInterface.php new file mode 100644 index 000000000..d2b7d3712 --- /dev/null +++ b/src/Serializer/EnvelopItems/EnvelopeItemInterface.php @@ -0,0 +1,15 @@ + (string) $event->getType(), + 'content_type' => 'application/json', + ]; + + $payload = [ + 'timestamp' => $event->getTimestamp(), + 'platform' => 'php', + 'sdk' => [ + 'name' => $event->getSdkIdentifier(), + 'version' => $event->getSdkVersion(), + ], + ]; + + if ($event->getStartTimestamp() !== null) { + $payload['start_timestamp'] = $event->getStartTimestamp(); + } + + if ($event->getLevel() !== null) { + $payload['level'] = (string) $event->getLevel(); + } + + if ($event->getTransaction() !== null) { + $payload['transaction'] = $event->getTransaction(); + } + + if ($event->getServerName() !== null) { + $payload['server_name'] = $event->getServerName(); + } + + if ($event->getRelease() !== null) { + $payload['release'] = $event->getRelease(); + } + + if ($event->getEnvironment() !== null) { + $payload['environment'] = $event->getEnvironment(); + } + + if (!empty($event->getFingerprint())) { + $payload['fingerprint'] = $event->getFingerprint(); + } + + if (!empty($event->getModules())) { + $payload['modules'] = $event->getModules(); + } + + if (!empty($event->getExtra())) { + $payload['extra'] = $event->getExtra(); + } + + if (!empty($event->getTags())) { + $payload['tags'] = $event->getTags(); + } + + $user = $event->getUser(); + if ($user !== null) { + $payload['user'] = array_merge($user->getMetadata(), [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + 'ip_address' => $user->getIpAddress(), + 'segment' => $user->getSegment(), + ]); + } + + $osContext = $event->getOsContext(); + if ($osContext !== null) { + $payload['contexts']['os'] = [ + 'name' => $osContext->getName(), + 'version' => $osContext->getVersion(), + 'build' => $osContext->getBuild(), + 'kernel_version' => $osContext->getKernelVersion(), + ]; + } + + $runtimeContext = $event->getRuntimeContext(); + if ($runtimeContext !== null) { + $payload['contexts']['runtime'] = [ + 'name' => $runtimeContext->getName(), + 'version' => $runtimeContext->getVersion(), + ]; + } + + if (!empty($event->getContexts())) { + $payload['contexts'] = array_merge($payload['contexts'] ?? [], $event->getContexts()); + } + + if (!empty($event->getBreadcrumbs())) { + $payload['breadcrumbs']['values'] = array_map([self::class, 'serializeBreadcrumb'], $event->getBreadcrumbs()); + } + + if (!empty($event->getRequest())) { + $payload['request'] = $event->getRequest(); + } + + if ($event->getMessage() !== null) { + if (empty($event->getMessageParams())) { + $payload['message'] = $event->getMessage(); + } else { + $payload['message'] = [ + 'message' => $event->getMessage(), + 'params' => $event->getMessageParams(), + 'formatted' => $event->getMessageFormatted() ?? vsprintf($event->getMessage(), $event->getMessageParams()), + ]; + } + } + + $exceptions = $event->getExceptions(); + for ($i = \count($exceptions) - 1; $i >= 0; --$i) { + $payload['exception']['values'][] = self::serializeException($exceptions[$i]); + } + + $stacktrace = $event->getStacktrace(); + if ($stacktrace !== null) { + $payload['stacktrace'] = [ + 'frames' => array_map([self::class, 'serializeStacktraceFrame'], $stacktrace->getFrames()), + ]; + } + + return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + } + + /** + * @return array + * + * @psalm-return array{ + * type: string, + * value: string, + * stacktrace?: array{ + * frames: array> + * }, + * mechanism?: array{ + * type: string, + * handled: boolean, + * data?: array + * } + * } + */ + protected static function serializeException(ExceptionDataBag $exception): array + { + $exceptionMechanism = $exception->getMechanism(); + $exceptionStacktrace = $exception->getStacktrace(); + $result = [ + 'type' => $exception->getType(), + 'value' => $exception->getValue(), + ]; + + if ($exceptionStacktrace !== null) { + $result['stacktrace'] = [ + 'frames' => array_map([self::class, 'serializeStacktraceFrame'], $exceptionStacktrace->getFrames()), + ]; + } + + if ($exceptionMechanism !== null) { + $result['mechanism'] = [ + 'type' => $exceptionMechanism->getType(), + 'handled' => $exceptionMechanism->isHandled(), + ]; + + if ($exceptionMechanism->getData() !== []) { + $result['mechanism']['data'] = $exceptionMechanism->getData(); + } + } + + return $result; + } + + /** + * @return array + * + * @psalm-return array{ + * filename: string, + * lineno: int, + * in_app: bool, + * abs_path?: string, + * function?: string, + * raw_function?: string, + * pre_context?: string[], + * context_line?: string, + * post_context?: string[], + * vars?: array + * } + */ + protected static function serializeStacktraceFrame(Frame $frame): array + { + $result = [ + 'filename' => $frame->getFile(), + 'lineno' => $frame->getLine(), + 'in_app' => $frame->isInApp(), + ]; + + if ($frame->getAbsoluteFilePath() !== null) { + $result['abs_path'] = $frame->getAbsoluteFilePath(); + } + + if ($frame->getFunctionName() !== null) { + $result['function'] = $frame->getFunctionName(); + } + + if ($frame->getRawFunctionName() !== null) { + $result['raw_function'] = $frame->getRawFunctionName(); + } + + if (!empty($frame->getPreContext())) { + $result['pre_context'] = $frame->getPreContext(); + } + + if ($frame->getContextLine() !== null) { + $result['context_line'] = $frame->getContextLine(); + } + + if (!empty($frame->getPostContext())) { + $result['post_context'] = $frame->getPostContext(); + } + + if (!empty($frame->getVars())) { + $result['vars'] = $frame->getVars(); + } + + return $result; + } +} diff --git a/src/Serializer/EnvelopItems/ProfileItem.php b/src/Serializer/EnvelopItems/ProfileItem.php new file mode 100644 index 000000000..90fdca9ec --- /dev/null +++ b/src/Serializer/EnvelopItems/ProfileItem.php @@ -0,0 +1,35 @@ + 'profile', + 'content_type' => 'application/json', + ]; + + $profile = $event->getSdkMetadata('profile'); + if (!$profile instanceof Profile) { + return ''; + } + + $payload = $profile->getFormattedData($event); + if ($payload === null) { + return ''; + } + + return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + } +} diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php new file mode 100644 index 000000000..f44dc38d0 --- /dev/null +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -0,0 +1,181 @@ + (string) $event->getType(), + 'content_type' => 'application/json', + ]; + + $payload = [ + 'timestamp' => $event->getTimestamp(), + 'platform' => 'php', + 'sdk' => [ + 'name' => $event->getSdkIdentifier(), + 'version' => $event->getSdkVersion(), + ], + ]; + + if ($event->getStartTimestamp() !== null) { + $payload['start_timestamp'] = $event->getStartTimestamp(); + } + + if ($event->getLevel() !== null) { + $payload['level'] = (string) $event->getLevel(); + } + + if ($event->getTransaction() !== null) { + $payload['transaction'] = $event->getTransaction(); + } + + if ($event->getServerName() !== null) { + $payload['server_name'] = $event->getServerName(); + } + + if ($event->getRelease() !== null) { + $payload['release'] = $event->getRelease(); + } + + if ($event->getEnvironment() !== null) { + $payload['environment'] = $event->getEnvironment(); + } + + if (!empty($event->getFingerprint())) { + $payload['fingerprint'] = $event->getFingerprint(); + } + + if (!empty($event->getModules())) { + $payload['modules'] = $event->getModules(); + } + + if (!empty($event->getExtra())) { + $payload['extra'] = $event->getExtra(); + } + + if (!empty($event->getTags())) { + $payload['tags'] = $event->getTags(); + } + + $user = $event->getUser(); + if ($user !== null) { + $payload['user'] = array_merge($user->getMetadata(), [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + 'ip_address' => $user->getIpAddress(), + 'segment' => $user->getSegment(), + ]); + } + + $osContext = $event->getOsContext(); + if ($osContext !== null) { + $payload['contexts']['os'] = [ + 'name' => $osContext->getName(), + 'version' => $osContext->getVersion(), + 'build' => $osContext->getBuild(), + 'kernel_version' => $osContext->getKernelVersion(), + ]; + } + + $runtimeContext = $event->getRuntimeContext(); + if ($runtimeContext !== null) { + $payload['contexts']['runtime'] = [ + 'name' => $runtimeContext->getName(), + 'version' => $runtimeContext->getVersion(), + ]; + } + + if (!empty($event->getContexts())) { + $payload['contexts'] = array_merge($payload['contexts'] ?? [], $event->getContexts()); + } + + if (!empty($event->getBreadcrumbs())) { + $payload['breadcrumbs']['values'] = array_map([self::class, 'serializeBreadcrumb'], $event->getBreadcrumbs()); + } + + if (!empty($event->getRequest())) { + $payload['request'] = $event->getRequest(); + } + + $payload['spans'] = array_values(array_map([self::class, 'serializeSpan'], $event->getSpans())); + + $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); + if ($transactionMetadata instanceof TransactionMetadata) { + $payload['transaction_info']['source'] = (string) $transactionMetadata->getSource(); + } + + return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + } + + /** + * @return array + * + * @psalm-return array{ + * span_id: string, + * trace_id: string, + * parent_span_id?: string, + * start_timestamp: float, + * timestamp?: float, + * status?: string, + * description?: string, + * op?: string, + * data?: array, + * tags?: array + * } + */ + protected static function serializeSpan(Span $span): array + { + $result = [ + 'span_id' => (string) $span->getSpanId(), + 'trace_id' => (string) $span->getTraceId(), + 'start_timestamp' => $span->getStartTimestamp(), + ]; + + if ($span->getParentSpanId() !== null) { + $result['parent_span_id'] = (string) $span->getParentSpanId(); + } + + if ($span->getEndTimestamp() !== null) { + $result['timestamp'] = $span->getEndTimestamp(); + } + + if ($span->getStatus() !== null) { + $result['status'] = (string) $span->getStatus(); + } + + if ($span->getDescription() !== null) { + $result['description'] = $span->getDescription(); + } + + if ($span->getOp() !== null) { + $result['op'] = $span->getOp(); + } + + if (!empty($span->getData())) { + $result['data'] = $span->getData(); + } + + if (!empty($span->getTags())) { + $result['tags'] = $span->getTags(); + } + + return $result; + } +} diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 0fdb2a320..ad1365b3a 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -4,16 +4,14 @@ namespace Sentry\Serializer; -use Sentry\Breadcrumb; use Sentry\Event; use Sentry\EventType; -use Sentry\ExceptionDataBag; -use Sentry\Frame; use Sentry\Options; -use Sentry\Profiling\Profile; +use Sentry\Serializer\EnvelopItems\CheckInItem; +use Sentry\Serializer\EnvelopItems\EventItem; +use Sentry\Serializer\EnvelopItems\ProfileItem; +use Sentry\Serializer\EnvelopItems\TransactionItem; use Sentry\Tracing\DynamicSamplingContext; -use Sentry\Tracing\Span; -use Sentry\Tracing\TransactionMetadata; use Sentry\Util\JSON; /** @@ -38,199 +36,6 @@ public function __construct(Options $options) * {@inheritdoc} */ public function serialize(Event $event): string - { - if (EventType::transaction() === $event->getType()) { - $transactionEnvelope = $this->serializeAsEnvelope($event); - - // Attach a new envelope item containing the profile data - if ($event->getSdkMetadata('profile') !== null) { - $profileEnvelope = $this->seralizeProfileAsEnvelope($event); - if ($profileEnvelope !== null) { - return sprintf("%s\n%s", $transactionEnvelope, $profileEnvelope); - } - } - - return $transactionEnvelope; - } - - return $this->serializeAsEnvelope($event); - } - - private function serializeAsEvent(Event $event): string - { - $result = $this->toArray($event); - - return JSON::encode($result); - } - - private function serializeAsCheckInEvent(Event $event): string - { - $result = []; - - $checkIn = $event->getCheckIn(); - if ($checkIn !== null) { - $result = [ - 'check_in_id' => $checkIn->getId(), - 'monitor_slug' => $checkIn->getMonitorSlug(), - 'status' => (string) $checkIn->getStatus(), - 'duration' => $checkIn->getDuration(), - 'release' => $checkIn->getRelease(), - 'environment' => $checkIn->getEnvironment(), - ]; - - if ($checkIn->getMonitorConfig() !== null) { - $result['monitor_config'] = $checkIn->getMonitorConfig()->toArray(); - } - - if (!empty($event->getContexts()['trace'])) { - $result['contexts']['trace'] = $event->getContexts()['trace']; - } - } - - return JSON::encode($result); - } - - /** - * @return array - */ - public function toArray(Event $event): array - { - $result = [ - 'event_id' => (string) $event->getId(), - 'timestamp' => $event->getTimestamp(), - 'platform' => 'php', - 'sdk' => [ - 'name' => $event->getSdkIdentifier(), - 'version' => $event->getSdkVersion(), - ], - ]; - - if ($event->getStartTimestamp() !== null) { - $result['start_timestamp'] = $event->getStartTimestamp(); - } - - if ($event->getLevel() !== null) { - $result['level'] = (string) $event->getLevel(); - } - - if ($event->getLogger() !== null) { - $result['logger'] = $event->getLogger(); - } - - if ($event->getTransaction() !== null) { - $result['transaction'] = $event->getTransaction(); - } - - if ($event->getServerName() !== null) { - $result['server_name'] = $event->getServerName(); - } - - if ($event->getRelease() !== null) { - $result['release'] = $event->getRelease(); - } - - if ($event->getEnvironment() !== null) { - $result['environment'] = $event->getEnvironment(); - } - - if (!empty($event->getFingerprint())) { - $result['fingerprint'] = $event->getFingerprint(); - } - - if (!empty($event->getModules())) { - $result['modules'] = $event->getModules(); - } - - if (!empty($event->getExtra())) { - $result['extra'] = $event->getExtra(); - } - - if (!empty($event->getTags())) { - $result['tags'] = $event->getTags(); - } - - $user = $event->getUser(); - - if ($user !== null) { - $result['user'] = array_merge($user->getMetadata(), [ - 'id' => $user->getId(), - 'username' => $user->getUsername(), - 'email' => $user->getEmail(), - 'ip_address' => $user->getIpAddress(), - 'segment' => $user->getSegment(), - ]); - } - - $osContext = $event->getOsContext(); - $runtimeContext = $event->getRuntimeContext(); - - if ($osContext !== null) { - $result['contexts']['os'] = [ - 'name' => $osContext->getName(), - 'version' => $osContext->getVersion(), - 'build' => $osContext->getBuild(), - 'kernel_version' => $osContext->getKernelVersion(), - ]; - } - - if ($runtimeContext !== null) { - $result['contexts']['runtime'] = [ - 'name' => $runtimeContext->getName(), - 'version' => $runtimeContext->getVersion(), - ]; - } - - if (!empty($event->getContexts())) { - $result['contexts'] = array_merge($result['contexts'] ?? [], $event->getContexts()); - } - - if (!empty($event->getBreadcrumbs())) { - $result['breadcrumbs']['values'] = array_map([$this, 'serializeBreadcrumb'], $event->getBreadcrumbs()); - } - - if (!empty($event->getRequest())) { - $result['request'] = $event->getRequest(); - } - - if ($event->getMessage() !== null) { - if (empty($event->getMessageParams())) { - $result['message'] = $event->getMessage(); - } else { - $result['message'] = [ - 'message' => $event->getMessage(), - 'params' => $event->getMessageParams(), - 'formatted' => $event->getMessageFormatted() ?? vsprintf($event->getMessage(), $event->getMessageParams()), - ]; - } - } - - $exceptions = $event->getExceptions(); - - for ($i = \count($exceptions) - 1; $i >= 0; --$i) { - $result['exception']['values'][] = $this->serializeException($exceptions[$i]); - } - - if (EventType::transaction() === $event->getType()) { - $result['spans'] = array_values(array_map([$this, 'serializeSpan'], $event->getSpans())); - - $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); - if ($transactionMetadata instanceof TransactionMetadata) { - $result['transaction_info']['source'] = (string) $transactionMetadata->getSource(); - } - } - - $stacktrace = $event->getStacktrace(); - - if ($stacktrace !== null) { - $result['stacktrace'] = [ - 'frames' => array_map([$this, 'serializeStacktraceFrame'], $stacktrace->getFrames()), - ]; - } - - return $result; - } - - private function serializeAsEnvelope(Event $event): string { // @see https://develop.sentry.dev/sdk/envelopes/#envelope-headers $envelopeHeader = [ @@ -244,7 +49,6 @@ private function serializeAsEnvelope(Event $event): string ]; $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); - if ($dynamicSamplingContext instanceof DynamicSamplingContext) { $entries = $dynamicSamplingContext->getEntries(); @@ -253,224 +57,28 @@ private function serializeAsEnvelope(Event $event): string } } - $itemHeader = [ - 'type' => (string) $event->getType(), - 'content_type' => 'application/json', - ]; - - if (EventType::checkIn() === $event->getType()) { - $seralizedEvent = $this->serializeAsCheckInEvent($event); - } else { - $seralizedEvent = $this->serializeAsEvent($event); - } - - return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $seralizedEvent); - } - - private function seralizeProfileAsEnvelope(Event $event): ?string - { - $itemHeader = [ - 'type' => 'profile', - 'content_type' => 'application/json', - ]; - - $profile = $event->getSdkMetadata('profile'); - if (!$profile instanceof Profile) { - return null; - } - - $profileData = $profile->getFormattedData($event); - if ($profileData === null) { - return null; - } - - return sprintf("%s\n%s", JSON::encode($itemHeader), JSON::encode($profileData)); - } - - /** - * @return array - * - * @psalm-return array{ - * type: string, - * category: string, - * level: string, - * timestamp: float, - * message?: string, - * data?: array - * } - */ - private function serializeBreadcrumb(Breadcrumb $breadcrumb): array - { - $result = [ - 'type' => $breadcrumb->getType(), - 'category' => $breadcrumb->getCategory(), - 'level' => $breadcrumb->getLevel(), - 'timestamp' => $breadcrumb->getTimestamp(), - ]; - - if ($breadcrumb->getMessage() !== null) { - $result['message'] = $breadcrumb->getMessage(); - } - - if (!empty($breadcrumb->getMetadata())) { - $result['data'] = $breadcrumb->getMetadata(); - } - - return $result; - } - - /** - * @return array - * - * @psalm-return array{ - * type: string, - * value: string, - * stacktrace?: array{ - * frames: array> - * }, - * mechanism?: array{ - * type: string, - * handled: boolean, - * data?: array - * } - * } - */ - private function serializeException(ExceptionDataBag $exception): array - { - $exceptionMechanism = $exception->getMechanism(); - $exceptionStacktrace = $exception->getStacktrace(); - $result = [ - 'type' => $exception->getType(), - 'value' => $exception->getValue(), - ]; - - if ($exceptionStacktrace !== null) { - $result['stacktrace'] = [ - 'frames' => array_map([$this, 'serializeStacktraceFrame'], $exceptionStacktrace->getFrames()), - ]; - } - - if ($exceptionMechanism !== null) { - $result['mechanism'] = [ - 'type' => $exceptionMechanism->getType(), - 'handled' => $exceptionMechanism->isHandled(), - ]; - - if ($exceptionMechanism->getData() !== []) { - $result['mechanism']['data'] = $exceptionMechanism->getData(); - } - } - - return $result; - } - - /** - * @return array - * - * @psalm-return array{ - * filename: string, - * lineno: int, - * in_app: bool, - * abs_path?: string, - * function?: string, - * raw_function?: string, - * pre_context?: string[], - * context_line?: string, - * post_context?: string[], - * vars?: array - * } - */ - private function serializeStacktraceFrame(Frame $frame): array - { - $result = [ - 'filename' => $frame->getFile(), - 'lineno' => $frame->getLine(), - 'in_app' => $frame->isInApp(), - ]; - - if ($frame->getAbsoluteFilePath() !== null) { - $result['abs_path'] = $frame->getAbsoluteFilePath(); - } - - if ($frame->getFunctionName() !== null) { - $result['function'] = $frame->getFunctionName(); - } - - if ($frame->getRawFunctionName() !== null) { - $result['raw_function'] = $frame->getRawFunctionName(); - } - - if (!empty($frame->getPreContext())) { - $result['pre_context'] = $frame->getPreContext(); - } - - if ($frame->getContextLine() !== null) { - $result['context_line'] = $frame->getContextLine(); - } - - if (!empty($frame->getPostContext())) { - $result['post_context'] = $frame->getPostContext(); - } - - if (!empty($frame->getVars())) { - $result['vars'] = $frame->getVars(); - } - - return $result; - } - - /** - * @return array - * - * @psalm-return array{ - * span_id: string, - * trace_id: string, - * parent_span_id?: string, - * start_timestamp: float, - * timestamp?: float, - * status?: string, - * description?: string, - * op?: string, - * data?: array, - * tags?: array - * } - */ - private function serializeSpan(Span $span): array - { - $result = [ - 'span_id' => (string) $span->getSpanId(), - 'trace_id' => (string) $span->getTraceId(), - 'start_timestamp' => $span->getStartTimestamp(), - ]; - - if ($span->getParentSpanId() !== null) { - $result['parent_span_id'] = (string) $span->getParentSpanId(); - } - - if ($span->getEndTimestamp() !== null) { - $result['timestamp'] = $span->getEndTimestamp(); - } - - if ($span->getStatus() !== null) { - $result['status'] = (string) $span->getStatus(); - } - - if ($span->getDescription() !== null) { - $result['description'] = $span->getDescription(); - } - - if ($span->getOp() !== null) { - $result['op'] = $span->getOp(); - } - - if (!empty($span->getData())) { - $result['data'] = $span->getData(); - } - - if (!empty($span->getTags())) { - $result['tags'] = $span->getTags(); + $items = ''; + + switch ($event->getType()) { + case EventType::event(): + $items = EventItem::toEnvelopeItem($event); + break; + case EventType::transaction(): + $transactionItem = TransactionItem::toEnvelopeItem($event); + if ($event->getSdkMetadata('profile') !== null) { + $profileItem = ProfileItem::toEnvelopeItem($event); + if ($profileItem !== '') { + $items = sprintf("%s\n%s", $transactionItem, $profileItem); + break; + } + } + $items = $transactionItem; + break; + case EventType::checkIn(): + $items = CheckInItem::toEnvelopeItem($event); + break; } - return $result; + return sprintf("%s\n%s", JSON::encode($envelopeHeader), $items); } } diff --git a/src/Serializer/Traits/BreadcrumbSeralizerTrait.php b/src/Serializer/Traits/BreadcrumbSeralizerTrait.php new file mode 100644 index 000000000..7fe10ce25 --- /dev/null +++ b/src/Serializer/Traits/BreadcrumbSeralizerTrait.php @@ -0,0 +1,45 @@ + + * + * @psalm-return array{ + * type: string, + * category: string, + * level: string, + * timestamp: float, + * message?: string, + * data?: array + * } + */ + protected static function serializeBreadcrumb(Breadcrumb $breadcrumb): array + { + $result = [ + 'type' => $breadcrumb->getType(), + 'category' => $breadcrumb->getCategory(), + 'level' => $breadcrumb->getLevel(), + 'timestamp' => $breadcrumb->getTimestamp(), + ]; + + if ($breadcrumb->getMessage() !== null) { + $result['message'] = $breadcrumb->getMessage(); + } + + if (!empty($breadcrumb->getMetadata())) { + $result['data'] = $breadcrumb->getMetadata(); + } + + return $result; + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 5e149f7b2..f212776b6 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -65,14 +65,13 @@ public static function serializeAsEnvelopeDataProvider(): iterable <<setLevel(Severity::error()); - $event->setLogger('app.php'); $event->setTransaction('/users//'); $event->setServerName('foo.example.com'); $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); @@ -169,7 +168,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} +{"timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion"},"start_timestamp":1597790835,"level":"error","transaction":"\/users\/\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} TEXT ]; @@ -181,7 +180,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable << Date: Tue, 31 Oct 2023 12:36:12 +0100 Subject: [PATCH 0920/1161] Add HTTP client request body compression (#1615) --- phpstan-baseline.neon | 2 +- src/HttpClient/HttpClient.php | 15 ++++++++--- src/Options.php | 47 +++++++++++++++++------------------ tests/OptionsTest.php | 18 +++++++------- 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e008f9ef9..cf0df747f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -206,7 +206,7 @@ parameters: path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:isCompressionEnabled\\(\\) should return bool but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:isHttpCompressionEnabled\\(\\) should return bool but returns mixed\\.$#" count: 1 path: src/Options.php diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 8ea8ebc48..f28351b8e 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -37,13 +37,23 @@ public function sendRequest(string $requestData, Options $options): Response $curlHandle = curl_init(); + $requestHeaders = Http::getRequestHeaders($dsn, $this->sdkIdentifier, $this->sdkVersion); + + if ( + \extension_loaded('zlib') + && $options->isHttpCompressionEnabled() + ) { + $requestData = gzcompress($requestData, -1, \ZLIB_ENCODING_GZIP); + $requestHeaders['Content-Encoding'] = 'gzip'; + } + $responseHeaders = []; $responseHeaderCallback = function ($curlHandle, $headerLine) use (&$responseHeaders): int { return Http::parseResponseHeaders($headerLine, $responseHeaders); }; curl_setopt($curlHandle, \CURLOPT_URL, $dsn->getEnvelopeApiEndpointUrl()); - curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, Http::getRequestHeaders($dsn, $this->sdkIdentifier, $this->sdkVersion)); + curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, $requestHeaders); curl_setopt($curlHandle, \CURLOPT_USERAGENT, $this->sdkIdentifier . '/' . $this->sdkVersion); curl_setopt($curlHandle, \CURLOPT_TIMEOUT, $options->getHttpTimeout()); curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, $options->getHttpConnectTimeout()); @@ -70,9 +80,6 @@ public function sendRequest(string $requestData, Options $options): Response curl_setopt($curlHandle, \CURLOPT_PROXYUSERPWD, $httpProxyAuthentication); } - /** - * @TODO(michi) add request compression (gzip/brotli) depending on availiable extensions - */ $body = curl_exec($curlHandle); if ($body === false) { diff --git a/src/Options.php b/src/Options.php index dda20c051..22bd8dc70 100644 --- a/src/Options.php +++ b/src/Options.php @@ -238,28 +238,6 @@ public function setContextLines(?int $contextLines): self return $this; } - /** - * Returns whether the requests should be compressed using GZIP or not. - */ - public function isCompressionEnabled(): bool - { - return $this->options['enable_compression']; - } - - /** - * Sets whether the request should be compressed using JSON or not. - * - * @param bool $enabled Flag indicating whether the request should be compressed - */ - public function setEnableCompression(bool $enabled): self - { - $options = array_merge($this->options, ['enable_compression' => $enabled]); - - $this->options = $this->resolver->resolve($options); - - return $this; - } - /** * Gets the environment. */ @@ -858,6 +836,26 @@ public function setHttpSslVerifyPeer(bool $httpSslVerifyPeer): self return $this; } + /** + * Returns whether the requests should be compressed using GZIP or not. + */ + public function isHttpCompressionEnabled(): bool + { + return $this->options['http_compression']; + } + + /** + * Sets whether the request should be compressed using JSON or not. + */ + public function setEnableHttpCompression(bool $enabled): self + { + $options = array_merge($this->options, ['http_compression' => $enabled]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Gets whether the silenced errors should be captured or not. * @@ -995,7 +993,6 @@ private function configureOptions(OptionsResolver $resolver): void 'profiles_sample_rate' => null, 'attach_stacktrace' => false, 'context_lines' => 5, - 'enable_compression' => true, 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, 'logger' => 'php', 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, @@ -1027,6 +1024,7 @@ private function configureOptions(OptionsResolver $resolver): void 'http_connect_timeout' => self::DEFAULT_HTTP_CONNECT_TIMEOUT, 'http_timeout' => self::DEFAULT_HTTP_TIMEOUT, 'http_ssl_verify_peer' => true, + 'http_compression' => true, 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', 'class_serializers' => [], @@ -1040,7 +1038,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('profiles_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); - $resolver->setAllowedTypes('enable_compression', 'bool'); $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('in_app_exclude', 'string[]'); $resolver->setAllowedTypes('in_app_include', 'string[]'); @@ -1067,6 +1064,8 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('http_proxy_authentication', ['null', 'string']); $resolver->setAllowedTypes('http_connect_timeout', ['int', 'float']); $resolver->setAllowedTypes('http_timeout', ['int', 'float']); + $resolver->setAllowedTypes('http_ssl_verify_peer', 'bool'); + $resolver->setAllowedTypes('http_compression', 'bool'); $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); $resolver->setAllowedTypes('class_serializers', 'array'); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index a43337195..1b1e7c145 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -170,15 +170,6 @@ static function (): void {}, null, ]; - yield [ - 'enable_compression', - false, - 'isCompressionEnabled', - 'setEnableCompression', - null, - null, - ]; - yield [ 'environment', 'foo', @@ -422,6 +413,15 @@ static function (): void {}, null, ]; + yield [ + 'http_compression', + false, + 'isHttpCompressionEnabled', + 'setEnableHttpCompression', + null, + null, + ]; + yield [ 'capture_silenced_errors', true, From 6afcaf58645bc74d471c055aaebffb477c71a5a2 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Oct 2023 12:36:20 +0100 Subject: [PATCH 0921/1161] Remove `EventType::default` (#1616) --- src/EventType.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/EventType.php b/src/EventType.php index c4e79c4d0..beb04930f 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -27,22 +27,11 @@ private function __construct(string $value) $this->value = $value; } - /** - * Creates an instance of this enum for the "default" value. - */ - public static function default(): self - { - return self::getInstance('default'); - } - public static function event(): self { return self::getInstance('event'); } - /** - * Creates an instance of this enum for the "transaction" value. - */ public static function transaction(): self { return self::getInstance('transaction'); From c6d04afad93ee2c8ea3fd689324d3f2635125d0a Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Oct 2023 12:36:47 +0100 Subject: [PATCH 0922/1161] Add ResponseTest (#1617) --- tests/HttpClient/ResponseTest.php | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/HttpClient/ResponseTest.php diff --git a/tests/HttpClient/ResponseTest.php b/tests/HttpClient/ResponseTest.php new file mode 100644 index 000000000..44f50d9fb --- /dev/null +++ b/tests/HttpClient/ResponseTest.php @@ -0,0 +1,71 @@ + [ + 'application/json', + ], + ], + '' + ); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertTrue($response->isSuccess()); + $this->assertTrue($response->hasHeader('content-type')); + $this->assertSame(['application/json'], $response->getHeader('content-type')); + $this->assertSame(['application/json'], $response->getHeader('Content-Type')); + $this->assertSame('application/json', $response->getHeaderLine('content-type')); + $this->assertSame('application/json', $response->getHeaderLine('Content-Type')); + $this->assertSame('', $response->getError()); + $this->assertFalse($response->hasError()); + } + + public function testResponseFailure() + { + $response = new Response( + 500, + [], + 'Something went wrong!' + ); + + $this->assertSame(500, $response->getStatusCode()); + $this->assertFalse($response->isSuccess()); + $this->assertFalse($response->hasHeader('content-type')); + $this->assertSame([], $response->getHeader('content-type')); + $this->assertSame([], $response->getHeader('Content-Type')); + $this->assertSame('', $response->getHeaderLine('content-type')); + $this->assertSame('', $response->getHeaderLine('Content-Type')); + $this->assertSame('Something went wrong!', $response->getError()); + $this->assertTrue($response->hasError()); + } + + public function testResponseMultiValueHeader() + { + $response = new Response( + 200, + [ + 'X-Foo' => [ + 'one', + 'two', + 'three', + ], + ], + '' + ); + + $this->assertSame(['one', 'two', 'three'], $response->getHeader('x-foo')); + $this->assertSame('one,two,three', $response->getHeaderLine('x-foo')); + } +} From 9775ecf666c2c7352c880306799ebc082e1e1bdd Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Oct 2023 14:31:52 +0100 Subject: [PATCH 0923/1161] Fix setting cURL request headers (#1620) --- src/HttpClient/HttpClient.php | 2 +- src/Util/Http.php | 4 ++-- tests/Util/HttpTest.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index f28351b8e..98075fbdf 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -44,7 +44,7 @@ public function sendRequest(string $requestData, Options $options): Response && $options->isHttpCompressionEnabled() ) { $requestData = gzcompress($requestData, -1, \ZLIB_ENCODING_GZIP); - $requestHeaders['Content-Encoding'] = 'gzip'; + $requestHeaders[] = 'Content-Encoding: gzip'; } $responseHeaders = []; diff --git a/src/Util/Http.php b/src/Util/Http.php index 9bfdd5d1e..efe903ad7 100644 --- a/src/Util/Http.php +++ b/src/Util/Http.php @@ -24,8 +24,8 @@ public static function getRequestHeaders(Dsn $dsn, string $sdkIdentifier, string ]; return [ - 'Content-Type' => 'application/x-sentry-envelope', - 'X-Sentry-Auth' => 'Sentry ' . implode(', ', $authHeader), + 'Content-Type: application/x-sentry-envelope', + 'X-Sentry-Auth: Sentry ' . implode(', ', $authHeader), ]; } diff --git a/tests/Util/HttpTest.php b/tests/Util/HttpTest.php index 51a6a650a..342ad1b75 100644 --- a/tests/Util/HttpTest.php +++ b/tests/Util/HttpTest.php @@ -25,8 +25,8 @@ public static function getRequestHeadersDataProvider(): \Generator 'sentry.sdk.identifier', '1.2.3', [ - 'Content-Type' => 'application/x-sentry-envelope', - 'X-Sentry-Auth' => 'Sentry sentry_version=7, sentry_client=sentry.sdk.identifier/1.2.3, sentry_key=public', + 'Content-Type: application/x-sentry-envelope', + 'X-Sentry-Auth: Sentry sentry_version=7, sentry_client=sentry.sdk.identifier/1.2.3, sentry_key=public', ], ]; } From fe6a68b706bde00da1fcc448568acde5ff6149e5 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 31 Oct 2023 14:32:05 +0100 Subject: [PATCH 0924/1161] Add composer `check` script (#1621) --- composer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/composer.json b/composer.json index dc7197367..4ff1f8e95 100644 --- a/composer.json +++ b/composer.json @@ -60,6 +60,12 @@ } }, "scripts": { + "check": [ + "@cs-check", + "@phpstan", + "@psalm", + "@tests" + ], "tests": [ "vendor/bin/phpunit --verbose" ], From 661cffb6f22e3c92001d840f1189b8c76d64c237 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 2 Nov 2023 13:07:00 +0100 Subject: [PATCH 0925/1161] [4.x] Fix fluent return type (#1622) --- src/Tracing/Span.php | 44 ++++++++++++++++++++++-------- src/Tracing/SpanContext.php | 53 +++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 496efb2d4..3db829d3a 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -130,8 +130,10 @@ public function getTraceId(): TraceId * Sets the ID that determines which trace the span belongs to. * * @param TraceId $traceId The ID + * + * @return $this */ - public function setTraceId(TraceId $traceId): self + public function setTraceId(TraceId $traceId) { $this->traceId = $traceId; @@ -150,8 +152,10 @@ public function getParentSpanId(): ?SpanId * Sets the ID that determines which span is the parent of the current one. * * @param SpanId|null $parentSpanId The ID + * + * @return $this */ - public function setParentSpanId(?SpanId $parentSpanId): self + public function setParentSpanId(?SpanId $parentSpanId) { $this->parentSpanId = $parentSpanId; @@ -170,8 +174,10 @@ public function getStartTimestamp(): float * Sets the timestamp representing when the measuring started. * * @param float $startTimestamp The timestamp + * + * @return $this */ - public function setStartTimestamp(float $startTimestamp): self + public function setStartTimestamp(float $startTimestamp) { $this->startTimestamp = $startTimestamp; @@ -200,8 +206,10 @@ public function getDescription(): ?string * the span but is consistent across instances of the span. * * @param string|null $description The description + * + * @return $this */ - public function setDescription(?string $description): self + public function setDescription(?string $description) { $this->description = $description; @@ -220,8 +228,10 @@ public function getOp(): ?string * Sets a short code identifying the type of operation the span is measuring. * * @param string|null $op The short code + * + * @return $this */ - public function setOp(?string $op): self + public function setOp(?string $op) { $this->op = $op; @@ -240,8 +250,10 @@ public function getStatus(): ?SpanStatus * Sets the status of the span/transaction. * * @param SpanStatus|null $status The status + * + * @return $this */ - public function setStatus(?SpanStatus $status): self + public function setStatus(?SpanStatus $status) { $this->status = $status; @@ -252,8 +264,10 @@ public function setStatus(?SpanStatus $status): self * Sets the HTTP status code and the status of the span/transaction. * * @param int $statusCode The HTTP status code + * + * @return $this */ - public function setHttpStatus(int $statusCode): self + public function setHttpStatus(int $statusCode) { $this->tags['http.status_code'] = (string) $statusCode; @@ -281,8 +295,10 @@ public function getTags(): array * the existing ones. * * @param array $tags The tags + * + * @return $this */ - public function setTags(array $tags): self + public function setTags(array $tags) { $this->tags = array_merge($this->tags, $tags); @@ -309,8 +325,10 @@ public function getSampled(): ?bool * Sets the flag determining whether this span should be sampled or not. * * @param bool $sampled Whether to sample or not this span + * + * @return $this */ - public function setSampled(?bool $sampled): self + public function setSampled(?bool $sampled) { $this->sampled = $sampled; @@ -332,8 +350,10 @@ public function getData(): array * the existing one. * * @param array $data The data + * + * @return $this */ - public function setData(array $data): self + public function setData(array $data) { $this->data = array_merge($this->data, $data); @@ -440,8 +460,10 @@ public function getSpanRecorder(): ?SpanRecorder /** * Detaches the span recorder from this instance. + * + * @return $this */ - public function detachSpanRecorder(): self + public function detachSpanRecorder() { $this->spanRecorder = null; diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index a1afa3f77..65df6dab9 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -66,7 +66,10 @@ public function getDescription(): ?string return $this->description; } - public function setDescription(?string $description): self + /** + * @return $this + */ + public function setDescription(?string $description) { $this->description = $description; @@ -78,7 +81,10 @@ public function getOp(): ?string return $this->op; } - public function setOp(?string $op): self + /** + * @return $this + */ + public function setOp(?string $op) { $this->op = $op; @@ -90,7 +96,10 @@ public function getStatus(): ?SpanStatus return $this->status; } - public function setStatus(?SpanStatus $status): self + /** + * @return $this + */ + public function setStatus(?SpanStatus $status) { $this->status = $status; @@ -102,7 +111,10 @@ public function getParentSpanId(): ?SpanId return $this->parentSpanId; } - public function setParentSpanId(?SpanId $parentSpanId): self + /** + * @return $this + */ + public function setParentSpanId(?SpanId $parentSpanId) { $this->parentSpanId = $parentSpanId; @@ -114,7 +126,10 @@ public function getSampled(): ?bool return $this->sampled; } - public function setSampled(?bool $sampled): self + /** + * @return $this + */ + public function setSampled(?bool $sampled) { $this->sampled = $sampled; @@ -126,7 +141,10 @@ public function getSpanId(): ?SpanId return $this->spanId; } - public function setSpanId(?SpanId $spanId): self + /** + * @return $this + */ + public function setSpanId(?SpanId $spanId) { $this->spanId = $spanId; @@ -138,7 +156,10 @@ public function getTraceId(): ?TraceId return $this->traceId; } - public function setTraceId(?TraceId $traceId): self + /** + * @return $this + */ + public function setTraceId(?TraceId $traceId) { $this->traceId = $traceId; @@ -155,8 +176,10 @@ public function getTags(): array /** * @param array $tags + * + * @return $this */ - public function setTags(array $tags): self + public function setTags(array $tags) { $this->tags = $tags; @@ -173,8 +196,10 @@ public function getData(): array /** * @param array $data + * + * @return $this */ - public function setData(array $data): self + public function setData(array $data) { $this->data = $data; @@ -186,7 +211,10 @@ public function getStartTimestamp(): ?float return $this->startTimestamp; } - public function setStartTimestamp(?float $startTimestamp): self + /** + * @return $this + */ + public function setStartTimestamp(?float $startTimestamp) { $this->startTimestamp = $startTimestamp; @@ -198,7 +226,10 @@ public function getEndTimestamp(): ?float return $this->endTimestamp; } - public function setEndTimestamp(?float $endTimestamp): self + /** + * @return $this + */ + public function setEndTimestamp(?float $endTimestamp) { $this->endTimestamp = $endTimestamp; From 7baa6b8376a800e7ecfe4c80fd5c8e9143823bae Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 6 Nov 2023 09:01:33 +0100 Subject: [PATCH 0926/1161] Handle emtpy DSN (#1624) --- src/Transport/HttpTransport.php | 4 +++ src/Transport/NullTransport.php | 26 ----------------- tests/Transport/HttpTransportTest.php | 16 ++++++++--- tests/Transport/NullTransportTest.php | 40 --------------------------- 4 files changed, 16 insertions(+), 70 deletions(-) delete mode 100644 src/Transport/NullTransport.php delete mode 100644 tests/Transport/NullTransportTest.php diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index efb9505c4..db91d264a 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -65,6 +65,10 @@ public function __construct( */ public function send(Event $event): Result { + if ($this->options->getDsn() === null) { + return new Result(ResultStatus::skipped(), $event); + } + $eventType = $event->getType(); if ($this->rateLimiter->isRateLimited($eventType)) { $this->logger->warning( diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php deleted file mode 100644 index 10d275565..000000000 --- a/src/Transport/NullTransport.php +++ /dev/null @@ -1,26 +0,0 @@ -willReturn(new Response($httpStatusCode, [], '')); $transport = new HttpTransport( - new Options(), + new Options([ + 'dsn' => 'http://public@example.com/1', + ]), $this->httpClient, $this->payloadSerializer ); @@ -112,7 +114,9 @@ public function testSendFailsDueToHttpClientException(): void ->will($this->throwException($exception)); $transport = new HttpTransport( - new Options(), + new Options([ + 'dsn' => 'http://public@example.com/1', + ]), $this->httpClient, $this->payloadSerializer, $logger @@ -143,7 +147,9 @@ public function testSendFailsDueToCulrError(): void ->willReturn(new Response(0, [], 'cURL Error (6) Could not resolve host: example.com')); $transport = new HttpTransport( - new Options(), + new Options([ + 'dsn' => 'http://public@example.com/1', + ]), $this->httpClient, $this->payloadSerializer, $logger @@ -182,7 +188,9 @@ public function testSendFailsDueToExceedingRateLimits(): void ->willReturn(new Response(429, ['Retry-After' => ['60']], '')); $transport = new HttpTransport( - new Options(), + new Options([ + 'dsn' => 'http://public@example.com/1', + ]), $this->httpClient, $this->payloadSerializer, $logger diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php deleted file mode 100644 index dbefc9add..000000000 --- a/tests/Transport/NullTransportTest.php +++ /dev/null @@ -1,40 +0,0 @@ -transport = new NullTransport(); - } - - public function testSend(): void - { - $event = Event::createEvent(); - - $result = $this->transport->send($event); - - $this->assertSame(ResultStatus::skipped(), $result->getStatus()); - $this->assertSame($event, $result->getEvent()); - } - - public function testClose(): void - { - $response = $this->transport->close(); - - $this->assertSame(ResultStatus::success(), $response->getStatus()); - } -} From bf716d8e68a4192da54f0bae57926f76730b8af6 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 6 Nov 2023 09:01:49 +0100 Subject: [PATCH 0927/1161] Refactor `logger` option (#1625) --- phpstan-baseline.neon | 12 +++- src/Client.php | 11 ++-- src/ClientBuilder.php | 7 +++ src/Logger/DebugFileLogger.php | 29 +++++++++ src/Logger/DebugStdOutLogger.php | 19 ++++++ src/Options.php | 33 +++------- src/Transport/Result.php | 4 +- tests/ClientTest.php | 4 -- tests/OptionsTest.php | 100 ++----------------------------- 9 files changed, 84 insertions(+), 135 deletions(-) create mode 100644 src/Logger/DebugFileLogger.php create mode 100644 src/Logger/DebugStdOutLogger.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index cf0df747f..7083cd320 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -30,6 +30,16 @@ parameters: count: 1 path: src/Integration/RequestIntegration.php + - + message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/Logger/DebugFileLogger.php + + - + message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/Logger/DebugStdOutLogger.php + - message: "#^Parameter \\#1 \\$level of method Monolog\\\\Handler\\\\AbstractHandler\\:\\:__construct\\(\\) expects 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|'ALERT'\\|'alert'\\|'CRITICAL'\\|'critical'\\|'DEBUG'\\|'debug'\\|'EMERGENCY'\\|'emergency'\\|'ERROR'\\|'error'\\|'INFO'\\|'info'\\|'NOTICE'\\|'notice'\\|'WARNING'\\|'warning'\\|Monolog\\\\Level, int\\|Monolog\\\\Level\\|string given\\.$#" count: 1 @@ -136,7 +146,7 @@ parameters: path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getLogger\\(\\) should return string but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getLogger\\(\\) should return Psr\\\\Log\\\\LoggerInterface\\|null but returns mixed\\.$#" count: 1 path: src/Options.php diff --git a/src/Client.php b/src/Client.php index f93e6210f..5cc99cd3e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -93,11 +93,12 @@ public function __construct( ) { $this->options = $options; $this->transport = $transport; - $this->logger = $logger ?? new NullLogger(); - $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); - $this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer ?? new RepresentationSerializer($this->options)); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; $this->sdkVersion = $sdkVersion ?? self::SDK_VERSION; + $this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer ?? new RepresentationSerializer($this->options)); + $this->logger = $logger ?? new NullLogger(); + + $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); } /** @@ -269,10 +270,6 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT); } - if ($event->getLogger() === null) { - $event->setLogger($this->options->getLogger(false)); - } - $isTransaction = EventType::transaction() === $event->getType(); $sampleRate = $this->options->getSampleRate(); diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 0594ba08e..54a433eb0 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -63,6 +63,8 @@ public function __construct(?Options $options = null) { $this->options = $options ?? new Options(); + $this->logger = $this->options->getLogger() ?? null; + $this->httpClient = $this->options->getHttpClient() ?? new HttpClient($this->sdkIdentifier, $this->sdkVersion); $this->transport = $this->options->getTransport() ?? new HttpTransport( $this->options, @@ -92,6 +94,11 @@ public function setRepresentationSerializer(RepresentationSerializerInterface $r return $this; } + public function getLogger(): ?LoggerInterface + { + return $this->logger; + } + public function setLogger(LoggerInterface $logger): self { $this->logger = $logger; diff --git a/src/Logger/DebugFileLogger.php b/src/Logger/DebugFileLogger.php new file mode 100644 index 000000000..da406c44f --- /dev/null +++ b/src/Logger/DebugFileLogger.php @@ -0,0 +1,29 @@ +filePath = $filePath; + } + + /** + * @param mixed $level + * @param mixed[] $context + */ + public function log($level, \Stringable|string $message, array $context = []): void + { + file_put_contents($this->filePath, sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message), \FILE_APPEND); + } +} diff --git a/src/Logger/DebugStdOutLogger.php b/src/Logger/DebugStdOutLogger.php new file mode 100644 index 000000000..fc1212931 --- /dev/null +++ b/src/Logger/DebugStdOutLogger.php @@ -0,0 +1,19 @@ +options['logger']; } /** - * Sets the logger used by Sentry. - * - * @param string $logger The logger - * - * @deprecated since version 3.2, to be removed in 4.0 + * Sets a PSR-3 compatible logger to log internal debug messages. */ - public function setLogger(string $logger): self + public function setLogger(LoggerInterface $logger): self { - @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - $options = array_merge($this->options, ['logger' => $logger]); $this->options = $this->resolver->resolve($options); @@ -994,7 +983,7 @@ private function configureOptions(OptionsResolver $resolver): void 'attach_stacktrace' => false, 'context_lines' => 5, 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, - 'logger' => 'php', + 'logger' => null, 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), @@ -1041,7 +1030,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('in_app_exclude', 'string[]'); $resolver->setAllowedTypes('in_app_include', 'string[]'); - $resolver->setAllowedTypes('logger', ['null', 'string']); + $resolver->setAllowedTypes('logger', ['null', LoggerInterface::class]); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); @@ -1089,14 +1078,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setNormalizer('in_app_include', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); - - $resolver->setNormalizer('logger', function (SymfonyOptions $options, ?string $value): ?string { - if ($value !== 'php') { - @trigger_error('The option "logger" is deprecated.', \E_USER_DEPRECATED); - } - - return $value; - }); } /** diff --git a/src/Transport/Result.php b/src/Transport/Result.php index 881624f11..a4a6b8238 100644 --- a/src/Transport/Result.php +++ b/src/Transport/Result.php @@ -9,8 +9,10 @@ /** * This class contains the details of the sending operation of an event, e.g. * if it was sent successfully or if it was skipped because of some reason. + * + * @internal */ -final class Result +class Result { /** * @var ResultStatus The status of the sending operation of the event diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 3b5011ddf..30d2842a5 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -217,7 +217,6 @@ public static function captureEventDataProvider(): \Generator { $event = Event::createEvent(); $expectedEvent = clone $event; - $expectedEvent->setLogger('php'); $expectedEvent->setServerName('example.com'); $expectedEvent->setRelease('0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'); $expectedEvent->setEnvironment('development'); @@ -241,7 +240,6 @@ public static function captureEventDataProvider(): \Generator $event->setTags(['context' => 'production']); $expectedEvent = clone $event; - $expectedEvent->setLogger('php'); $expectedEvent->setTags(['context' => 'production', 'ios_version' => '14.0']); yield 'Options set && event properties set => event properties override options' => [ @@ -259,7 +257,6 @@ public static function captureEventDataProvider(): \Generator $event->setServerName('example.com'); $expectedEvent = clone $event; - $expectedEvent->setLogger('php'); $expectedEvent->setEnvironment('production'); yield 'Environment option set to null && no event property set => fallback to default value' => [ @@ -274,7 +271,6 @@ public static function captureEventDataProvider(): \Generator $event->setExceptions([new ExceptionDataBag(new \ErrorException())]); $expectedEvent = clone $event; - $expectedEvent->setLogger('php'); $expectedEvent->setEnvironment('production'); yield 'Error level is set && exception is instance of ErrorException => preserve the error level set by the user' => [ diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 1b1e7c145..09bec7b9b 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -5,6 +5,7 @@ namespace Sentry\Tests; use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; use Sentry\Dsn; use Sentry\HttpClient\HttpClient; use Sentry\Options; @@ -45,13 +46,8 @@ public function testConstructor( string $option, $value, string $getterMethod, - ?string $setterMethod, - ?string $expectedGetterDeprecationMessage + ?string $setterMethod ): void { - if ($expectedGetterDeprecationMessage !== null) { - $this->expectDeprecation($expectedGetterDeprecationMessage); - } - $options = new Options([$option => $value]); $this->assertEquals($value, $options->$getterMethod()); @@ -66,18 +62,8 @@ public function testGettersAndSetters( string $option, $value, string $getterMethod, - ?string $setterMethod, - ?string $expectedGetterDeprecationMessage, - ?string $expectedSetterDeprecationMessage + ?string $setterMethod ): void { - if ($expectedSetterDeprecationMessage !== null) { - $this->expectDeprecation($expectedSetterDeprecationMessage); - } - - if ($expectedGetterDeprecationMessage !== null) { - $this->expectDeprecation($expectedGetterDeprecationMessage); - } - $options = new Options(); if ($setterMethod !== null) { @@ -94,8 +80,6 @@ public static function optionsDataProvider(): \Generator ['foo', 'bar'], 'getPrefixes', 'setPrefixes', - null, - null, ]; yield [ @@ -103,8 +87,6 @@ public static function optionsDataProvider(): \Generator 0.5, 'getSampleRate', 'setSampleRate', - null, - null, ]; yield [ @@ -112,8 +94,6 @@ public static function optionsDataProvider(): \Generator 0.5, 'getTracesSampleRate', 'setTracesSampleRate', - null, - null, ]; yield [ @@ -121,8 +101,6 @@ public static function optionsDataProvider(): \Generator null, 'getTracesSampleRate', 'setTracesSampleRate', - null, - null, ]; yield [ @@ -130,8 +108,6 @@ public static function optionsDataProvider(): \Generator static function (): void {}, 'getTracesSampler', 'setTracesSampler', - null, - null, ]; yield [ @@ -139,8 +115,6 @@ static function (): void {}, true, 'getEnableTracing', 'setEnableTracing', - null, - null, ]; yield [ @@ -148,8 +122,6 @@ static function (): void {}, 0.5, 'getProfilesSampleRate', 'setProfilesSampleRate', - null, - null, ]; yield [ @@ -157,8 +129,6 @@ static function (): void {}, false, 'shouldAttachStacktrace', 'setAttachStacktrace', - null, - null, ]; yield [ @@ -166,8 +136,6 @@ static function (): void {}, 3, 'getContextLines', 'setContextLines', - null, - null, ]; yield [ @@ -175,8 +143,6 @@ static function (): void {}, 'foo', 'getEnvironment', 'setEnvironment', - null, - null, ]; yield [ @@ -184,8 +150,6 @@ static function (): void {}, ['foo', 'bar'], 'getInAppExcludedPaths', 'setInAppExcludedPaths', - null, - null, ]; yield [ @@ -193,17 +157,13 @@ static function (): void {}, ['foo', 'bar'], 'getInAppIncludedPaths', 'setInAppIncludedPaths', - null, - null, ]; yield [ 'logger', - 'foo', + new NullLogger(), 'getLogger', 'setLogger', - 'Method Sentry\\Options::getLogger() is deprecated since version 3.2 and will be removed in 4.0.', - 'Method Sentry\\Options::setLogger() is deprecated since version 3.2 and will be removed in 4.0.', ]; yield [ @@ -211,8 +171,6 @@ static function (): void {}, 'dev', 'getRelease', 'setRelease', - null, - null, ]; yield [ @@ -220,8 +178,6 @@ static function (): void {}, 'foo', 'getServerName', 'setServerName', - null, - null, ]; yield [ @@ -229,8 +185,6 @@ static function (): void {}, ['foo', 'bar'], 'getTags', 'setTags', - null, - null, ]; yield [ @@ -238,8 +192,6 @@ static function (): void {}, 0, 'getErrorTypes', 'setErrorTypes', - null, - null, ]; yield [ @@ -247,8 +199,6 @@ static function (): void {}, 50, 'getMaxBreadcrumbs', 'setMaxBreadcrumbs', - null, - null, ]; yield [ @@ -256,8 +206,6 @@ static function (): void {}, ['foo', 'bar'], 'getIgnoreExceptions', 'setIgnoreExceptions', - null, - null, ]; yield [ @@ -265,8 +213,6 @@ static function (): void {}, ['foo', 'bar'], 'getIgnoreTransactions', 'setIgnoreTransactions', - null, - null, ]; yield [ @@ -274,8 +220,6 @@ static function (): void {}, static function (): void {}, 'getBeforeSendCallback', 'setBeforeSendCallback', - null, - null, ]; yield [ @@ -283,8 +227,6 @@ static function (): void {}, static function (): void {}, 'getBeforeSendTransactionCallback', 'setBeforeSendTransactionCallback', - null, - null, ]; yield [ @@ -292,8 +234,6 @@ static function (): void {}, ['www.example.com'], 'getTracePropagationTargets', 'setTracePropagationTargets', - null, - null, ]; yield [ @@ -301,8 +241,6 @@ static function (): void {}, static function (): void {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback', - null, - null, ]; yield [ @@ -310,8 +248,6 @@ static function (): void {}, true, 'shouldSendDefaultPii', 'setSendDefaultPii', - null, - null, ]; yield [ @@ -319,8 +255,6 @@ static function (): void {}, false, 'hasDefaultIntegrations', 'setDefaultIntegrations', - null, - null, ]; yield [ @@ -328,8 +262,6 @@ static function (): void {}, 50, 'getMaxValueLength', 'setMaxValueLength', - null, - null, ]; yield [ @@ -337,8 +269,6 @@ static function (): void {}, new HttpTransport(new Options(), new HttpClient('foo', 'bar'), new PayloadSerializer(new Options())), 'getTransport', 'setTransport', - null, - null, ]; yield [ @@ -346,8 +276,6 @@ static function (): void {}, new HttpClient('foo', 'bar'), 'getHttpClient', 'setHttpClient', - null, - null, ]; yield [ @@ -355,8 +283,6 @@ static function (): void {}, '127.0.0.1', 'getHttpProxy', 'setHttpProxy', - null, - null, ]; yield [ @@ -364,8 +290,6 @@ static function (): void {}, 'username:password', 'getHttpProxyAuthentication', 'setHttpProxyAuthentication', - null, - null, ]; yield [ @@ -373,8 +297,6 @@ static function (): void {}, 1, 'getHttpTimeout', 'setHttpTimeout', - null, - null, ]; yield [ @@ -382,8 +304,6 @@ static function (): void {}, 1.2, 'getHttpTimeout', 'setHttpTimeout', - null, - null, ]; yield [ @@ -391,8 +311,6 @@ static function (): void {}, 1, 'getHttpConnectTimeout', 'setHttpConnectTimeout', - null, - null, ]; yield [ @@ -400,8 +318,6 @@ static function (): void {}, 1.2, 'getHttpConnectTimeout', 'setHttpConnectTimeout', - null, - null, ]; yield [ @@ -409,8 +325,6 @@ static function (): void {}, false, 'getHttpSslVerifyPeer', 'setHttpSslVerifyPeer', - null, - null, ]; yield [ @@ -418,8 +332,6 @@ static function (): void {}, false, 'isHttpCompressionEnabled', 'setEnableHttpCompression', - null, - null, ]; yield [ @@ -427,8 +339,6 @@ static function (): void {}, true, 'shouldCaptureSilencedErrors', 'setCaptureSilencedErrors', - null, - null, ]; yield [ @@ -436,8 +346,6 @@ static function (): void {}, 'small', 'getMaxRequestBodySize', 'setMaxRequestBodySize', - null, - null, ]; } From 4839e3bc4331df60e3b912827a95295884136ab5 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 6 Nov 2023 09:02:00 +0100 Subject: [PATCH 0928/1161] Add Request class (#1623) --- src/HttpClient/HttpClient.php | 7 ++++++- src/HttpClient/HttpClientInterface.php | 2 +- src/HttpClient/Request.php | 26 ++++++++++++++++++++++++++ src/Transport/HttpTransport.php | 6 +++++- tests/ClientBuilderTest.php | 3 ++- 5 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/HttpClient/Request.php diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 98075fbdf..96a3dd01a 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -28,13 +28,18 @@ public function __construct(string $sdkIdentifier, string $sdkVersion) $this->sdkVersion = $sdkVersion; } - public function sendRequest(string $requestData, Options $options): Response + public function sendRequest(Request $request, Options $options): Response { $dsn = $options->getDsn(); if ($dsn === null) { throw new \RuntimeException('The DSN option must be set to use the HttpClient.'); } + $requestData = $request->getStringBody(); + if ($requestData === null) { + throw new \RuntimeException('The request data is empty.'); + } + $curlHandle = curl_init(); $requestHeaders = Http::getRequestHeaders($dsn, $this->sdkIdentifier, $this->sdkVersion); diff --git a/src/HttpClient/HttpClientInterface.php b/src/HttpClient/HttpClientInterface.php index c3af5cd25..40742be2a 100644 --- a/src/HttpClient/HttpClientInterface.php +++ b/src/HttpClient/HttpClientInterface.php @@ -8,5 +8,5 @@ interface HttpClientInterface { - public function sendRequest(string $requestData, Options $options): Response; + public function sendRequest(Request $request, Options $options): Response; } diff --git a/src/HttpClient/Request.php b/src/HttpClient/Request.php new file mode 100644 index 000000000..91627ddf6 --- /dev/null +++ b/src/HttpClient/Request.php @@ -0,0 +1,26 @@ +stringBody; + } + + public function setStringBody(string $stringBody): void + { + $this->stringBody = $stringBody; + } +} diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index db91d264a..f666af971 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -8,6 +8,7 @@ use Psr\Log\NullLogger; use Sentry\Event; use Sentry\HttpClient\HttpClientInterface; +use Sentry\HttpClient\Request; use Sentry\Options; use Sentry\Serializer\PayloadSerializerInterface; @@ -79,8 +80,11 @@ public function send(Event $event): Result return new Result(ResultStatus::rateLimit()); } + $request = new Request(); + $request->setStringBody($this->payloadSerializer->serialize($event)); + try { - $response = $this->httpClient->sendRequest($this->payloadSerializer->serialize($event), $this->options); + $response = $this->httpClient->sendRequest($request, $this->options); } catch (\Throwable $exception) { $this->logger->error( sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 552310d25..ae74beabb 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -10,6 +10,7 @@ use Sentry\Event; use Sentry\HttpClient\HttpClient; use Sentry\HttpClient\HttpClientInterface; +use Sentry\HttpClient\Request; use Sentry\HttpClient\Response; use Sentry\Integration\IntegrationInterface; use Sentry\Options; @@ -123,7 +124,7 @@ public function setupOnce(): void final class CustomHttpClient implements HttpClientInterface { - public function sendRequest(string $requestData, Options $options): Response + public function sendRequest(Request $request, Options $options): Response { return new Response(0, [], ''); } From 207ba04da406ddbb4c9775c064694c8b26d1921b Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 6 Nov 2023 10:54:23 +0100 Subject: [PATCH 0929/1161] Prepare 4.0 (#1607) Co-authored-by: Alex Bouma --- CHANGELOG.md | 693 ++++++------------------------------------------- README.md | 37 +-- UPGRADE-4.0.md | 70 ++++- 3 files changed, 149 insertions(+), 651 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d402977..e8f353f04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,659 +1,126 @@ # CHANGELOG -## 3.22.0 +## 4.0.0 -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.22.0. +The Sentry SDK team is thrilled to announce the immediate availability of Sentry PHP SDK v4.0.0. -### Features +# Breaking Change -- Adopt Starfish HTTP attributes in spans and breadcrumbs [(#1581)](https://github.com/getsentry/sentry-php/pull/1581) +Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list of breaking changes. -### Bug Fixes +- This version exclusively uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/) to send event data to Sentry. -- Don't add empty HTTP fragment or query strings to breadcrumb data [(#1588)](https://github.com/getsentry/sentry-php/pull/1588) + If you are using [sentry.io](https://sentry.io), no action is needed. + If you are using an on-premise/self-hosted installation of Sentry, the minimum requirement is now version `>= v20.6.0`. -### Misc +- You need to have `ext-curl` installed to use the SDK. -- Remove obsolete `tags` option depreaction [(#1588)](https://github.com/getsentry/sentry-php/pull/1588) -- Run CI on PHP 8.3 [(1591)](https://github.com/getsentry/sentry-php/pull/1591) -- Add support for `symfony/options-resolver: ^7.0` [(1597)](https://github.com/getsentry/sentry-php/pull/1597) - -## 3.21.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.21.0. - -### Features - -- Add `Sentry::captureCheckIn()` [(#1573)](https://github.com/getsentry/sentry-php/pull/1573) - - Sending check-ins from the SDK is now simplified. - - ```php - $checkInId = Sentry\captureCheckIn( - slug: 'monitor-slug', - status: CheckInStatus::inProgress() - ); - - - // do something - - Sentry\captureCheckIn( - checkInId: $checkInId, - slug: 'monitor-slug', - status: CheckInStatus::ok() - ); - ``` - - You can also pass in a `monitorConfig` object as well as the `duration`. - -- Undeprecate the `tags` option [(#1561)](https://github.com/getsentry/sentry-php/pull/1561) - - You can now set tags that are applied to each event when calling `Sentry::init()`. +- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_errors` option instead. ```php Sentry\init([ - 'tags' => [ - 'foo' => 'bar', - ], - ]) + 'ignore_exceptions' => [BadThingsHappenedException::class], + ]); ``` + + This option performs an [`is_a`](https://www.php.net/manual/en/function.is-a.php) check now, so you can also ignore more generic exceptions. -- Apply the `prefixes`option to profiling frames [(#1568)](https://github.com/getsentry/sentry-php/pull/1568) +# Features - If you added the `prefixes` option when calling `Sentry::init()`, this option will now also apply to profile frames. +- Add new fluent APIs [(#1601)](https://github.com/getsentry/sentry-php/pull/1601) ```php - Sentry\init([ - 'prefixes' => ['/var/www/html'], - ]) + // Before + $transactionContext = new TransactionContext(); + $transactionContext->setName('GET /example'); + $transactionContext->setOp('http.server'); + + // After + $transactionContext = (new TransactionContext()) + ->setName('GET /example'); + ->setOp('http.server'); ``` -### Misc - -- Deduplicate profile stacks and frames [(#1570)](https://github.com/getsentry/sentry-php/pull/1570) - - This will decrease the payload size of the `profile` event payload. - -- Add the transaction's sampling decision to the trace envelope header [(#1562)](https://github.com/getsentry/sentry-php/pull/1562) - -## 3.20.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.20.1. - -### Bug Fixes - -- Use the result of `isTracingEnabled()` to determine the behaviour of `getBaggage()` and `getTraceparent()` [(#1555)](https://github.com/getsentry/sentry-php/pull/1555) - -### Misc - -- Always return a `TransactionContext` from `continueTrace()` [(#1556)](https://github.com/getsentry/sentry-php/pull/1556) - -## 3.20.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.20.0. - -### Features - -- Tracing without Performance [(#1516)](https://github.com/getsentry/sentry-php/pull/1516) - - You can now set up distributed tracing without the need to use the performance APIs. - This allows you to connect your errors that hail from other Sentry instrumented applications to errors in your PHP application. - - To continue a trace, fetch the incoming Sentry tracing headers and call `\Sentry\continueTrace()` as early as possible in the request cycle. +- Simplify the breadcrumb API [(#1603)](https://github.com/getsentry/sentry-php/pull/1603) ```php - $sentryTraceHeader = $request->getHeaderLine('sentry-trace'); - $baggageHeader = $request->getHeaderLine('baggage'); + // Before + \Sentry\addBreadcrumb( + new \Sentry\Breadcrumb( + \Sentry\Breadcrumb::LEVEL_INFO, + \Sentry\Breadcrumb::TYPE_DEFAULT, + 'auth', // category + 'User authenticated', // message (optional) + ['user_id' => $userId] // data (optional) + ) + ); - continueTrace($sentryTraceHeader, $baggageHeader); + // After + \Sentry\addBreadcrumb( + category: 'auth', + message: 'User authenticated', // optional + metadata: ['user_id' => $userId], // optional + level: Breadcrumb::LEVEL_INFO, // set by default + type: Breadcrumb::TYPE_DEFAULT, // set by default + ); ``` - To continue a trace outward, you may attach the Sentry tracing headers to any HTTP client request. - You can fetch the required header values by calling `\Sentry\getBaggage()` and `\Sentry\getTraceparent()`. - -- Upserting Cron Monitors [(#1511)](https://github.com/getsentry/sentry-php/pull/1511) - - You can now create and update your Cron Monitors programmatically with code. - Read more about this in our [docs](https://docs.sentry.io/platforms/php/crons/#upserting-cron-monitors). - -## 3.19.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.19.1. - -### Bug Fixes - -- Use HTTP/1.1 when compression is enabled [(#1542)](https://github.com/getsentry/sentry-php/pull/1542) - -## 3.19.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.19.0. - -### Misc - -- Add support for `guzzlehttp/promises` v2 [(#1536)](https://github.com/getsentry/sentry-php/pull/1536) - -## 3.18.2 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.2. - -### Bug Fixes - -- Require php-http/message-factory [(#1534)](https://github.com/getsentry/sentry-php/pull/1534) - -## 3.18.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.1. - -### Bug Fixes - -- Guard against empty profiles [(#1528)](https://github.com/getsentry/sentry-php/pull/1528) -- Ignore empty context values [(#1529)](https://github.com/getsentry/sentry-php/pull/1529) - -## 3.18.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.0. +- New `logger` option [(#1625)](https://github.com/getsentry/sentry-php/pull/1625) -### Features - -- Add `TransactionContext::fromEnvironment` [(#1519)](https://github.com/getsentry/sentry-php/pull/1519) - -### Misc - -- Sent all events to the `/envelope` endpoint if tracing is enabled [(#1518)](https://github.com/getsentry/sentry-php/pull/1518) -- Attach the Dynamic Sampling Context to error events [(#1522)](https://github.com/getsentry/sentry-php/pull/1522) - -## 3.17.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.17.0. - -### Features - -- Add `ignore_exceptions` & `ignore_transactions` options [(#1503)](https://github.com/getsentry/sentry-php/pull/1503) - - We deprecated the [IgnoreErrorsIntegration](https://docs.sentry.io/platforms/php/integrations/#ignoreerrorsintegration) in favor of this new option. - The option will also take [previous exceptions](https://www.php.net/manual/en/exception.getprevious.php) into account. + To make it easier to debug the internals of the SDK, the `logger` option now accepts a `Psr\Log\LoggerInterface` instance. + We do provide two implementations, `Sentry\Logger\DebugFileLogger` and `Sentry\Logger\DebugStdOutLogger`. ```php - \Sentry\init([ - 'ignore_exceptions' => [BadThingsHappenedException::class], + // This logs messages to the provided file path + Sentry\init([ + 'logger' => new DebugFileLogger(filePath: ROOT . DS . 'sentry.log'), ]); - ``` - - To ignore a transaction being sent to Sentry, add its name to the config option. - You can find the transaction name on the [Performance page](https://sentry.io/performance/). - ```php - \Sentry\init([ - 'ignore_transactions' => ['GET /health'], + // This logs messages to stdout + Sentry\init([ + 'logger' => new DebugStdOutLogger(), ]); ``` -### Misc - - - Bump `php-http/discovery` to `^1.15` [(#1504)](https://github.com/getsentry/sentry-php/pull/1504) +- New default cURL HTTP client [(#1589)](https://github.com/getsentry/sentry-php/pull/1589) - You may need to allow the added composer plugin, introduced in `php-http/discovery v1.15.0`, to execute when running `composer update`. - We previously pinned this package to version `<1.15`. - Due to conflicts with other packages, we decided to lift this restriction. - -## 3.16.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.16.0. -This release adds initial support for [Cron Monitoring](https://docs.sentry.io/product/crons/). - -> **Warning** -> Cron Monitoring is currently in beta. Beta features are still in-progress and may have bugs. We recognize the irony. -> If you have any questions or feedback, please email us at crons-feedback@sentry.io, reach out via Discord (#cronjobs), or open an issue. - -### Features - -- Add inital support for Cron Monitoring [(#1467)](https://github.com/getsentry/sentry-php/pull/1467) - - You can use Cron Monitoring to monitor your cron jobs. No pun intended. - - Add the code below to your application or script that is invoked by your cron job. - The first Check-In will let Sentry know that your job started, with the second Check-In reporting the outcome. + The SDK now ships with its own HTTP client based on cURL. A few new options were added. ```php - ', - status: CheckInStatus::inProgress(), - ); - - $event = Event::createCheckIn(); - $event->setCheckIn($checkIn); - - $this->hub->captureEvent($event); - - try { - - // do stuff - - $checkIn->setStatus(CheckInStatus::ok()); - } catch (Throwable $e) { - $checkIn->setStatus(CheckInStatus::error()); - } - - $event = Event::createCheckIn(); - $event->setCheckIn($checkIn); - - $this->hub->captureEvent($event); + Sentry\init([ + 'http_proxy_authentication' => 'username:password', // user name and password to use for proxy authentication + 'http_ssl_verify_peer' => false, // default true, verify the peer's SSL certificate + 'http_compression' => false, // default true, http request body compression + ]); ``` - If you only want to check if a cron did run, you may create a "Heartbeat" instead. - Add the code below to your application or script that is invoked by your cron job. - + To use a different client, you may use the `http_client` option. ```php - ', - status: CheckInStatus::ok(), // or - CheckInStatus::error() - duration: 10, // optional - duration in seconds - ); - - $event = Event::createCheckIn(); - $event->setCheckIn($checkIn); - - $this->hub->captureEvent($event); - ``` - -- Introduce a new `trace` helper function [(#1490)](https://github.com/getsentry/sentry-php/pull/1490) - - We made it a tad easier to add custom tracing spans to your application. - - ```php - $spanContext = new SpanContext(); - $spanContext->setOp('function'); - $spanContext->setDescription('Soemthing to be traced'); - - trace( - function (Scope $scope) { - // something to be traced - }, - $spanContext, - ); - ``` - -## 3.15.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.15.0. -This release adds initial support for [Profiling](https://docs.sentry.io/product/profiling/). - -> **Warning** -> Profiling is currently in beta. Beta features are still in-progress and may have bugs. We recognize the irony. -> If you have any questions or feedback, please email us at profiling@sentry.io, reach out via Discord (#profiling), or open an issue. - -Profiling is only available on Sentry SaaS (sentry.io). Support for Sentry self-hosted is planned once Profiling is released into GA. - -### Features - -- Add initial support for profiling [(#1477)](https://github.com/getsentry/sentry-php/pull/1477) - - Under the hood, we're using Wikipedia's sampling profiler [Excimer](https://github.com/wikimedia/mediawiki-php-excimer). - We chose this profiler for its low overhead and for being used in production by one of the largest PHP-powered websites in the world. - - Excimer works with PHP 7.2 and up, for PHP 8.2 support, make sure to use Excimer version 1.1.0. - - There is currently no support for either Windows or macOS. + use Sentry\Client; + use Sentry\HttpClient\HttpClientInterface; + use Sentry\HttpClient\Request; + use Sentry\HttpClient\Response; + use Sentry\Options; - You can install Excimer via your operating systems package manager. + $httpClient = new class() implements HttpClientInterface { + public function sendRequest(Request $request, Options $options): Response + { - ```bash - apt-get install php-excimer - ``` - - If no suitable version is available, you may build Excimer from source. - - ```bash - git clone https://github.com/wikimedia/mediawiki-php-excimer.git - - cd excimer/ - phpize && ./configure && make && sudo make install - ``` - - Depending on your environment, you may need to enable the Excimer extension afterward. - - ```bash - phpenmod -s fpm excimer - # or - phpenmod -s apache2 excimer - ``` - - Once the extension is installed, you may enable profiling by adding the new `profiles_sample_rate` config option to your `Sentry::init` method. - - ```php - \Sentry\init([ - 'dsn' => '__DSN__', - 'traces_sample_rate' => 1.0, - 'profiles_sample_rate' => 1.0, + // your custom implementation + + return new Response($response->getStatusCode(), $response->getHeaders(), ''); + } + }; + + Sentry\init([ + 'http_client' => $httpClient, ]); ``` - Profiles are being sampled in relation to your `traces_sample_rate`. - - Please note that the profiler is started inside transactions only. If you're not using our [Laravel](https://github.com/getsentry/sentry-laravel) or [Symfony](https://github.com/getsentry/sentry-symfony) SDKs, you may need to manually add transactions to your application as described [here](https://docs.sentry.io/platforms/php/performance/instrumentation/custom-instrumentation/). - - #### Other things you should consider: - - - The current sample rate of the profiler is set to 101Hz (every ~10ms). A minimum of two samples is required for a profile being sent, hence requests that finish in less than ~20ms won't hail any profiles. - - The maximum duration of a profile is 30s, hence we do not recommend enabling the extension in an CLI environment. - - By design, the profiler will take samples at the end of any userland functions. You may see long sample durations on tasks like HTTP client requests and DB queries. - You can read more about Excimer's architecture [here](https://techblog.wikimedia.org/2021/03/03/profiling-php-in-production-at-scale/). - -## 3.14.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.14.0. - -### Features - -- Add a new `enable_tracing: true/false` option, an alternative for `traces_sample_rate: 1.0/null` [(#1458)](https://github.com/getsentry/sentry-php/pull/1458) - -### Bug Fixes - -- Fix missing keys in the request body [(#1470)](https://github.com/getsentry/sentry-php/pull/1470) -- Add support for partial JSON encoding [(#1481)](https://github.com/getsentry/sentry-php/pull/1481) -- Prevent calling *magic methods* when retrieving the ID from an object [(#1483)](https://github.com/getsentry/sentry-php/pull/1483) -- Only serialize scalar object IDs [(#1485)](https://github.com/getsentry/sentry-php/pull/1485) - -### Misc - -- The SDK is now licensed under MIT [(#1471)](https://github.com/getsentry/sentry-php/pull/1471) - - Read more about Sentry's licensing [here](https://open.sentry.io/licensing/). -- Deprecate `Client::__construct` `$serializer` argument. It is currently un-used [(#1482)](https://github.com/getsentry/sentry-php/pull/1482) - -## 3.13.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.13.1. - -### Bug Fixes - -- Sanatize HTTP client spans & breadcrumbs [(#1453)](https://github.com/getsentry/sentry-php/pull/1453) -- Pin php-http/discovery to `< 1.15` to disable some unwanted side-effect introduced in this new minor version [(#1464)](https://github.com/getsentry/sentry-php/pull/1464) - -## 3.13.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.13.0. - -### Features - -- Object IDs are now automatically serialized as part of a stack trace frame [(#1443)](https://github.com/getsentry/sentry-php/pull/1443) - - If `Obj::getID()` or `Obj->id` is accessible, this value will be displayed inside the stack trace frame on the issue details page. - To attach local variables to your stack trace, make sure `zend.exception_ignore_arg: 0` is set in your `php.ini`. - See https://docs.sentry.io/platforms/php/troubleshooting/#missing-variables-in-stack-traces - -- Add more functionality to the `ExceptionMechanism::class` [(#1450)](https://github.com/getsentry/sentry-php/pull/1450) - - Attach arbitrary data - ```php - $hint = EventHint::fromArray([ - 'exception' => $exception, - 'mechanism' => new ExceptionMechanism( - ExceptionMechanism::TYPE_GENERIC, - false, - [ - 'key' => 'value', - //... - ], - ), - ]); - captureEvent(Event::createEvent(), $hint); - ``` - Learn more about the interface of the `ExceptionMechanism` on https://develop.sentry.dev/sdk/event-payloads/exception/#exception-mechanism - - Access or mutate `ExceptionMechanism::data` via `ExceptionMechanism::getData()` and `ExceptionMechanism::setData()` - - If an exception contains a user-provided `code`, the value will be serialized into the event and displayed on the issues details page. - ```php - throw new \Exception('Oh no!', 123); - ``` - -## 3.12.1 (2023-01-12) - -- fix: Allow `null` on `getTracesSampleRate` and `setTracesSampleRate` in `Options` class (#1441) - -## 3.12.0 (2022-11-22) - -- feat: Add `before_send_transaction` option (#1424) -- fix: Set `traces_sample_rate` to `null` by default (#1428) - -## 3.11.0 (2022-10-25) - -- fix: Only include the transaction name to the DSC if it has good quality (#1410) -- ref: Enable the ModulesIntegration by default (#1415) -- ref: Expose the ExceptionMechanism through the event hint (#1416) - -## 3.10.0 (2022-10-19) - -- ref: Add correct `never` option for `max_request_body_size` (#1397) - - Deprecate `max_request_body_size.none` in favour of `max_request_body_size.never` -- fix: Sampling now correctly takes in account the parent sampling decision if available instead of always being `false` when tracing is disabled (#1407) - -## 3.9.1 (2022-10-11) - -- fix: Suppress errors on is_callable (#1401) - -## 3.9.0 (2022-10-05) - -- feat: Add `trace_propagation_targets` option (#1396) -- feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) -- feat: Add support for Dynamic Sampling (#1360) - - Add `segment` to `UserDataBag` - - Add `TransactionSource`, to set information about the transaction name via `TransactionContext::setSource()` (#1382) - - Deprecate `TransactionContext::fromSentryTrace()` in favor of `TransactionContext::fromHeaders()` - -## 3.8.1 (2022-09-21) - -- fix: Use constant for the SDK version (#1374) -- fix: Do not throw an TypeError on numeric HTTP headers (#1370) - -## 3.8.0 (2022-09-05) - -- Add `Sentry\Monolog\BreadcrumbHandler`, a Monolog handler to allow registration of logs as breadcrumbs (#1199) -- Do not setup any error handlers if the DSN is null (#1349) -- Add setter for type on the `ExceptionDataBag` (#1347) -- Drop symfony/polyfill-uuid in favour of a standalone implementation (#1346) - -## 3.7.0 (2022-07-18) - -- Fix `Scope::getTransaction()` so that it returns also unsampled transactions (#1334) -- Set the event extras by taking the data from the Monolog record's extra (#1330) - -## 3.6.1 (2022-06-27) - -- Set the `sentry-trace` header when using the tracing middleware (#1331) - -## 3.6.0 (2022-06-10) - -- Add support for `monolog/monolog:^3.0` (#1321) -- Add `setTag` and `removeTag` public methods to `Event` for easier manipulation of tags (#1324) - -## 3.5.0 (2022-05-19) - -- Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) -- Fix stripping of memory addresses from stacktrace frames of anonymous classes in PHP `>=7.4.2` (#1314) -- Set the default `send_attempts` to `0` (this disables retries) and deprecate the option. If you require retries you can increase the `send_attempts` option to the desired value. (#1312) -- Add `http_connect_timeout` and `http_timeout` client options (#1282) - -## 3.4.0 (2022-03-14) - -- Update Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) -- Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization -- The `withScope` methods now return the callback's return value (#1263) -- Set the event extras by taking the data from the Monolog record's context (#1244) -- Make the `StacktraceBuilder` class part of the public API and add the `Client::getStacktraceBuilder()` method to build custom stacktraces (#1124) -- Support handling the server rate-limits when sending events to Sentry (#1291) -- Treat the project ID component of the DSN as a `string` rather than an `integer` (#1293) - -## 3.3.7 (2022-01-19) - -- Fix the serialization of a `callable` when the autoloader throws exceptions (#1280) - -## 3.3.6 (2022-01-14) - -- Optimize `Span` constructor and add benchmarks (#1274) -- Handle autoloader that throws an exception while trying to serialize a possible callable (#1276) - -## 3.3.5 (2021-12-27) - -- Bump the minimum required version of the `jean85/pretty-package-versions` package (#1267) - -## 3.3.4 (2021-11-08) - -- Avoid overwriting the error level set by the user on the event when capturing an `ErrorException` exception (#1251) -- Allow installing the project alongside Symfony `6.x` components (#1257) -- Run the test suite against PHP `8.1` (#1245) - -## 3.3.3 (2021-10-04) - -- Fix fatal error in the `EnvironmentIntegration` integration if the `php_uname` function is disabled (#1243) - -## 3.3.2 (2021-07-19) - -- Allow installation of `guzzlehttp/psr7:^2.0` (#1225) -- Allow installation of `psr/log:^1.0|^2.0|^3.0` (#1229) - -## 3.3.1 (2021-06-21) - -- Fix missing collecting of frames's arguments when using `captureEvent()` without expliciting a stacktrace or an exception (#1223) - -## 3.3.0 (2021-05-26) - -- Allow setting a custom timestamp on the breadcrumbs (#1193) -- Add option `ignore_tags` to `IgnoreErrorsIntegration` in order to ignore exceptions by tags values (#1201) - -## 3.2.2 (2021-05-06) - -- Fix missing handling of `EventHint` in the `HubAdapter::capture*()` methods (#1206) - -## 3.2.1 (2021-04-06) - -- Changes behaviour of `error_types` option when not set: before it defaulted to `error_reporting()` statically at SDK initialization; now it will be evaluated each time during error handling to allow silencing errors temporarily (#1196) - -## 3.2.0 (2021-03-03) - -- Make the HTTP headers sanitizable in the `RequestIntegration` integration instead of removing them entirely (#1161) -- Deprecate the `logger` option (#1167) -- Pass the event hint from the `capture*()` methods down to the `before_send` callback (#1138) -- Deprecate the `tags` option, see the [docs](https://docs.sentry.io/platforms/php/guides/laravel/enriching-events/tags/) for other ways to set tags (#1174) -- Make sure the `environment` field is set to `production` if it has not been overridden explicitly (#1116) - -## 3.1.5 (2021-02-18) - -- Fix incorrect detection of silenced errors (by the `@` operator) (#1183) - -## 3.1.4 (2021-02-02) - -- Allow jean85/pretty-package-versions 2.0 (#1170) - -## 3.1.3 (2021-01-25) - -- Fix the fetching of the version of the SDK (#1169) -- Add the `$customSamplingContext` argument to `Hub::startTransaction()` and `HubAdapter::startTransaction()` to fix deprecations thrown in Symfony (#1176) - -## 3.1.2 (2021-01-08) - -- Fix unwanted call to the `before_send` callback with transaction events, use `traces_sampler` instead to filter transactions (#1158) -- Fix the `logger` option not being applied to the event object (#1165) -- Fix a bug that made some event attributes being overwritten by option config values when calling `captureEvent()` (#1148) - -## 3.1.1 (2020-12-07) - -- Add support for PHP 8.0 (#1087) -- Change the error handling for silenced fatal errors using `@` to use a mask check in order to be php 8 compatible (#1141) -- Update the `guzzlehttp/promises` package to the minimum required version compatible with PHP 8 (#1144) -- Update the `symfony/options-resolver` package to the minimum required version compatible with PHP 8 (#1144) - -## 3.1.0 (2020-12-01) - -- Fix capturing of the request body in the `RequestIntegration` integration (#1139) -- Deprecate `SpanContext::fromTraceparent()` in favor of `TransactionContext::fromSentryTrace()` (#1134) -- Allow setting custom data on the sampling context by passing it as 2nd argument of the `startTransaction()` function (#1134) -- Add setter for value on the `ExceptionDataBag` (#1100) -- Add `Scope::removeTag` method (#1126) - -## 3.0.4 (2020-11-06) - -- Fix stacktrace missing from payload for non-exception events (#1123) -- Fix capturing of the request body in the `RequestIntegration` integration when the stream is empty (#1119) - -## 3.0.3 (2020-10-12) - -- Fix missing source code excerpts for stacktrace frames whose absolute file path is equal to the file path (#1104) -- Fix requirements to construct a valid object instance of the `UserDataBag` class (#1108) - -## 3.0.2 (2020-10-02) - -- Fix use of the `sample_rate` option rather than `traces_sample_rate` when capturing a `Transaction` (#1106) - -## 3.0.1 (2020-10-01) - -- Fix use of `Transaction` instead of `Span` in the `GuzzleMiddleware` middleware (#1099) - -## 3.0.0 (2020-09-28) - -**Tracing API** - -In this version we released API for Tracing. `\Sentry\startTransaction` is your entry point for manual instrumentation. -More information can be found in our [Performance](https://docs.sentry.io/platforms/php/performance/) docs. - -**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are -using an on-premise installation it requires Sentry version `>= v20.6.0` to work. If you are using -[sentry.io](https://sentry.io) nothing will change and no action is needed. - -- [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) -- [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) -- [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) -- [BC BREAK] Remove deprecated code to return the event ID as a `string` rather than an object instance from the transport, the client and the hub (#1036) -- [BC BREAK] Remove some deprecated methods from the `Options` class. (#1047) -- [BC BREAK] Remove the deprecated code from the `ModulesIntegration` integration (#1047) -- [BC BREAK] Remove the deprecated code from the `RequestIntegration` integration (#1047) -- [BC BREAK] Remove the deprecated code from the `Breadcrumb` class (#1047) -- [BC BREAK] Remove the deprecated methods from the `ClientBuilderInterface` interface and its implementations (#1047) -- [BC BREAK] The `Scope::setUser()` method now always merges the given data with the existing one instead of replacing it as a whole (#1047) -- [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) -- [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) -- [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) -- [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) -- [BC BREAK] Remove the `FlushableClientInterface` and the `ClosableTransportInterface` interfaces (#1079) -- [BC BREAK] Remove the `SpoolTransport` transport and all its related classes (#1080) -- Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) -- Refactor how the event data gets serialized to JSON (#1077) -- Add `traces_sampler` option to set custom sample rate callback (#1083) -- [BC BREAK] Add named constructors to the `Event` class (#1085) -- Raise the minimum version of PHP to `7.2` and the minimum version of some dependencies (#1088) -- [BC BREAK] Change the `captureEvent` to only accept an instance of the `Event` class rather than also a plain array (#1094) -- Add Guzzle middleware to trace performance of HTTP requests (#1096) - -## 3.0.0-beta1 (2020-09-03) - -**Tracing API** - -In this version we released API for Tracing. `\Sentry\startTransaction` is your entry point for manual instrumentation. -More information can be found in our [Performance](https://docs.sentry.io/product/performance/) docs or specific -[PHP SDK](https://docs.sentry.io/platforms/php/) docs. + To use a different transport, you may use the `transport` option. A custom transport must implement the `TransportInterface`. + If you use the `transport` option, the `http_client` option has no effect. -**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are -using an on-premise installation it requires Sentry version `>= v20.6.0` to work. If you are using -[sentry.io](https://sentry.io) nothing will change and no action is needed. +# Misc -- [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) -- [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) -- [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) -- [BC BREAK] Remove deprecated code to return the event ID as a `string` rather than an object instance from the transport, the client and the hub (#1036) -- [BC BREAK] Remove some deprecated methods from the `Options` class. (#1047) -- [BC BREAK] Remove the deprecated code from the `ModulesIntegration` integration (#1047) -- [BC BREAK] Remove the deprecated code from the `RequestIntegration` integration (#1047) -- [BC BREAK] Remove the deprecated code from the `Breadcrumb` class (#1047) -- [BC BREAK] Remove the deprecated methods from the `ClientBuilderInterface` interface and its implementations (#1047) -- [BC BREAK] The `Scope::setUser()` method now always merges the given data with the existing one instead of replacing it as a whole (#1047) -- [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) -- [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) -- [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) -- [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) -- [BC BREAK] Remove the `FlushableClientInterface` and the `ClosableTransportInterface` interfaces (#1079) -- [BC BREAK] Remove the `SpoolTransport` transport and all its related classes (#1080) -- Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) -- Refactor how the event data gets serialized to JSON (#1077) +- The abandoned package `php-http/message-factory` was removed. \ No newline at end of file diff --git a/README.md b/README.md index 12da4a6b2..1acc03d2a 100644 --- a/README.md +++ b/README.md @@ -24,37 +24,16 @@ information needed to prioritize, identify, reproduce and fix each issue. ### Install -To install the SDK you will need to be using [Composer]([https://getcomposer.org/) -in your project. To install it please see the [docs](https://getcomposer.org/download/). - -This is our "core" SDK, meaning that all the important code regarding error handling lives here. -If you are happy with using the HTTP client we recommend install the SDK like: [`sentry/sdk`](https://github.com/getsentry/sentry-php-sdk) +Install the SDK using [Composer](https://getcomposer.org/). ```bash -composer require sentry/sdk +composer require sentry/sentry ``` -This package (`sentry/sentry`) is not tied to any specific library that sends HTTP messages. Instead, -it uses [Httplug](https://github.com/php-http/httplug) to let users choose whichever -PSR-7 implementation and HTTP client they want to use. - -If you just want to get started quickly you should run the following command: - -```bash -composer require sentry/sentry php-http/curl-client -``` - -This is basically what our metapackage (`sentry/sdk`) provides. - -This will install the library itself along with an HTTP client adapter that uses -cURL as transport method (provided by Httplug). You do not have to use those -packages if you do not want to. The SDK does not care about which transport method -you want to use because it's an implementation detail of your application. You may -use any package that provides [`php-http/async-client-implementation`](https://packagist.org/providers/php-http/async-client-implementation) -and [`http-message-implementation`](https://packagist.org/providers/psr/http-message-implementation). - ### Configuration +Initialize the SDK as early as possible in your application. + ```php \Sentry\init(['dsn' => '___PUBLIC_DSN___' ]); ``` @@ -76,7 +55,7 @@ The following integrations are fully supported and maintained by the Sentry team - [Symfony](https://github.com/getsentry/sentry-symfony) - [Laravel](https://github.com/getsentry/sentry-laravel) -## 3rd party integrations +## 3rd party integrations using the old SDK 3.x The following integrations are available and maintained by members of the Sentry community. @@ -91,14 +70,14 @@ The following integrations are available and maintained by members of the Sentry - [October CMS](https://github.com/OFFLINE-GmbH/oc-sentry-plugin) - ... feel free to be famous, create a port to your favourite platform! -## 3rd party integrations using old SDK 2.x +## 3rd party integrations using the old SDK 2.x - [Neos Flow](https://github.com/networkteam/Networkteam.SentryClient) - [OXID eShop](https://github.com/OXIDprojects/sentry) - [TYPO3](https://github.com/networkteam/sentry_client) - [CakePHP](https://github.com/Connehito/cake-sentry/tree/3.x) -## 3rd party integrations using old SDK 1.x +## 3rd party integrations using the old SDK 1.x - [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient) - [OpenCart](https://github.com/BurdaPraha/oc_sentry) @@ -116,7 +95,7 @@ Please refer to [CONTRIBUTING.md](CONTRIBUTING.md). ## Getting help/support -If you need help setting up or configuring the PHP SDK (or anything else in the Sentry universe) please head over to the [Sentry Community on Discord](https://discord.com/invite/Ww9hbqr). There is a ton of great people in our Discord community ready to help you! +If you need help setting up or configuring the PHP SDK (or anything else in the Sentry universe) please head over to the [Sentry Community on Discord](https://discord.com/invite/sentry). There is a ton of great people in our Discord community ready to help you! ## Resources diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 34a12f5a9..16eaf1ce9 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -1,13 +1,65 @@ # Upgrade 3.x to 4.0 -- The `send_attempts` option was removed. You may implement a custom transport if you rely on this behaviour. -- `SpanContext::fromTraceparent()` was removed. Use `Sentry\continueTrace()` instead. -- `TransactionContext::fromSentryTrace()` was removed. Use `Sentry\continueTrace()` instead. +- This version exclusively uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/) to send event data to Sentry. + + If you are using [sentry.io](https://sentry.io), no action is needed. + If you are using an on-premise/self-hosted installation of Sentry, the minimum requirement is now version `>= v20.6.0`. + +- Added `ext-curl` as a composer requirement. + - The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_errors` option instead. -- `Sentry\Exception\InvalidArgumentException` was removed. Use `\InvalidArgumentException` instead. -- `Sentry\Exception/ExceptionInterface` was removed. -- Removed `ClientBuilderInterface::setSerializer()`. + + ```php + Sentry\init([ + 'ignore_exceptions' => [BadThingsHappenedException::class], + ]); + ``` + + This option performs an [`is_a`](https://www.php.net/manual/en/function.is-a.php) check, so you can also ignore more generic exceptions. + +- Removed support for `symfony/options-resolver: ^3.4.43`. + +- The `RequestFetcher` now relies on `guzzlehttp/psr7: ^1.8.4|^2.1.1`. + +- Added new methods to `ClientInterface` + + ```php + public function getCspReportUrl(): ?string; + + public function getStacktraceBuilder(): StacktraceBuilder; + ``` + +- Added new methods to `HubInterface` + + ```php + public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string; + ``` + +- The new default value for the `trace_propagation_targets` option is now `null`. To not attach any headers to outgoing requests, using the `GuzzleTracingMiddleware`, set this option to `[]`. +- The `ignore_errors` option now performs a `is_a` check on the provided class strings. +- The `send_attempts` option was removed. You may implement a custom transport if you rely on this behaviour. +- The `enable_compression` option was removed. Use `http_compression` instead. +- The `logger` option now accepts a `Psr\Log\LoggerInterface` instance instead of `string`. + +- Removed `Options::getSendAttempts/setSendAttempts()`. +- Removed `Options::isCompressionEnabled/setEnableCompression()`. Use `Options::isHttpCompressionEnabled/setEnableHttpCompression()` instead. +- Removed `SpanContext::fromTraceparent()`. Use `Sentry\continueTrace()` instead. +- Removed `TransactionContext::fromSentryTrace()`. Use `Sentry\continueTrace()` instead. +- Removed `Sentry\Exception\InvalidArgumentException`. Use `\InvalidArgumentException` instead. +- Removed `Sentry\Exception/ExceptionInterface`. +- Removed `ClientBuilderInterface()`. - Removed `ClientBuilder::setSerializer()`. -- Removed `Client::__construct()` param SerializerInterface $serializer. -- Change return type of `Dsn:: getProjectId()` to string. -- Most setters now return `$this` instead of `void`. +- Removed `ClientBuilder::setTransportFactory()`. You can set a custom transport via the `transport` option. +- Removed `Client::__construct()` parameter `SerializerInterface $serializer`. +- Removed `TransportFactoryInterface`. +- Removed `DefaultTransportFactory`. +- Removed `HttpClientFactoryInterface`. +- Removed `HttpClientFactory`. +- Removed `NullTransport`. +- Removed `Dsn::getSecretKey()`. +- Removed `Dsn::setSecretKey()`. +- Removed `EventType::default()`. + +- Added return type to `Dsn::getProjectId(): string`. +- Changed return type to `Options::getLogger(): ?LoggerInterface`. +- Changed parameter type of `Options::setLogger(LoggerInterface $logger)`. From 7e10bf3fd0bee2f2eee6981e509522ab0c523d5e Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 6 Nov 2023 10:16:08 +0000 Subject: [PATCH 0930/1161] release: 4.0.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 5cc99cd3e..c67bb1fd3 100644 --- a/src/Client.php +++ b/src/Client.php @@ -34,7 +34,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '3.22.0'; + public const SDK_VERSION = '4.0.0'; /** * @var Options The client options From 045127877c43b880f014d96e92e37ff700714076 Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Mon, 6 Nov 2023 12:46:03 +0100 Subject: [PATCH 0931/1161] Fix invalid option name (#1627) --- CHANGELOG.md | 12 ++++++------ UPGRADE-4.0.md | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f353f04..0095c1e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,14 +15,14 @@ Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list o - You need to have `ext-curl` installed to use the SDK. -- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_errors` option instead. +- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_exceptions` option instead. ```php Sentry\init([ 'ignore_exceptions' => [BadThingsHappenedException::class], ]); ``` - + This option performs an [`is_a`](https://www.php.net/manual/en/function.is-a.php) check now, so you can also ignore more generic exceptions. # Features @@ -57,7 +57,7 @@ Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list o // After \Sentry\addBreadcrumb( - category: 'auth', + category: 'auth', message: 'User authenticated', // optional metadata: ['user_id' => $userId], // optional level: Breadcrumb::LEVEL_INFO, // set by default @@ -108,11 +108,11 @@ Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list o { // your custom implementation - + return new Response($response->getStatusCode(), $response->getHeaders(), ''); } }; - + Sentry\init([ 'http_client' => $httpClient, ]); @@ -123,4 +123,4 @@ Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list o # Misc -- The abandoned package `php-http/message-factory` was removed. \ No newline at end of file +- The abandoned package `php-http/message-factory` was removed. diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 16eaf1ce9..49194cae9 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -7,14 +7,14 @@ - Added `ext-curl` as a composer requirement. -- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_errors` option instead. +- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_exceptions` option instead. ```php Sentry\init([ 'ignore_exceptions' => [BadThingsHappenedException::class], ]); ``` - + This option performs an [`is_a`](https://www.php.net/manual/en/function.is-a.php) check, so you can also ignore more generic exceptions. - Removed support for `symfony/options-resolver: ^3.4.43`. @@ -36,7 +36,7 @@ ``` - The new default value for the `trace_propagation_targets` option is now `null`. To not attach any headers to outgoing requests, using the `GuzzleTracingMiddleware`, set this option to `[]`. -- The `ignore_errors` option now performs a `is_a` check on the provided class strings. +- The `ignore_exceptions` option now performs a `is_a` check on the provided class strings. - The `send_attempts` option was removed. You may implement a custom transport if you rely on this behaviour. - The `enable_compression` option was removed. Use `http_compression` instead. - The `logger` option now accepts a `Psr\Log\LoggerInterface` instance instead of `string`. From 5fb16a5c2773090c5ac440da21ba70e12e08c5c6 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 6 Nov 2023 13:08:28 +0100 Subject: [PATCH 0932/1161] Add permissions to GitHub actions (#1628) --- .github/workflows/ci.yml | 5 +++++ .github/workflows/publish-release.yaml | 3 +++ .github/workflows/static-analysis.yaml | 3 +++ 3 files changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7b150bed..6b8e8c43b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,11 +7,16 @@ on: - master - develop - release/** + +permissions: + contents: read + # Cancel in progress workflows on pull_requests. # https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true + jobs: tests: name: Tests diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 73f83e74e..e61043eb7 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -10,6 +10,9 @@ on: description: Force a release even when there are release-blockers (optional) required: false +permissions: + contents: read + jobs: release: runs-on: ubuntu-latest diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 3c95813ec..d7e3c5124 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -7,6 +7,9 @@ on: - master - develop +permissions: + contents: read + jobs: php-cs-fixer: name: PHP-CS-Fixer From 574d50429aa92beacab62300ea068f1ec8c6b3d7 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 7 Nov 2023 08:43:56 +0100 Subject: [PATCH 0933/1161] E2E HTTP client tests (#1626) --- tests/HttpClient/HttpClientTest.php | 98 +++++++++++++++++++ tests/HttpClient/TestServer.php | 145 ++++++++++++++++++++++++++++ tests/testserver/.gitignore | 1 + tests/testserver/index.php | 48 +++++++++ 4 files changed, 292 insertions(+) create mode 100644 tests/HttpClient/HttpClientTest.php create mode 100644 tests/HttpClient/TestServer.php create mode 100644 tests/testserver/.gitignore create mode 100644 tests/testserver/index.php diff --git a/tests/HttpClient/HttpClientTest.php b/tests/HttpClient/HttpClientTest.php new file mode 100644 index 000000000..e6b0d23f0 --- /dev/null +++ b/tests/HttpClient/HttpClientTest.php @@ -0,0 +1,98 @@ +startTestServer(); + + $options = new Options([ + 'dsn' => "http://publicKey@{$testServer}/200", + ]); + + $request = new Request(); + $request->setStringBody('test'); + + $client = new HttpClient($sdkIdentifier = 'sentry.php', $sdkVersion = 'testing'); + $response = $client->sendRequest($request, $options); + + $serverOutput = $this->stopTestServer(); + + $this->assertTrue($response->isSuccess()); + $this->assertEquals(200, $response->getStatusCode()); + + // This assertion is here to test that the response headers are correctly parsed + $this->assertEquals(200, (int) $response->getHeaderLine('x-sentry-test-server-status-code')); + + $this->assertTrue($serverOutput['compressed']); + $this->assertEquals($response->getStatusCode(), $serverOutput['status']); + $this->assertEquals($request->getStringBody(), $serverOutput['body']); + $this->assertEquals('/api/200/envelope/', $serverOutput['server']['REQUEST_URI']); + $this->assertEquals("{$sdkIdentifier}/{$sdkVersion}", $serverOutput['headers']['User-Agent']); + + $expectedHeaders = Http::getRequestHeaders($options->getDsn(), $sdkIdentifier, $sdkVersion); + foreach ($expectedHeaders as $expectedHeader) { + [$headerName, $headerValue] = explode(': ', $expectedHeader); + $this->assertEquals($headerValue, $serverOutput['headers'][$headerName]); + } + } + + public function testClientMakesUncompressedRequestWhenCompressionDisabled(): void + { + $testServer = $this->startTestServer(); + + $options = new Options([ + 'dsn' => "http://publicKey@{$testServer}/200", + 'http_compression' => false, + ]); + + $request = new Request(); + $request->setStringBody('test'); + + $client = new HttpClient('sentry.php', 'testing'); + $response = $client->sendRequest($request, $options); + + $serverOutput = $this->stopTestServer(); + + $this->assertTrue($response->isSuccess()); + $this->assertFalse($serverOutput['compressed']); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals($response->getStatusCode(), $serverOutput['status']); + $this->assertEquals($request->getStringBody(), $serverOutput['body']); + $this->assertEquals(\strlen($request->getStringBody()), $serverOutput['headers']['Content-Length']); + } + + public function testThrowsExceptionIfDsnOptionIsNotSet(): void + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The DSN option must be set to use the HttpClient.'); + + $options = new Options(['dsn' => null]); + + $client = new HttpClient('sentry.php', 'testing'); + $client->sendRequest(new Request(), $options); + } + + public function testThrowsExceptionIfRequestDataIsEmpty(): void + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The request data is empty.'); + + $options = new Options(['dsn' => 'https://publicKey@example.com/1']); + + $client = new HttpClient('sentry.php', 'testing'); + $client->sendRequest(new Request(), $options); + } +} diff --git a/tests/HttpClient/TestServer.php b/tests/HttpClient/TestServer.php new file mode 100644 index 000000000..5a12b2cf8 --- /dev/null +++ b/tests/HttpClient/TestServer.php @@ -0,0 +1,145 @@ +startTestServer()` to start the server and get the address. + * After you have made your request, call `$this->stopTestServer()` to stop the server and get the output. + * + * Thanks to Stripe for the inspiration: https://github.com/stripe/stripe-php/blob/e0a960c8655b21b21c3ba2e5927f432eeda9105f/tests/TestServer.php + */ +trait TestServer +{ + /** + * @var string the path to the output file + */ + protected static $serverOutputFile = __DIR__ . '/../testserver/output.json'; + + /** + * @var resource|null the server process handle + */ + protected $serverProcess; + + /** + * @var resource|null the server stderr handle + */ + protected $serverStderr; + + /** + * @var int the port on which the server is listening, this default value was randomly chosen + */ + protected $serverPort = 44884; + + public function startTestServer(): string + { + if ($this->serverProcess !== null) { + throw new \RuntimeException('There is already a test server instance running.'); + } + + if (file_exists(self::$serverOutputFile)) { + unlink(self::$serverOutputFile); + } + + $pipes = []; + + $this->serverProcess = proc_open( + $command = sprintf( + 'php -S localhost:%d -t %s', + $this->serverPort, + realpath(__DIR__ . '/../testserver') + ), + [2 => ['pipe', 'w']], + $pipes + ); + + $this->serverStderr = $pipes[2]; + + $pid = proc_get_status($this->serverProcess)['pid']; + + if (!\is_resource($this->serverProcess)) { + throw new \RuntimeException("Error starting test server on pid {$pid}, command failed: {$command}"); + } + + while (true) { + $conn = @fsockopen('localhost', $this->serverPort); + if (\is_resource($conn)) { + fclose($conn); + + break; + } + } + + return "localhost:{$this->serverPort}"; + } + + /** + * Stop the test server and return the output from the server. + * + * @return array{ + * body: string, + * status: int, + * server: array, + * headers: array, + * compressed: bool, + * } + */ + public function stopTestServer(): array + { + if (!$this->serverProcess) { + throw new \RuntimeException('There is no test server instance running.'); + } + + for ($i = 0; $i < 20; ++$i) { + $status = proc_get_status($this->serverProcess); + + if (!$status['running']) { + break; + } + + $this->killServerProcess($status['pid']); + + usleep(10000); + } + + if ($status['running']) { + throw new \RuntimeException('Could not kill test server'); + } + + if (!file_exists(self::$serverOutputFile)) { + stream_set_blocking($this->serverStderr, false); + $stderrOutput = stream_get_contents($this->serverStderr); + + echo $stderrOutput . \PHP_EOL; + + throw new \RuntimeException('Test server did not write output file'); + } + + proc_close($this->serverProcess); + + $this->serverProcess = null; + $this->serverStderr = null; + + return json_decode(file_get_contents(self::$serverOutputFile), true); + } + + private function killServerProcess(int $pid): void + { + if (\PHP_OS_FAMILY === 'Windows') { + exec("taskkill /pid {$pid} /f /t"); + } else { + // Kills any child processes -- the php test server appears to start up a child. + exec("pkill -P {$pid}"); + + // Kill the parent process. + exec("kill {$pid}"); + } + + proc_terminate($this->serverProcess, 9); + } +} diff --git a/tests/testserver/.gitignore b/tests/testserver/.gitignore new file mode 100644 index 000000000..e102ff534 --- /dev/null +++ b/tests/testserver/.gitignore @@ -0,0 +1 @@ +output.json diff --git a/tests/testserver/index.php b/tests/testserver/index.php new file mode 100644 index 000000000..ed763a59d --- /dev/null +++ b/tests/testserver/index.php @@ -0,0 +1,48 @@ +/envelope/`. +// We use the project ID to determine the status code so we need to extract it from the path +$path = trim(parse_url($_SERVER['REQUEST_URI'], \PHP_URL_PATH), '/'); + +if (!preg_match('/api\/\d+\/envelope/', $path)) { + http_response_code(204); + + return; +} + +$status = (int) explode('/', $path)[1]; + +$headers = getallheaders(); + +$rawBody = file_get_contents('php://input'); + +$compressed = false; + +if (!isset($headers['Content-Encoding'])) { + $body = $rawBody; +} elseif ($headers['Content-Encoding'] === 'gzip') { + $body = gzdecode($rawBody); + $compressed = true; +} else { + $body = '__unable to decode body__'; +} + +$output = [ + 'body' => $body, + 'status' => $status, + 'server' => $_SERVER, + 'headers' => $headers, + 'compressed' => $compressed, +]; + +file_put_contents(__DIR__ . '/output.json', json_encode($output, \JSON_PRETTY_PRINT)); + +header('X-Sentry-Test-Server-Status-Code: ' . $status); + +http_response_code($status); + +return 'Processed.'; From da24b20dd8096650d9406310b6799da1832be16c Mon Sep 17 00:00:00 2001 From: mark burdett Date: Mon, 6 Nov 2023 23:51:03 -0800 Subject: [PATCH 0934/1161] Drupal integration supports the 4.x SDK (#1630) --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1acc03d2a..277c3e537 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,15 @@ The following integrations are fully supported and maintained by the Sentry team - [Symfony](https://github.com/getsentry/sentry-symfony) - [Laravel](https://github.com/getsentry/sentry-laravel) -## 3rd party integrations using the old SDK 3.x +## 3rd party integrations using SDK 4.x The following integrations are available and maintained by members of the Sentry community. - [Drupal](https://www.drupal.org/project/raven) +- ... feel free to be famous, create a port to your favourite platform! + +## 3rd party integrations using the old SDK 3.x + - [Neos Flow](https://github.com/flownative/flow-sentry) - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [ZendFramework](https://github.com/facile-it/sentry-module) @@ -68,7 +72,6 @@ The following integrations are available and maintained by members of the Sentry - [CakePHP 3.0 - 4.3](https://github.com/Connehito/cake-sentry) - [CakePHP 4.4+](https://github.com/lordsimal/cakephp-sentry) - [October CMS](https://github.com/OFFLINE-GmbH/oc-sentry-plugin) -- ... feel free to be famous, create a port to your favourite platform! ## 3rd party integrations using the old SDK 2.x From fbe4c4a3e1b26ca5302d1c431359226c92b1fba3 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 7 Nov 2023 12:04:44 +0100 Subject: [PATCH 0935/1161] WordPress integration supports the 4.x SDK (#1631) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 277c3e537..0302ada86 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,12 @@ The following integrations are fully supported and maintained by the Sentry team The following integrations are available and maintained by members of the Sentry community. - [Drupal](https://www.drupal.org/project/raven) +- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - ... feel free to be famous, create a port to your favourite platform! ## 3rd party integrations using the old SDK 3.x - [Neos Flow](https://github.com/flownative/flow-sentry) -- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - [ZendFramework](https://github.com/facile-it/sentry-module) - [Yii2](https://github.com/notamedia/yii2-sentry) - [Silverstripe](https://github.com/phptek/silverstripe-sentry) From 198828374e21e25b3e1c66ad956fee61b19cccd5 Mon Sep 17 00:00:00 2001 From: mark burdett Date: Tue, 7 Nov 2023 12:55:00 -0800 Subject: [PATCH 0936/1161] Check if the cURL extension is installed (#1632) --- src/HttpClient/HttpClient.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 96a3dd01a..5c98fe7bf 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -40,6 +40,10 @@ public function sendRequest(Request $request, Options $options): Response throw new \RuntimeException('The request data is empty.'); } + if (!\extension_loaded('curl')) { + throw new \RuntimeException('The cURL PHP extension must be enabled to use the HttpClient.'); + } + $curlHandle = curl_init(); $requestHeaders = Http::getRequestHeaders($dsn, $this->sdkIdentifier, $this->sdkVersion); From 33f6bf7a5f58c6f7aded76e98950b5b0c22dc01c Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 12:04:40 +0100 Subject: [PATCH 0937/1161] Fix capturing OOM errors when very memory constrained (#1636) --- .github/workflows/ci.yml | 7 +- phpunit.xml.dist | 6 +- src/ErrorHandler.php | 63 +++++++++++++-- ...ry_fatal_error_increases_memory_limit.phpt | 77 +++++++++++++++++++ ...er_captures_out_of_memory_fatal_error.phpt | 22 +++--- ...r_handler_handles_exception_only_once.phpt | 36 +++++++++ 6 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 tests/phpt-oom/out_of_memory_fatal_error_increases_memory_limit.phpt create mode 100644 tests/phpt/error_handler_handles_exception_only_once.phpt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b8e8c43b..153d44d88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,8 +74,11 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest if: ${{ matrix.dependencies == 'lowest' }} - - name: Run tests - run: vendor/bin/phpunit --coverage-clover=coverage.xml + - name: Run unit tests + run: vendor/bin/phpunit --testsuite unit --coverage-clover=coverage.xml + # The reason for running some OOM tests without coverage is that because the coverage information collector can cause another OOM event invalidating the test + - name: Run out of memory tests (without coverage) + run: vendor/bin/phpunit --testsuite oom --no-coverage - name: Upload code coverage uses: codecov/codecov-action@v3 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0f5d14742..d064f8775 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,10 +12,14 @@
- + tests tests/phpt + + + tests/phpt-oom + diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index fc6ff3253..ef558d919 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -22,7 +22,14 @@ final class ErrorHandler * * @internal */ - public const DEFAULT_RESERVED_MEMORY_SIZE = 10240; + public const DEFAULT_RESERVED_MEMORY_SIZE = 16 * 1024; // 16 KiB + + /** + * The regular expression used to match the message of an out of memory error. + * + * Regex inspired by https://github.com/php/php-src/blob/524b13460752fba908f88e3c4428b91fa66c083a/Zend/tests/new_oom.phpt#L15 + */ + private const OOM_MESSAGE_MATCHER = '/^Allowed memory size of (?\d+) bytes exhausted[^\r\n]* \(tried to allocate \d+ bytes\)/'; /** * The fatal error types that cannot be silenced using the @ operator in PHP 8+. @@ -89,11 +96,27 @@ final class ErrorHandler private $isFatalErrorHandlerRegistered = false; /** - * @var string|null A portion of pre-allocated memory data that will be reclaimed - * in case a fatal error occurs to handle it + * @var int|null the amount of bytes of memory to increase the memory limit by when we are capturing a out of memory error, set to null to not increase the memory limit + */ + private $memoryLimitIncreaseOnOutOfMemoryErrorValue = 5 * 1024 * 1024; // 5 MiB + + /** + * @var bool Whether the memory limit has been increased + */ + private static $didIncreaseMemoryLimit = false; + + /** + * @var string|null A portion of pre-allocated memory data that will be reclaimed in case a fatal error occurs to handle it + * + * @phpstan-ignore-next-line This property is used to reserve memory for the fatal error handler and is thus never read */ private static $reservedMemory; + /** + * @var bool Whether the fatal error handler should be disabled + */ + private static $disableFatalErrorHandler = false; + /** * @var string[] List of error levels and their description */ @@ -254,6 +277,20 @@ public function addExceptionHandlerListener(callable $listener): void $this->exceptionListeners[] = $listener; } + /** + * Sets the amount of memory to increase the memory limit by when we are capturing a out of memory error. + * + * @param int|null $valueInBytes the number of bytes to increase the memory limit by, or null to not increase the memory limit + */ + public function setMemoryLimitIncreaseOnOutOfMemoryErrorInBytes(?int $valueInBytes): void + { + if ($valueInBytes !== null && $valueInBytes <= 0) { + throw new \InvalidArgumentException('The $valueInBytes argument must be greater than 0 or null.'); + } + + $this->memoryLimitIncreaseOnOutOfMemoryErrorValue = $valueInBytes; + } + /** * Handles errors by capturing them through the client according to the * configured bit field. @@ -315,16 +352,28 @@ private function handleError(int $level, string $message, string $file, int $lin */ private function handleFatalError(): void { - // If there is not enough memory that can be used to handle the error - // do nothing - if (self::$reservedMemory === null) { + if (self::$disableFatalErrorHandler) { return; } + // Free the reserved memory that allows us to potentially handle OOM errors self::$reservedMemory = null; + $error = error_get_last(); if (!empty($error) && $error['type'] & (\E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_CORE_WARNING | \E_COMPILE_ERROR | \E_COMPILE_WARNING)) { + // If we did not do so already and we are allowed to increase the memory limit, we do so when we detect an OOM error + if (self::$didIncreaseMemoryLimit === false + && $this->memoryLimitIncreaseOnOutOfMemoryErrorValue !== null + && preg_match(self::OOM_MESSAGE_MATCHER, $error['message'], $matches) === 1 + ) { + $currentMemoryLimit = (int) $matches['memory_limit']; + + ini_set('memory_limit', (string) ($currentMemoryLimit + $this->memoryLimitIncreaseOnOutOfMemoryErrorValue)); + + self::$didIncreaseMemoryLimit = true; + } + $errorAsException = new FatalErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']); $this->exceptionReflection->setValue($errorAsException, []); @@ -369,7 +418,7 @@ private function handleException(\Throwable $exception): void // native PHP handler to prevent an infinite loop if ($exception === $previousExceptionHandlerException) { // Disable the fatal error handler or the error will be reported twice - self::$reservedMemory = null; + self::$disableFatalErrorHandler = true; throw $exception; } diff --git a/tests/phpt-oom/out_of_memory_fatal_error_increases_memory_limit.phpt b/tests/phpt-oom/out_of_memory_fatal_error_increases_memory_limit.phpt new file mode 100644 index 000000000..acef1edd9 --- /dev/null +++ b/tests/phpt-oom/out_of_memory_fatal_error_increases_memory_limit.phpt @@ -0,0 +1,77 @@ +--TEST-- +Test that when handling a out of memory error the memory limit is increased with 5 MiB and the event is serialized and ready to be sent +--INI-- +memory_limit=67108864 +--FILE-- + 'http://public@example.com/sentry/1', +]); + +$transport = new class(new PayloadSerializer($options)) implements TransportInterface { + private $payloadSerializer; + + public function __construct(PayloadSerializerInterface $payloadSerializer) + { + $this->payloadSerializer = $payloadSerializer; + } + + public function send(Event $event): Result + { + $serialized = $this->payloadSerializer->serialize($event); + + echo 'Transport called' . \PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); + } +}; + +$options->setTransport($transport); + +$client = (new ClientBuilder($options))->getClient(); + +SentrySdk::init()->bindClient($client); + +echo 'Before OOM memory limit: ' . \ini_get('memory_limit'); + +register_shutdown_function(function () { + echo 'After OOM memory limit: ' . \ini_get('memory_limit'); +}); + +$array = []; +for ($i = 0; $i < 100000000; ++$i) { + $array[] = 'sentry'; +} +--EXPECTF-- +Before OOM memory limit: 67108864 +Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d +Transport called +After OOM memory limit: 72351744 diff --git a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt index 0c788d88b..bcc62e32b 100644 --- a/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_out_of_memory_fatal_error.phpt @@ -1,7 +1,7 @@ --TEST-- -Test catching out of memory fatal error +Test catching out of memory fatal error without increasing memory limit --INI-- -memory_limit=128M +memory_limit=67108864 --FILE-- addErrorHandlerListener(static function (): void { - echo 'Error listener called (it should not have been)' . PHP_EOL; -}); - -$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(1024 * 1024); +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); $errorHandler->addFatalErrorHandlerListener(static function (): void { echo 'Fatal error listener called' . PHP_EOL; -}); -$errorHandler = ErrorHandler::registerOnceExceptionHandler(); -$errorHandler->addExceptionHandlerListener(static function (): void { - echo 'Exception listener called (it should not have been)' . PHP_EOL; + echo 'After OOM memory limit: ' . ini_get('memory_limit'); }); +$errorHandler->setMemoryLimitIncreaseOnOutOfMemoryErrorInBytes(null); + +echo 'Before OOM memory limit: ' . ini_get('memory_limit'); + $foo = str_repeat('x', 1024 * 1024 * 1024); ?> --EXPECTF-- +Before OOM memory limit: 67108864 Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d Fatal error listener called +After OOM memory limit: 67108864 diff --git a/tests/phpt/error_handler_handles_exception_only_once.phpt b/tests/phpt/error_handler_handles_exception_only_once.phpt new file mode 100644 index 000000000..b57432a79 --- /dev/null +++ b/tests/phpt/error_handler_handles_exception_only_once.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test that exceptions are only handled once +--FILE-- +addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called (should not happen)' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { + echo 'Exception listener called' . PHP_EOL; +}); + +throw new \Exception('foo bar'); +--EXPECTF-- +Exception listener called + +Fatal error: Uncaught Exception: foo bar in %s:%d +Stack trace: +%a From df4c078a349f588fc7c704d66f3c46c26487d9e2 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 12:24:35 +0100 Subject: [PATCH 0938/1161] Prepare 4.0.1 (#1637) --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0095c1e6e..856adcf02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG +## 4.0.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.0.1. + +### Bug Fixes + +- Fix capturing out-of-memory errors when memory-constrained [(#1636)](https://github.com/getsentry/sentry-php/pull/1636) +- Check if the cURL extension is installed [(#1632)](https://github.com/getsentry/sentry-php/pull/1632) + ## 4.0.0 The Sentry SDK team is thrilled to announce the immediate availability of Sentry PHP SDK v4.0.0. From d37e615aee7bb4012fc6562835eea67deb817e89 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 13:56:48 +0100 Subject: [PATCH 0939/1161] Improve flakiness of HTTP E2E tests (#1640) --- tests/HttpClient/TestServer.php | 19 +++++++++++++++---- tests/testserver/index.php | 8 ++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/HttpClient/TestServer.php b/tests/HttpClient/TestServer.php index 5a12b2cf8..901dc26f6 100644 --- a/tests/HttpClient/TestServer.php +++ b/tests/HttpClient/TestServer.php @@ -66,16 +66,27 @@ public function startTestServer(): string throw new \RuntimeException("Error starting test server on pid {$pid}, command failed: {$command}"); } + $address = "localhost:{$this->serverPort}"; + + $streamContext = stream_context_create(['http' => ['timeout' => 1]]); + + // Wait for the server to be ready to answer HTTP requests while (true) { - $conn = @fsockopen('localhost', $this->serverPort); - if (\is_resource($conn)) { - fclose($conn); + $response = @file_get_contents("http://{$address}/ping", false, $streamContext); + if ($response === 'pong') { break; } + + usleep(10000); + } + + // Ensure the process is still running + if (!proc_get_status($this->serverProcess)['running']) { + throw new \RuntimeException("Error starting test server on pid {$pid}, command failed: {$command}"); } - return "localhost:{$this->serverPort}"; + return $address; } /** diff --git a/tests/testserver/index.php b/tests/testserver/index.php index ed763a59d..b7f0709a4 100644 --- a/tests/testserver/index.php +++ b/tests/testserver/index.php @@ -8,6 +8,14 @@ // We use the project ID to determine the status code so we need to extract it from the path $path = trim(parse_url($_SERVER['REQUEST_URI'], \PHP_URL_PATH), '/'); +if (strpos($path, 'ping') === 0) { + http_response_code(200); + + echo 'pong'; + + return; +} + if (!preg_match('/api\/\d+\/envelope/', $path)) { http_response_code(204); From 304b85cf42478dd19a7a8059800dd3680cac8cb5 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 13 Nov 2023 11:25:29 +0000 Subject: [PATCH 0940/1161] release: 4.0.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index c67bb1fd3..5c33b15b0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -34,7 +34,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.0.0'; + public const SDK_VERSION = '4.0.1'; /** * @var Options The client options From fa41522f1fe5ff77a33a9db749c0ca7ec332f50a Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 13 Nov 2023 12:47:45 +0100 Subject: [PATCH 0941/1161] Disable Codecov status --- codecov.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codecov.yml b/codecov.yml index 5c45b27ee..7990059fe 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,6 +6,5 @@ ignore: coverage: status: - project: - default: - threshold: 0.1% # allow for 0.1% reduction of coverage without failing + project: off + patch: off From ad2541a206c65c7a67791be0935de5f6cafbca3e Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 13 Nov 2023 14:55:08 +0100 Subject: [PATCH 0942/1161] Add missing `merge_target` to release action (#1641) --- .github/workflows/publish-release.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index e61043eb7..b3453ccb5 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -9,6 +9,10 @@ on: force: description: Force a release even when there are release-blockers (optional) required: false + merge_target: + description: Target branch to merge into. Uses the default branch as a fallback (optional) + required: false + default: master permissions: contents: read @@ -30,3 +34,4 @@ jobs: with: version: ${{ github.event.inputs.version }} force: ${{ github.event.inputs.force }} + merge_target: ${{ github.event.inputs.merge_target }} From be51e60bfedc3ad2eb1734e9a5c231bd7f67c922 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 13 Nov 2023 17:14:48 +0100 Subject: [PATCH 0943/1161] Some cleanup (#1642) --- .github/workflows/ci.yml | 1 - composer.json | 26 +++++++++----------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 153d44d88..3c6258586 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,6 @@ on: push: branches: - master - - develop - release/** permissions: diff --git a/composer.json b/composer.json index 4ff1f8e95..76aa2bb51 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "sentry/sentry", "type": "library", - "description": "A PHP SDK for Sentry (http://sentry.io)", + "description": "PHP SDK for Sentry (http://sentry.io)", "keywords": [ "sentry", "log", @@ -9,7 +9,9 @@ "error-monitoring", "error-handler", "crash-reporting", - "crash-reports" + "crash-reports", + "profiling", + "tracing" ], "homepage": "http://sentry.io", "license": "MIT", @@ -66,21 +68,11 @@ "@psalm", "@tests" ], - "tests": [ - "vendor/bin/phpunit --verbose" - ], - "cs-check": [ - "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run" - ], - "cs-fix": [ - "vendor/bin/php-cs-fixer fix --verbose --diff" - ], - "phpstan": [ - "vendor/bin/phpstan analyse" - ], - "psalm": [ - "vendor/bin/psalm" - ] + "tests": "vendor/bin/phpunit --verbose", + "cs-check": "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run", + "cs-fix": "vendor/bin/php-cs-fixer fix --verbose --diff", + "phpstan": "vendor/bin/phpstan analyse", + "psalm": "vendor/bin/psalm" }, "config": { "sort-packages": true From 0ac1dbe44cc31f8393b406ae9820f6529ba6acab Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 20 Nov 2023 22:57:40 +0100 Subject: [PATCH 0944/1161] Normalize response status (#1644) --- src/Tracing/Span.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 3db829d3a..d8d1859ca 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -5,6 +5,8 @@ namespace Sentry\Tracing; use Sentry\EventId; +use Sentry\SentrySdk; +use Sentry\State\Scope; /** * This class stores all the information about a span. @@ -269,7 +271,11 @@ public function setStatus(?SpanStatus $status) */ public function setHttpStatus(int $statusCode) { - $this->tags['http.status_code'] = (string) $statusCode; + SentrySdk::getCurrentHub()->configureScope(function (Scope $scope) use ($statusCode) { + $scope->setContext('response', [ + 'status_code' => $statusCode, + ]); + }); $status = SpanStatus::createFromHttpStatusCode($statusCode); From 65d4e2d13f15a753b2c5620efaab2a621230d472 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 29 Nov 2023 14:06:56 +0100 Subject: [PATCH 0945/1161] Add support for Spotlight (#1647) --- phpstan-baseline.neon | 5 +++ src/HttpClient/Request.php | 5 +++ src/Options.php | 16 +++++++++ src/Spotlight/SpotlightClient.php | 58 +++++++++++++++++++++++++++++++ src/Transport/HttpTransport.php | 32 +++++++++++++++++ tests/OptionsTest.php | 7 ++++ 6 files changed, 123 insertions(+) create mode 100644 src/Spotlight/SpotlightClient.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7083cd320..f3d8f886d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -220,6 +220,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:isSpotlightEnabled\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:shouldAttachStacktrace\\(\\) should return bool but returns mixed\\.$#" count: 1 diff --git a/src/HttpClient/Request.php b/src/HttpClient/Request.php index 91627ddf6..eb3aefd50 100644 --- a/src/HttpClient/Request.php +++ b/src/HttpClient/Request.php @@ -14,6 +14,11 @@ final class Request */ private $stringBody; + public function hasStringBody(): bool + { + return $this->stringBody !== null; + } + public function getStringBody(): ?string { return $this->stringBody; diff --git a/src/Options.php b/src/Options.php index 87665ac90..2831fa4e8 100644 --- a/src/Options.php +++ b/src/Options.php @@ -329,6 +329,20 @@ public function setLogger(LoggerInterface $logger): self return $this; } + public function isSpotlightEnabled(): bool + { + return $this->options['spotlight']; + } + + public function enableSpotlight(bool $enable): self + { + $options = array_merge($this->options, ['spotlight' => $enable]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Gets the release tag to be passed with every event sent to Sentry. */ @@ -984,6 +998,7 @@ private function configureOptions(OptionsResolver $resolver): void 'context_lines' => 5, 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, 'logger' => null, + 'spotlight' => false, 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), @@ -1031,6 +1046,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('in_app_exclude', 'string[]'); $resolver->setAllowedTypes('in_app_include', 'string[]'); $resolver->setAllowedTypes('logger', ['null', LoggerInterface::class]); + $resolver->setAllowedTypes('spotlight', 'bool'); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); diff --git a/src/Spotlight/SpotlightClient.php b/src/Spotlight/SpotlightClient.php new file mode 100644 index 000000000..f32af4cda --- /dev/null +++ b/src/Spotlight/SpotlightClient.php @@ -0,0 +1,58 @@ +getStringBody(); + if ($requestData === null) { + throw new \RuntimeException('The request data is empty.'); + } + + $curlHandle = curl_init(); + + curl_setopt($curlHandle, \CURLOPT_URL, $url); + curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, [ + 'Content-Type: application/x-sentry-envelope', + ]); + curl_setopt($curlHandle, \CURLOPT_TIMEOUT, 2.0); + curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, 1.0); + curl_setopt($curlHandle, \CURLOPT_ENCODING, ''); + curl_setopt($curlHandle, \CURLOPT_POST, true); + curl_setopt($curlHandle, \CURLOPT_POSTFIELDS, $requestData); + curl_setopt($curlHandle, \CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1); + + $body = curl_exec($curlHandle); + + if ($body === false) { + $errorCode = curl_errno($curlHandle); + $error = curl_error($curlHandle); + curl_close($curlHandle); + + $message = 'cURL Error (' . $errorCode . ') ' . $error; + + return new Response(0, [], $message); + } + + $statusCode = curl_getinfo($curlHandle, \CURLINFO_HTTP_CODE); + + curl_close($curlHandle); + + return new Response($statusCode, [], ''); + } +} diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index f666af971..f95373c36 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -11,6 +11,7 @@ use Sentry\HttpClient\Request; use Sentry\Options; use Sentry\Serializer\PayloadSerializerInterface; +use Sentry\Spotlight\SpotlightClient; /** * @internal @@ -66,6 +67,8 @@ public function __construct( */ public function send(Event $event): Result { + $this->sendRequestToSpotlight($event); + if ($this->options->getDsn() === null) { return new Result(ResultStatus::skipped(), $event); } @@ -116,4 +119,33 @@ public function close(?int $timeout = null): Result { return new Result(ResultStatus::success()); } + + private function sendRequestToSpotlight(Event $event): void + { + if (!$this->options->isSpotlightEnabled()) { + return; + } + + $request = new Request(); + $request->setStringBody($this->payloadSerializer->serialize($event)); + + try { + $spotLightResponse = SpotlightClient::sendRequest( + $request, + 'http://localhost:8969/stream' + ); + + if ($spotLightResponse->hasError()) { + $this->logger->info( + sprintf('Failed to send the event to Spotlight. Reason: "%s".', $spotLightResponse->getError()), + ['event' => $event] + ); + } + } catch (\Throwable $exception) { + $this->logger->info( + sprintf('Failed to send the event to Spotlight. Reason: "%s".', $exception->getMessage()), + ['exception' => $exception, 'event' => $event] + ); + } + } } diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 09bec7b9b..ff577a29f 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -166,6 +166,13 @@ static function (): void {}, 'setLogger', ]; + yield [ + 'spotlight', + true, + 'isSpotlightEnabled', + 'EnableSpotlight', + ]; + yield [ 'release', 'dev', From f2e11da5207ac3de86dd5340e499d3b6185cf756 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 4 Dec 2023 13:40:47 +0100 Subject: [PATCH 0946/1161] Prepare 4.1.0 (#1653) --- CHANGELOG.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 856adcf02..083a14e87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # CHANGELOG +## 4.1.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.1.0. + +### Features + +- Add support for Spotlight [(#1647)](https://github.com/getsentry/sentry-php/pull/1647) + + Spotlight is Sentry for Development. Inspired by an old project, Django Debug Toolbar. Spotlight brings a rich debug overlay into development environments, and it does it by leveraging the existing power of Sentry's SDKs. + + To learn more about Spotlight, go to https://spotlightjs.com/. + +### Misc + +- Normalize `response` status [(#1644)](https://github.com/getsentry/sentry-php/pull/1644) + ## 4.0.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.0.1. @@ -13,7 +29,7 @@ The Sentry SDK team is happy to announce the immediate availability of Sentry PH The Sentry SDK team is thrilled to announce the immediate availability of Sentry PHP SDK v4.0.0. -# Breaking Change +### Breaking Change Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list of breaking changes. @@ -34,7 +50,7 @@ Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list o This option performs an [`is_a`](https://www.php.net/manual/en/function.is-a.php) check now, so you can also ignore more generic exceptions. -# Features +### Features - Add new fluent APIs [(#1601)](https://github.com/getsentry/sentry-php/pull/1601) @@ -130,6 +146,6 @@ Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list o To use a different transport, you may use the `transport` option. A custom transport must implement the `TransportInterface`. If you use the `transport` option, the `http_client` option has no effect. -# Misc +### Misc - The abandoned package `php-http/message-factory` was removed. From 89666f297891ff937fceb2f3d1fb967a6848cf37 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 4 Dec 2023 12:41:21 +0000 Subject: [PATCH 0947/1161] release: 4.1.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 5c33b15b0..bb1eb3c7e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -34,7 +34,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.0.1'; + public const SDK_VERSION = '4.1.0'; /** * @var Options The client options From 4e58990c46d01f8b8f2b6c402d53611eabdb46b9 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 4 Dec 2023 16:01:45 +0100 Subject: [PATCH 0948/1161] Update UPGRADE-4.0.md (#1654) --- UPGRADE-4.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 49194cae9..fd4dffce7 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -59,6 +59,7 @@ - Removed `Dsn::getSecretKey()`. - Removed `Dsn::setSecretKey()`. - Removed `EventType::default()`. +- Removed adding the value of the `logger` option as a tag on the event. If you rely on this behaviour, add the tag manually. - Added return type to `Dsn::getProjectId(): string`. - Changed return type to `Options::getLogger(): ?LoggerInterface`. From f0f553a0eccca5b9b7883061a4b5fbeab75875e4 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:25:02 +0100 Subject: [PATCH 0949/1161] Support symfony/phpunit-bridge ^7.0 (#1660) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 76aa2bb51..798b05541 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^8.5.14|^9.4", - "symfony/phpunit-bridge": "^5.2|^6.0", + "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", "vimeo/psalm": "^4.17" }, "suggest": { From 943714eb03f014d186b53669cb07a8e26d69c146 Mon Sep 17 00:00:00 2001 From: Ant Weedon <81413869+adnweedon@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:28:12 +0000 Subject: [PATCH 0950/1161] Add a config option to allow overriding Spotlight url (#1659) Co-authored-by: Michi Hoffmann --- phpstan-baseline.neon | 5 +++++ src/Options.php | 16 ++++++++++++++++ src/Transport/HttpTransport.php | 2 +- tests/OptionsTest.php | 7 +++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f3d8f886d..7f0676d7b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -185,6 +185,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getSpotlightUrl\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getTags\\(\\) should return array\\ but returns mixed\\.$#" count: 1 diff --git a/src/Options.php b/src/Options.php index 2831fa4e8..546b05b67 100644 --- a/src/Options.php +++ b/src/Options.php @@ -343,6 +343,20 @@ public function enableSpotlight(bool $enable): self return $this; } + public function getSpotlightUrl(): string + { + return $this->options['spotlight_url']; + } + + public function setSpotlightUrl(string $url): self + { + $options = array_merge($this->options, ['spotlight_url' => $url]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Gets the release tag to be passed with every event sent to Sentry. */ @@ -999,6 +1013,7 @@ private function configureOptions(OptionsResolver $resolver): void 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, 'logger' => null, 'spotlight' => false, + 'spotlight_url' => 'http://localhost:8969', 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), @@ -1047,6 +1062,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('in_app_include', 'string[]'); $resolver->setAllowedTypes('logger', ['null', LoggerInterface::class]); $resolver->setAllowedTypes('spotlight', 'bool'); + $resolver->setAllowedTypes('spotlight_url', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index f95373c36..8e1cb647a 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -132,7 +132,7 @@ private function sendRequestToSpotlight(Event $event): void try { $spotLightResponse = SpotlightClient::sendRequest( $request, - 'http://localhost:8969/stream' + $this->options->getSpotlightUrl() . '/stream' ); if ($spotLightResponse->hasError()) { diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index ff577a29f..454a69324 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -173,6 +173,13 @@ static function (): void {}, 'EnableSpotlight', ]; + yield [ + 'spotlight_url', + 'http://google.com', + 'getSpotlightUrl', + 'setSpotlightUrl', + ]; + yield [ 'release', 'dev', From 99849632b70df6f06c84f6ffe65ed7113c783451 Mon Sep 17 00:00:00 2001 From: mark burdett Date: Wed, 13 Dec 2023 10:48:51 -0800 Subject: [PATCH 0951/1161] Restore event logger to payload (#1657) --- src/Serializer/EnvelopItems/EventItem.php | 4 ++++ tests/Serializer/PayloadSerializerTest.php | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Serializer/EnvelopItems/EventItem.php b/src/Serializer/EnvelopItems/EventItem.php index bdf5cbb40..f4fb825e7 100644 --- a/src/Serializer/EnvelopItems/EventItem.php +++ b/src/Serializer/EnvelopItems/EventItem.php @@ -41,6 +41,10 @@ public static function toEnvelopeItem(Event $event): string $payload['level'] = (string) $event->getLevel(); } + if ($event->getLogger() !== null) { + $payload['logger'] = $event->getLogger(); + } + if ($event->getTransaction() !== null) { $payload['transaction'] = $event->getTransaction(); } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index f212776b6..a3e8e5c08 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -72,6 +72,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setLevel(Severity::error()); + $event->setLogger('app.php'); $event->setTransaction('/users//'); $event->setServerName('foo.example.com'); $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); @@ -168,7 +169,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} +{"timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion"},"start_timestamp":1597790835,"level":"error","logger":"app.php","transaction":"\/users\/\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} TEXT ]; From 478371bdc5be996d48c881e0e9ce8e69101b19e1 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 15 Dec 2023 10:56:21 -0800 Subject: [PATCH 0952/1161] Remove `ClientBuilder` @internal annotation (#1661) --- src/ClientBuilder.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 54a433eb0..28563fe87 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -14,8 +14,6 @@ /** * A configurable builder for Client objects. - * - * @internal */ final class ClientBuilder { From cb2734f945bcec032fcbb32b00c5c400636edf16 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 18 Dec 2023 21:53:04 +0100 Subject: [PATCH 0953/1161] Only apply `sample_rate` to errors/messages (#1662) --- src/Client.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Client.php b/src/Client.php index bb1eb3c7e..f3c10ca04 100644 --- a/src/Client.php +++ b/src/Client.php @@ -270,10 +270,11 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT); } - $isTransaction = EventType::transaction() === $event->getType(); + $isEvent = EventType::event() === $event->getType(); $sampleRate = $this->options->getSampleRate(); - if (!$isTransaction && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { + // only sample with the `sample_rate` on errors/messages + if ($isEvent && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]); return null; From bfad80dc45bc1cef0f0049fa521806e7fc087f31 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 19 Dec 2023 17:42:39 +0100 Subject: [PATCH 0954/1161] Prepare 4.2.0 (#1664) --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 083a14e87..fcd683640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # CHANGELOG +## 4.2.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.2.0. + +### Features + +- Add a config option to allow overriding the Spotlight url [(#1659)](https://github.com/getsentry/sentry-php/pull/1659) + + ```php + Sentry\init([ + 'spotlight_url' => 'http://localhost:8969', + ]); + ``` + +### Bug Fixes + +- Restore setting the `logger` value on the event payload [(#1657)](https://github.com/getsentry/sentry-php/pull/1657) + +- Only apply the `sample_rate` on error/message events [(#1662)](https://github.com/getsentry/sentry-php/pull/1662) + + This fixes an issue where Cron Check-Ins were wrongly sampled out if a `sample_rate` lower than `1.0` is used. + +### Misc + +- Remove the `@internal` annotation from `ClientBuilder::class` [(#1661)](https://github.com/getsentry/sentry-php/pull/1661) + ## 4.1.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.1.0. From d858b32e0f9733f41dab847a0dc8c8198ba17d8b Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 19 Dec 2023 16:43:17 +0000 Subject: [PATCH 0955/1161] release: 4.2.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index f3c10ca04..88a0c50d3 100644 --- a/src/Client.php +++ b/src/Client.php @@ -34,7 +34,7 @@ final class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.1.0'; + public const SDK_VERSION = '4.2.0'; /** * @var Options The client options From 00a1e6052a5e8ccad8bc7c8532d17a6cb77e0519 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 20 Dec 2023 16:55:55 +0100 Subject: [PATCH 0956/1161] Disallow to seralize the `HubAdapter` (#1663) --- src/State/HubAdapter.php | 8 ++++++++ tests/State/HubAdapterTest.php | 12 ++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index a2a5c79e7..503153860 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -210,4 +210,12 @@ public function __wakeup() { throw new \BadMethodCallException('Unserializing instances of this class is forbidden.'); } + + /** + * @see https://www.php.net/manual/en/language.oop5.magic.php#object.sleep + */ + public function __sleep() + { + throw new \BadMethodCallException('Serializing instances of this class is forbidden.'); + } } diff --git a/tests/State/HubAdapterTest.php b/tests/State/HubAdapterTest.php index 982cfa00e..5f4a6fbf4 100644 --- a/tests/State/HubAdapterTest.php +++ b/tests/State/HubAdapterTest.php @@ -42,12 +42,20 @@ public function testGetInstanceReturnsUncloneableInstance(): void clone HubAdapter::getInstance(); } - public function testGetInstanceReturnsUnserializableInstance(): void + public function testHubAdapterThrowsExceptionOnSerialization(): void + { + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage('Serializing instances of this class is forbidden.'); + + serialize(HubAdapter::getInstance()); + } + + public function testHubAdapterThrowsExceptionOnUnserialization(): void { $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('Unserializing instances of this class is forbidden.'); - unserialize(serialize(HubAdapter::getInstance())); + unserialize('O:23:"Sentry\State\HubAdapter":0:{}'); } public function testGetClient(): void From c3e68c49543f85a4f89078b27acaea804e93cc7d Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 21 Dec 2023 10:49:09 +0100 Subject: [PATCH 0957/1161] Remove the final keyword from `Hub`, `Client` and `Scope` (#1665) --- src/Client.php | 4 +--- src/State/Hub.php | 2 +- src/State/Scope.php | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Client.php b/src/Client.php index 88a0c50d3..6b5ea99ce 100644 --- a/src/Client.php +++ b/src/Client.php @@ -16,10 +16,8 @@ /** * Default implementation of the {@see ClientInterface} interface. - * - * @author Stefano Arlandini */ -final class Client implements ClientInterface +class Client implements ClientInterface { /** * The version of the protocol to communicate with the Sentry server. diff --git a/src/State/Hub.php b/src/State/Hub.php index 9ac3ff0e4..0bd7aa3c8 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -22,7 +22,7 @@ /** * This class is a basic implementation of the {@see HubInterface} interface. */ -final class Hub implements HubInterface +class Hub implements HubInterface { /** * @var Layer[] The stack of client/scope pairs diff --git a/src/State/Scope.php b/src/State/Scope.php index 0f3984aea..d10e39025 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -19,7 +19,7 @@ * The scope holds data that should implicitly be sent with Sentry events. It * can hold context data, extra parameters, level overrides, fingerprints etc. */ -final class Scope +class Scope { /** * @var PropagationContext From 4f703f92074c41870b54db3a3540109dea6c33c2 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 21 Dec 2023 15:56:01 +0100 Subject: [PATCH 0958/1161] Do not overwrite trace context if already present (#1668) --- src/State/Scope.php | 9 +++++++-- tests/State/HubTest.php | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/State/Scope.php b/src/State/Scope.php index d10e39025..546072f4e 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -378,9 +378,12 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op /** * Apply the trace context to errors if there is a Span on the Scope. * Else fallback to the propagation context. + * But do not override a trace context already present. */ if ($this->span !== null) { - $event->setContext('trace', $this->span->getTraceContext()); + if (!\array_key_exists('trace', $event->getContexts())) { + $event->setContext('trace', $this->span->getTraceContext()); + } // Apply the dynamic sampling context to errors if there is a Transaction on the Scope $transaction = $this->span->getTransaction(); @@ -388,7 +391,9 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op $event->setSdkMetadata('dynamic_sampling_context', $transaction->getDynamicSamplingContext()); } } else { - $event->setContext('trace', $this->propagationContext->getTraceContext()); + if (!\array_key_exists('trace', $event->getContexts())) { + $event->setContext('trace', $this->propagationContext->getTraceContext()); + } $dynamicSamplingContext = $this->propagationContext->getDynamicSamplingContext(); if ($dynamicSamplingContext === null && $options !== null) { diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index f3949bf04..a2d014648 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -851,4 +851,33 @@ public function testGetTransactionReturnsNullIfNoTransactionIsSetOnTheScope(): v $this->assertNull($hub->getTransaction()); } + + public function testEventTraceContextIsAlwaysFilled(): void + { + $hub = new Hub(); + + $event = Event::createEvent(); + + $hub->configureScope(function (Scope $scope) use ($event): void { + $event = $scope->applyToEvent($event); + + $this->assertNotEmpty($event->getContexts()['trace']); + }); + } + + public function testEventTraceContextIsNotOverridenWhenPresent(): void + { + $hub = new Hub(); + + $traceContext = ['foo' => 'bar']; + + $event = Event::createEvent(); + $event->setContext('trace', $traceContext); + + $hub->configureScope(function (Scope $scope) use ($event, $traceContext): void { + $event = $scope->applyToEvent($event); + + $this->assertEquals($event->getContexts()['trace'], $traceContext); + }); + } } From 2f6b4131877dda8dd1d62e67581a9999df8a21be Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 21 Dec 2023 15:58:37 +0100 Subject: [PATCH 0959/1161] Fix not serializing breadcrumbs compatible with Sentry when numeric backed array data is present (#1669) --- src/Serializer/Traits/BreadcrumbSeralizerTrait.php | 4 ++-- tests/Serializer/PayloadSerializerTest.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Serializer/Traits/BreadcrumbSeralizerTrait.php b/src/Serializer/Traits/BreadcrumbSeralizerTrait.php index 7fe10ce25..23e840380 100644 --- a/src/Serializer/Traits/BreadcrumbSeralizerTrait.php +++ b/src/Serializer/Traits/BreadcrumbSeralizerTrait.php @@ -20,7 +20,7 @@ trait BreadcrumbSeralizerTrait * level: string, * timestamp: float, * message?: string, - * data?: array + * data?: object * } */ protected static function serializeBreadcrumb(Breadcrumb $breadcrumb): array @@ -37,7 +37,7 @@ protected static function serializeBreadcrumb(Breadcrumb $breadcrumb): array } if (!empty($breadcrumb->getMetadata())) { - $result['data'] = $breadcrumb->getMetadata(); + $result['data'] = (object) $breadcrumb->getMetadata(); } return $result; diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index a3e8e5c08..9c294fa72 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -83,6 +83,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable $event->setBreadcrumb([ new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'log'), new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_NAVIGATION, 'log', null, ['from' => '/login', 'to' => '/dashboard']), + new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, 'log', null, ['foo', 'bar']), ]); $event->setUser(UserDataBag::createFromArray([ @@ -169,7 +170,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} +{"timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion"},"start_timestamp":1597790835,"level":"error","logger":"app.php","transaction":"\/users\/\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}},{"type":"default","category":"log","level":"info","timestamp":1597790835,"data":{"0":"foo","1":"bar"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} TEXT ]; From c88988dbf99a2defaee857f2c15669cb5b79cb01 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 21 Dec 2023 17:01:18 +0100 Subject: [PATCH 0960/1161] Add support for Sentry Developer Metrics (#1619) Co-authored-by: Alex Bouma --- phpstan-baseline.neon | 10 + src/Event.php | 29 ++ src/EventType.php | 5 + .../FrameContextifierIntegration.php | 32 +- src/Metrics/Metrics.php | 135 +++++++++ src/Metrics/MetricsAggregator.php | 153 ++++++++++ src/Metrics/MetricsUnit.php | 170 +++++++++++ src/Metrics/Types/AbstractType.php | 129 ++++++++ src/Metrics/Types/CounterType.php | 51 ++++ src/Metrics/Types/DistributionType.php | 51 ++++ src/Metrics/Types/GaugeType.php | 92 ++++++ src/Metrics/Types/SetType.php | 57 ++++ src/Options.php | 22 ++ src/Serializer/EnvelopItems/EventItem.php | 58 +--- src/Serializer/EnvelopItems/MetricsItem.php | 110 +++++++ .../EnvelopItems/TransactionItem.php | 4 + src/Serializer/PayloadSerializer.php | 4 + .../Traits/StacktraceFrameSeralizerTrait.php | 68 +++++ src/Tracing/Span.php | 69 +++++ src/functions.php | 6 + tests/Metrics/MetricsTest.php | 284 ++++++++++++++++++ tests/OptionsTest.php | 7 + tests/Serializer/PayloadSerializerTest.php | 32 ++ tests/bootstrap.php | 2 + 24 files changed, 1519 insertions(+), 61 deletions(-) create mode 100644 src/Metrics/Metrics.php create mode 100644 src/Metrics/MetricsAggregator.php create mode 100644 src/Metrics/MetricsUnit.php create mode 100644 src/Metrics/Types/AbstractType.php create mode 100644 src/Metrics/Types/CounterType.php create mode 100644 src/Metrics/Types/DistributionType.php create mode 100644 src/Metrics/Types/GaugeType.php create mode 100644 src/Metrics/Types/SetType.php create mode 100644 src/Serializer/EnvelopItems/MetricsItem.php create mode 100644 src/Serializer/Traits/StacktraceFrameSeralizerTrait.php create mode 100644 tests/Metrics/MetricsTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7f0676d7b..906166b06 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -40,6 +40,11 @@ parameters: count: 1 path: src/Logger/DebugStdOutLogger.php + - + message: "#^Method Sentry\\\\Metrics\\\\Types\\\\AbstractType\\:\\:add\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/Metrics/Types/AbstractType.php + - message: "#^Parameter \\#1 \\$level of method Monolog\\\\Handler\\\\AbstractHandler\\:\\:__construct\\(\\) expects 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|'ALERT'\\|'alert'\\|'CRITICAL'\\|'critical'\\|'DEBUG'\\|'debug'\\|'EMERGENCY'\\|'emergency'\\|'ERROR'\\|'error'\\|'INFO'\\|'info'\\|'NOTICE'\\|'notice'\\|'WARNING'\\|'warning'\\|Monolog\\\\Level, int\\|Monolog\\\\Level\\|string given\\.$#" count: 1 @@ -230,6 +235,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:shouldAttachMetricCodeLocations\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:shouldAttachStacktrace\\(\\) should return bool but returns mixed\\.$#" count: 1 diff --git a/src/Event.php b/src/Event.php index b9fd06e27..4a77b948e 100644 --- a/src/Event.php +++ b/src/Event.php @@ -6,6 +6,7 @@ use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; +use Sentry\Metrics\Types\AbstractType; use Sentry\Profiling\Profile; use Sentry\Tracing\Span; @@ -55,6 +56,11 @@ final class Event */ private $checkIn; + /** + * @var array The metrics data + */ + private $metrics = []; + /** * @var string|null The name of the server (e.g. the host name) */ @@ -210,6 +216,11 @@ public static function createCheckIn(?EventId $eventId = null): self return new self($eventId, EventType::checkIn()); } + public static function createMetrics(?EventId $eventId = null): self + { + return new self($eventId, EventType::metrics()); + } + /** * Gets the ID of this event. */ @@ -354,6 +365,24 @@ public function setCheckIn(?CheckIn $checkIn): self return $this; } + /** + * @return array + */ + public function getMetrics(): array + { + return $this->metrics; + } + + /** + * @param array $metrics + */ + public function setMetrics(array $metrics): self + { + $this->metrics = $metrics; + + return $this; + } + /** * Gets the name of the server. */ diff --git a/src/EventType.php b/src/EventType.php index beb04930f..208789e3a 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -42,6 +42,11 @@ public static function checkIn(): self return self::getInstance('check_in'); } + public static function metrics(): self + { + return self::getInstance('metrics'); + } + public function __toString(): string { return $this->value; diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index d0ed04f99..178f04fd4 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Event; +use Sentry\Frame; use Sentry\SentrySdk; use Sentry\Stacktrace; use Sentry\State\Scope; @@ -65,6 +66,13 @@ public function setupOnce(): void } } + foreach ($event->getMetrics() as $metric) { + if ($metric->hasCodeLocation()) { + $frame = $metric->getCodeLocation(); + $integration->addContextToStacktraceFrame($maxContextLines, $frame); + } + } + return $event; }); } @@ -78,16 +86,30 @@ public function setupOnce(): void private function addContextToStacktraceFrames(int $maxContextLines, Stacktrace $stacktrace): void { foreach ($stacktrace->getFrames() as $frame) { - if ($frame->isInternal() || $frame->getAbsoluteFilePath() === null) { + if ($frame->isInternal()) { continue; } - $sourceCodeExcerpt = $this->getSourceCodeExcerpt($maxContextLines, $frame->getAbsoluteFilePath(), $frame->getLine()); + $this->addContextToStacktraceFrame($maxContextLines, $frame); + } + } - $frame->setPreContext($sourceCodeExcerpt['pre_context']); - $frame->setContextLine($sourceCodeExcerpt['context_line']); - $frame->setPostContext($sourceCodeExcerpt['post_context']); + /** + * Contextifies the given frame. + * + * @param int $maxContextLines The maximum number of lines of code to read + */ + private function addContextToStacktraceFrame(int $maxContextLines, Frame $frame): void + { + if ($frame->getAbsoluteFilePath() === null) { + return; } + + $sourceCodeExcerpt = $this->getSourceCodeExcerpt($maxContextLines, $frame->getAbsoluteFilePath(), $frame->getLine()); + + $frame->setPreContext($sourceCodeExcerpt['pre_context']); + $frame->setContextLine($sourceCodeExcerpt['context_line']); + $frame->setPostContext($sourceCodeExcerpt['post_context']); } /** diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php new file mode 100644 index 000000000..1b4b35937 --- /dev/null +++ b/src/Metrics/Metrics.php @@ -0,0 +1,135 @@ +aggregator = new MetricsAggregator(); + } + + public static function getInstance(): self + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * @param int|float $value + * @param string[] $tags + */ + public function increment( + string $key, + $value, + ?MetricsUnit $unit = null, + array $tags = [], + ?int $timestamp = null, + int $stackLevel = 0 + ): void { + $this->aggregator->add( + CounterType::TYPE, + $key, + $value, + $unit, + $tags, + $timestamp, + $stackLevel + ); + } + + /** + * @param int|float $value + * @param string[] $tags + */ + public function distribution( + string $key, + $value, + ?MetricsUnit $unit = null, + array $tags = [], + ?int $timestamp = null, + int $stackLevel = 0 + ): void { + $this->aggregator->add( + DistributionType::TYPE, + $key, + $value, + $unit, + $tags, + $timestamp, + $stackLevel + ); + } + + /** + * @param int|float $value + * @param string[] $tags + */ + public function gauge( + string $key, + $value, + ?MetricsUnit $unit = null, + array $tags = [], + ?int $timestamp = null, + int $stackLevel = 0 + ): void { + $this->aggregator->add( + GaugeType::TYPE, + $key, + $value, + $unit, + $tags, + $timestamp, + $stackLevel + ); + } + + /** + * @param int|string $value + * @param string[] $tags + */ + public function set( + string $key, + $value, + ?MetricsUnit $unit = null, + array $tags = [], + ?int $timestamp = null, + int $stackLevel = 0 + ): void { + $this->aggregator->add( + SetType::TYPE, + $key, + $value, + $unit, + $tags, + $timestamp, + $stackLevel + ); + } + + public function flush(): ?EventId + { + return $this->aggregator->flush(); + } +} diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php new file mode 100644 index 000000000..8cb50f036 --- /dev/null +++ b/src/Metrics/MetricsAggregator.php @@ -0,0 +1,153 @@ + + */ + private $buckets = []; + + private const METRIC_TYPES = [ + CounterType::TYPE => CounterType::class, + DistributionType::TYPE => DistributionType::class, + GaugeType::TYPE => GaugeType::class, + SetType::TYPE => SetType::class, + ]; + + /** + * @param string[] $tags + * @param int|float|string $value + */ + public function add( + string $type, + string $key, + $value, + ?MetricsUnit $unit, + array $tags, + ?int $timestamp, + int $stackLevel + ): void { + if ($timestamp === null) { + $timestamp = time(); + } + if ($unit === null) { + $unit = MetricsUnit::none(); + } + + $tags = $this->serializeTags($tags); + + $bucketTimestamp = floor($timestamp / self::ROLLUP_IN_SECONDS); + $bucketKey = md5( + $type . + $key . + $unit . + implode('', $tags) . + $bucketTimestamp + ); + + if (\array_key_exists($bucketKey, $this->buckets)) { + $metric = $this->buckets[$bucketKey]; + $metric->add($value); + } else { + $metricTypeClass = self::METRIC_TYPES[$type]; + /** @var AbstractType $metric */ + /** @phpstan-ignore-next-line SetType accepts int|float|string, others only int|float */ + $metric = new $metricTypeClass($key, $value, $unit, $tags, $timestamp); + $this->buckets[$bucketKey] = $metric; + } + + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + if ($client !== null) { + $options = $client->getOptions(); + + if ( + $options->shouldAttachMetricCodeLocations() + && !$metric->hasCodeLocation() + ) { + $metric->addCodeLocation($stackLevel); + } + } + + $span = $hub->getSpan(); + if ($span !== null) { + $span->setMetricsSummary($type, $key, $value, $unit, $tags); + } + } + + public function flush(): ?EventId + { + $hub = SentrySdk::getCurrentHub(); + $event = Event::createMetrics()->setMetrics($this->buckets); + + $this->buckets = []; + + return $hub->captureEvent($event); + } + + /** + * @param string[] $tags + * + * @return string[] + */ + private function serializeTags(array $tags): array + { + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + if ($client !== null) { + $options = $client->getOptions(); + + $defaultTags = [ + 'environment' => $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, + ]; + + $release = $options->getRelease(); + if ($release !== null) { + $defaultTags['release'] = $release; + } + + $hub->configureScope(function (Scope $scope) use (&$defaultTags) { + $transaction = $scope->getTransaction(); + if ( + $transaction !== null + // Only include the transaction name if it has good quality + && $transaction->getMetadata()->getSource() !== TransactionSource::url() + ) { + $defaultTags['transaction'] = $transaction->getName(); + } + }); + + $tags = array_merge($defaultTags, $tags); + } + + // It's very important to sort the tags in order to obtain the same bucket key. + ksort($tags); + + return $tags; + } +} diff --git a/src/Metrics/MetricsUnit.php b/src/Metrics/MetricsUnit.php new file mode 100644 index 000000000..f417a5599 --- /dev/null +++ b/src/Metrics/MetricsUnit.php @@ -0,0 +1,170 @@ + A list of cached enum instances + */ + private static $instances = []; + + private function __construct(string $value) + { + $this->value = $value; + } + + public static function nanosecond(): self + { + return self::getInstance('nanosecond'); + } + + public static function microsecond(): self + { + return self::getInstance('microsecond'); + } + + public static function millisecond(): self + { + return self::getInstance('millisecond'); + } + + public static function second(): self + { + return self::getInstance('second'); + } + + public static function minute(): self + { + return self::getInstance('minute'); + } + + public static function hour(): self + { + return self::getInstance('hour'); + } + + public static function day(): self + { + return self::getInstance('day'); + } + + public static function week(): self + { + return self::getInstance('week'); + } + + public static function bit(): self + { + return self::getInstance('bit'); + } + + public static function byte(): self + { + return self::getInstance('byte'); + } + + public static function kilobyte(): self + { + return self::getInstance('kilobyte'); + } + + public static function kibibyte(): self + { + return self::getInstance('kibibyte'); + } + + public static function megabyte(): self + { + return self::getInstance('megabyte'); + } + + public static function mebibyte(): self + { + return self::getInstance('mebibyte'); + } + + public static function gigabyte(): self + { + return self::getInstance('gigabyte'); + } + + public static function gibibyte(): self + { + return self::getInstance('gibibyte'); + } + + public static function terabyte(): self + { + return self::getInstance('terabyte'); + } + + public static function tebibyte(): self + { + return self::getInstance('tebibyte'); + } + + public static function petabyte(): self + { + return self::getInstance('petabyte'); + } + + public static function pebibyte(): self + { + return self::getInstance('pebibyte'); + } + + public static function exabyte(): self + { + return self::getInstance('exabyte'); + } + + public static function exbibyte(): self + { + return self::getInstance('exbibyte'); + } + + public static function ratio(): self + { + return self::getInstance('ratio'); + } + + public static function percent(): self + { + return self::getInstance('percent'); + } + + public static function none(): self + { + return self::getInstance('none'); + } + + public static function custom(string $unit): self + { + return new self($unit); + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Metrics/Types/AbstractType.php b/src/Metrics/Types/AbstractType.php new file mode 100644 index 000000000..7a089b8c6 --- /dev/null +++ b/src/Metrics/Types/AbstractType.php @@ -0,0 +1,129 @@ +key = $key; + $this->unit = $unit; + $this->tags = $tags; + $this->timestamp = $timestamp; + } + + abstract public function add($value): void; + + /** + * @return array + */ + abstract public function serialize(): array; + + abstract public function getType(): string; + + public function getKey(): string + { + return $this->key; + } + + public function getUnit(): MetricsUnit + { + return $this->unit; + } + + /** + * @return string[] + */ + public function getTags(): array + { + return $this->tags; + } + + public function getTimestamp(): int + { + return $this->timestamp; + } + + /** + * @phpstan-assert-if-true !null $this->getCodeLocation() + */ + public function hasCodeLocation(): bool + { + return $this->codeLocation !== null; + } + + public function getCodeLocation(): ?Frame + { + return $this->codeLocation; + } + + public function addCodeLocation(int $stackLevel): void + { + $client = SentrySdk::getCurrentHub()->getClient(); + if ($client === null) { + return; + } + + $options = $client->getOptions(); + + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3 + $stackLevel); + $frame = end($backtrace); + + // If we don't have a valid frame there is no code location to resolve + if ($frame === false || empty($frame['file']) || empty($frame['line'])) { + return; + } + + $frameBuilder = new FrameBuilder($options, new RepresentationSerializer($options)); + $this->codeLocation = $frameBuilder->buildFromBacktraceFrame($frame['file'], $frame['line'], $frame); + } + + public function getMri(): string + { + return sprintf( + '%s:%s@%s', + $this->getType(), + $this->getKey(), + (string) $this->getUnit() + ); + } +} diff --git a/src/Metrics/Types/CounterType.php b/src/Metrics/Types/CounterType.php new file mode 100644 index 000000000..6f54b3f9c --- /dev/null +++ b/src/Metrics/Types/CounterType.php @@ -0,0 +1,51 @@ +value = (float) $value; + } + + /** + * @param int|float $value + */ + public function add($value): void + { + $this->value += (float) $value; + } + + public function serialize(): array + { + return [$this->value]; + } + + public function getType(): string + { + return self::TYPE; + } +} diff --git a/src/Metrics/Types/DistributionType.php b/src/Metrics/Types/DistributionType.php new file mode 100644 index 000000000..fa43e4f48 --- /dev/null +++ b/src/Metrics/Types/DistributionType.php @@ -0,0 +1,51 @@ + + */ + private $values; + + /** + * @param int|float $value + */ + public function __construct(string $key, $value, MetricsUnit $unit, array $tags, int $timestamp) + { + parent::__construct($key, $unit, $tags, $timestamp); + + $this->add($value); + } + + /** + * @param int|float $value + */ + public function add($value): void + { + $this->values[] = (float) $value; + } + + public function serialize(): array + { + return $this->values; + } + + public function getType(): string + { + return self::TYPE; + } +} diff --git a/src/Metrics/Types/GaugeType.php b/src/Metrics/Types/GaugeType.php new file mode 100644 index 000000000..1803365a9 --- /dev/null +++ b/src/Metrics/Types/GaugeType.php @@ -0,0 +1,92 @@ +last = $value; + $this->min = $value; + $this->max = $value; + $this->sum = $value; + $this->count = 1; + } + + /** + * @param int|float $value + */ + public function add($value): void + { + $value = (float) $value; + + $this->last = $value; + $this->min = min($this->min, $value); + $this->max = max($this->min, $value); + $this->sum += $value; + ++$this->count; + } + + /** + * @return array + */ + public function serialize(): array + { + return [ + $this->last, + $this->min, + $this->max, + $this->sum, + $this->count, + ]; + } + + public function getType(): string + { + return self::TYPE; + } +} diff --git a/src/Metrics/Types/SetType.php b/src/Metrics/Types/SetType.php new file mode 100644 index 000000000..d761bc264 --- /dev/null +++ b/src/Metrics/Types/SetType.php @@ -0,0 +1,57 @@ + + */ + private $values; + + /** + * @param int|string $value + */ + public function __construct(string $key, $value, MetricsUnit $unit, array $tags, int $timestamp) + { + parent::__construct($key, $unit, $tags, $timestamp); + + $this->add($value); + } + + /** + * @param int|string $value + */ + public function add($value): void + { + $this->values[] = $value; + } + + public function serialize(): array + { + foreach ($this->values as $key => $value) { + if (\is_string($value)) { + $this->values[$key] = crc32($value); + } + } + + return $this->values; + } + + public function getType(): string + { + return self::TYPE; + } +} diff --git a/src/Options.php b/src/Options.php index 546b05b67..79b71fc8b 100644 --- a/src/Options.php +++ b/src/Options.php @@ -217,6 +217,26 @@ public function setAttachStacktrace(bool $enable): self return $this; } + /** + * Gets whether a metric has their code location attached. + */ + public function shouldAttachMetricCodeLocations(): bool + { + return $this->options['attach_metric_code_locations']; + } + + /** + * Sets whether a metric will have their code location attached. + */ + public function setAttachMetricCodeLocations(bool $enable): self + { + $options = array_merge($this->options, ['attach_metric_code_locations' => $enable]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Gets the number of lines of code context to capture, or null if none. */ @@ -1009,6 +1029,7 @@ private function configureOptions(OptionsResolver $resolver): void 'traces_sampler' => null, 'profiles_sample_rate' => null, 'attach_stacktrace' => false, + 'attach_metric_code_locations' => false, 'context_lines' => 5, 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, 'logger' => null, @@ -1056,6 +1077,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); $resolver->setAllowedTypes('profiles_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); + $resolver->setAllowedTypes('attach_metric_code_locations', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('in_app_exclude', 'string[]'); diff --git a/src/Serializer/EnvelopItems/EventItem.php b/src/Serializer/EnvelopItems/EventItem.php index f4fb825e7..a9b0fdad6 100644 --- a/src/Serializer/EnvelopItems/EventItem.php +++ b/src/Serializer/EnvelopItems/EventItem.php @@ -6,8 +6,8 @@ use Sentry\Event; use Sentry\ExceptionDataBag; -use Sentry\Frame; use Sentry\Serializer\Traits\BreadcrumbSeralizerTrait; +use Sentry\Serializer\Traits\StacktraceFrameSeralizerTrait; use Sentry\Util\JSON; /** @@ -16,6 +16,7 @@ class EventItem implements EnvelopeItemInterface { use BreadcrumbSeralizerTrait; + use StacktraceFrameSeralizerTrait; public static function toEnvelopeItem(Event $event): string { @@ -189,59 +190,4 @@ protected static function serializeException(ExceptionDataBag $exception): array return $result; } - - /** - * @return array - * - * @psalm-return array{ - * filename: string, - * lineno: int, - * in_app: bool, - * abs_path?: string, - * function?: string, - * raw_function?: string, - * pre_context?: string[], - * context_line?: string, - * post_context?: string[], - * vars?: array - * } - */ - protected static function serializeStacktraceFrame(Frame $frame): array - { - $result = [ - 'filename' => $frame->getFile(), - 'lineno' => $frame->getLine(), - 'in_app' => $frame->isInApp(), - ]; - - if ($frame->getAbsoluteFilePath() !== null) { - $result['abs_path'] = $frame->getAbsoluteFilePath(); - } - - if ($frame->getFunctionName() !== null) { - $result['function'] = $frame->getFunctionName(); - } - - if ($frame->getRawFunctionName() !== null) { - $result['raw_function'] = $frame->getRawFunctionName(); - } - - if (!empty($frame->getPreContext())) { - $result['pre_context'] = $frame->getPreContext(); - } - - if ($frame->getContextLine() !== null) { - $result['context_line'] = $frame->getContextLine(); - } - - if (!empty($frame->getPostContext())) { - $result['post_context'] = $frame->getPostContext(); - } - - if (!empty($frame->getVars())) { - $result['vars'] = $frame->getVars(); - } - - return $result; - } } diff --git a/src/Serializer/EnvelopItems/MetricsItem.php b/src/Serializer/EnvelopItems/MetricsItem.php new file mode 100644 index 000000000..3bd0b7c3a --- /dev/null +++ b/src/Serializer/EnvelopItems/MetricsItem.php @@ -0,0 +1,110 @@ +getMetrics(); + if (empty($metrics)) { + return ''; + } + + $statsdPayload = []; + $metricMetaPayload = []; + + foreach ($metrics as $metric) { + // key - my.metric + $line = preg_replace(self::KEY_PATTERN, '_', $metric->getKey()); + + if ($metric->getUnit() !== MetricsUnit::none()) { + // unit - @second + $line .= '@' . $metric->getunit(); + } + + foreach ($metric->serialize() as $value) { + // value - 2:3:4... + $line .= ':' . $value; + } + + // type - |c|, |d|, ... + $line .= '|' . $metric->getType() . '|'; + + $tags = ''; + foreach ($metric->getTags() as $key => $value) { + $tags .= preg_replace(self::KEY_PATTERN, '_', $key) . + ':' . preg_replace(self::VALUE_PATTERN, '', $value); + } + + // tags - #key:value,key:value... + $line .= '#' . $tags . '|'; + // timestamp - T123456789 + $line .= 'T' . $metric->getTimestamp(); + + $statsdPayload[] = $line; + + if ($metric->hasCodeLocation()) { + $metricMetaPayload[$metric->getMri()][] = array_merge( + ['type' => 'location'], + self::serializeStacktraceFrame($metric->getCodeLocation()) + ); + } + } + + $statsdPayload = implode("\n", $statsdPayload); + + $statsdHeader = [ + 'type' => 'statsd', + 'length' => mb_strlen($statsdPayload), + ]; + + if (!empty($metricMetaPayload)) { + $metricMetaPayload = JSON::encode([ + 'timestamp' => time(), + 'mapping' => $metricMetaPayload, + ]); + + $metricMetaHeader = [ + 'type' => 'metric_meta', + 'length' => mb_strlen($metricMetaPayload), + ]; + + return sprintf( + "%s\n%s\n%s\n%s", + JSON::encode($statsdHeader), + $statsdPayload, + JSON::encode($metricMetaHeader), + $metricMetaPayload + ); + } + + return sprintf( + "%s\n%s", + JSON::encode($statsdHeader), + $statsdPayload + ); + } +} diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index f44dc38d0..5296299be 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -176,6 +176,10 @@ protected static function serializeSpan(Span $span): array $result['tags'] = $span->getTags(); } + if (!empty($span->getMetricsSummary())) { + $result['metrics_summary'] = $span->getMetricsSummary(); + } + return $result; } } diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index ad1365b3a..e531a9a0e 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -9,6 +9,7 @@ use Sentry\Options; use Sentry\Serializer\EnvelopItems\CheckInItem; use Sentry\Serializer\EnvelopItems\EventItem; +use Sentry\Serializer\EnvelopItems\MetricsItem; use Sentry\Serializer\EnvelopItems\ProfileItem; use Sentry\Serializer\EnvelopItems\TransactionItem; use Sentry\Tracing\DynamicSamplingContext; @@ -77,6 +78,9 @@ public function serialize(Event $event): string case EventType::checkIn(): $items = CheckInItem::toEnvelopeItem($event); break; + case EventType::metrics(): + $items = MetricsItem::toEnvelopeItem($event); + break; } return sprintf("%s\n%s", JSON::encode($envelopeHeader), $items); diff --git a/src/Serializer/Traits/StacktraceFrameSeralizerTrait.php b/src/Serializer/Traits/StacktraceFrameSeralizerTrait.php new file mode 100644 index 000000000..32e777f7b --- /dev/null +++ b/src/Serializer/Traits/StacktraceFrameSeralizerTrait.php @@ -0,0 +1,68 @@ + + * + * @psalm-return array{ + * filename: string, + * lineno: int, + * in_app: bool, + * abs_path?: string, + * function?: string, + * raw_function?: string, + * pre_context?: string[], + * context_line?: string, + * post_context?: string[], + * vars?: array + * } + */ + protected static function serializeStacktraceFrame(Frame $frame): array + { + $result = [ + 'filename' => $frame->getFile(), + 'lineno' => $frame->getLine(), + 'in_app' => $frame->isInApp(), + ]; + + if ($frame->getAbsoluteFilePath() !== null) { + $result['abs_path'] = $frame->getAbsoluteFilePath(); + } + + if ($frame->getFunctionName() !== null) { + $result['function'] = $frame->getFunctionName(); + } + + if ($frame->getRawFunctionName() !== null) { + $result['raw_function'] = $frame->getRawFunctionName(); + } + + if (!empty($frame->getPreContext())) { + $result['pre_context'] = $frame->getPreContext(); + } + + if ($frame->getContextLine() !== null) { + $result['context_line'] = $frame->getContextLine(); + } + + if (!empty($frame->getPostContext())) { + $result['post_context'] = $frame->getPostContext(); + } + + if (!empty($frame->getVars())) { + $result['vars'] = $frame->getVars(); + } + + return $result; + } +} diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index d8d1859ca..08f0b771d 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -5,11 +5,21 @@ namespace Sentry\Tracing; use Sentry\EventId; +use Sentry\Metrics\MetricsUnit; +use Sentry\Metrics\Types\SetType; use Sentry\SentrySdk; use Sentry\State\Scope; /** * This class stores all the information about a span. + * + * @phpstan-type MetricsSummary array{ + * min: int|float, + * max: int|float, + * sum: int|float, + * count: int, + * tags: array, + * } */ class Span { @@ -78,6 +88,11 @@ class Span */ protected $transaction; + /** + * @var array + */ + protected $metricsSummary = []; + /** * Constructor. * @@ -476,6 +491,60 @@ public function detachSpanRecorder() return $this; } + /** + * @return array + */ + public function getMetricsSummary(): array + { + return $this->metricsSummary; + } + + /** + * @param string|int|float $value + * @param string[] $tags + */ + public function setMetricsSummary( + string $type, + string $key, + $value, + MetricsUnit $unit, + array $tags + ): void { + $mri = sprintf('%s:%s@%s', $type, $key, (string) $unit); + $bucketKey = $mri . implode('', $tags); + + if (\array_key_exists($bucketKey, $this->metricsSummary)) { + if ($type === SetType::TYPE) { + $value = 1.0; + } else { + $value = (float) $value; + } + + $summary = $this->metricsSummary[$bucketKey]; + $this->metricsSummary[$bucketKey] = [ + 'min' => min($summary['min'], $value), + 'max' => max($summary['max'], $value), + 'sum' => $summary['sum'] + $value, + 'count' => $summary['count'] + 1, + 'tags' => $tags, + ]; + } else { + if ($type === SetType::TYPE) { + $value = 0.0; + } else { + $value = (float) $value; + } + + $this->metricsSummary[$bucketKey] = [ + 'min' => $value, + 'max' => $value, + 'sum' => $value, + 'count' => 1, + 'tags' => $tags, + ]; + } + } + /** * Returns the transaction containing this span. */ diff --git a/src/functions.php b/src/functions.php index 9dba70ab1..465ad95eb 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,6 +4,7 @@ namespace Sentry; +use Sentry\Metrics\Metrics; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SpanContext; @@ -268,3 +269,8 @@ function continueTrace(string $sentryTrace, string $baggage): TransactionContext return TransactionContext::fromHeaders($sentryTrace, $baggage); } + +function metrics(): Metrics +{ + return Metrics::getInstance(); +} diff --git a/tests/Metrics/MetricsTest.php b/tests/Metrics/MetricsTest.php new file mode 100644 index 000000000..87b26e4bc --- /dev/null +++ b/tests/Metrics/MetricsTest.php @@ -0,0 +1,284 @@ +createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'release' => '1.0.0', + 'environment' => 'development', + 'attach_metric_code_locations' => true, + ])); + + $self = $this; + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(static function (Event $event) use ($self): bool { + $metric = $event->getMetrics()['92ed00fdaf9543ff4cace691f8a5166b']; + + $self->assertSame(CounterType::TYPE, $metric->getType()); + $self->assertSame('foo', $metric->getKey()); + $self->assertSame([3.0], $metric->serialize()); + $self->assertSame(MetricsUnit::second(), $metric->getUnit()); + $self->assertSame( + [ + 'environment' => 'development', + 'foo' => 'bar', + 'release' => '1.0.0', + ], + $metric->getTags() + ); + $self->assertSame(1699412953, $metric->getTimestamp()); + + $codeLocation = $metric->getCodeLocation(); + + $self->assertSame('Sentry\Metrics\Metrics::increment', $codeLocation->getFunctionName()); + + return true; + })); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + metrics()->increment( + 'foo', + 1, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->increment( + 'foo', + 2, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->flush(); + } + + public function testDistribution(): void + { + ClockMock::withClockMock(1699412953); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'release' => '1.0.0', + 'environment' => 'development', + 'attach_metric_code_locations' => true, + ])); + + $self = $this; + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(static function (Event $event) use ($self): bool { + $metric = $event->getMetrics()['8a817dcdb12cfffc1fa8b459ad0c9d56']; + + $self->assertSame(DistributionType::TYPE, $metric->getType()); + $self->assertSame('foo', $metric->getKey()); + $self->assertSame([1.0, 2.0], $metric->serialize()); + $self->assertSame(MetricsUnit::second(), $metric->getUnit()); + $self->assertSame( + [ + 'environment' => 'development', + 'foo' => 'bar', + 'release' => '1.0.0', + ], + $metric->getTags() + ); + $self->assertSame(1699412953, $metric->getTimestamp()); + + $codeLocation = $metric->getCodeLocation(); + + $self->assertSame('Sentry\Metrics\Metrics::distribution', $codeLocation->getFunctionName()); + + return true; + })); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + metrics()->distribution( + 'foo', + 1, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->distribution( + 'foo', + 2, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->flush(); + } + + public function testGauge(): void + { + ClockMock::withClockMock(1699412953); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'release' => '1.0.0', + 'environment' => 'development', + 'attach_metric_code_locations' => true, + ])); + + $self = $this; + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(static function (Event $event) use ($self): bool { + $metric = $event->getMetrics()['d2a09273b9c61b66a0e6ee79c1babfed']; + + $self->assertSame(GaugeType::TYPE, $metric->getType()); + $self->assertSame('foo', $metric->getKey()); + $self->assertSame([ + 2.0, // last + 1.0, // min + 2.0, // max + 3.0, // sum, + 2, // count, + ], $metric->serialize()); + $self->assertSame(MetricsUnit::second(), $metric->getUnit()); + $self->assertSame( + [ + 'environment' => 'development', + 'foo' => 'bar', + 'release' => '1.0.0', + ], + $metric->getTags() + ); + $self->assertSame(1699412953, $metric->getTimestamp()); + + $codeLocation = $metric->getCodeLocation(); + + $self->assertSame('Sentry\Metrics\Metrics::gauge', $codeLocation->getFunctionName()); + + return true; + })); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + metrics()->gauge( + 'foo', + 1, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->gauge( + 'foo', + 2, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->flush(); + } + + public function testSet(): void + { + ClockMock::withClockMock(1699412953); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'release' => '1.0.0', + 'environment' => 'development', + 'attach_metric_code_locations' => true, + ])); + + $self = $this; + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(static function (Event $event) use ($self): bool { + $metric = $event->getMetrics()['c900a5750d0bc79016c29a7f0bdcd937']; + + $self->assertSame(SetType::TYPE, $metric->getType()); + $self->assertSame('foo', $metric->getKey()); + $self->assertSame([1, 1, 2356372769], $metric->serialize()); + $self->assertSame(MetricsUnit::second(), $metric->getUnit()); + $self->assertSame( + [ + 'environment' => 'development', + 'foo' => 'bar', + 'release' => '1.0.0', + ], + $metric->getTags() + ); + $self->assertSame(1699412953, $metric->getTimestamp()); + + $codeLocation = $metric->getCodeLocation(); + + $self->assertSame('Sentry\Metrics\Metrics::set', $codeLocation->getFunctionName()); + + return true; + })); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + metrics()->set( + 'foo', + 1, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->set( + 'foo', + 1, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->set( + 'foo', + 'foo', + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->flush(); + } +} diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 454a69324..1df9f0555 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -131,6 +131,13 @@ static function (): void {}, 'setAttachStacktrace', ]; + yield [ + 'attach_metric_code_locations', + false, + 'shouldAttachMetricCodeLocations', + 'setAttachMetricCodeLocations', + ]; + yield [ 'context_lines', 3, diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 9c294fa72..2819b941f 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -16,6 +16,11 @@ use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; +use Sentry\Metrics\MetricsUnit; +use Sentry\Metrics\Types\CounterType; +use Sentry\Metrics\Types\DistributionType; +use Sentry\Metrics\Types\GaugeType; +use Sentry\Metrics\Types\SetType; use Sentry\MonitorConfig; use Sentry\MonitorSchedule; use Sentry\Options; @@ -402,6 +407,33 @@ public static function serializeAsEnvelopeDataProvider(): iterable {"event_id":"fc9442f5aef34234bb22b9a615e30ccd","sent_at":"2020-08-18T22:47:15Z","dsn":"http:\/\/public@example.com\/sentry\/1","sdk":{"name":"sentry.php","version":"$sdkVersion"}} {"type":"check_in","content_type":"application\/json"} {"check_in_id":"$checkinId","monitor_slug":"my-monitor","status":"ok","duration":10,"release":"1.0.0","environment":"dev","monitor_config":{"schedule":{"type":"crontab","value":"0 0 * * *","unit":""},"checkin_margin":10,"max_runtime":12,"timezone":"Europe\/Amsterdam"},"contexts":{"trace":{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"5dd538dc297544cc"}}} +TEXT + , + false, + ]; + + $counter = new CounterType('counter', 1.0, MetricsUnit::second(), ['foo' => 'bar'], 1597790835); + $distribution = new DistributionType('distribution', 1.0, MetricsUnit::second(), ['$foo$' => '%bar%'], 1597790835); + $gauge = new GaugeType('gauge', 1.0, MetricsUnit::second(), ['föö' => 'bär'], 1597790835); + $set = new SetType('set', 1.0, MetricsUnit::second(), ['%{key}' => '$value$'], 1597790835); + + $event = Event::createMetrics(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setMetrics([ + $counter, + $distribution, + $gauge, + $set, + ]); + + yield [ + $event, + << Date: Thu, 21 Dec 2023 17:38:56 +0100 Subject: [PATCH 0961/1161] Prepare 4.3.0 (#1667) Co-authored-by: Alex Bouma --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd683640..d478ebfeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,50 @@ # CHANGELOG +## 4.3.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.3.0. + +### Features + +- Add support for Sentry Developer Metrics [(#1619)](https://github.com/getsentry/sentry-php/pull/1619) + + ```php + use function Sentry\metrics; + + // Add 4 to a counter named hits + metrics()->increment(key: 'hits', value: 4); + + // Add 25 to a distribution named response_time with unit milliseconds + metrics()->distribution(key: 'response_time', value: 25, unit: MetricsUnit::millisecond()); + + // Add 2 to gauge named parallel_requests, tagged with type: "a" + metrics()->gauge(key: 'parallel_requests, value: 2, tags: ['type': 'a']); + + // Add a user's email to a set named users.sessions, tagged with role: "admin" + metrics()->set('users.sessions, 'jane.doe@example.com', null, ['role' => User::admin()]); + + // Add 2 to gauge named `parallel_requests`, tagged with `type: "a"` + Sentry.metrics.gauge('parallel_requests', 2, { tags: { type: 'a' } }); + + // Flush the metrics to Sentry + metrics()->flush(); + + // We recommend registering the flushing in a shutdown function + register_shutdown_function(static fn () => metrics()->flush()); + ``` + + To learn more about Sentry Developer Merics, join the discussion at https://github.com/getsentry/sentry-php/discussions/1666. + +### Bug Fixes + +- Disallow to seralize the `HubAdapter::class` [(#1663)](https://github.com/getsentry/sentry-php/pull/1663) +- Do not overwrite trace context on event [(#1668)](https://github.com/getsentry/sentry-php/pull/1668) +- Serialize breadcrumb data to display correct in the Sentry UI [(#1669)](https://github.com/getsentry/sentry-php/pull/1669) + +### Misc + +- Remove the `final` keyword from `Hub::class`, `Client::class` and `Scope::class` [(#1665)](https://github.com/getsentry/sentry-php/pull/1665) + ## 4.2.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.2.0. From 7e71a72baa2c7439507d868487bb36226a8a4e0c Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 21 Dec 2023 16:39:28 +0000 Subject: [PATCH 0962/1161] release: 4.3.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 6b5ea99ce..668e72e3f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.2.0'; + public const SDK_VERSION = '4.3.0'; /** * @var Options The client options From b3dd0430328cc080604f017ca904b08ca9ba9c52 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 22 Dec 2023 03:35:26 +0100 Subject: [PATCH 0963/1161] Remove internal annotation from MetricsUnit (#1671) --- src/Metrics/MetricsUnit.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Metrics/MetricsUnit.php b/src/Metrics/MetricsUnit.php index f417a5599..3e39d56fe 100644 --- a/src/Metrics/MetricsUnit.php +++ b/src/Metrics/MetricsUnit.php @@ -4,9 +4,6 @@ namespace Sentry\Metrics; -/** - * @internal - */ final class MetricsUnit implements \Stringable { /** From 493d8111e61c8e6ac0c63f5d848889f8192faecf Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 22 Dec 2023 19:03:41 +0100 Subject: [PATCH 0964/1161] Fix tags not being serialized correctly for metrics payload (#1672) --- src/Serializer/EnvelopItems/MetricsItem.php | 11 +++++++---- tests/Serializer/PayloadSerializerTest.php | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Serializer/EnvelopItems/MetricsItem.php b/src/Serializer/EnvelopItems/MetricsItem.php index 3bd0b7c3a..306ee44fa 100644 --- a/src/Serializer/EnvelopItems/MetricsItem.php +++ b/src/Serializer/EnvelopItems/MetricsItem.php @@ -53,14 +53,17 @@ public static function toEnvelopeItem(Event $event): string // type - |c|, |d|, ... $line .= '|' . $metric->getType() . '|'; - $tags = ''; + $tags = []; foreach ($metric->getTags() as $key => $value) { - $tags .= preg_replace(self::KEY_PATTERN, '_', $key) . + $tags[] = preg_replace(self::KEY_PATTERN, '_', $key) . ':' . preg_replace(self::VALUE_PATTERN, '', $value); } - // tags - #key:value,key:value... - $line .= '#' . $tags . '|'; + if (!empty($tags)) { + // tags - #key:value,key:value... + $line .= '#' . implode(',', $tags) . '|'; + } + // timestamp - T123456789 $line .= 'T' . $metric->getTimestamp(); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 2819b941f..3746423c4 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -412,10 +412,11 @@ public static function serializeAsEnvelopeDataProvider(): iterable false, ]; - $counter = new CounterType('counter', 1.0, MetricsUnit::second(), ['foo' => 'bar'], 1597790835); + $counter = new CounterType('counter', 1.0, MetricsUnit::second(), ['foo' => 'bar', 'baz' => 'qux'], 1597790835); $distribution = new DistributionType('distribution', 1.0, MetricsUnit::second(), ['$foo$' => '%bar%'], 1597790835); $gauge = new GaugeType('gauge', 1.0, MetricsUnit::second(), ['föö' => 'bär'], 1597790835); $set = new SetType('set', 1.0, MetricsUnit::second(), ['%{key}' => '$value$'], 1597790835); + $noTags = new CounterType('no_tags', 1.0, MetricsUnit::second(), [], 1597790835); $event = Event::createMetrics(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setMetrics([ @@ -423,17 +424,19 @@ public static function serializeAsEnvelopeDataProvider(): iterable $distribution, $gauge, $set, + $noTags, ]); yield [ $event, << Date: Fri, 22 Dec 2023 19:46:19 +0100 Subject: [PATCH 0965/1161] Prepare 4.3.1 (#1673) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d478ebfeb..87c20fc13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # CHANGELOG +## 4.3.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.3.1. + +### Bug Fixes + +- Fix tags not being serialized correctly for metrics [(#1672)](https://github.com/getsentry/sentry-php/pull/1672) + +### Misc + +- Remove `@internal` annotation from `MetricsUnit` class [(#1671)](https://github.com/getsentry/sentry-php/pull/1671) + ## 4.3.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.3.0. From cd89f230bda0833cb9992ebe9a1b7d24d6ee245b Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Fri, 22 Dec 2023 18:46:49 +0000 Subject: [PATCH 0966/1161] release: 4.3.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 668e72e3f..4a1a5d229 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.3.0'; + public const SDK_VERSION = '4.3.1'; /** * @var Options The client options From 013b822d388301a48c05cdd2c9a0a262abc2c1fb Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 9 Jan 2024 15:11:40 +0100 Subject: [PATCH 0967/1161] Do not send an empty event if no metrics are in the bucket (#1676) --- src/CheckIn.php | 4 ++-- src/Metrics/MetricsAggregator.php | 4 ++++ src/functions.php | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CheckIn.php b/src/CheckIn.php index 1a0eafbe5..8c31eb015 100644 --- a/src/CheckIn.php +++ b/src/CheckIn.php @@ -19,7 +19,7 @@ final class CheckIn private $monitorSlug; /** - * @var \Sentry\CheckInStatus The status of the check-in + * @var CheckInStatus The status of the check-in */ private $status; @@ -39,7 +39,7 @@ final class CheckIn private $duration; /** - * @var \Sentry\MonitorConfig|null The monitor configuration + * @var MonitorConfig|null The monitor configuration */ private $monitorConfig; diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php index 8cb50f036..14ec87e22 100644 --- a/src/Metrics/MetricsAggregator.php +++ b/src/Metrics/MetricsAggregator.php @@ -101,6 +101,10 @@ public function add( public function flush(): ?EventId { + if ($this->buckets === []) { + return null; + } + $hub = SentrySdk::getCurrentHub(); $event = Event::createMetrics()->setMetrics($this->buckets); diff --git a/src/functions.php b/src/functions.php index 465ad95eb..6896b99b9 100644 --- a/src/functions.php +++ b/src/functions.php @@ -148,7 +148,7 @@ function withScope(callable $callback) * Sentry. * * @param TransactionContext $context Properties of the new transaction - * @param array $customSamplingContext Additional context that will be passed to the {@see \Sentry\Tracing\SamplingContext} + * @param array $customSamplingContext Additional context that will be passed to the {@see Tracing\SamplingContext} */ function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { From 65d9ff384a3677724cdb2ea0cb82f732f3274cb6 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 9 Jan 2024 15:38:57 +0100 Subject: [PATCH 0968/1161] Implement Metrics::timing (#1670) Co-authored-by: Michi Hoffmann --- src/Metrics/Metrics.php | 47 +++++++++++++++---- src/Metrics/MetricsAggregator.php | 8 ++-- src/Metrics/Types/AbstractType.php | 6 +-- tests/Metrics/MetricsTest.php | 74 ++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php index 1b4b35937..66616ee20 100644 --- a/src/Metrics/Metrics.php +++ b/src/Metrics/Metrics.php @@ -37,8 +37,8 @@ public static function getInstance(): self } /** - * @param int|float $value - * @param string[] $tags + * @param int|float $value + * @param array $tags */ public function increment( string $key, @@ -60,8 +60,8 @@ public function increment( } /** - * @param int|float $value - * @param string[] $tags + * @param int|float $value + * @param array $tags */ public function distribution( string $key, @@ -83,8 +83,8 @@ public function distribution( } /** - * @param int|float $value - * @param string[] $tags + * @param int|float $value + * @param array $tags */ public function gauge( string $key, @@ -106,8 +106,8 @@ public function gauge( } /** - * @param int|string $value - * @param string[] $tags + * @param int|string $value + * @param array $tags */ public function set( string $key, @@ -128,6 +128,37 @@ public function set( ); } + /** + * @template T + * + * @param callable(): T $callable + * @param array $tags + * + * @return T + */ + public function timing( + string $key, + callable $callable, + array $tags = [], + int $stackLevel = 0 + ) { + $startTimestamp = microtime(true); + + $result = $callable(); + + $this->aggregator->add( + DistributionType::TYPE, + $key, + microtime(true) - $startTimestamp, + MetricsUnit::second(), + $tags, + (int) $startTimestamp, + $stackLevel + ); + + return $result; + } + public function flush(): ?EventId { return $this->aggregator->flush(); diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php index 14ec87e22..edd448772 100644 --- a/src/Metrics/MetricsAggregator.php +++ b/src/Metrics/MetricsAggregator.php @@ -38,8 +38,8 @@ final class MetricsAggregator ]; /** - * @param string[] $tags - * @param int|float|string $value + * @param array $tags + * @param int|float|string $value */ public function add( string $type, @@ -114,9 +114,9 @@ public function flush(): ?EventId } /** - * @param string[] $tags + * @param array $tags * - * @return string[] + * @return array */ private function serializeTags(array $tags): array { diff --git a/src/Metrics/Types/AbstractType.php b/src/Metrics/Types/AbstractType.php index 7a089b8c6..1b80bb6c3 100644 --- a/src/Metrics/Types/AbstractType.php +++ b/src/Metrics/Types/AbstractType.php @@ -26,7 +26,7 @@ abstract class AbstractType private $unit; /** - * @var string[] + * @var array */ private $tags; @@ -41,7 +41,7 @@ abstract class AbstractType private $codeLocation; /** - * @param string[] $tags + * @param array $tags */ public function __construct(string $key, MetricsUnit $unit, array $tags, int $timestamp) { @@ -71,7 +71,7 @@ public function getUnit(): MetricsUnit } /** - * @return string[] + * @return array */ public function getTags(): array { diff --git a/tests/Metrics/MetricsTest.php b/tests/Metrics/MetricsTest.php index 87b26e4bc..354030f1f 100644 --- a/tests/Metrics/MetricsTest.php +++ b/tests/Metrics/MetricsTest.php @@ -145,6 +145,80 @@ public function testDistribution(): void metrics()->flush(); } + public function testTiming(): void + { + ClockMock::withClockMock(1699412953); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'release' => '1.0.0', + 'environment' => 'development', + 'attach_metric_code_locations' => true, + ])); + + $self = $this; + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(static function (Event $event) use ($self): bool { + $metric = $event->getMetrics()['8a817dcdb12cfffc1fa8b459ad0c9d56']; + + $self->assertSame(DistributionType::TYPE, $metric->getType()); + $self->assertSame('foo', $metric->getKey()); + $self->assertSame([1.0, 2.0], $metric->serialize()); + $self->assertSame(MetricsUnit::second(), $metric->getUnit()); + $self->assertSame( + [ + 'environment' => 'development', + 'foo' => 'bar', + 'release' => '1.0.0', + ], + $metric->getTags() + ); + $self->assertSame(1699412953, $metric->getTimestamp()); + + $codeLocation = $metric->getCodeLocation(); + + $self->assertSame('Sentry\Metrics\Metrics::timing', $codeLocation->getFunctionName()); + + return true; + })); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + $firstTimingResult = metrics()->timing( + 'foo', + static function () { + // Move the clock forward 1 second + ClockMock::withClockMock(1699412954); + + return '1second'; + }, + ['foo' => 'bar'] + ); + + $this->assertEquals('1second', $firstTimingResult); + + ClockMock::withClockMock(1699412953); + + $secondTimingResult = metrics()->timing( + 'foo', + static function () { + // Move the clock forward 2 seconds + ClockMock::withClockMock(1699412955); + }, + ['foo' => 'bar'] + ); + + $this->assertNull($secondTimingResult); + + metrics()->flush(); + } + public function testGauge(): void { ClockMock::withClockMock(1699412953); From 7c2f6055c73db5b6858f0b64f820442596c4c9c1 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 17 Jan 2024 17:24:38 +0100 Subject: [PATCH 0969/1161] Add cron monitor wrapper (#1679) Co-authored-by: Alex Bouma --- src/functions.php | 32 ++++++++++++++++- tests/FunctionsTest.php | 80 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/functions.php b/src/functions.php index 6896b99b9..c79939d23 100644 --- a/src/functions.php +++ b/src/functions.php @@ -81,6 +81,37 @@ function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ? return SentrySdk::getCurrentHub()->captureCheckIn($slug, $status, $duration, $monitorConfig, $checkInId); } +/** + * Execute the given callable while wrapping it in a monitor check-in. + * + * @param string $slug Identifier of the Monitor + * @param callable $callback The callable that is going to be monitored + * @param MonitorConfig|null $monitorConfig Configuration of the Monitor + * + * @return mixed + */ +function withMonitor(string $slug, callable $callback, ?MonitorConfig $monitorConfig = null) +{ + $checkInId = SentrySdk::getCurrentHub()->captureCheckIn($slug, CheckInStatus::inProgress(), null, $monitorConfig); + + $status = CheckInStatus::ok(); + $duration = 0; + + try { + $start = microtime(true); + $result = $callback(); + $duration = microtime(true) - $start; + + return $result; + } catch (\Throwable $e) { + $status = CheckInStatus::error(); + + throw $e; + } finally { + SentrySdk::getCurrentHub()->captureCheckIn($slug, $status, $duration, $monitorConfig, $checkInId); + } +} + /** * Records a new breadcrumb which will be attached to future events. They * will be added to subsequent events to provide more context on user's @@ -157,7 +188,6 @@ function startTransaction(TransactionContext $context, array $customSamplingCont /** * Execute the given callable while wrapping it in a span added as a child to the current transaction and active span. - * * If there is no transaction active this is a no-op and the scope passed to the trace callable will be unused. * * @template T diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 53218169d..b3758c42b 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -42,6 +42,7 @@ use function Sentry\init; use function Sentry\startTransaction; use function Sentry\trace; +use function Sentry\withMonitor; use function Sentry\withScope; final class FunctionsTest extends TestCase @@ -200,7 +201,7 @@ public function testCaptureCheckIn(): void 'UTC' ); - $hub = $this->createMock(StubHubInterface::class); + $hub = $this->createMock(HubInterface::class); $hub->expects($this->once()) ->method('captureCheckIn') ->with('test-crontab', CheckInStatus::ok(), 10, $monitorConfig, $checkInId) @@ -217,6 +218,78 @@ public function testCaptureCheckIn(): void )); } + public function testWithMonitor(): void + { + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->exactly(2)) + ->method('captureCheckIn') + ->with( + $this->callback(function (string $slug): bool { + return $slug === 'test-crontab'; + }), + $this->callback(function (CheckInStatus $checkInStatus): bool { + // just check for type CheckInStatus + return true; + }), + $this->anything(), + $this->callback(function (MonitorConfig $monitorConfig): bool { + return $monitorConfig->getSchedule()->getValue() === '*/5 * * * *' + && $monitorConfig->getSchedule()->getType() === MonitorSchedule::TYPE_CRONTAB + && $monitorConfig->getCheckinMargin() === 5 + && $monitorConfig->getMaxRuntime() === 30 + && $monitorConfig->getTimezone() === 'UTC'; + }) + ); + + SentrySdk::setCurrentHub($hub); + + withMonitor('test-crontab', function () { + // Do something... + }, new MonitorConfig( + new MonitorSchedule(MonitorSchedule::TYPE_CRONTAB, '*/5 * * * *'), + 5, + 30, + 'UTC' + )); + } + + public function testWithMonitorCallableThrows(): void + { + $this->expectException(\Exception::class); + + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->exactly(2)) + ->method('captureCheckIn') + ->with( + $this->callback(function (string $slug): bool { + return $slug === 'test-crontab'; + }), + $this->callback(function (CheckInStatus $checkInStatus): bool { + // just check for type CheckInStatus + return true; + }), + $this->anything(), + $this->callback(function (MonitorConfig $monitorConfig): bool { + return $monitorConfig->getSchedule()->getValue() === '*/5 * * * *' + && $monitorConfig->getSchedule()->getType() === MonitorSchedule::TYPE_CRONTAB + && $monitorConfig->getCheckinMargin() === 5 + && $monitorConfig->getMaxRuntime() === 30 + && $monitorConfig->getTimezone() === 'UTC'; + }) + ); + + SentrySdk::setCurrentHub($hub); + + withMonitor('test-crontab', function () { + throw new \Exception(); + }, new MonitorConfig( + new MonitorSchedule(MonitorSchedule::TYPE_CRONTAB, '*/5 * * * *'), + 5, + 30, + 'UTC' + )); + } + public function testAddBreadcrumb(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); @@ -440,8 +513,3 @@ public function testContinueTrace(): void }); } } - -interface StubHubInterface extends HubInterface -{ - public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string; -} From 7ac8703dc0395f8fd67fd7e5976fbd5656d1b04a Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 17 Jan 2024 17:27:32 +0100 Subject: [PATCH 0970/1161] Deprecate user segment (#1681) --- src/Tracing/DynamicSamplingContext.php | 10 ---------- src/UserDataBag.php | 4 ++++ tests/Tracing/DynamicSamplingContextTest.php | 19 +------------------ 3 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index c3a704a53..cf159ed59 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -182,12 +182,6 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h } } - $hub->configureScope(static function (Scope $scope) use ($samplingContext): void { - if ($scope->getUser() !== null && $scope->getUser()->getSegment() !== null) { - $samplingContext->set('user_segment', $scope->getUser()->getSegment()); - } - }); - if ($transaction->getSampled() !== null) { $samplingContext->set('sampled', $transaction->getSampled() ? 'true' : 'false'); } @@ -218,10 +212,6 @@ public static function fromOptions(Options $options, Scope $scope): self $samplingContext->set('environment', $options->getEnvironment()); } - if ($scope->getUser() !== null && $scope->getUser()->getSegment() !== null) { - $samplingContext->set('user_segment', $scope->getUser()->getSegment()); - } - $samplingContext->freeze(); return $samplingContext; diff --git a/src/UserDataBag.php b/src/UserDataBag.php index 5e08dbb5b..ec536faef 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -183,6 +183,8 @@ public function setEmail(?string $email): self /** * Gets the segement of the user. + * + * @deprecated since version 4.4. To be removed in version 5.0 */ public function getSegment(): ?string { @@ -193,6 +195,8 @@ public function getSegment(): ?string * Sets the segment of the user. * * @param string|null $segment The segment + * + * @deprecated since version 4.4. To be removed in version 5.0. You may use a custom tag or context instead. */ public function setSegment(?string $segment): self { diff --git a/tests/Tracing/DynamicSamplingContextTest.php b/tests/Tracing/DynamicSamplingContextTest.php index 137e7030c..73ecb5052 100644 --- a/tests/Tracing/DynamicSamplingContextTest.php +++ b/tests/Tracing/DynamicSamplingContextTest.php @@ -15,7 +15,6 @@ use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; -use Sentry\UserDataBag; final class DynamicSamplingContextTest extends TestCase { @@ -29,7 +28,6 @@ public function testFromHeader( ?string $expectedSampleRate, ?string $expectedRelease, ?string $expectedEnvironment, - ?string $expectedUserSegment, ?string $expectedTransaction ): void { $samplingContext = DynamicSamplingContext::fromHeader($header); @@ -39,7 +37,6 @@ public function testFromHeader( $this->assertSame($expectedSampleRate, $samplingContext->get('sample_rate')); $this->assertSame($expectedRelease, $samplingContext->get('release')); $this->assertSame($expectedEnvironment, $samplingContext->get('environment')); - $this->assertSame($expectedUserSegment, $samplingContext->get('user_segment')); $this->assertSame($expectedTransaction, $samplingContext->get('transaction')); } @@ -64,7 +61,6 @@ public static function fromHeaderDataProvider(): \Generator null, null, null, - null, ]; yield [ @@ -74,7 +70,6 @@ public static function fromHeaderDataProvider(): \Generator '1', '1.0.0', 'test', - 'my_segment', '', ]; } @@ -90,13 +85,7 @@ public function testFromTransaction(): void 'environment' => 'test', ])); - $user = new UserDataBag(); - $user->setSegment('my_segment'); - - $scope = new Scope(); - $scope->setUser($user); - - $hub = new Hub($client, $scope); + $hub = new Hub($client); $transactionContext = new TransactionContext(); $transactionContext->setName('foo'); @@ -112,7 +101,6 @@ public function testFromTransaction(): void $this->assertSame('public', $samplingContext->get('public_key')); $this->assertSame('1.0.0', $samplingContext->get('release')); $this->assertSame('test', $samplingContext->get('environment')); - $this->assertSame('my_segment', $samplingContext->get('user_segment')); $this->assertTrue($samplingContext->isFrozen()); } @@ -143,11 +131,7 @@ public function testFromOptions(): void $propagationContext = PropagationContext::fromDefaults(); $propagationContext->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); - $user = new UserDataBag(); - $user->setSegment('my_segment'); - $scope = new Scope(); - $scope->setUser($user); $scope->setPropagationContext($propagationContext); $samplingContext = DynamicSamplingContext::fromOptions($options, $scope); @@ -157,7 +141,6 @@ public function testFromOptions(): void $this->assertSame('public', $samplingContext->get('public_key')); $this->assertSame('1.0.0', $samplingContext->get('release')); $this->assertSame('test', $samplingContext->get('environment')); - $this->assertSame('my_segment', $samplingContext->get('user_segment')); $this->assertTrue($samplingContext->isFrozen()); } From 1cc36e6464da66cfb37c8b6a1fe605a4553e0baa Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 18 Jan 2024 13:58:29 +0100 Subject: [PATCH 0971/1161] Rename `$callable` to `$callback` (#1683) --- src/Metrics/Metrics.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php index 66616ee20..235783451 100644 --- a/src/Metrics/Metrics.php +++ b/src/Metrics/Metrics.php @@ -131,20 +131,20 @@ public function set( /** * @template T * - * @param callable(): T $callable + * @param callable(): T $callback * @param array $tags * * @return T */ public function timing( string $key, - callable $callable, + callable $callback, array $tags = [], int $stackLevel = 0 ) { $startTimestamp = microtime(true); - $result = $callable(); + $result = $callback(); $this->aggregator->add( DistributionType::TYPE, From 62274fb8a1a4ed5e0aaa6a3320de56b310422907 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 18 Jan 2024 15:28:07 +0100 Subject: [PATCH 0972/1161] Add `TransactionContext::make()` & `SpanContext::make()` (#1684) --- src/Tracing/SpanContext.php | 8 ++++++++ src/Tracing/TransactionContext.php | 8 ++++++++ tests/Tracing/SpanTest.php | 2 +- tests/Tracing/TransactionTest.php | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 65df6dab9..a4a22e2e2 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -61,6 +61,14 @@ class SpanContext */ private $endTimestamp; + /** + * @return self + */ + public static function make() + { + return new self(); + } + public function getDescription(): ?string { return $this->description; diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index e452de72a..2b23d3666 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -42,6 +42,14 @@ public function __construct( $this->metadata = $metadata ?? new TransactionMetadata(); } + /** + * @return self + */ + public static function make() + { + return new self(); + } + /** * Gets the name of the transaction. */ diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index 9c2387415..406118333 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -56,7 +56,7 @@ public function testStartChild(): void ->setSpanId(SpanId::generate()) ->setTraceId(TraceId::generate()); - $spanContext2 = (new SpanContext()) + $spanContext2 = SpanContext::make() ->setSampled(true) ->setParentSpanId($spanContext2ParentSpanId) ->setTraceId($spanContext2TraceId); diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index 34cfe2b0e..576a689f8 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -28,7 +28,7 @@ public function testFinish(): void ClockMock::withClockMock(1600640877); $expectedEventId = null; - $transactionContext = (new TransactionContext()) + $transactionContext = TransactionContext::make() ->setTags(['ios_version' => '4.0']) ->setSampled(true) ->setStartTimestamp(1600640865); From a31d418e910105dba29c23e6aab89e1d6af66db3 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 18 Jan 2024 15:30:55 +0100 Subject: [PATCH 0973/1161] Add support for W3C traceparent header (#1680) --- src/Tracing/GuzzleTracingMiddleware.php | 3 ++ src/Tracing/PropagationContext.php | 30 ++++++++++--- src/Tracing/Span.php | 14 ++++++ src/Tracing/TransactionContext.php | 21 ++++++++- src/functions.php | 32 +++++++++++++- tests/FunctionsTest.php | 44 +++++++++++++++++++ tests/Tracing/GuzzleTracingMiddlewareTest.php | 5 +++ tests/Tracing/PropagationContextTest.php | 17 +++++++ tests/Tracing/TransactionContextTest.php | 30 +++++++++++++ 9 files changed, 188 insertions(+), 8 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 10690abb0..3d5232db1 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -15,6 +15,7 @@ use function Sentry\getBaggage; use function Sentry\getTraceparent; +use function Sentry\getW3CTraceparent; /** * This handler traces each outgoing HTTP request by recording performance data. @@ -33,6 +34,7 @@ public static function trace(?HubInterface $hub = null): \Closure if (self::shouldAttachTracingHeaders($client, $request)) { $request = $request ->withHeader('sentry-trace', getTraceparent()) + ->withHeader('traceparent', getW3CTraceparent()) ->withHeader('baggage', getBaggage()); } @@ -60,6 +62,7 @@ public static function trace(?HubInterface $hub = null): \Closure if (self::shouldAttachTracingHeaders($client, $request)) { $request = $request ->withHeader('sentry-trace', $childSpan->toTraceparent()) + ->withHeader('traceparent', $childSpan->toW3CTraceparent()) ->withHeader('baggage', $childSpan->toBaggage()); } diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index 851ecc527..4271a314e 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -9,7 +9,9 @@ final class PropagationContext { - private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + + private const W3C_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0]{2})?-?(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01]{2})?[ \\t]*$/i'; /** * @var TraceId The trace id @@ -49,12 +51,12 @@ public static function fromDefaults(): self public static function fromHeaders(string $sentryTraceHeader, string $baggageHeader): self { - return self::parseTraceAndBaggage($sentryTraceHeader, $baggageHeader); + return self::parseTraceparentAndBaggage($sentryTraceHeader, $baggageHeader); } public static function fromEnvironment(string $sentryTrace, string $baggage): self { - return self::parseTraceAndBaggage($sentryTrace, $baggage); + return self::parseTraceparentAndBaggage($sentryTrace, $baggage); } /** @@ -65,6 +67,14 @@ public function toTraceparent(): string return sprintf('%s-%s', (string) $this->traceId, (string) $this->spanId); } + /** + * Returns a string that can be used for the W3C `traceparent` header & meta tag. + */ + public function toW3CTraceparent(): string + { + return sprintf('00-%s-%s', (string) $this->traceId, (string) $this->spanId); + } + /** * Returns a string that can be used for the `baggage` header & meta tag. */ @@ -149,12 +159,22 @@ public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplin return $this; } - private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self + private static function parseTraceparentAndBaggage(string $traceparent, string $baggage): self { $context = self::fromDefaults(); $hasSentryTrace = false; - if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) { + if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) { + if (!empty($matches['trace_id'])) { + $context->traceId = new TraceId($matches['trace_id']); + $hasSentryTrace = true; + } + + if (!empty($matches['span_id'])) { + $context->parentSpanId = new SpanId($matches['span_id']); + $hasSentryTrace = true; + } + } elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) { if (!empty($matches['trace_id'])) { $context->traceId = new TraceId($matches['trace_id']); $hasSentryTrace = true; diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 08f0b771d..f743d0aa9 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -567,6 +567,20 @@ public function toTraceparent(): string return sprintf('%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled); } + /** + * Returns a string that can be used for the W3C `traceparent` header & meta tag. + */ + public function toW3CTraceparent(): string + { + $sampled = ''; + + if ($this->sampled !== null) { + $sampled = $this->sampled ? '-01' : '-00'; + } + + return sprintf('00-%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled); + } + /** * Returns a string that can be used for the `baggage` header & meta tag. */ diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index 2b23d3666..e804a1bd9 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -6,7 +6,9 @@ final class TransactionContext extends SpanContext { - private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + + private const W3C_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0]{2})?-?(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01]{2})?[ \\t]*$/i'; public const DEFAULT_NAME = ''; @@ -149,7 +151,7 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag $context = new self(); $hasSentryTrace = false; - if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) { + if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) { if (!empty($matches['trace_id'])) { $context->traceId = new TraceId($matches['trace_id']); $hasSentryTrace = true; @@ -164,6 +166,21 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag $context->parentSampled = $matches['sampled'] === '1'; $hasSentryTrace = true; } + } elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) { + if (!empty($matches['trace_id'])) { + $context->traceId = new TraceId($matches['trace_id']); + $hasSentryTrace = true; + } + + if (!empty($matches['span_id'])) { + $context->parentSpanId = new SpanId($matches['span_id']); + $hasSentryTrace = true; + } + + if (isset($matches['sampled'])) { + $context->parentSampled = $matches['sampled'] === '01'; + $hasSentryTrace = true; + } } $samplingContext = DynamicSamplingContext::fromHeader($baggage); diff --git a/src/functions.php b/src/functions.php index c79939d23..293e6884e 100644 --- a/src/functions.php +++ b/src/functions.php @@ -224,7 +224,7 @@ function trace(callable $trace, SpanContext $context) } /** - * Creates the current traceparent string, to be used as a HTTP header value + * Creates the current Sentry traceparent string, to be used as a HTTP header value * or HTML meta tag value. * This function is context aware, as in it either returns the traceparent based * on the current span, or the scope's propagation context. @@ -253,6 +253,36 @@ function getTraceparent(): string return $traceParent; } +/** + * Creates the current W3C traceparent string, to be used as a HTTP header value + * or HTML meta tag value. + * This function is context aware, as in it either returns the traceparent based + * on the current span, or the scope's propagation context. + */ +function getW3CTraceparent(): string +{ + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + if ($client !== null) { + $options = $client->getOptions(); + + if ($options !== null && $options->isTracingEnabled()) { + $span = SentrySdk::getCurrentHub()->getSpan(); + if ($span !== null) { + return $span->toW3CTraceparent(); + } + } + } + + $traceParent = ''; + $hub->configureScope(function (Scope $scope) use (&$traceParent) { + $traceParent = $scope->getPropagationContext()->toW3CTraceparent(); + }); + + return $traceParent; +} + /** * Creates the baggage content string, to be used as a HTTP header value * or HTML meta tag value. diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index b3758c42b..73ce3c109 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -39,6 +39,7 @@ use function Sentry\continueTrace; use function Sentry\getBaggage; use function Sentry\getTraceparent; +use function Sentry\getW3CTraceparent; use function Sentry\init; use function Sentry\startTransaction; use function Sentry\trace; @@ -429,6 +430,49 @@ public function testTraceparentWithTracingEnabled(): void $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); } + public function testW3CTraceparentWithTracingDisabled(): void + { + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); + + $scope = new Scope($propagationContext); + + $hub = new Hub(null, $scope); + + SentrySdk::setCurrentHub($hub); + + $traceParent = getW3CTraceparent(); + + $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); + } + + public function testW3CTraceparentWithTracingEnabled(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1.0, + ])); + + $hub = new Hub($client); + + SentrySdk::setCurrentHub($hub); + + $spanContext = (new SpanContext()) + ->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')) + ->setSpanId(new SpanId('566e3688a61d4bc8')); + + $span = new Span($spanContext); + + $hub->setSpan($span); + + $traceParent = getW3CTraceparent(); + + $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); + } + public function testBaggageWithTracingDisabled(): void { $propagationContext = PropagationContext::fromDefaults(); diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 54934cb43..ac9aa76c9 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -77,9 +77,11 @@ public function testTraceHeaders(Request $request, Options $options, bool $heade $function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface { if ($headersShouldBePresent) { $this->assertNotEmpty($request->getHeader('sentry-trace')); + $this->assertNotEmpty($request->getHeader('traceparent')); $this->assertNotEmpty($request->getHeader('baggage')); } else { $this->assertEmpty($request->getHeader('sentry-trace')); + $this->assertEmpty($request->getHeader('traceparent')); $this->assertEmpty($request->getHeader('baggage')); } @@ -112,9 +114,11 @@ public function testTraceHeadersWithTransacttion(Request $request, Options $opti $function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface { if ($headersShouldBePresent) { $this->assertNotEmpty($request->getHeader('sentry-trace')); + $this->assertNotEmpty($request->getHeader('traceparent')); $this->assertNotEmpty($request->getHeader('baggage')); } else { $this->assertEmpty($request->getHeader('sentry-trace')); + $this->assertEmpty($request->getHeader('traceparent')); $this->assertEmpty($request->getHeader('baggage')); } @@ -241,6 +245,7 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec $middleware = GuzzleTracingMiddleware::trace($hub); $function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface { $this->assertNotEmpty($request->getHeader('sentry-trace')); + $this->assertNotEmpty($request->getHeader('traceparent')); $this->assertNotEmpty($request->getHeader('baggage')); if ($expectedPromiseResult instanceof \Throwable) { return new RejectedPromise($expectedPromiseResult); diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index a80d9d17f..6327b0ccb 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -78,6 +78,14 @@ public static function tracingDataProvider(): iterable true, ]; + yield [ + '00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01', + '', + new TraceId('566e3688a61d4bc888951642d6f14a19'), + new SpanId('566e3688a61d4bc8'), + true, + ]; + yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', @@ -104,6 +112,15 @@ public function testToTraceparent() $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toTraceparent()); } + public function testToW3CTraceparent() + { + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); + + $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toW3CTraceparent()); + } + public function testToBaggage() { $dynamicSamplingContext = DynamicSamplingContext::fromHeader('sentry-trace_id=566e3688a61d4bc888951642d6f14a19'); diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index c4c1176e2..80ee26d7f 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -117,6 +117,36 @@ public static function tracingDataProvider(): iterable true, ]; + yield [ + '00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-00', + '', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + false, + DynamicSamplingContext::class, + true, + ]; + + yield [ + '00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01', + '', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + true, + DynamicSamplingContext::class, + true, + ]; + + yield [ + '00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', + '', + new SpanId('566e3688a61d4bc8'), + new TraceId('566e3688a61d4bc888951642d6f14a19'), + null, + DynamicSamplingContext::class, + true, + ]; + yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', From 29d00b0a416b8b547c40106f1d3f46ae2e95c904 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 19 Jan 2024 14:13:58 +0100 Subject: [PATCH 0974/1161] Add `failure_issue_threshold` & `recovery_threshold` to MonitorConfig (#1685) --- src/MonitorConfig.php | 42 +++++++++++++++++++++- tests/MonitorConfigTest.php | 8 ++++- tests/Serializer/PayloadSerializerTest.php | 6 ++-- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/MonitorConfig.php b/src/MonitorConfig.php index c03c82700..d2954e917 100644 --- a/src/MonitorConfig.php +++ b/src/MonitorConfig.php @@ -26,16 +26,30 @@ final class MonitorConfig */ private $timezone; + /** + * @var int|null The number of consecutive failed check-ins it takes before an issue is created + */ + private $failureIssueThreshold; + + /** + * @var int|null The number of consecutive OK check-ins it takes before an issue is resolved + */ + private $recoveryThreshold; + public function __construct( MonitorSchedule $schedule, ?int $checkinMargin = null, ?int $maxRuntime = null, - ?string $timezone = null + ?string $timezone = null, + ?int $failureIssueThreshold = null, + ?int $recoveryThreshold = null ) { $this->schedule = $schedule; $this->checkinMargin = $checkinMargin; $this->maxRuntime = $maxRuntime; $this->timezone = $timezone; + $this->failureIssueThreshold = $failureIssueThreshold; + $this->recoveryThreshold = $recoveryThreshold; } public function getSchedule(): MonitorSchedule @@ -86,6 +100,30 @@ public function setTimezone(?string $timezone): self return $this; } + public function getFailureRecoveryThreshold(): ?int + { + return $this->failureIssueThreshold; + } + + public function setFailureRecoveryThreshold(?int $failureIssueThreshold): self + { + $this->failureIssueThreshold = $failureIssueThreshold; + + return $this; + } + + public function getRecoveryThreshold(): ?int + { + return $this->recoveryThreshold; + } + + public function setRecoveryThreshold(?int $recoveryThreshold): self + { + $this->recoveryThreshold = $recoveryThreshold; + + return $this; + } + /** * @return array */ @@ -96,6 +134,8 @@ public function toArray(): array 'checkin_margin' => $this->checkinMargin, 'max_runtime' => $this->maxRuntime, 'timezone' => $this->timezone, + 'failure_issue_threshold' => $this->failureIssueThreshold, + 'recovery_threshold' => $this->recoveryThreshold, ]; } } diff --git a/tests/MonitorConfigTest.php b/tests/MonitorConfigTest.php index e2107165e..580a56607 100644 --- a/tests/MonitorConfigTest.php +++ b/tests/MonitorConfigTest.php @@ -17,13 +17,17 @@ public function testConstructor(): void MonitorSchedule::crontab('* * * * *'), 10, 12, - 'Europe/Amsterdam' + 'Europe/Amsterdam', + 5, + 10 ); $this->assertEquals($monitorSchedule, $monitorConfig->getSchedule()); $this->assertEquals(10, $monitorConfig->getCheckinMargin()); $this->assertEquals(12, $monitorConfig->getMaxRuntime()); $this->assertEquals('Europe/Amsterdam', $monitorConfig->getTimezone()); + $this->assertEquals(5, $monitorConfig->getFailureRecoveryThreshold()); + $this->assertEquals(10, $monitorConfig->getRecoveryThreshold()); } /** @@ -46,6 +50,8 @@ public static function gettersAndSettersDataProvider(): array ['getCheckinMargin', 'setCheckinMargin', 10], ['getMaxRuntime', 'setMaxRuntime', 12], ['getTimezone', 'setTimezone', 'Europe/Amsterdam'], + ['getFailureRecoveryThreshold', 'setFailureRecoveryThreshold', 5], + ['getRecoveryThreshold', 'setRecoveryThreshold', 10], ]; } } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 3746423c4..19e00acc8 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -390,7 +390,9 @@ public static function serializeAsEnvelopeDataProvider(): iterable MonitorSchedule::crontab('0 0 * * *'), 10, 12, - 'Europe/Amsterdam' + 'Europe/Amsterdam', + 5, + 10 ) ); @@ -406,7 +408,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable << Date: Mon, 22 Jan 2024 04:01:09 -0800 Subject: [PATCH 0975/1161] Allow fluent use of Transaction::setName() method (#1687) --- src/Tracing/Transaction.php | 6 +++++- tests/Tracing/TransactionTest.php | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index 4c832d369..ba72c2e80 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -70,10 +70,14 @@ public function getName(): string * Sets the name of this transaction. * * @param string $name The name + * + * @return $this */ - public function setName(string $name): void + public function setName(string $name): self { $this->name = $name; + + return $this; } /** diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index 576a689f8..262559c4b 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -86,6 +86,18 @@ public function testFinishDoesNothingIfSampledFlagIsNotTrue(): void $transaction->finish(); } + public function testFluentApi(): void + { + $transaction = new Transaction(TransactionContext::make()); + $tags = ['foo' => 'bar']; + $name = 'baz'; + $transaction->setTags($tags) + ->setName($name) + ->finish(); + $this->assertSame($tags, $transaction->getTags()); + $this->assertSame($name, $transaction->getName()); + } + /** * @dataProvider parentTransactionContextDataProvider */ From 40ddd184692d397b21b25997d2c8454e92e8c0c7 Mon Sep 17 00:00:00 2001 From: Joe Shaw Date: Mon, 22 Jan 2024 12:04:14 +0000 Subject: [PATCH 0976/1161] http_ssl_verify_peer sets CURLOPT_SSL_VERIFYPEER correctly (#1686) Co-authored-by: Michi Hoffmann --- src/HttpClient/HttpClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 5c98fe7bf..779088d25 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -74,8 +74,8 @@ public function sendRequest(Request $request, Options $options): Response curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1); $httpSslVerifyPeer = $options->getHttpSslVerifyPeer(); - if ($httpSslVerifyPeer) { - curl_setopt($curlHandle, \CURLOPT_SSL_VERIFYPEER, true); + if (!$httpSslVerifyPeer) { + curl_setopt($curlHandle, \CURLOPT_SSL_VERIFYPEER, false); } $httpProxy = $options->getHttpProxy(); From 4301e89872c5170970d50825be867ceae2bd5088 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:29:48 +0100 Subject: [PATCH 0977/1161] chore(deps): bump actions/cache from 3 to 4 (#1688) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c6258586..6017bab07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: shell: bash - name: Cache Composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.directory }} key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} From 1a110bfbf590e5bd6dff851b4fa78e2c58dd48d6 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 23 Jan 2024 10:46:15 +0100 Subject: [PATCH 0978/1161] Prepare 4.4.0 (#1689) --- CHANGELOG.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87c20fc13..ee4471b60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,63 @@ # CHANGELOG +## 4.4.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.4.0. + +### Features + +- Add `metrics()->timing()` [(#1670)](https://github.com/getsentry/sentry-php/pull/1670) + + This allows you to emit a distribution metric based on the duration of the provided callback. + + ```php + use function Sentry\metrics; + + metrics()->timing( + key: 'my-metric', + callback: fn() => doSomething(), + ); + ``` + +- Add `withMonitor()` [(#1679)](https://github.com/getsentry/sentry-php/pull/1679) + + This wraps a callback into monitor check-ins. + + ```php + use function Sentry\withMonitor; + + withMonitor( + slug: 'my-monitor', + callback: fn () => doSomething(), + monitorConfig: new MonitorConfig(...), + ); + ``` + +- Add new `failure_issue_threshold` and `recovery_threshold` configuration to `MonitorConfig` [(#1685)](https://github.com/getsentry/sentry-php/pull/1685) + +- Add `TransactionContext::make()` and `SpanContext::make()` [(#1684)](https://github.com/getsentry/sentry-php/pull/1684) + + ```php + use Sentry\Tracing\SpanContext; + + $spanCpntext = SpanContext::make() + ->setOp('http.client') + ->setDescription('GET https://example.com') + ``` +- Add support for fluent use of `Transaction::setName()` [(#1687)](https://github.com/getsentry/sentry-php/pull/1687) + +- Add support for the W3C `traceparent` header [(#1680)](https://github.com/getsentry/sentry-php/pull/1680) + +### Bug Fixes + +- Do not send an empty event if no metrics are in the bucket [(#1676)](https://github.com/getsentry/sentry-php/pull/1676) + +- Fix the `http_ssl_verify_peer` option to set the correct value to `CURLOPT_SSL_VERIFYPEER` [(#1686)](https://github.com/getsentry/sentry-php/pull/1686) + +### Misc + +- Depreacted `UserDataBag::getSegment()` and `UserDataBag::setSegment()`. You may use a custom tag or context instead [(#1681)](https://github.com/getsentry/sentry-php/pull/1681) + ## 4.3.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.3.1. From 95a428a59ebddf786a27f09d19ec395a32f62082 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 23 Jan 2024 09:49:55 +0000 Subject: [PATCH 0979/1161] release: 4.4.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 4a1a5d229..c97f7eb3a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.3.1'; + public const SDK_VERSION = '4.4.0'; /** * @var Options The client options From 7a56b92ff7105de60b3cd605e11b2aeb6dd801dd Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 23 Jan 2024 13:26:40 +0100 Subject: [PATCH 0980/1161] Add `before_send_check_in` & `before_send_metrics` (#1690) --- phpstan-baseline.neon | 10 +++ src/Client.php | 34 ++++++---- src/Options.php | 62 +++++++++++++++++ tests/ClientTest.php | 152 ++++++++++++++++++++++++++++++++++++++++++ tests/OptionsTest.php | 14 ++++ 5 files changed, 258 insertions(+), 14 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 906166b06..e981a4f43 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -60,6 +60,16 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendCheckInCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: \\(Sentry\\\\Event\\|null\\) but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendMetricsCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: \\(Sentry\\\\Event\\|null\\) but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendTransactionCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: \\(Sentry\\\\Event\\|null\\) but returns mixed\\.$#" count: 1 diff --git a/src/Client.php b/src/Client.php index c97f7eb3a..a15e3c0aa 100644 --- a/src/Client.php +++ b/src/Client.php @@ -359,26 +359,32 @@ private function applyIgnoreOptions(Event $event): ?Event private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event { - if ($event->getType() === EventType::event()) { - return ($this->options->getBeforeSendCallback())($event, $hint); - } - - if ($event->getType() === EventType::transaction()) { - return ($this->options->getBeforeSendTransactionCallback())($event, $hint); + switch ($event->getType()) { + case EventType::event(): + return ($this->options->getBeforeSendCallback())($event, $hint); + case EventType::transaction(): + return ($this->options->getBeforeSendTransactionCallback())($event, $hint); + case EventType::checkIn(): + return ($this->options->getBeforeSendCheckInCallback())($event, $hint); + case EventType::metrics(): + return ($this->options->getBeforeSendMetricsCallback())($event, $hint); + default: + return $event; } - - return $event; } private function getBeforeSendCallbackName(Event $event): string { - $beforeSendCallbackName = 'before_send'; - - if ($event->getType() === EventType::transaction()) { - $beforeSendCallbackName = 'before_send_transaction'; + switch ($event->getType()) { + case EventType::transaction(): + return 'before_send_transaction'; + case EventType::checkIn(): + return 'before_send_check_in'; + case EventType::metrics(): + return 'before_send_metrics'; + default: + return 'before_send'; } - - return $beforeSendCallbackName; } /** diff --git a/src/Options.php b/src/Options.php index 79b71fc8b..2a948281a 100644 --- a/src/Options.php +++ b/src/Options.php @@ -535,6 +535,62 @@ public function setBeforeSendTransactionCallback(callable $callback): self return $this; } + /** + * Gets a callback that will be invoked before a check-in is sent to the server. + * If `null` is returned it won't be sent. + * + * @psalm-return callable(Event, ?EventHint): ?Event + */ + public function getBeforeSendCheckInCallback(): callable + { + return $this->options['before_send_check_in']; + } + + /** + * Sets a callable to be called to decide whether a check-in should + * be captured or not. + * + * @param callable $callback The callable + * + * @psalm-param callable(Event, ?EventHint): ?Event $callback + */ + public function setBeforeSendCheckInCallback(callable $callback): self + { + $options = array_merge($this->options, ['before_send_check_in' => $callback]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + + /** + * Gets a callback that will be invoked before metrics are sent to the server. + * If `null` is returned it won't be sent. + * + * @psalm-return callable(Event, ?EventHint): ?Event + */ + public function getBeforeSendMetricsCallback(): callable + { + return $this->options['before_send_metrics']; + } + + /** + * Sets a callable to be called to decide whether metrics should + * be send or not. + * + * @param callable $callback The callable + * + * @psalm-param callable(Event, ?EventHint): ?Event $callback + */ + public function setBeforeSendMetricsCallback(callable $callback): self + { + $options = array_merge($this->options, ['before_send_metrics' => $callback]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Gets an allow list of trace propagation targets. * @@ -1046,6 +1102,12 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send_transaction' => static function (Event $transaction): Event { return $transaction; }, + 'before_send_check_in' => static function (Event $checkIn): Event { + return $checkIn; + }, + 'before_send_metrics' => static function (Event $metrics): Event { + return $metrics; + }, 'trace_propagation_targets' => null, 'tags' => [], 'error_types' => null, diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 30d2842a5..484d7a149 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -494,6 +494,16 @@ public static function processEventChecksBeforeSendOptionDataProvider(): \Genera Event::createTransaction(), false, ]; + + yield [ + Event::createCheckIn(), + false, + ]; + + yield [ + Event::createMetrics(), + false, + ]; } /** @@ -527,6 +537,102 @@ public static function processEventChecksBeforeSendTransactionOptionDataProvider Event::createTransaction(), true, ]; + + yield [ + Event::createCheckIn(), + false, + ]; + + yield [ + Event::createMetrics(), + false, + ]; + } + + /** + * @dataProvider processEventChecksBeforeSendCheckInOptionDataProvider + */ + public function testProcessEventChecksBeforeSendCheckInOption(Event $event, bool $expectedBeforeSendCall): void + { + $beforeSendCalled = false; + $options = [ + 'before_send_check_in' => static function () use (&$beforeSendCalled) { + $beforeSendCalled = true; + + return null; + }, + ]; + + $client = ClientBuilder::create($options)->getClient(); + $client->captureEvent($event); + + $this->assertSame($expectedBeforeSendCall, $beforeSendCalled); + } + + public static function processEventChecksBeforeSendCheckInOptionDataProvider(): \Generator + { + yield [ + Event::createEvent(), + false, + ]; + + yield [ + Event::createTransaction(), + false, + ]; + + yield [ + Event::createCheckIn(), + true, + ]; + + yield [ + Event::createMetrics(), + false, + ]; + } + + /** + * @dataProvider processEventChecksBeforeSendMetricsOptionDataProvider + */ + public function testProcessEventChecksBeforeMetricsSendOption(Event $event, bool $expectedBeforeSendCall): void + { + $beforeSendCalled = false; + $options = [ + 'before_send_metrics' => static function () use (&$beforeSendCalled) { + $beforeSendCalled = true; + + return null; + }, + ]; + + $client = ClientBuilder::create($options)->getClient(); + $client->captureEvent($event); + + $this->assertSame($expectedBeforeSendCall, $beforeSendCalled); + } + + public static function processEventChecksBeforeSendMetricsOptionDataProvider(): \Generator + { + yield [ + Event::createEvent(), + false, + ]; + + yield [ + Event::createTransaction(), + false, + ]; + + yield [ + Event::createCheckIn(), + false, + ]; + + yield [ + Event::createMetrics(), + true, + ]; } public function testProcessEventDiscardsEventWhenSampleRateOptionIsZero(): void @@ -681,6 +787,52 @@ public function testProcessEventDiscardsEventWhenBeforeSendTransactionCallbackRe $client->captureEvent(Event::createTransaction()); } + public function testProcessEventDiscardsEventWhenBeforeSendCheckInCallbackReturnsNull(): void + { + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because the "before_send_check_in" callback returned "null".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'before_send_check_in' => static function () { + return null; + }, + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureEvent(Event::createCheckIn()); + } + + public function testProcessEventDiscardsEventWhenBeforeSendMetricsCallbackReturnsNull(): void + { + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because the "before_send_metrics" callback returned "null".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'before_send_metrics' => static function () { + return null; + }, + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureEvent(Event::createMetrics()); + } + public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): void { /** @var LoggerInterface&MockObject $logger */ diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 1df9f0555..7f1a409a7 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -250,6 +250,20 @@ static function (): void {}, 'setBeforeSendTransactionCallback', ]; + yield [ + 'before_send_check_in', + static function (): void {}, + 'getBeforeSendCheckInCallback', + 'setBeforeSendCheckInCallback', + ]; + + yield [ + 'before_send_metrics', + static function (): void {}, + 'getBeforeSendMetricsCallback', + 'setBeforeSendMetricsCallback', + ]; + yield [ 'trace_propagation_targets', ['www.example.com'], From 2094d9cff63a71eff5dff67be9aaa0863472ae90 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 26 Jan 2024 22:44:29 +0100 Subject: [PATCH 0981/1161] Fix `_metrics_summary` formatting (#1682) --- src/Metrics/MetricsAggregator.php | 2 +- .../EnvelopItems/TransactionItem.php | 11 +- src/Tracing/Span.php | 17 ++- tests/Metrics/MetricsTest.php | 56 ++++---- tests/Serializer/PayloadSerializerTest.php | 43 +++++- tests/Tracing/SpanTest.php | 132 ++++++++++++++++++ 6 files changed, 221 insertions(+), 40 deletions(-) diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php index edd448772..5014c97fe 100644 --- a/src/Metrics/MetricsAggregator.php +++ b/src/Metrics/MetricsAggregator.php @@ -64,7 +64,7 @@ public function add( $type . $key . $unit . - implode('', $tags) . + serialize($tags) . $bucketTimestamp ); diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index 5296299be..acb0272c4 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -177,7 +177,16 @@ protected static function serializeSpan(Span $span): array } if (!empty($span->getMetricsSummary())) { - $result['metrics_summary'] = $span->getMetricsSummary(); + $formattedSummary = []; + $summary = $span->getMetricsSummary(); + + foreach ($summary as $mri => $metrics) { + foreach ($metrics as $metric) { + $formattedSummary[$mri][] = $metric; + } + } + + $result['_metrics_summary'] = $formattedSummary; } return $result; diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index f743d0aa9..c2c0f79ab 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -89,7 +89,7 @@ class Span protected $transaction; /** - * @var array + * @var array> */ protected $metricsSummary = []; @@ -492,7 +492,7 @@ public function detachSpanRecorder() } /** - * @return array + * @return array> */ public function getMetricsSummary(): array { @@ -511,17 +511,20 @@ public function setMetricsSummary( array $tags ): void { $mri = sprintf('%s:%s@%s', $type, $key, (string) $unit); - $bucketKey = $mri . implode('', $tags); + $bucketKey = $mri . serialize($tags); - if (\array_key_exists($bucketKey, $this->metricsSummary)) { + if ( + isset($this->metricsSummary[$mri]) + && \array_key_exists($bucketKey, $this->metricsSummary[$mri]) + ) { if ($type === SetType::TYPE) { $value = 1.0; } else { $value = (float) $value; } - $summary = $this->metricsSummary[$bucketKey]; - $this->metricsSummary[$bucketKey] = [ + $summary = $this->metricsSummary[$mri][$bucketKey]; + $this->metricsSummary[$mri][$bucketKey] = [ 'min' => min($summary['min'], $value), 'max' => max($summary['max'], $value), 'sum' => $summary['sum'] + $value, @@ -535,7 +538,7 @@ public function setMetricsSummary( $value = (float) $value; } - $this->metricsSummary[$bucketKey] = [ + $this->metricsSummary[$mri][$bucketKey] = [ 'min' => $value, 'max' => $value, 'sum' => $value, diff --git a/tests/Metrics/MetricsTest.php b/tests/Metrics/MetricsTest.php index 354030f1f..8dc7388a8 100644 --- a/tests/Metrics/MetricsTest.php +++ b/tests/Metrics/MetricsTest.php @@ -40,7 +40,7 @@ public function testIncrement(): void $client->expects($this->once()) ->method('captureEvent') ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['92ed00fdaf9543ff4cace691f8a5166b']; + $metric = $event->getMetrics()['2794a118fd879e10a3a97836df803872']; $self->assertSame(CounterType::TYPE, $metric->getType()); $self->assertSame('foo', $metric->getKey()); @@ -102,7 +102,7 @@ public function testDistribution(): void $client->expects($this->once()) ->method('captureEvent') ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['8a817dcdb12cfffc1fa8b459ad0c9d56']; + $metric = $event->getMetrics()['edb74f95b4572e82dc4600cfeea76181']; $self->assertSame(DistributionType::TYPE, $metric->getType()); $self->assertSame('foo', $metric->getKey()); @@ -162,30 +162,30 @@ public function testTiming(): void $self = $this; $client->expects($this->once()) - ->method('captureEvent') - ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['8a817dcdb12cfffc1fa8b459ad0c9d56']; - - $self->assertSame(DistributionType::TYPE, $metric->getType()); - $self->assertSame('foo', $metric->getKey()); - $self->assertSame([1.0, 2.0], $metric->serialize()); - $self->assertSame(MetricsUnit::second(), $metric->getUnit()); - $self->assertSame( - [ - 'environment' => 'development', - 'foo' => 'bar', - 'release' => '1.0.0', - ], - $metric->getTags() - ); - $self->assertSame(1699412953, $metric->getTimestamp()); - - $codeLocation = $metric->getCodeLocation(); - - $self->assertSame('Sentry\Metrics\Metrics::timing', $codeLocation->getFunctionName()); - - return true; - })); + ->method('captureEvent') + ->with($this->callback(static function (Event $event) use ($self): bool { + $metric = $event->getMetrics()['edb74f95b4572e82dc4600cfeea76181']; + + $self->assertSame(DistributionType::TYPE, $metric->getType()); + $self->assertSame('foo', $metric->getKey()); + $self->assertSame([1.0, 2.0], $metric->serialize()); + $self->assertSame(MetricsUnit::second(), $metric->getUnit()); + $self->assertSame( + [ + 'environment' => 'development', + 'foo' => 'bar', + 'release' => '1.0.0', + ], + $metric->getTags() + ); + $self->assertSame(1699412953, $metric->getTimestamp()); + + $codeLocation = $metric->getCodeLocation(); + + $self->assertSame('Sentry\Metrics\Metrics::timing', $codeLocation->getFunctionName()); + + return true; + })); $hub = new Hub($client); SentrySdk::setCurrentHub($hub); @@ -238,7 +238,7 @@ public function testGauge(): void $client->expects($this->once()) ->method('captureEvent') ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['d2a09273b9c61b66a0e6ee79c1babfed']; + $metric = $event->getMetrics()['f39c23c73897d4f006fd617f76664571']; $self->assertSame(GaugeType::TYPE, $metric->getType()); $self->assertSame('foo', $metric->getKey()); @@ -306,7 +306,7 @@ public function testSet(): void $client->expects($this->once()) ->method('captureEvent') ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['c900a5750d0bc79016c29a7f0bdcd937']; + $metric = $event->getMetrics()['868b190d923bbd619570328d7ba3e4cd']; $self->assertSame(SetType::TYPE, $metric->getType()); $self->assertSame('foo', $metric->getKey()); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 19e00acc8..2b7f775ba 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -319,7 +319,6 @@ public static function serializeAsEnvelopeDataProvider(): iterable {"timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion"},"spans":[],"transaction_info":{"source":"custom"}} TEXT , - false, ]; $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); @@ -411,7 +410,6 @@ public static function serializeAsEnvelopeDataProvider(): iterable {"check_in_id":"$checkinId","monitor_slug":"my-monitor","status":"ok","duration":10,"release":"1.0.0","environment":"dev","monitor_config":{"schedule":{"type":"crontab","value":"0 0 * * *","unit":""},"checkin_margin":10,"max_runtime":12,"timezone":"Europe\/Amsterdam","failure_issue_threshold":5,"recovery_threshold":10},"contexts":{"trace":{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"5dd538dc297544cc"}}} TEXT , - false, ]; $counter = new CounterType('counter', 1.0, MetricsUnit::second(), ['foo' => 'bar', 'baz' => 'qux'], 1597790835); @@ -441,7 +439,46 @@ public static function serializeAsEnvelopeDataProvider(): iterable no_tags@second:1|c|T1597790835 TEXT , - false, + ]; + + $span = new Span(); + $span->setSpanId(new SpanId('5dd538dc297544cc')); + $span->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); + $span->setMetricsSummary( + CounterType::TYPE, + 'counter', + 10, + MetricsUnit::custom('star'), + [ + 'repository' => 'client', + ] + ); + $span->setMetricsSummary( + CounterType::TYPE, + 'counter', + 50, + MetricsUnit::custom('star'), + [ + 'repository' => 'client', + ] + ); + + $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setSpans([$span]); + $event->setTransaction('GET /'); + $event->setContext('trace', [ + 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', + 'span_id' => '5dd538dc297544cc', + ]); + + yield [ + $event, + <<setMetricsSummary( + CounterType::TYPE, + 'counter', + 10, + MetricsUnit::custom('star'), + [ + 'repository' => 'client', + ] + ); + $span->setMetricsSummary( + CounterType::TYPE, + 'counter', + 50, + MetricsUnit::custom('star'), + [ + 'repository' => 'client', + ] + ); + $span->setMetricsSummary( + CounterType::TYPE, + 'counter', + 10, + MetricsUnit::custom('star'), + [ + 'repository' => 'server', + ] + ); + + $span->setMetricsSummary( + DistributionType::TYPE, + 'distribution', + 10.2, + MetricsUnit::millisecond(), + [] + ); + $span->setMetricsSummary( + DistributionType::TYPE, + 'distribution', + 5.7, + MetricsUnit::millisecond(), + [] + ); + + $span->setMetricsSummary( + GaugeType::TYPE, + 'gauge', + 10, + MetricsUnit::none(), + [] + ); + $span->setMetricsSummary( + GaugeType::TYPE, + 'gauge', + 20, + MetricsUnit::none(), + [] + ); + + $span->setMetricsSummary( + SetType::TYPE, + 'set', + 'jane@doe@example.com', + MetricsUnit::custom('user'), + [] + ); + $span->setMetricsSummary( + SetType::TYPE, + 'set', + 'jon@doe@example.com', + MetricsUnit::custom('user'), + [] + ); + + $this->assertSame([ + 'c:counter@star' => [ + 'c:counter@stara:1:{s:10:"repository";s:6:"client";}' => [ + 'min' => 10.0, + 'max' => 50.0, + 'sum' => 60.0, + 'count' => 2, + 'tags' => [ + 'repository' => 'client', + ], + ], + 'c:counter@stara:1:{s:10:"repository";s:6:"server";}' => [ + 'min' => 10.0, + 'max' => 10.0, + 'sum' => 10.0, + 'count' => 1, + 'tags' => [ + 'repository' => 'server', + ], + ], + ], + 'd:distribution@millisecond' => [ + 'd:distribution@milliseconda:0:{}' => [ + 'min' => 5.7, + 'max' => 10.2, + 'sum' => 15.899999999999999, + 'count' => 2, + 'tags' => [], + ], + ], + 'g:gauge@none' => [ + 'g:gauge@nonea:0:{}' => [ + 'min' => 10.0, + 'max' => 20.0, + 'sum' => 30.0, + 'count' => 2, + 'tags' => [], + ], + ], + 's:set@user' => [ + 's:set@usera:0:{}' => [ + 'min' => 0.0, + 'max' => 1.0, + 'sum' => 1.0, + 'count' => 2, + 'tags' => [], + ], + ], + ], $span->getMetricsSummary()); + } } From 992566ea8b8350521a340779626a95154c2aab37 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 26 Jan 2024 22:46:57 +0100 Subject: [PATCH 0982/1161] Widen logger type hints (#1691) --- src/Logger/DebugFileLogger.php | 2 +- src/Logger/DebugStdOutLogger.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Logger/DebugFileLogger.php b/src/Logger/DebugFileLogger.php index da406c44f..610182f87 100644 --- a/src/Logger/DebugFileLogger.php +++ b/src/Logger/DebugFileLogger.php @@ -22,7 +22,7 @@ public function __construct(string $filePath) * @param mixed $level * @param mixed[] $context */ - public function log($level, \Stringable|string $message, array $context = []): void + public function log($level, $message, array $context = []): void { file_put_contents($this->filePath, sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message), \FILE_APPEND); } diff --git a/src/Logger/DebugStdOutLogger.php b/src/Logger/DebugStdOutLogger.php index fc1212931..eaaba6a10 100644 --- a/src/Logger/DebugStdOutLogger.php +++ b/src/Logger/DebugStdOutLogger.php @@ -12,7 +12,7 @@ class DebugStdOutLogger extends AbstractLogger * @param mixed $level * @param mixed[] $context */ - public function log($level, \Stringable|string $message, array $context = []): void + public function log($level, $message, array $context = []): void { file_put_contents('php://stdout', sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message)); } From 9c494abe9af4e09ec40f552991b3d3d9a55323e6 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 29 Jan 2024 15:32:47 +0100 Subject: [PATCH 0983/1161] Allow whitespace in metric tag values (#1692) --- src/Serializer/EnvelopItems/MetricsItem.php | 2 +- tests/Serializer/PayloadSerializerTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Serializer/EnvelopItems/MetricsItem.php b/src/Serializer/EnvelopItems/MetricsItem.php index 306ee44fa..77ae05757 100644 --- a/src/Serializer/EnvelopItems/MetricsItem.php +++ b/src/Serializer/EnvelopItems/MetricsItem.php @@ -24,7 +24,7 @@ class MetricsItem implements EnvelopeItemInterface /** * @var string */ - private const VALUE_PATTERN = '/[^\w\d_:\/@\.{}\[\]$-]+/i'; + private const VALUE_PATTERN = '/[^\w\d\s_:\/@\.{}\[\]$-]+/i'; public static function toEnvelopeItem(Event $event): string { diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 2b7f775ba..2c7214eab 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -412,7 +412,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable , ]; - $counter = new CounterType('counter', 1.0, MetricsUnit::second(), ['foo' => 'bar', 'baz' => 'qux'], 1597790835); + $counter = new CounterType('counter', 1.0, MetricsUnit::second(), ['foo' => 'bar', 'route' => 'GET /foo'], 1597790835); $distribution = new DistributionType('distribution', 1.0, MetricsUnit::second(), ['$foo$' => '%bar%'], 1597790835); $gauge = new GaugeType('gauge', 1.0, MetricsUnit::second(), ['föö' => 'bär'], 1597790835); $set = new SetType('set', 1.0, MetricsUnit::second(), ['%{key}' => '$value$'], 1597790835); @@ -431,8 +431,8 @@ public static function serializeAsEnvelopeDataProvider(): iterable $event, << Date: Mon, 29 Jan 2024 16:53:30 +0100 Subject: [PATCH 0984/1161] Prepare 4.5.0 (#1693) --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee4471b60..985632ab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # CHANGELOG +## 4.5.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.5.0. + +### Features + +- Add `before_send_check_in` and `before_send_metrics` [(#1690)](https://github.com/getsentry/sentry-php/pull/1690) + + ```php + \Sentry\init([ + 'before_send_check_in' => function (\Sentry\Event $event) { + $checkIn = $event->getCheckIn(), + // modify the check-in or return null to not send it + }, + ]); + ``` + + ```php + \Sentry\init([ + 'before_send_metrics' => function (\Sentry\Event $event) { + $metrics = $event->getMetrics(), + // modify the metrics or return null to not send it + }, + ]); + ``` + +### Bug Fixes + +- Fix `_metrics_summary` formatting [(#1682)](https://github.com/getsentry/sentry-php/pull/1682) + +- Fix `DebugFileLogger` and `DebugStdOutLogger` to be usable with PHP 7.2 and up [(#1691)](https://github.com/getsentry/sentry-php/pull/1691) + +- Allow whitespace in metric tag values [(#1692)](https://github.com/getsentry/sentry-php/pull/1692) + ## 4.4.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.4.0. From 9821f52f208c23fd42c96fe3f3616469dc8c92c3 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 29 Jan 2024 15:54:04 +0000 Subject: [PATCH 0985/1161] release: 4.5.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index a15e3c0aa..3e49d5638 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.4.0'; + public const SDK_VERSION = '4.5.0'; /** * @var Options The client options From a6e06f0b7a17e7f68e11297427da76bfe01a3ca3 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Mon, 29 Jan 2024 17:16:10 +0100 Subject: [PATCH 0986/1161] Fix SDK version in tests --- tests/Serializer/PayloadSerializerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 2c7214eab..cad4b93fe 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -474,9 +474,9 @@ public static function serializeAsEnvelopeDataProvider(): iterable yield [ $event, << Date: Tue, 6 Feb 2024 14:00:03 +0100 Subject: [PATCH 0987/1161] Remove final from `Metrics` class (#1697) --- src/Metrics/Metrics.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php index 235783451..b218b3749 100644 --- a/src/Metrics/Metrics.php +++ b/src/Metrics/Metrics.php @@ -10,7 +10,7 @@ use Sentry\Metrics\Types\GaugeType; use Sentry\Metrics\Types\SetType; -final class Metrics +class Metrics { /** * @var self|null From 6f916e9ac1b86ecb3cfca11712e1c8d4e750e4bc Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 8 Feb 2024 11:45:54 +0100 Subject: [PATCH 0988/1161] Lazy initialize default values in client builder (#1699) --- src/Client.php | 16 ++++++ src/ClientBuilder.php | 33 ++++++----- src/Transport/HttpTransport.php | 8 +++ tests/ClientBuilderTest.php | 98 +++++++++++++++++++++++++++++++-- 4 files changed, 133 insertions(+), 22 deletions(-) diff --git a/src/Client.php b/src/Client.php index 3e49d5638..ee061f38b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -229,6 +229,22 @@ public function getStacktraceBuilder(): StacktraceBuilder return $this->stacktraceBuilder; } + /** + * @internal + */ + public function getLogger(): LoggerInterface + { + return $this->logger; + } + + /** + * @internal + */ + public function getTransport(): TransportInterface + { + return $this->transport; + } + /** * Assembles an event and prepares it to be sent of to Sentry. * diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 28563fe87..4ad8544f1 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -23,12 +23,12 @@ final class ClientBuilder private $options; /** - * @var TransportInterface The transport + * @var TransportInterface|null The transport */ private $transport; /** - * @var HttpClientInterface The HTTP client + * @var HttpClientInterface|null The HTTP client */ private $httpClient; @@ -60,16 +60,6 @@ final class ClientBuilder public function __construct(?Options $options = null) { $this->options = $options ?? new Options(); - - $this->logger = $this->options->getLogger() ?? null; - - $this->httpClient = $this->options->getHttpClient() ?? new HttpClient($this->sdkIdentifier, $this->sdkVersion); - $this->transport = $this->options->getTransport() ?? new HttpTransport( - $this->options, - $this->httpClient, - new PayloadSerializer($this->options), - $this->logger - ); } /** @@ -94,7 +84,7 @@ public function setRepresentationSerializer(RepresentationSerializerInterface $r public function getLogger(): ?LoggerInterface { - return $this->logger; + return $this->logger ?? $this->options->getLogger(); } public function setLogger(LoggerInterface $logger): self @@ -120,7 +110,14 @@ public function setSdkVersion(string $sdkVersion): self public function getTransport(): TransportInterface { - return $this->transport; + return $this->transport + ?? $this->options->getTransport() + ?? new HttpTransport( + $this->options, + $this->getHttpClient(), + new PayloadSerializer($this->options), + $this->getLogger() + ); } public function setTransport(TransportInterface $transport): self @@ -132,7 +129,9 @@ public function setTransport(TransportInterface $transport): self public function getHttpClient(): HttpClientInterface { - return $this->httpClient; + return $this->httpClient + ?? $this->options->getHttpClient() + ?? new HttpClient($this->sdkIdentifier, $this->sdkVersion); } public function setHttpClient(HttpClientInterface $httpClient): self @@ -146,11 +145,11 @@ public function getClient(): ClientInterface { return new Client( $this->options, - $this->transport, + $this->getTransport(), $this->sdkIdentifier, $this->sdkVersion, $this->representationSerializer, - $this->logger + $this->getLogger() ); } } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 8e1cb647a..9e72cf669 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -120,6 +120,14 @@ public function close(?int $timeout = null): Result return new Result(ResultStatus::success()); } + /** + * @internal + */ + public function getHttpClient(): HttpClientInterface + { + return $this->httpClient; + } + private function sendRequestToSpotlight(Event $event): void { if (!$this->options->isSpotlightEnabled()) { diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index ae74beabb..24ee86a0d 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -5,6 +5,8 @@ namespace Sentry\Tests; use PHPUnit\Framework\TestCase; +use Psr\Log\AbstractLogger; +use Psr\Log\LoggerInterface; use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; @@ -79,7 +81,7 @@ public function testCreateWithNoOptionsIsTheSameAsDefaultOptions(): void ); } - public function testDefaultHttpClientAndTransport() + public function testDefaultHttpClientAndTransport(): void { $options = new Options(); $clientBuilder = new ClientBuilder($options); @@ -88,7 +90,58 @@ public function testDefaultHttpClientAndTransport() $this->assertInstanceOf(HttpTransport::class, $clientBuilder->getTransport()); } - public function testSettingCustomHttpClinet() + public function testSettingCustomLogger(): void + { + $logger = new CustomLogger(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->setLogger($logger); + + $this->assertSame($logger, $clientBuilder->getLogger()); + + $client = $clientBuilder->getClient(); + + $this->assertInstanceOf(Client::class, $client); + $this->assertSame($logger, $client->getLogger()); + } + + public function testSettingCustomLoggerFromOptions(): void + { + $logger = new CustomLogger(); + + $options = new Options([ + 'logger' => $logger, + ]); + $clientBuilder = new ClientBuilder($options); + + $this->assertSame($logger, $clientBuilder->getLogger()); + + $client = $clientBuilder->getClient(); + + $this->assertInstanceOf(Client::class, $client); + $this->assertSame($logger, $client->getLogger()); + } + + public function testSettingCustomHttpClient(): void + { + $httpClient = new CustomHttpClient(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->setHttpClient($httpClient); + + $this->assertSame($httpClient, $clientBuilder->getHttpClient()); + + $client = $clientBuilder->getClient(); + + $this->assertInstanceOf(Client::class, $client); + + $transport = $client->getTransport(); + + $this->assertInstanceOf(HttpTransport::class, $transport); + $this->assertSame($httpClient, $transport->getHttpClient()); + } + + public function testSettingCustomHttpClientFromOptions(): void { $httpClient = new CustomHttpClient(); @@ -98,10 +151,33 @@ public function testSettingCustomHttpClinet() $clientBuilder = new ClientBuilder($options); $this->assertSame($httpClient, $clientBuilder->getHttpClient()); - $this->assertInstanceOf(HttpTransport::class, $clientBuilder->getTransport()); + + $client = $clientBuilder->getClient(); + + $this->assertInstanceOf(Client::class, $client); + + $transport = $client->getTransport(); + + $this->assertInstanceOf(HttpTransport::class, $transport); + $this->assertSame($httpClient, $transport->getHttpClient()); } - public function testSettingCustomTransport() + public function testSettingCustomTransport(): void + { + $transport = new CustomTransport(); + + $clientBuilder = new ClientBuilder(); + $clientBuilder->setTransport($transport); + + $this->assertSame($transport, $clientBuilder->getTransport()); + + $client = $clientBuilder->getClient(); + + $this->assertInstanceOf(Client::class, $client); + $this->assertSame($transport, $client->getTransport()); + } + + public function testSettingCustomTransportFromOptions(): void { $transport = new CustomTransport(); @@ -110,8 +186,12 @@ public function testSettingCustomTransport() ]); $clientBuilder = new ClientBuilder($options); - $this->assertInstanceOf(HttpClient::class, $clientBuilder->getHttpClient()); $this->assertSame($transport, $clientBuilder->getTransport()); + + $client = $clientBuilder->getClient(); + + $this->assertInstanceOf(Client::class, $client); + $this->assertSame($transport, $client->getTransport()); } } @@ -142,3 +222,11 @@ public function close(?int $timeout = null): Result return new Result(ResultStatus::success()); } } + +final class CustomLogger extends AbstractLogger implements LoggerInterface +{ + public function log($level, $message, array $context = []): void + { + // noop + } +} From 2c1f5dd81af02948318b3a61fdf21e20c8ac358e Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 8 Feb 2024 13:55:48 +0100 Subject: [PATCH 0989/1161] Add the SAPI to the runtime context (#1700) --- src/Context/RuntimeContext.php | 27 ++++++++++++++++++- src/Integration/EnvironmentIntegration.php | 4 +++ src/Profiling/Profile.php | 1 + src/Serializer/EnvelopItems/EventItem.php | 1 + .../EnvelopItems/TransactionItem.php | 1 + tests/Context/RuntimeContextTest.php | 5 +++- .../EnvironmentIntegrationTest.php | 9 ++++--- tests/Profiling/ProfileTest.php | 5 +++- tests/Serializer/PayloadSerializerTest.php | 12 +++++---- 9 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 65ea1fa36..02407fd45 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -21,13 +21,19 @@ final class RuntimeContext */ private $version; + /** + * @var string|null The SAPI (Server API) name + */ + private $sapi; + /** * Constructor. * * @param string $name The name of the runtime * @param string|null $version The version of the runtime + * @param string|null $sapi The SAPI name of the runtime */ - public function __construct(string $name, ?string $version = null) + public function __construct(string $name, ?string $version = null, ?string $sapi = null) { if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); @@ -35,6 +41,7 @@ public function __construct(string $name, ?string $version = null) $this->name = $name; $this->version = $version; + $this->sapi = $sapi; } /** @@ -76,4 +83,22 @@ public function setVersion(?string $version): void { $this->version = $version; } + + /** + * Gets the SAPI of the runtime. + */ + public function getSAPI(): ?string + { + return $this->sapi; + } + + /** + * Sets the SAPI of the runtime. + * + * @param string|null $sapi The SAPI name + */ + public function setSAPI(?string $sapi): void + { + $this->sapi = $sapi; + } } diff --git a/src/Integration/EnvironmentIntegration.php b/src/Integration/EnvironmentIntegration.php index fb0f591e3..c404216ea 100644 --- a/src/Integration/EnvironmentIntegration.php +++ b/src/Integration/EnvironmentIntegration.php @@ -45,6 +45,10 @@ private function updateRuntimeContext(?RuntimeContext $runtimeContext): RuntimeC $runtimeContext->setVersion(PHPVersion::parseVersion()); } + if ($runtimeContext->getSAPI() === null) { + $runtimeContext->setSAPI(\PHP_SAPI); + } + return $runtimeContext; } diff --git a/src/Profiling/Profile.php b/src/Profiling/Profile.php index cd1da1373..bf0c595a4 100644 --- a/src/Profiling/Profile.php +++ b/src/Profiling/Profile.php @@ -261,6 +261,7 @@ public function getFormattedData(Event $event): ?array 'environment' => $event->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, 'runtime' => [ 'name' => $runtimeContext->getName(), + 'sapi' => $runtimeContext->getSAPI(), 'version' => $runtimeContext->getVersion(), ], 'timestamp' => $startTime->format(\DATE_RFC3339_EXTENDED), diff --git a/src/Serializer/EnvelopItems/EventItem.php b/src/Serializer/EnvelopItems/EventItem.php index a9b0fdad6..9b0eb50fe 100644 --- a/src/Serializer/EnvelopItems/EventItem.php +++ b/src/Serializer/EnvelopItems/EventItem.php @@ -103,6 +103,7 @@ public static function toEnvelopeItem(Event $event): string if ($runtimeContext !== null) { $payload['contexts']['runtime'] = [ 'name' => $runtimeContext->getName(), + 'sapi' => $runtimeContext->getSAPI(), 'version' => $runtimeContext->getVersion(), ]; } diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index acb0272c4..56f3e61b4 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -98,6 +98,7 @@ public static function toEnvelopeItem(Event $event): string if ($runtimeContext !== null) { $payload['contexts']['runtime'] = [ 'name' => $runtimeContext->getName(), + 'sapi' => $runtimeContext->getSAPI(), 'version' => $runtimeContext->getVersion(), ]; } diff --git a/tests/Context/RuntimeContextTest.php b/tests/Context/RuntimeContextTest.php index 59464a83e..753a6a93c 100644 --- a/tests/Context/RuntimeContextTest.php +++ b/tests/Context/RuntimeContextTest.php @@ -11,9 +11,10 @@ final class RuntimeContextTest extends TestCase { public function testConstructor(): void { - $context = new RuntimeContext('php', '7.4'); + $context = new RuntimeContext('php', '7.4', 'fpm'); $this->assertSame('php', $context->getName()); + $this->assertSame('fpm', $context->getSAPI()); $this->assertSame('7.4', $context->getVersion()); } @@ -29,9 +30,11 @@ public function testGettersAndSetters(): void { $context = new RuntimeContext('php'); $context->setName('go'); + $context->setSAPI('fpm'); $context->setVersion('1.15'); $this->assertSame('go', $context->getName()); + $this->assertSame('fpm', $context->getSAPI()); $this->assertSame('1.15', $context->getVersion()); } } diff --git a/tests/Integration/EnvironmentIntegrationTest.php b/tests/Integration/EnvironmentIntegrationTest.php index 02c195170..788b95fa8 100644 --- a/tests/Integration/EnvironmentIntegrationTest.php +++ b/tests/Integration/EnvironmentIntegrationTest.php @@ -51,6 +51,7 @@ public function testInvoke(bool $isIntegrationEnabled, ?RuntimeContext $initialR $this->assertNull($runtimeContext); } else { $this->assertSame($expectedRuntimeContext->getName(), $runtimeContext->getName()); + $this->assertSame($expectedRuntimeContext->getSAPI(), $runtimeContext->getSAPI()); $this->assertSame($expectedRuntimeContext->getVersion(), $runtimeContext->getVersion()); } @@ -79,15 +80,15 @@ public static function invokeDataProvider(): iterable true, null, null, - new RuntimeContext('php', PHPVersion::parseVersion()), + new RuntimeContext('php', PHPVersion::parseVersion(), 'cli'), new OsContext(php_uname('s'), php_uname('r'), php_uname('v'), php_uname('a')), ]; yield 'Integration enabled && event context data filled => do nothing' => [ true, - new RuntimeContext('go', '1.15'), + new RuntimeContext('go', '1.15', 'cli'), new OsContext('iOS', '13.5.1', '17F80', 'Darwin Kernel Version 19.5.0: Tue May 26 20:56:31 PDT 2020; root:xnu-6153.122.2~1/RELEASE_ARM64_T8015'), - new RuntimeContext('go', '1.15'), + new RuntimeContext('go', '1.15', 'cli'), new OsContext('iOS', '13.5.1', '17F80', 'Darwin Kernel Version 19.5.0: Tue May 26 20:56:31 PDT 2020; root:xnu-6153.122.2~1/RELEASE_ARM64_T8015'), ]; @@ -95,7 +96,7 @@ public static function invokeDataProvider(): iterable true, new RuntimeContext('php'), new OsContext('Linux'), - new RuntimeContext('php', PHPVersion::parseVersion()), + new RuntimeContext('php', PHPVersion::parseVersion(), 'cli'), new OsContext('Linux', php_uname('r'), php_uname('v'), php_uname('a')), ]; } diff --git a/tests/Profiling/ProfileTest.php b/tests/Profiling/ProfileTest.php index 513210c23..9c8d0e382 100644 --- a/tests/Profiling/ProfileTest.php +++ b/tests/Profiling/ProfileTest.php @@ -41,7 +41,8 @@ public static function formattedDataDataProvider(): \Generator ]); $event->setRuntimeContext(new RuntimeContext( 'php', - '8.2.3' + '8.2.3', + 'cli' )); $event->setOsContext(new OsContext( 'macOS', @@ -131,6 +132,7 @@ public static function formattedDataDataProvider(): \Generator 'environment' => 'dev', 'runtime' => [ 'name' => 'php', + 'sapi' => 'cli', 'version' => '8.2.3', ], 'timestamp' => '2023-02-28T08:41:00.000+00:00', @@ -231,6 +233,7 @@ public static function formattedDataDataProvider(): \Generator 'environment' => 'dev', 'runtime' => [ 'name' => 'php', + 'sapi' => 'cli', 'version' => '8.2.3', ], 'timestamp' => '2023-02-28T08:41:00.000+00:00', diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index cad4b93fe..e8162f9d4 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -136,7 +136,8 @@ public static function serializeAsEnvelopeDataProvider(): iterable $event->setRuntimeContext(new RuntimeContext( 'php', - '7.4.3' + '7.4.3', + 'cli' )); $event->setContext('electron', [ @@ -175,7 +176,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}},{"type":"default","category":"log","level":"info","timestamp":1597790835,"data":{"0":"foo","1":"bar"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} +{"timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion"},"start_timestamp":1597790835,"level":"error","logger":"app.php","transaction":"\/users\/\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","sapi":"cli","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}},{"type":"default","category":"log","level":"info","timestamp":1597790835,"data":{"0":"foo","1":"bar"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} TEXT ]; @@ -250,7 +251,8 @@ public static function serializeAsEnvelopeDataProvider(): iterable ]); $event->setRuntimeContext(new RuntimeContext( 'php', - '8.2.3' + '8.2.3', + 'cli' )); $event->setOsContext(new OsContext( 'macOS', @@ -300,9 +302,9 @@ public static function serializeAsEnvelopeDataProvider(): iterable << Date: Tue, 13 Feb 2024 01:39:17 -0800 Subject: [PATCH 0990/1161] short circuit ignore_exceptions (#1701) --- src/Client.php | 37 ++++++++++++++++++++++++++++--------- tests/ClientTest.php | 8 ++------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/Client.php b/src/Client.php index ee061f38b..b324f20b8 100644 --- a/src/Client.php +++ b/src/Client.php @@ -148,6 +148,16 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope */ public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId { + $className = \get_class($exception); + if ($this->isIgnoredException($className)) { + $this->logger->info( + 'The event will be discarded because it matches an entry in "ignore_exceptions".', + ['className' => $className] + ); + + return null; // short circuit to avoid unnecessary processing + } + $hint = $hint ?? new EventHint(); if ($hint->exception === null) { @@ -330,6 +340,17 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco return $event; } + private function isIgnoredException(string $className): bool + { + foreach ($this->options->getIgnoreExceptions() as $ignoredException) { + if (is_a($className, $ignoredException, true)) { + return true; + } + } + + return false; + } + private function applyIgnoreOptions(Event $event): ?Event { if ($event->getType() === EventType::event()) { @@ -340,15 +361,13 @@ private function applyIgnoreOptions(Event $event): ?Event } foreach ($exceptions as $exception) { - foreach ($this->options->getIgnoreExceptions() as $ignoredException) { - if (is_a($exception->getType(), $ignoredException, true)) { - $this->logger->info( - 'The event will be discarded because it matches an entry in "ignore_exceptions".', - ['event' => $event] - ); - - return null; - } + if ($this->isIgnoredException($exception->getType())) { + $this->logger->info( + 'The event will be discarded because it matches an entry in "ignore_exceptions".', + ['event' => $event] + ); + + return null; } } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 484d7a149..14751fb8b 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -679,9 +679,7 @@ public function testProcessEventDiscardsEventWhenIgnoreExceptionsMatches(): void $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) ->method('info') - ->with('The event will be discarded because it matches an entry in "ignore_exceptions".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->with('The event will be discarded because it matches an entry in "ignore_exceptions".'); $options = [ 'ignore_exceptions' => [\Exception::class], @@ -702,9 +700,7 @@ public function testProcessEventDiscardsEventWhenParentHierarchyOfIgnoreExceptio $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) ->method('info') - ->with('The event will be discarded because it matches an entry in "ignore_exceptions".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->with('The event will be discarded because it matches an entry in "ignore_exceptions".'); $options = [ 'ignore_exceptions' => [\RuntimeException::class], From 74caa6674063d97c1d740a5b4786b0188238a101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Pavl=C3=ADk?= Date: Tue, 13 Feb 2024 11:43:35 +0100 Subject: [PATCH 0991/1161] Log also exception when failed to get source code excerpt (#1678) Co-authored-by: Michi Hoffmann --- src/Integration/FrameContextifierIntegration.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 178f04fd4..9b1a7b3b6 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -164,7 +164,10 @@ private function getSourceCodeExcerpt(int $maxContextLines, string $filePath, in $file->next(); } } catch (\Throwable $exception) { - $this->logger->warning(sprintf('Failed to get the source code excerpt for the file "%s".', $filePath)); + $this->logger->warning( + sprintf('Failed to get the source code excerpt for the file "%s".', $filePath), + ['exception' => $exception] + ); } return $frame; From cde1d675dd476b6773be46b81ddf1e3d1d9b026e Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 13 Feb 2024 12:20:59 +0100 Subject: [PATCH 0992/1161] Attach `_metrics_summary` to transactions (#1702) --- src/Event.php | 31 +++++- .../EnvelopItems/TransactionItem.php | 40 +++++-- src/Tracing/Transaction.php | 4 + tests/Metrics/MetricsTest.php | 103 ++++++++++++++++++ tests/Serializer/PayloadSerializerTest.php | 15 ++- 5 files changed, 182 insertions(+), 11 deletions(-) diff --git a/src/Event.php b/src/Event.php index 4a77b948e..aded2808b 100644 --- a/src/Event.php +++ b/src/Event.php @@ -13,7 +13,13 @@ /** * This is the base class for classes containing event data. * - * @author Stefano Arlandini + * @phpstan-type MetricsSummary array{ + * min: int|float, + * max: int|float, + * sum: int|float, + * count: int, + * tags: array, + * } */ final class Event { @@ -61,6 +67,11 @@ final class Event */ private $metrics = []; + /** + * @var array> + */ + private $metricsSummary = []; + /** * @var string|null The name of the server (e.g. the host name) */ @@ -383,6 +394,24 @@ public function setMetrics(array $metrics): self return $this; } + /** + * @return array> + */ + public function getMetricsSummary(): array + { + return $this->metricsSummary; + } + + /** + * @param array> $metricsSummary + */ + public function setMetricsSummary(array $metricsSummary): self + { + $this->metricsSummary = $metricsSummary; + + return $this; + } + /** * Gets the name of the server. */ diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index 56f3e61b4..91520922d 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -12,6 +12,14 @@ /** * @internal + * + * @phpstan-type MetricsSummary array{ + * min: int|float, + * max: int|float, + * sum: int|float, + * count: int, + * tags: array, + * } */ class TransactionItem implements EnvelopeItemInterface { @@ -117,6 +125,10 @@ public static function toEnvelopeItem(Event $event): string $payload['spans'] = array_values(array_map([self::class, 'serializeSpan'], $event->getSpans())); + if (!empty($event->getMetricsSummary())) { + $payload['_metrics_summary'] = self::serializeMetricsSummary($event->getMetricsSummary()); + } + $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); if ($transactionMetadata instanceof TransactionMetadata) { $payload['transaction_info']['source'] = (string) $transactionMetadata->getSource(); @@ -139,6 +151,7 @@ public static function toEnvelopeItem(Event $event): string * op?: string, * data?: array, * tags?: array + * _metrics_summary?: array * } */ protected static function serializeSpan(Span $span): array @@ -178,18 +191,27 @@ protected static function serializeSpan(Span $span): array } if (!empty($span->getMetricsSummary())) { - $formattedSummary = []; - $summary = $span->getMetricsSummary(); + $result['_metrics_summary'] = self::serializeMetricsSummary($span->getMetricsSummary()); + } - foreach ($summary as $mri => $metrics) { - foreach ($metrics as $metric) { - $formattedSummary[$mri][] = $metric; - } - } + return $result; + } + + /** + * @param array> $metricsSummary + * + * @return array + */ + protected static function serializeMetricsSummary(array $metricsSummary): array + { + $formattedSummary = []; - $result['_metrics_summary'] = $formattedSummary; + foreach ($metricsSummary as $mri => $metrics) { + foreach ($metrics as $metric) { + $formattedSummary[$mri][] = $metric; + } } - return $result; + return $formattedSummary; } } diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index ba72c2e80..a0faf4215 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -190,6 +190,10 @@ public function finish(?float $endTimestamp = null): ?EventId } } + if (!empty($this->getMetricsSummary())) { + $event->setMetricsSummary($this->getMetricsSummary()); + } + return $this->hub->captureEvent($event); } } diff --git a/tests/Metrics/MetricsTest.php b/tests/Metrics/MetricsTest.php index 8dc7388a8..5e19d9750 100644 --- a/tests/Metrics/MetricsTest.php +++ b/tests/Metrics/MetricsTest.php @@ -15,6 +15,8 @@ use Sentry\Options; use Sentry\SentrySdk; use Sentry\State\Hub; +use Sentry\Tracing\SpanContext; +use Sentry\Tracing\TransactionContext; use Symfony\Bridge\PhpUnit\ClockMock; use function Sentry\metrics; @@ -355,4 +357,105 @@ public function testSet(): void metrics()->flush(); } + + public function testMetricsSummary(): void + { + ClockMock::withClockMock(1699412953); + + /** @var ClientInterface&MockObject $client */ + $client = $this->createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'enable_tracing' => true, + 'environment' => 'development', + 'release' => '1.0.0', + ])); + + $self = $this; + + $client->expects($this->once()) + ->method('captureEvent') + ->with($this->callback(static function (Event $event) use ($self): bool { + $self->assertSame( + [ + 'c:foo@second' => [ + 'c:foo@seconda:4:{s:11:"environment";s:11:"development";s:3:"foo";s:3:"bar";s:7:"release";s:5:"1.0.0";s:11:"transaction";s:12:"GET /metrics";}' => [ + 'min' => 1.0, + 'max' => 1.0, + 'sum' => 1.0, + 'count' => 1, + 'tags' => [ + 'environment' => 'development', + 'foo' => 'bar', + 'release' => '1.0.0', + 'transaction' => 'GET /metrics', + ], + ], + ], + ], + $event->getMetricsSummary() + ); + + $self->assertSame( + [ + 'c:foo@second' => [ + 'c:foo@seconda:4:{s:11:"environment";s:11:"development";s:3:"foo";s:3:"bar";s:7:"release";s:5:"1.0.0";s:11:"transaction";s:12:"GET /metrics";}' => [ + 'min' => 1.0, + 'max' => 1.0, + 'sum' => 2.0, + 'count' => 2, + 'tags' => [ + 'environment' => 'development', + 'foo' => 'bar', + 'release' => '1.0.0', + 'transaction' => 'GET /metrics', + ], + ], + ], + ], + $event->getSpans()[0]->getMetricsSummary() + ); + + return true; + })); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + $transactionContext = TransactionContext::make() + ->setName('GET /metrics') + ->setOp('http.server'); + $transaction = $hub->startTransaction($transactionContext); + $hub->setSpan($transaction); + + metrics()->increment( + 'foo', + 1, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + $spanContext = SpanContext::make() + ->setOp('function'); + $span = $transaction->startChild($spanContext); + $hub->setSpan($span); + + metrics()->increment( + 'foo', + 1, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + metrics()->increment( + 'foo', + 1, + MetricsUnit::second(), + ['foo' => 'bar'] + ); + + $span->finish(); + $transaction->finish(); + } } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index e8162f9d4..2ebd5e823 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -472,13 +472,26 @@ public static function serializeAsEnvelopeDataProvider(): iterable 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', 'span_id' => '5dd538dc297544cc', ]); + $event->setMetricsSummary([ + 'c:counter@star' => [ + 'c:counter@stara:0:{s:10:"repository";s:6:"client";}' => [ + 'min' => 1, + 'max' => 1, + 'sum' => 1, + 'count' => 1, + 'tags' => [ + 'repository' => 'client', + ], + ], + ], + ]); yield [ $event, << Date: Tue, 13 Feb 2024 12:32:00 +0100 Subject: [PATCH 0993/1161] Prepare 4.6.0 (#1703) --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 985632ab6..e16524d9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # CHANGELOG +## 4.6.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.6.0. + +### Features + +- Add the PHP SAPI to the runtime context [(#1700)](https://github.com/getsentry/sentry-php/pull/1700) + +### Bug Fixes + +- Correctly apply properties/options in `ClientBuilder::class` [(#1699)](https://github.com/getsentry/sentry-php/pull/1699) +- Attach `_metrics_summary` to transactions [(#1702)](https://github.com/getsentry/sentry-php/pull/1702) + +### Misc + +- Remove `final` from `Metrics::class` [(#1697)](https://github.com/getsentry/sentry-php/pull/1697) +- Return early when using `ignore_exceptions` [(#1701)](https://github.com/getsentry/sentry-php/pull/1701) +- Attach exceptions to the log message from `FrameContextifierIntegration::class` [(#1678)](https://github.com/getsentry/sentry-php/pull/1678) + ## 4.5.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.5.0. From 30d98a460ab10f7b7032d76c62da5b1ce6c0765d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 13 Feb 2024 11:32:56 +0000 Subject: [PATCH 0994/1161] release: 4.6.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index b324f20b8..5556575a5 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.5.0'; + public const SDK_VERSION = '4.6.0'; /** * @var Options The client options From 6f8cce5441da92829e84c3c2549625860f11787f Mon Sep 17 00:00:00 2001 From: ju1ius Date: Tue, 27 Feb 2024 20:05:48 +0100 Subject: [PATCH 0995/1161] adds `JSON_ERROR_NON_BACKED_ENUM` to allowed `JSON::encode()` errors. (#1707) --- src/Util/JSON.php | 3 +++ tests/Util/Fixtures/NonBackedEnum.php | 11 +++++++++++ tests/Util/JSONTest.php | 12 ++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 tests/Util/Fixtures/NonBackedEnum.php diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 9c568c52f..9824cbd74 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -35,6 +35,9 @@ public static function encode($data, int $options = 0, int $maxDepth = 512): str $encodedData = json_encode($data, $options, $maxDepth); $allowedErrors = [\JSON_ERROR_NONE, \JSON_ERROR_RECURSION, \JSON_ERROR_INF_OR_NAN, \JSON_ERROR_UNSUPPORTED_TYPE]; + if (\defined('JSON_ERROR_NON_BACKED_ENUM')) { + $allowedErrors[] = \JSON_ERROR_NON_BACKED_ENUM; + } $encounteredAnyError = json_last_error() !== \JSON_ERROR_NONE; diff --git a/tests/Util/Fixtures/NonBackedEnum.php b/tests/Util/Fixtures/NonBackedEnum.php new file mode 100644 index 000000000..8cef91f05 --- /dev/null +++ b/tests/Util/Fixtures/NonBackedEnum.php @@ -0,0 +1,11 @@ +assertSame('{}', JSON::encode([], \JSON_FORCE_OBJECT)); } + /** + * The `JSON_ERROR_NON_BACKED_ENUM` constant is only exposed from 8.1.5 and up. + * + * @requires PHP >= 8.1.5 + */ + public function testEncodeGracefullyHandlesUnitEnums(): void + { + $result = JSON::encode([NonBackedEnum::None, NonBackedEnum::Some]); + $this->assertSame('[0,0]', $result); + } + /** * @dataProvider decodeDataProvider */ From 8a252ab3983188f34ca8379f18d58217a03a4af5 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 5 Mar 2024 17:23:37 +0100 Subject: [PATCH 0996/1161] Skip loading stubs if excimer is availiable (#1710) --- stubs/ExcimerLog.stub | 59 +++++++++++++++++++++----------------- stubs/ExcimerLogEntry.stub | 3 ++ stubs/ExcimerProfiler.stub | 3 ++ stubs/ExcimerTimer.stub | 3 ++ stubs/autoload.php | 4 +++ stubs/globals.stub | 3 ++ 6 files changed, 48 insertions(+), 27 deletions(-) diff --git a/stubs/ExcimerLog.stub b/stubs/ExcimerLog.stub index 6cc5e2fe4..a80a83a9c 100644 --- a/stubs/ExcimerLog.stub +++ b/stubs/ExcimerLog.stub @@ -1,6 +1,9 @@ Date: Fri, 8 Mar 2024 08:17:08 +0100 Subject: [PATCH 0997/1161] Always add sampled flag to W3C traceparent (#1713) --- src/Tracing/PropagationContext.php | 2 +- src/Tracing/Span.php | 7 +++++-- tests/FunctionsTest.php | 7 ++++--- tests/Tracing/PropagationContextTest.php | 2 +- tests/Tracing/TransactionContextTest.php | 10 ---------- 5 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index 4271a314e..f63dc4285 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -72,7 +72,7 @@ public function toTraceparent(): string */ public function toW3CTraceparent(): string { - return sprintf('00-%s-%s', (string) $this->traceId, (string) $this->spanId); + return sprintf('00-%s-%s-00', (string) $this->traceId, (string) $this->spanId); } /** diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index c2c0f79ab..17d4ea25f 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -578,10 +578,13 @@ public function toW3CTraceparent(): string $sampled = ''; if ($this->sampled !== null) { - $sampled = $this->sampled ? '-01' : '-00'; + $sampled = $this->sampled ? '01' : '00'; + } else { + // If no sampling decision was made, set the flag to 00 + $sampled = '00'; } - return sprintf('00-%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled); + return sprintf('00-%s-%s-%s', (string) $this->traceId, (string) $this->spanId, $sampled); } /** diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 73ce3c109..77a5c6ee7 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -444,7 +444,7 @@ public function testW3CTraceparentWithTracingDisabled(): void $traceParent = getW3CTraceparent(); - $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); + $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-00', $traceParent); } public function testW3CTraceparentWithTracingEnabled(): void @@ -462,7 +462,8 @@ public function testW3CTraceparentWithTracingEnabled(): void $spanContext = (new SpanContext()) ->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')) - ->setSpanId(new SpanId('566e3688a61d4bc8')); + ->setSpanId(new SpanId('566e3688a61d4bc8')) + ->setSampled(true); $span = new Span($spanContext); @@ -470,7 +471,7 @@ public function testW3CTraceparentWithTracingEnabled(): void $traceParent = getW3CTraceparent(); - $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); + $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01', $traceParent); } public function testBaggageWithTracingDisabled(): void diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index 6327b0ccb..b60fbc701 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -118,7 +118,7 @@ public function testToW3CTraceparent() $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); - $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toW3CTraceparent()); + $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-00', $propagationContext->toW3CTraceparent()); } public function testToBaggage() diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index 80ee26d7f..67bc5ced6 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -137,16 +137,6 @@ public static function tracingDataProvider(): iterable true, ]; - yield [ - '00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', - '', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - null, - DynamicSamplingContext::class, - true, - ]; - yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', From 44046c2aa3a01420daa7d98d301072e5c85800f0 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 8 Mar 2024 09:17:37 +0100 Subject: [PATCH 0998/1161] Prepare 4.6.1 (#1714) --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e16524d9e..c22f1ff15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG +## 4.6.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.6.1. + +### Bug Fixes + +- Always add the sampled flag to the W3C `traceparent` header [(#1713)](https://github.com/getsentry/sentry-php/pull/1713) +- Add `JSON_ERROR_NON_BACKED_ENUM` to allowed `JSON::encode()` errors. [(#1707)](https://github.com/getsentry/sentry-php/pull/1707) + ## 4.6.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.6.0. From 5a94184175e5830b589bf923da8c9c3af2c0f409 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Fri, 8 Mar 2024 08:18:09 +0000 Subject: [PATCH 0999/1161] release: 4.6.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 5556575a5..28f3b00e5 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.6.0'; + public const SDK_VERSION = '4.6.1'; /** * @var Options The client options From 76a218417d2ed11683f050d693679720251f2036 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 14 Mar 2024 10:24:53 +0100 Subject: [PATCH 1000/1161] Improve debugging experience (#1705) Co-authored-by: Michi Hoffmann --- src/Client.php | 24 +- src/EventType.php | 15 + src/Integration/IntegrationRegistry.php | 19 +- src/Options.php | 9 + src/Profiling/Profile.php | 20 + src/Profiling/Profiler.php | 24 +- src/StacktraceBuilder.php | 5 + src/State/Hub.php | 36 +- src/Tracing/Transaction.php | 4 +- src/Transport/HttpTransport.php | 49 +- src/Transport/RateLimiter.php | 66 +-- src/Util/PHPConfiguration.php | 20 + tests/ClientTest.php | 436 +++++++++--------- tests/Integration/IntegrationRegistryTest.php | 91 +--- tests/Transport/HttpTransportTest.php | 74 ++- tests/Transport/RateLimiterTest.php | 91 ++-- 16 files changed, 554 insertions(+), 429 deletions(-) create mode 100644 src/Util/PHPConfiguration.php diff --git a/src/Client.php b/src/Client.php index 28f3b00e5..406721f37 100644 --- a/src/Client.php +++ b/src/Client.php @@ -151,7 +151,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null, ?E $className = \get_class($exception); if ($this->isIgnoredException($className)) { $this->logger->info( - 'The event will be discarded because it matches an entry in "ignore_exceptions".', + 'The exception will be discarded because it matches an entry in "ignore_exceptions".', ['className' => $className] ); @@ -294,17 +294,24 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT); } + $eventDescription = sprintf( + '%s%s [%s]', + $event->getLevel() !== null ? $event->getLevel() . ' ' : '', + (string) $event->getType(), + (string) $event->getId() + ); + $isEvent = EventType::event() === $event->getType(); $sampleRate = $this->options->getSampleRate(); // only sample with the `sample_rate` on errors/messages if ($isEvent && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { - $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]); + $this->logger->info(sprintf('The %s will be discarded because it has been sampled.', $eventDescription), ['event' => $event]); return null; } - $event = $this->applyIgnoreOptions($event); + $event = $this->applyIgnoreOptions($event, $eventDescription); if ($event === null) { return null; @@ -316,7 +323,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco if ($event === null) { $this->logger->info( - 'The event will be discarded because one of the event processors returned "null".', + sprintf('The %s will be discarded because one of the event processors returned "null".', $eventDescription), ['event' => $beforeEventProcessors] ); @@ -330,7 +337,8 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco if ($event === null) { $this->logger->info( sprintf( - 'The event will be discarded because the "%s" callback returned "null".', + 'The %s will be discarded because the "%s" callback returned "null".', + $eventDescription, $this->getBeforeSendCallbackName($beforeSendCallback) ), ['event' => $beforeSendCallback] @@ -351,7 +359,7 @@ private function isIgnoredException(string $className): bool return false; } - private function applyIgnoreOptions(Event $event): ?Event + private function applyIgnoreOptions(Event $event, string $eventDescription): ?Event { if ($event->getType() === EventType::event()) { $exceptions = $event->getExceptions(); @@ -363,7 +371,7 @@ private function applyIgnoreOptions(Event $event): ?Event foreach ($exceptions as $exception) { if ($this->isIgnoredException($exception->getType())) { $this->logger->info( - 'The event will be discarded because it matches an entry in "ignore_exceptions".', + sprintf('The %s will be discarded because it matches an entry in "ignore_exceptions".', $eventDescription), ['event' => $event] ); @@ -381,7 +389,7 @@ private function applyIgnoreOptions(Event $event): ?Event if (\in_array($transactionName, $this->options->getIgnoreTransactions(), true)) { $this->logger->info( - 'The event will be discarded because it matches a entry in "ignore_transactions".', + sprintf('The %s will be discarded because it matches a entry in "ignore_transactions".', $eventDescription), ['event' => $event] ); diff --git a/src/EventType.php b/src/EventType.php index 208789e3a..7e34cefc1 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -47,6 +47,21 @@ public static function metrics(): self return self::getInstance('metrics'); } + /** + * List of all cases on the enum. + * + * @return self[] + */ + public static function cases(): array + { + return [ + self::event(), + self::transaction(), + self::checkIn(), + self::metrics(), + ]; + } + public function __toString(): string { return $this->value; diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index e7209f4c7..40eb9c06b 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -51,29 +51,38 @@ public static function getInstance(): self public function setupIntegrations(Options $options, LoggerInterface $logger): array { $integrations = []; + $installed = []; foreach ($this->getIntegrationsToSetup($options) as $integration) { - $integrations[\get_class($integration)] = $integration; + $integrationName = \get_class($integration); - $this->setupIntegration($integration, $logger); + $integrations[$integrationName] = $integration; + + if ($this->setupIntegration($integration)) { + $installed[] = $integrationName; + } + } + + if (\count($installed) > 0) { + $logger->debug(sprintf('The "%s" integration(s) have been installed.', implode(', ', $installed))); } return $integrations; } - private function setupIntegration(IntegrationInterface $integration, LoggerInterface $logger): void + private function setupIntegration(IntegrationInterface $integration): bool { $integrationName = \get_class($integration); if (isset($this->integrations[$integrationName])) { - return; + return false; } $integration->setupOnce(); $this->integrations[$integrationName] = true; - $logger->debug(sprintf('The "%s" integration has been installed.', $integrationName)); + return true; } /** diff --git a/src/Options.php b/src/Options.php index 2a948281a..bd04615db 100644 --- a/src/Options.php +++ b/src/Options.php @@ -5,6 +5,7 @@ namespace Sentry; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Sentry\HttpClient\HttpClientInterface; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\IntegrationInterface; @@ -337,6 +338,14 @@ public function getLogger(): ?LoggerInterface return $this->options['logger']; } + /** + * Helper to always get a logger instance even if it was not set. + */ + public function getLoggerOrNullLogger(): LoggerInterface + { + return $this->getLogger() ?? new NullLogger(); + } + /** * Sets a PSR-3 compatible logger to log internal debug messages. */ diff --git a/src/Profiling/Profile.php b/src/Profiling/Profile.php index bf0c595a4..06d895558 100644 --- a/src/Profiling/Profile.php +++ b/src/Profiling/Profile.php @@ -4,6 +4,8 @@ namespace Sentry\Profiling; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; use Sentry\Event; @@ -118,9 +120,15 @@ final class Profile */ private $options; + /** + * @var LoggerInterface + */ + private $logger; + public function __construct(?Options $options = null) { $this->options = $options; + $this->logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger(); } public function setStartTimeStamp(float $startTimeStamp): void @@ -147,20 +155,28 @@ public function setEventId(EventId $eventId): void public function getFormattedData(Event $event): ?array { if (!$this->validateExcimerLog()) { + $this->logger->warning('The profile does not contain enough samples, the profile will be discarded.'); + return null; } $osContext = $event->getOsContext(); if (!$this->validateOsContext($osContext)) { + $this->logger->warning('The OS context is not missing or invalid, the profile will be discarded.'); + return null; } $runtimeContext = $event->getRuntimeContext(); if (!$this->validateRuntimeContext($runtimeContext)) { + $this->logger->warning('The runtime context is not missing or invalid, the profile will be discarded.'); + return null; } if (!$this->validateEvent($event)) { + $this->logger->warning('The event is missing a transaction and/or trace ID, the profile will be discarded.'); + return null; } @@ -238,11 +254,15 @@ public function getFormattedData(Event $event): ?array } if (!$this->validateMaxDuration((float) $duration)) { + $this->logger->warning(sprintf('The profile is %ss which is longer than the allowed %ss, the profile will be discarded.', (float) $duration, self::MAX_PROFILE_DURATION)); + return null; } $startTime = \DateTime::createFromFormat('U.u', number_format($this->startTimeStamp, 4, '.', ''), new \DateTimeZone('UTC')); if ($startTime === false) { + $this->logger->warning(sprintf('The start time (%s) of the profile is not valid, the profile will be discarded.', $this->startTimeStamp)); + return null; } diff --git a/src/Profiling/Profiler.php b/src/Profiling/Profiler.php index dca039c7d..7f4eb37c7 100644 --- a/src/Profiling/Profiler.php +++ b/src/Profiling/Profiler.php @@ -4,6 +4,8 @@ namespace Sentry\Profiling; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Sentry\Options; /** @@ -21,6 +23,11 @@ final class Profiler */ private $profile; + /** + * @var LoggerInterface + */ + private $logger; + /** * @var float The sample rate (10.01ms/101 Hz) */ @@ -33,6 +40,7 @@ final class Profiler public function __construct(?Options $options = null) { + $this->logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger(); $this->profile = new Profile($options); $this->initProfiler(); @@ -65,13 +73,17 @@ public function getProfile(): ?Profile private function initProfiler(): void { - if (\extension_loaded('excimer') && \PHP_VERSION_ID >= 70300) { - $this->profiler = new \ExcimerProfiler(); - $this->profile->setStartTimeStamp(microtime(true)); + if (!\extension_loaded('excimer')) { + $this->logger->warning('The profiler was started but is not available because the "excimer" extension is not loaded.'); - $this->profiler->setEventType(EXCIMER_REAL); - $this->profiler->setPeriod(self::SAMPLE_RATE); - $this->profiler->setMaxDepth(self::MAX_STACK_DEPTH); + return; } + + $this->profiler = new \ExcimerProfiler(); + $this->profile->setStartTimeStamp(microtime(true)); + + $this->profiler->setEventType(EXCIMER_REAL); + $this->profiler->setPeriod(self::SAMPLE_RATE); + $this->profiler->setMaxDepth(self::MAX_STACK_DEPTH); } } diff --git a/src/StacktraceBuilder.php b/src/StacktraceBuilder.php index d2e32b33f..6360c5560 100644 --- a/src/StacktraceBuilder.php +++ b/src/StacktraceBuilder.php @@ -5,6 +5,7 @@ namespace Sentry; use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\Util\PHPConfiguration; /** * This class builds {@see Stacktrace} objects from an instance of an exception @@ -28,6 +29,10 @@ final class StacktraceBuilder public function __construct(Options $options, RepresentationSerializerInterface $representationSerializer) { $this->frameBuilder = new FrameBuilder($options, $representationSerializer); + + if (PHPConfiguration::isBooleanIniOptionEnabled('zend.exception_ignore_args')) { + $options->getLoggerOrNullLogger()->warning('The "zend.exception_ignore_args" PHP setting is enabled which results in missing stack trace arguments, see: https://docs.sentry.io/platforms/php/troubleshooting/#missing-variables-in-stack-traces.'); + } } /** diff --git a/src/State/Hub.php b/src/State/Hub.php index 0bd7aa3c8..a1c01a5e2 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -4,6 +4,7 @@ namespace Sentry\State; +use Psr\Log\NullLogger; use Sentry\Breadcrumb; use Sentry\CheckIn; use Sentry\CheckInStatus; @@ -254,31 +255,42 @@ public function startTransaction(TransactionContext $context, array $customSampl $transaction = new Transaction($context, $this); $client = $this->getClient(); $options = $client !== null ? $client->getOptions() : null; + $logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger(); if ($options === null || !$options->isTracingEnabled()) { $transaction->setSampled(false); + $logger->warning(sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]); + return $transaction; } $samplingContext = SamplingContext::getDefault($context); $samplingContext->setAdditionalContext($customSamplingContext); - $tracesSampler = $options->getTracesSampler(); + $sampleSource = 'context'; if ($transaction->getSampled() === null) { + $tracesSampler = $options->getTracesSampler(); + if ($tracesSampler !== null) { $sampleRate = $tracesSampler($samplingContext); + + $sampleSource = 'config:traces_sampler'; } else { $sampleRate = $this->getSampleRate( $samplingContext->getParentSampled(), $options->getTracesSampleRate() ?? 0 ); + + $sampleSource = $samplingContext->getParentSampled() ? 'parent' : 'config:traces_sample_rate'; } if (!$this->isValidSampleRate($sampleRate)) { $transaction->setSampled(false); + $logger->warning(sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + return $transaction; } @@ -287,6 +299,8 @@ public function startTransaction(TransactionContext $context, array $customSampl if ($sampleRate === 0.0) { $transaction->setSampled(false); + $logger->info(sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is %s.', (string) $transaction->getTraceId(), $sampleSource, $sampleRate), ['context' => $context]); + return $transaction; } @@ -294,18 +308,24 @@ public function startTransaction(TransactionContext $context, array $customSampl } if (!$transaction->getSampled()) { + $logger->info(sprintf('Transaction [%s] was started but not sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + return $transaction; } + $logger->info(sprintf('Transaction [%s] was started and sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + $transaction->initSpanRecorder(); $profilesSampleRate = $options->getProfilesSampleRate(); - if ($this->sample($profilesSampleRate)) { - $transaction->initProfiler(); - $profiler = $transaction->getProfiler(); - if ($profiler !== null) { - $profiler->start(); - } + if ($profilesSampleRate === null) { + $logger->info(sprintf('Transaction [%s] is not profiling because `profiles_sample_rate` option is not set.', (string) $transaction->getTraceId())); + } elseif ($this->sample($profilesSampleRate)) { + $logger->info(sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId())); + + $transaction->initProfiler()->start(); + } else { + $logger->info(sprintf('Transaction [%s] is not profiling because it was not sampled.', (string) $transaction->getTraceId())); } return $transaction; @@ -371,7 +391,7 @@ private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampl */ private function sample($sampleRate): bool { - if ($sampleRate === 0.0) { + if ($sampleRate === 0.0 || $sampleRate === null) { return false; } diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index a0faf4215..735768c57 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -119,7 +119,7 @@ public function initSpanRecorder(int $maxSpans = 1000): self return $this; } - public function initProfiler(): self + public function initProfiler(): Profiler { if ($this->profiler === null) { $client = $this->hub->getClient(); @@ -128,7 +128,7 @@ public function initProfiler(): self $this->profiler = new Profiler($options); } - return $this; + return $this->profiler; } public function getProfiler(): ?Profiler diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 9e72cf669..74982a369 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -59,7 +59,7 @@ public function __construct( $this->httpClient = $httpClient; $this->payloadSerializer = $payloadSerializer; $this->logger = $logger ?? new NullLogger(); - $this->rateLimiter = new RateLimiter($this->logger); + $this->rateLimiter = new RateLimiter(); } /** @@ -69,10 +69,27 @@ public function send(Event $event): Result { $this->sendRequestToSpotlight($event); + $eventDescription = sprintf( + '%s%s [%s]', + $event->getLevel() !== null ? $event->getLevel() . ' ' : '', + (string) $event->getType(), + (string) $event->getId() + ); + if ($this->options->getDsn() === null) { + $this->logger->info(sprintf('Skipping %s, because no DSN is set.', $eventDescription), ['event' => $event]); + return new Result(ResultStatus::skipped(), $event); } + $targetDescription = sprintf( + '%s [project:%s]', + $this->options->getDsn()->getHost(), + $this->options->getDsn()->getProjectId() + ); + + $this->logger->info(sprintf('Sending %s to %s.', $eventDescription, $targetDescription), ['event' => $event]); + $eventType = $event->getType(); if ($this->rateLimiter->isRateLimited($eventType)) { $this->logger->warning( @@ -90,26 +107,40 @@ public function send(Event $event): Result $response = $this->httpClient->sendRequest($request, $this->options); } catch (\Throwable $exception) { $this->logger->error( - sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), + sprintf('Failed to send %s to %s. Reason: "%s".', $eventDescription, $targetDescription, $exception->getMessage()), ['exception' => $exception, 'event' => $event] ); return new Result(ResultStatus::failed()); } - $response = $this->rateLimiter->handleResponse($event, $response); - if ($response->isSuccess()) { - return new Result(ResultStatus::success(), $event); - } - if ($response->hasError()) { $this->logger->error( - sprintf('Failed to send the event to Sentry. Reason: "%s".', $response->getError()), + sprintf('Failed to send %s to %s. Reason: "%s".', $eventDescription, $targetDescription, $response->getError()), ['event' => $event] ); + + return new Result(ResultStatus::unknown()); } - return new Result(ResultStatus::createFromHttpStatusCode($response->getStatusCode())); + if ($this->rateLimiter->handleResponse($response)) { + $eventType = $event->getType(); + $disabledUntil = $this->rateLimiter->getDisabledUntil($eventType); + + $this->logger->warning( + sprintf('Rate limited exceeded for requests of type "%s", backing off until "%s".', (string) $eventType, gmdate(\DATE_ATOM, $disabledUntil)), + ['event' => $event] + ); + } + + $resultStatus = ResultStatus::createFromHttpStatusCode($response->getStatusCode()); + + $this->logger->info( + sprintf('Sent %s to %s. Result: "%s" (status: %s).', $eventDescription, $targetDescription, strtolower((string) $resultStatus), $response->getStatusCode()), + ['response' => $response, 'event' => $event] + ); + + return new Result($resultStatus, $event); } /** diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index f21d1e83b..4dc628ea4 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -4,9 +4,6 @@ namespace Sentry\Transport; -use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; -use Sentry\Event; use Sentry\EventType; use Sentry\HttpClient\Response; @@ -29,56 +26,13 @@ final class RateLimiter */ private const DEFAULT_RETRY_AFTER_DELAY_SECONDS = 60; - /** - * @var LoggerInterface An instance of a PSR-3 compatible logger - */ - private $logger; - /** * @var array The map of time instants for each event category after * which an HTTP request can be retried */ private $rateLimits = []; - public function __construct(?LoggerInterface $logger = null) - { - $this->logger = $logger ?? new NullLogger(); - } - - public function handleResponse(Event $event, Response $response): Response - { - if ($this->handleRateLimit($response)) { - $eventType = $event->getType(); - $disabledUntil = $this->getDisabledUntil($eventType); - - $this->logger->warning( - sprintf('Rate limited exceeded for requests of type "%s", backing off until "%s".', (string) $eventType, gmdate(\DATE_ATOM, $disabledUntil)), - ['event' => $event] - ); - } - - return $response; - } - - public function isRateLimited(EventType $eventType): bool - { - $disabledUntil = $this->getDisabledUntil($eventType); - - return $disabledUntil > time(); - } - - private function getDisabledUntil(EventType $eventType): int - { - $category = (string) $eventType; - - if ($eventType === EventType::event()) { - $category = 'error'; - } - - return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); - } - - private function handleRateLimit(Response $response): bool + public function handleResponse(Response $response): bool { $now = time(); @@ -107,6 +61,24 @@ private function handleRateLimit(Response $response): bool return false; } + public function isRateLimited(EventType $eventType): bool + { + $disabledUntil = $this->getDisabledUntil($eventType); + + return $disabledUntil > time(); + } + + public function getDisabledUntil(EventType $eventType): int + { + $category = (string) $eventType; + + if ($eventType === EventType::event()) { + $category = 'error'; + } + + return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); + } + private function parseRetryAfterHeader(int $currentTime, string $header): int { if (preg_match('/^\d+$/', $header) === 1) { diff --git a/src/Util/PHPConfiguration.php b/src/Util/PHPConfiguration.php new file mode 100644 index 000000000..bb3b06e23 --- /dev/null +++ b/src/Util/PHPConfiguration.php @@ -0,0 +1,20 @@ +createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('debug'); + ->method('debug'); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because one of the event processors returned "null".'); + ->method('info') + ->with(new StringMatchesFormatDescription('The event [%s] will be discarded because one of the event processors returned "null".')); $integration = new class($integrationCalled) implements IntegrationInterface { private $integrationCalled; @@ -82,20 +83,20 @@ public function testCaptureMessage(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $this->assertSame('foo', $event->getMessage()); - $this->assertEquals(Severity::fatal(), $event->getLevel()); + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertSame('foo', $event->getMessage()); + $this->assertEquals(Severity::fatal(), $event->getLevel()); - return true; - })) - ->willReturnCallback(static function (Event $event): Result { - return new Result(ResultStatus::success(), $event); - }); + return true; + })) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); $client = ClientBuilder::create() - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); $this->assertNotNull($client->captureMessage('foo', Severity::fatal())); } @@ -129,24 +130,24 @@ public function testCaptureException(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event) use ($exception): bool { - $this->assertCount(1, $event->getExceptions()); + ->method('send') + ->with($this->callback(function (Event $event) use ($exception): bool { + $this->assertCount(1, $event->getExceptions()); - $exceptionData = $event->getExceptions()[0]; + $exceptionData = $event->getExceptions()[0]; - $this->assertSame(\get_class($exception), $exceptionData->getType()); - $this->assertSame($exception->getMessage(), $exceptionData->getValue()); + $this->assertSame(\get_class($exception), $exceptionData->getType()); + $this->assertSame($exception->getMessage(), $exceptionData->getValue()); - return true; - })) - ->willReturnCallback(static function (Event $event): Result { - return new Result(ResultStatus::success(), $event); - }); + return true; + })) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); $client = ClientBuilder::create() - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); $this->assertNotNull($client->captureException($exception)); } @@ -192,7 +193,7 @@ public static function captureExceptionWithEventHintDataProvider(): \Generator } /** - * @group legacy + * @group legacy * * @dataProvider captureEventDataProvider */ @@ -200,15 +201,15 @@ public function testCaptureEvent(array $options, Event $event, Event $expectedEv { $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($expectedEvent) - ->willReturnCallback(static function (Event $event): Result { - return new Result(ResultStatus::success(), $event); - }); + ->method('send') + ->with($expectedEvent) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); $client = ClientBuilder::create($options) - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); $this->assertSame($event->getId(), $client->captureEvent($event)); } @@ -310,25 +311,25 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(static function (Event $event) use ($shouldAttachStacktrace): bool { - if ($shouldAttachStacktrace && $event->getStacktrace() === null) { - return false; - } - - if (!$shouldAttachStacktrace && $event->getStacktrace() !== null) { - return false; - } - - return true; - })) - ->willReturnCallback(static function (Event $event): Result { - return new Result(ResultStatus::success(), $event); - }); + ->method('send') + ->with($this->callback(static function (Event $event) use ($shouldAttachStacktrace): bool { + if ($shouldAttachStacktrace && $event->getStacktrace() === null) { + return false; + } + + if (!$shouldAttachStacktrace && $event->getStacktrace() !== null) { + return false; + } + + return true; + })) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); $client = ClientBuilder::create(['attach_stacktrace' => $attachStacktraceOption]) - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); $this->assertNotNull($client->captureEvent(Event::createEvent(), $hint)); } @@ -373,17 +374,17 @@ public function testCaptureEventPrefersExplicitStacktrace(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(static function (Event $event) use ($stacktrace): bool { - return $stacktrace === $event->getStacktrace(); - })) - ->willReturnCallback(static function (Event $event): Result { - return new Result(ResultStatus::success(), $event); - }); + ->method('send') + ->with($this->callback(static function (Event $event) use ($stacktrace): bool { + return $stacktrace === $event->getStacktrace(); + })) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); $this->assertNotNull($client->captureEvent(Event::createEvent(), EventHint::fromArray([ 'stacktrace' => $stacktrace, @@ -395,22 +396,22 @@ public function testCaptureLastError(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $exception = $event->getExceptions()[0]; + ->method('send') + ->with($this->callback(function (Event $event): bool { + $exception = $event->getExceptions()[0]; - $this->assertEquals('ErrorException', $exception->getType()); - $this->assertEquals('foo', $exception->getValue()); + $this->assertEquals('ErrorException', $exception->getType()); + $this->assertEquals('foo', $exception->getValue()); - return true; - })) - ->willReturnCallback(static function (Event $event): Result { - return new Result(ResultStatus::success(), $event); - }); + return true; + })) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); @trigger_error('foo', \E_USER_NOTICE); @@ -451,12 +452,12 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) - ->method('send') - ->with($this->anything()); + ->method('send') + ->with($this->anything()); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); error_clear_last(); @@ -639,20 +640,23 @@ public function testProcessEventDiscardsEventWhenSampleRateOptionIsZero(): void { $transport = $this->createMock(TransportInterface::class); $transport->expects($this->never()) - ->method('send') - ->with($this->anything()); + ->method('send') + ->with($this->anything()); $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because it has been sampled.', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->method('info') + ->with( + new StringMatchesFormatDescription('The event [%s] will be discarded because it has been sampled.'), + $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + }) + ); $client = ClientBuilder::create(['sample_rate' => 0]) - ->setTransport($transport) - ->setLogger($logger) - ->getClient(); + ->setTransport($transport) + ->setLogger($logger) + ->getClient(); $client->captureEvent(Event::createEvent()); } @@ -661,12 +665,12 @@ public function testProcessEventCapturesEventWhenSampleRateOptionIsAboveZero(): { $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->anything()); + ->method('send') + ->with($this->anything()); $client = ClientBuilder::create(['sample_rate' => 1]) - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); $client->captureEvent(Event::createEvent()); } @@ -678,16 +682,16 @@ public function testProcessEventDiscardsEventWhenIgnoreExceptionsMatches(): void /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because it matches an entry in "ignore_exceptions".'); + ->method('info') + ->with('The exception will be discarded because it matches an entry in "ignore_exceptions".'); $options = [ 'ignore_exceptions' => [\Exception::class], ]; $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); + ->setLogger($logger) + ->getClient(); $client->captureException($exception); } @@ -699,16 +703,16 @@ public function testProcessEventDiscardsEventWhenParentHierarchyOfIgnoreExceptio /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because it matches an entry in "ignore_exceptions".'); + ->method('info') + ->with('The exception will be discarded because it matches an entry in "ignore_exceptions".'); $options = [ 'ignore_exceptions' => [\RuntimeException::class], ]; $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); + ->setLogger($logger) + ->getClient(); $client->captureException($exception); } @@ -721,18 +725,21 @@ public function testProcessEventDiscardsEventWhenIgnoreTransactionsMatches(): vo /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because it matches a entry in "ignore_transactions".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->method('info') + ->with( + new StringMatchesFormatDescription('The transaction [%s] will be discarded because it matches a entry in "ignore_transactions".'), + $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + }) + ); $options = [ 'ignore_transactions' => ['GET /foo'], ]; $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); + ->setLogger($logger) + ->getClient(); $client->captureEvent($event); } @@ -742,10 +749,13 @@ public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull() /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because the "before_send" callback returned "null".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->method('info') + ->with( + new StringMatchesFormatDescription('The event [%s] will be discarded because the "before_send" callback returned "null".'), + $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + }) + ); $options = [ 'before_send' => static function () { @@ -754,8 +764,8 @@ public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull() ]; $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); + ->setLogger($logger) + ->getClient(); $client->captureEvent(Event::createEvent()); } @@ -765,10 +775,13 @@ public function testProcessEventDiscardsEventWhenBeforeSendTransactionCallbackRe /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because the "before_send_transaction" callback returned "null".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->method('info') + ->with( + new StringMatchesFormatDescription('The transaction [%s] will be discarded because the "before_send_transaction" callback returned "null".'), + $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + }) + ); $options = [ 'before_send_transaction' => static function () { @@ -777,8 +790,8 @@ public function testProcessEventDiscardsEventWhenBeforeSendTransactionCallbackRe ]; $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); + ->setLogger($logger) + ->getClient(); $client->captureEvent(Event::createTransaction()); } @@ -788,10 +801,13 @@ public function testProcessEventDiscardsEventWhenBeforeSendCheckInCallbackReturn /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because the "before_send_check_in" callback returned "null".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->method('info') + ->with( + new StringMatchesFormatDescription('The check_in [%s] will be discarded because the "before_send_check_in" callback returned "null".'), + $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + }) + ); $options = [ 'before_send_check_in' => static function () { @@ -800,8 +816,8 @@ public function testProcessEventDiscardsEventWhenBeforeSendCheckInCallbackReturn ]; $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); + ->setLogger($logger) + ->getClient(); $client->captureEvent(Event::createCheckIn()); } @@ -811,10 +827,13 @@ public function testProcessEventDiscardsEventWhenBeforeSendMetricsCallbackReturn /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because the "before_send_metrics" callback returned "null".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->method('info') + ->with( + new StringMatchesFormatDescription('The metrics [%s] will be discarded because the "before_send_metrics" callback returned "null".'), + $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + }) + ); $options = [ 'before_send_metrics' => static function () { @@ -823,8 +842,8 @@ public function testProcessEventDiscardsEventWhenBeforeSendMetricsCallbackReturn ]; $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); + ->setLogger($logger) + ->getClient(); $client->captureEvent(Event::createMetrics()); } @@ -834,14 +853,17 @@ public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): vo /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) - ->method('info') - ->with('The event will be discarded because one of the event processors returned "null".', $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - })); + ->method('info') + ->with( + new StringMatchesFormatDescription('The debug event [%s] will be discarded because one of the event processors returned "null".'), + $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + }) + ); $client = ClientBuilder::create([]) - ->setLogger($logger) - ->getClient(); + ->setLogger($logger) + ->getClient(); $scope = new Scope(); $scope->addEventProcessor(static function () { @@ -856,19 +878,19 @@ public function testAttachStacktrace(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $result = $event->getStacktrace(); + ->method('send') + ->with($this->callback(function (Event $event): bool { + $result = $event->getStacktrace(); - return $result !== null; - })) - ->willReturnCallback(static function (Event $event): Result { - return new Result(ResultStatus::success(), $event); - }); + return $result !== null; + })) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); $this->assertNotNull($client->captureMessage('test')); } @@ -878,13 +900,13 @@ public function testFlush(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('close') - ->with(10) - ->willReturn(new Result(ResultStatus::success())); + ->method('close') + ->with(10) + ->willReturn(new Result(ResultStatus::success())); $client = ClientBuilder::create() - ->setTransport($transport) - ->getClient(); + ->setTransport($transport) + ->getClient(); $response = $client->flush(10); @@ -896,12 +918,12 @@ public function testBuildEventInCLIDoesntSetTransaction(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $this->assertNull($event->getTransaction()); + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertNull($event->getTransaction()); - return true; - })); + return true; + })); $client = new Client( new Options(), @@ -923,23 +945,23 @@ public function testBuildEventWithException(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $capturedExceptions = $event->getExceptions(); + ->method('send') + ->with($this->callback(function (Event $event): bool { + $capturedExceptions = $event->getExceptions(); - $this->assertCount(2, $capturedExceptions); - $this->assertNotNull($capturedExceptions[0]->getStacktrace()); - $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 1]), $capturedExceptions[0]->getMechanism()); - $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); - $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); + $this->assertCount(2, $capturedExceptions); + $this->assertNotNull($capturedExceptions[0]->getStacktrace()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 1]), $capturedExceptions[0]->getMechanism()); + $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); + $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); - $this->assertNotNull($capturedExceptions[1]->getStacktrace()); - $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 0]), $capturedExceptions[1]->getMechanism()); - $this->assertSame(\RuntimeException::class, $capturedExceptions[1]->getType()); - $this->assertSame('testMessage2', $capturedExceptions[1]->getValue()); + $this->assertNotNull($capturedExceptions[1]->getStacktrace()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 0]), $capturedExceptions[1]->getMechanism()); + $this->assertSame(\RuntimeException::class, $capturedExceptions[1]->getType()); + $this->assertSame('testMessage2', $capturedExceptions[1]->getValue()); - return true; - })); + return true; + })); $client = new Client( $options, @@ -962,18 +984,18 @@ public function testBuildEventWithExceptionAndMechansim(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $capturedExceptions = $event->getExceptions(); + ->method('send') + ->with($this->callback(function (Event $event): bool { + $capturedExceptions = $event->getExceptions(); - $this->assertCount(1, $capturedExceptions); - $this->assertNotNull($capturedExceptions[0]->getStacktrace()); - $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false), $capturedExceptions[0]->getMechanism()); - $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); - $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); + $this->assertCount(1, $capturedExceptions); + $this->assertNotNull($capturedExceptions[0]->getStacktrace()); + $this->assertEquals(new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false), $capturedExceptions[0]->getMechanism()); + $this->assertSame(\Exception::class, $capturedExceptions[0]->getType()); + $this->assertSame('testMessage', $capturedExceptions[0]->getValue()); - return true; - })); + return true; + })); $client = new Client( $options, @@ -996,12 +1018,12 @@ public function testBuildWithErrorException(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $this->assertTrue(Severity::error()->isEqualTo($event->getLevel())); + ->method('send') + ->with($this->callback(function (Event $event): bool { + $this->assertTrue(Severity::error()->isEqualTo($event->getLevel())); - return true; - })); + return true; + })); $client = new Client( $options, @@ -1024,22 +1046,22 @@ public function testBuildWithStacktrace(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $stacktrace = $event->getStacktrace(); + ->method('send') + ->with($this->callback(function (Event $event): bool { + $stacktrace = $event->getStacktrace(); - $this->assertInstanceOf(Stacktrace::class, $stacktrace); + $this->assertInstanceOf(Stacktrace::class, $stacktrace); - /** @var Frame $lastFrame */ - $lastFrame = array_reverse($stacktrace->getFrames())[0]; + /** @var Frame $lastFrame */ + $lastFrame = array_reverse($stacktrace->getFrames())[0]; - $this->assertSame( - 'Client.php', - basename($lastFrame->getFile()) - ); + $this->assertSame( + 'Client.php', + basename($lastFrame->getFile()) + ); - return true; - })); + return true; + })); $client = new Client( $options, @@ -1059,22 +1081,22 @@ public function testBuildWithCustomStacktrace(): void /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); $transport->expects($this->once()) - ->method('send') - ->with($this->callback(function (Event $event): bool { - $stacktrace = $event->getStacktrace(); + ->method('send') + ->with($this->callback(function (Event $event): bool { + $stacktrace = $event->getStacktrace(); - $this->assertNotNull($stacktrace); + $this->assertNotNull($stacktrace); - /** @var Frame $lastFrame */ - $lastFrame = array_reverse($stacktrace->getFrames())[0]; + /** @var Frame $lastFrame */ + $lastFrame = array_reverse($stacktrace->getFrames())[0]; - $this->assertSame( - 'MyApp.php', - $lastFrame->getFile() - ); + $this->assertSame( + 'MyApp.php', + $lastFrame->getFile() + ); - return true; - })); + return true; + })); $client = new Client( $options, diff --git a/tests/Integration/IntegrationRegistryTest.php b/tests/Integration/IntegrationRegistryTest.php index 5b0059ab8..ace8ba958 100644 --- a/tests/Integration/IntegrationRegistryTest.php +++ b/tests/Integration/IntegrationRegistryTest.php @@ -31,20 +31,18 @@ public function testGetInstance(): void /** * @dataProvider setupIntegrationsDataProvider */ - public function testSetupIntegrations(Options $options, array $expectedDebugMessages, array $expectedIntegrations): void + public function testSetupIntegrations(Options $options, array $expectedIntegrations): void { $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->exactly(\count($expectedDebugMessages))) - ->method('debug') - ->withConsecutive(...array_map( - static function (string $debugMessage): array { - return [ - $debugMessage, - [], - ]; - }, - $expectedDebugMessages - )); + + if (\count($expectedIntegrations) > 0) { + $logger->expects($this->once()) + ->method('debug') + ->with(sprintf('The "%s" integration(s) have been installed.', implode(', ', array_keys($expectedIntegrations))), []); + } else { + $logger->expects($this->never()) + ->method('debug'); + } $this->assertEquals($expectedIntegrations, IntegrationRegistry::getInstance()->setupIntegrations($options, $logger)); } @@ -72,7 +70,6 @@ public function setupOnce(): void 'default_integrations' => false, ]), [], - [], ]; yield 'Default integrations and no user integrations' => [ @@ -80,16 +77,6 @@ public function setupOnce(): void 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, ]), - [ - 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', - 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', - 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', - ], [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), ErrorListenerIntegration::class => new ErrorListenerIntegration(), @@ -110,10 +97,6 @@ public function setupOnce(): void $integration2, ], ]), - [ - "The \"$integration1ClassName\" integration has been installed.", - "The \"$integration2ClassName\" integration has been installed.", - ], [ $integration1ClassName => $integration1, $integration2ClassName => $integration2, @@ -129,18 +112,6 @@ public function setupOnce(): void $integration2, ], ]), - [ - 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', - 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', - 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', - "The \"$integration1ClassName\" integration has been installed.", - "The \"$integration2ClassName\" integration has been installed.", - ], [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), ErrorListenerIntegration::class => new ErrorListenerIntegration(), @@ -164,17 +135,6 @@ public function setupOnce(): void $integration1, ], ]), - [ - 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', - 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', - 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', - "The \"$integration1ClassName\" integration has been installed.", - ], [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), ErrorListenerIntegration::class => new ErrorListenerIntegration(), @@ -196,16 +156,6 @@ public function setupOnce(): void new ModulesIntegration(), ], ]), - [ - 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', - 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', - 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', - ], [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), ErrorListenerIntegration::class => new ErrorListenerIntegration(), @@ -226,9 +176,6 @@ public function setupOnce(): void $integration1, ], ]), - [ - "The \"$integration1ClassName\" integration has been installed.", - ], [ $integration1ClassName => $integration1, ], @@ -242,7 +189,6 @@ public function setupOnce(): void }, ]), [], - [], ]; yield 'Default integrations and a callable as user integrations' => [ @@ -253,16 +199,6 @@ public function setupOnce(): void return $defaultIntegrations; }, ]), - [ - 'The "Sentry\\Integration\\ExceptionListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FatalErrorListenerIntegration" integration has been installed.', - 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', - 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', - 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', - ], [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), ErrorListenerIntegration::class => new ErrorListenerIntegration(), @@ -283,13 +219,6 @@ public function setupOnce(): void return $defaultIntegrations; }, ]), - [ - 'The "Sentry\\Integration\\RequestIntegration" integration has been installed.', - 'The "Sentry\\Integration\\TransactionIntegration" integration has been installed.', - 'The "Sentry\\Integration\\FrameContextifierIntegration" integration has been installed.', - 'The "Sentry\\Integration\\EnvironmentIntegration" integration has been installed.', - 'The "Sentry\\Integration\\ModulesIntegration" integration has been installed.', - ], [ RequestIntegration::class => new RequestIntegration(), TransactionIntegration::class => new TransactionIntegration(), diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index dddf99a25..93790e632 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -4,6 +4,7 @@ namespace Sentry\Tests\Transport; +use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; @@ -18,6 +19,11 @@ final class HttpTransportTest extends TestCase { + /** + * @var LoggerInterface&MockObject + */ + private $logger; + /** * @var MockObject&HttpAsyncClientInterface */ @@ -30,6 +36,7 @@ final class HttpTransportTest extends TestCase protected function setUp(): void { + $this->logger = $this->createMock(LoggerInterface::class); $this->httpClient = $this->createMock(HttpClientInterface::class); $this->payloadSerializer = $this->createMock(PayloadSerializerInterface::class); } @@ -37,7 +44,7 @@ protected function setUp(): void /** * @dataProvider sendDataProvider */ - public function testSend(int $httpStatusCode, ResultStatus $expectedResultStatus, bool $expectEventReturned): void + public function testSend(Response $response, ResultStatus $expectedResultStatus, bool $expectEventReturned, array $expectedLogMessages): void { $event = Event::createEvent(); @@ -48,16 +55,30 @@ public function testSend(int $httpStatusCode, ResultStatus $expectedResultStatus $this->httpClient->expects($this->once()) ->method('sendRequest') - ->willReturn(new Response($httpStatusCode, [], '')); + ->willReturn($response); + + foreach ($expectedLogMessages as $level => $messages) { + $this->logger->expects($this->exactly(\count($messages))) + ->method($level) + ->with($this->logicalOr( + ...array_map(function (string $message) { + return new StringMatchesFormatDescription($message); + }, $messages) + )); + } $transport = new HttpTransport( new Options([ 'dsn' => 'http://public@example.com/1', ]), $this->httpClient, - $this->payloadSerializer + $this->payloadSerializer, + $this->logger ); + // We need to mock the time to ensure that the rate limiter works as expected and we can easily assert the log messages + ClockMock::withClockMock(1644105600); + $result = $transport->send($event); $this->assertSame($expectedResultStatus, $result->getStatus()); @@ -69,27 +90,56 @@ public function testSend(int $httpStatusCode, ResultStatus $expectedResultStatus public static function sendDataProvider(): iterable { yield [ - 200, + new Response(200, [], ''), ResultStatus::success(), true, + [ + 'info' => [ + 'Sending event [%s] to %s [project:%s].', + 'Sent event [%s] to %s [project:%s]. Result: "success" (status: 200).', + ], + ], ]; yield [ - 401, + new Response(401, [], ''), ResultStatus::invalid(), false, + [ + 'info' => [ + 'Sending event [%s] to %s [project:%s].', + 'Sent event [%s] to %s [project:%s]. Result: "invalid" (status: 401).', + ], + ], ]; + ClockMock::withClockMock(1644105600); + yield [ - 429, + new Response(429, ['Retry-After' => ['60']], ''), ResultStatus::rateLimit(), false, + [ + 'info' => [ + 'Sending event [%s] to %s [project:%s].', + 'Sent event [%s] to %s [project:%s]. Result: "rate_limit" (status: 429).', + ], + 'warning' => [ + 'Rate limited exceeded for requests of type "event", backing off until "2022-02-06T00:01:00+00:00".', + ], + ], ]; yield [ - 500, + new Response(500, [], ''), ResultStatus::failed(), false, + [ + 'info' => [ + 'Sending event [%s] to %s [project:%s].', + 'Sent event [%s] to %s [project:%s]. Result: "failed" (status: 500).', + ], + ], ]; } @@ -102,7 +152,10 @@ public function testSendFailsDueToHttpClientException(): void $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) ->method('error') - ->with('Failed to send the event to Sentry. Reason: "foo".', ['exception' => $exception, 'event' => $event]); + ->with( + new StringMatchesFormatDescription('Failed to send event [%s] to %s [project:%s]. Reason: "foo".'), + ['exception' => $exception, 'event' => $event] + ); $this->payloadSerializer->expects($this->once()) ->method('serialize') @@ -135,7 +188,10 @@ public function testSendFailsDueToCulrError(): void $logger = $this->createMock(LoggerInterface::class); $logger->expects($this->once()) ->method('error') - ->with('Failed to send the event to Sentry. Reason: "cURL Error (6) Could not resolve host: example.com".', ['event' => $event]); + ->with( + new StringMatchesFormatDescription('Failed to send event [%s] to %s [project:%s]. Reason: "cURL Error (6) Could not resolve host: example.com".'), + ['event' => $event] + ); $this->payloadSerializer->expects($this->once()) ->method('serialize') diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index 14315d401..fe6963503 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -4,10 +4,7 @@ namespace Sentry\Tests\Transport; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; -use Sentry\Event; use Sentry\EventType; use Sentry\HttpClient\Response; use Sentry\Transport\RateLimiter; @@ -18,11 +15,6 @@ */ final class RateLimiterTest extends TestCase { - /** - * @var LoggerInterface&MockObject - */ - private $logger; - /** * @var RateLimiter */ @@ -30,62 +22,62 @@ final class RateLimiterTest extends TestCase protected function setUp(): void { - $this->logger = $this->createMock(LoggerInterface::class); - $this->rateLimiter = new RateLimiter($this->logger); + $this->rateLimiter = new RateLimiter(); } /** * @dataProvider handleResponseDataProvider */ - public function testHandleResponse(Event $event, Response $response, int $responseStatusCode): void + public function testHandleResponse(Response $response, bool $shouldBeHandled, array $eventTypesLimited = []): void { ClockMock::withClockMock(1644105600); - $this->logger->expects($response->isSuccess() ? $this->never() : $this->once()) - ->method('warning') - ->with('Rate limited exceeded for requests of type "event", backing off until "2022-02-06T00:01:00+00:00".', ['event' => $event]); + $rateLimiterResponse = $this->rateLimiter->handleResponse($response); - $rateLimiterResponse = $this->rateLimiter->handleResponse($event, $response); - - $this->assertSame($responseStatusCode, $rateLimiterResponse->getStatusCode()); + $this->assertSame($shouldBeHandled, $rateLimiterResponse); + $this->assertEventTypesAreRateLimited($eventTypesLimited); } public static function handleResponseDataProvider(): \Generator { yield 'Rate limits headers missing' => [ - Event::createEvent(), new Response(200, [], ''), - 200, + false, ]; yield 'Back-off using X-Sentry-Rate-Limits header with single category' => [ - Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => ['60:error:org']], ''), - 429, + true, + [ + EventType::event(), + ], ]; yield 'Back-off using X-Sentry-Rate-Limits header with multiple categories' => [ - Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => ['60:error;transaction:org']], ''), - 429, + true, + [ + EventType::event(), + EventType::transaction(), + ], ]; yield 'Back-off using X-Sentry-Rate-Limits header with missing categories should lock them all' => [ - Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => ['60::org']], ''), - 429, + true, + EventType::cases(), ]; yield 'Back-off using Retry-After header with number-based value' => [ - Event::createEvent(), new Response(429, ['Retry-After' => ['60']], ''), - 429, + true, + EventType::cases(), ]; yield 'Back-off using Retry-After header with date-based value' => [ - Event::createEvent(), new Response(429, ['Retry-After' => ['Sun, 02 February 2022 00:01:00 GMT']], ''), - 429, + true, + EventType::cases(), ]; } @@ -94,34 +86,39 @@ public function testIsRateLimited(): void // Events should not be rate-limited at all ClockMock::withClockMock(1644105600); - $this->assertFalse($this->rateLimiter->isRateLimited(EventType::event())); - $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); + $this->assertEventTypesAreRateLimited([]); - // Events should be rate-limited for 60 seconds, but transactions should - // still be allowed to be sent - $this->rateLimiter->handleResponse(Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => ['60:error:org']], '')); + // Events should be rate-limited for 60 seconds, but transactions should still be allowed to be sent + $this->rateLimiter->handleResponse(new Response(429, ['X-Sentry-Rate-Limits' => ['60:error:org']], '')); - $this->assertTrue($this->rateLimiter->isRateLimited(EventType::event())); - $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); + $this->assertEventTypesAreRateLimited([EventType::event()]); // Events should not be rate-limited anymore once the deadline expired ClockMock::withClockMock(1644105660); - $this->assertFalse($this->rateLimiter->isRateLimited(EventType::event())); - $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); + $this->assertEventTypesAreRateLimited([]); - // Both events and transactions should be rate-limited if all categories - // are - $this->rateLimiter->handleResponse(Event::createTransaction(), new Response(429, ['X-Sentry-Rate-Limits' => ['60:all:org']], '')); + // Both events and transactions should be rate-limited if all categories are + $this->rateLimiter->handleResponse(new Response(429, ['X-Sentry-Rate-Limits' => ['60:all:org']], '')); - $this->assertTrue($this->rateLimiter->isRateLimited(EventType::event())); - $this->assertTrue($this->rateLimiter->isRateLimited(EventType::transaction())); + $this->assertEventTypesAreRateLimited(EventType::cases()); - // Both events and transactions should not be rate-limited anymore once - // the deadline expired + // Both events and transactions should not be rate-limited anymore once the deadline expired ClockMock::withClockMock(1644105720); - $this->assertFalse($this->rateLimiter->isRateLimited(EventType::event())); - $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); + $this->assertEventTypesAreRateLimited([]); + } + + private function assertEventTypesAreRateLimited(array $eventTypesLimited): void + { + foreach ($eventTypesLimited as $eventType) { + $this->assertTrue($this->rateLimiter->isRateLimited($eventType)); + } + + $eventTypesNotLimited = array_diff(EventType::cases(), $eventTypesLimited); + + foreach ($eventTypesNotLimited as $eventType) { + $this->assertFalse($this->rateLimiter->isRateLimited($eventType)); + } } } From 36345f698e0f2afc4dad6a80c8abd2f6c079aa6a Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 2 Apr 2024 11:03:18 +0200 Subject: [PATCH 1001/1161] Depreacte `SpanStatus::resourceExchausted()` (#1725) --- src/Tracing/SpanStatus.php | 15 +++++++++++++-- tests/Monolog/BreadcrumbHandlerTest.php | 2 +- tests/Tracing/SpanStatusTest.php | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Tracing/SpanStatus.php b/src/Tracing/SpanStatus.php index 7cad0f216..33c384552 100644 --- a/src/Tracing/SpanStatus.php +++ b/src/Tracing/SpanStatus.php @@ -76,11 +76,22 @@ public static function failedPrecondition(): self * Gets an instance of this enum representing the fact that the server returned * 429 Too Many Requests. */ - public static function resourceExchausted(): self + public static function resourceExhausted(): self { return self::getInstance('resource_exhausted'); } + /** + * Gets an instance of this enum representing the fact that the server returned + * 429 Too Many Requests. + * + * @deprecated since version 4.7. To be removed in version 5.0. Use SpanStatus::resourceExhausted() instead. + */ + public static function resourceExchausted(): self + { + return self::resourceExhausted(); + } + /** * Gets an instance of this enum representing the fact that the server returned * 501 Not Implemented. @@ -163,7 +174,7 @@ public static function createFromHttpStatusCode(int $statusCode): self case $statusCode === 413: return self::failedPrecondition(); case $statusCode === 429: - return self::resourceExchausted(); + return self::resourceExhausted(); case $statusCode === 501: return self::unimplemented(); case $statusCode === 503: diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php index 8cd6a4d2f..ffef9e192 100644 --- a/tests/Monolog/BreadcrumbHandlerTest.php +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -76,7 +76,7 @@ public static function handleDataProvider(): iterable ]; yield 'with context' => [ - RecordFactory::create('foo bar', Logger::DEBUG, 'channel.foo', ['context' => ['foo' => 'bar']], []), + RecordFactory::create('foo bar', Logger::DEBUG, 'channel.foo', ['context' => ['foo' => 'bar']], []), $defaultBreadcrumb->withMetadata('context', ['foo' => 'bar']), ]; diff --git a/tests/Tracing/SpanStatusTest.php b/tests/Tracing/SpanStatusTest.php index 7e4969848..f9c2af443 100644 --- a/tests/Tracing/SpanStatusTest.php +++ b/tests/Tracing/SpanStatusTest.php @@ -45,7 +45,7 @@ public static function toStringDataProvider(): iterable ]; yield [ - SpanStatus::resourceExchausted(), + SpanStatus::resourceExhausted(), 'resource_exhausted', ]; From 3c50e8ba25a338ec64d0a97ae1f602453a5aa16f Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 2 Apr 2024 13:12:26 +0200 Subject: [PATCH 1002/1161] Handle `metric_bucket` rate limits (#1726) --- src/Transport/RateLimiter.php | 4 ++++ tests/Transport/RateLimiterTest.php | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index 4dc628ea4..330ba0dcd 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -76,6 +76,10 @@ public function getDisabledUntil(EventType $eventType): int $category = 'error'; } + if ($eventType === EventType::metrics()) { + $category = 'metric_bucket'; + } + return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); } diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index fe6963503..f813e9e16 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -54,11 +54,28 @@ public static function handleResponseDataProvider(): \Generator ]; yield 'Back-off using X-Sentry-Rate-Limits header with multiple categories' => [ - new Response(429, ['X-Sentry-Rate-Limits' => ['60:error;transaction:org']], ''), + new Response(429, ['X-Sentry-Rate-Limits' => ['60:error;transaction;metric_bucket:org']], ''), true, [ EventType::event(), EventType::transaction(), + EventType::metrics(), + ], + ]; + + yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category' => [ + new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded:custom']], ''), + true, + [ + EventType::metrics(), + ], + ]; + + yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category without reason code' => [ + new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization::custom']], ''), + true, + [ + EventType::metrics(), ], ]; From b59538581dc6fff19dd72b4a65bc3fb502b59ee1 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 5 Apr 2024 00:32:28 +0200 Subject: [PATCH 1003/1161] Handle namespaces for `metric_bucket` rate limits (#1728) --- src/Serializer/EnvelopItems/MetricsItem.php | 6 ++ src/Transport/HttpTransport.php | 12 +--- src/Transport/RateLimiter.php | 78 ++++++++++++++++++--- tests/Transport/HttpTransportTest.php | 4 +- tests/Transport/RateLimiterTest.php | 32 +++++++-- 5 files changed, 103 insertions(+), 29 deletions(-) diff --git a/src/Serializer/EnvelopItems/MetricsItem.php b/src/Serializer/EnvelopItems/MetricsItem.php index 77ae05757..92776c989 100644 --- a/src/Serializer/EnvelopItems/MetricsItem.php +++ b/src/Serializer/EnvelopItems/MetricsItem.php @@ -37,6 +37,12 @@ public static function toEnvelopeItem(Event $event): string $metricMetaPayload = []; foreach ($metrics as $metric) { + /** + * In case of us adding support for emitting metrics from other namespaces, + * we have to alter the RateLimiter::class to properly handle these + * namespaces. + */ + // key - my.metric $line = preg_replace(self::KEY_PATTERN, '_', $metric->getKey()); diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 74982a369..6fab31833 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -59,7 +59,7 @@ public function __construct( $this->httpClient = $httpClient; $this->payloadSerializer = $payloadSerializer; $this->logger = $logger ?? new NullLogger(); - $this->rateLimiter = new RateLimiter(); + $this->rateLimiter = new RateLimiter($this->logger); } /** @@ -123,15 +123,7 @@ public function send(Event $event): Result return new Result(ResultStatus::unknown()); } - if ($this->rateLimiter->handleResponse($response)) { - $eventType = $event->getType(); - $disabledUntil = $this->rateLimiter->getDisabledUntil($eventType); - - $this->logger->warning( - sprintf('Rate limited exceeded for requests of type "%s", backing off until "%s".', (string) $eventType, gmdate(\DATE_ATOM, $disabledUntil)), - ['event' => $event] - ); - } + $this->rateLimiter->handleResponse($response); $resultStatus = ResultStatus::createFromHttpStatusCode($response->getStatusCode()); diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index 330ba0dcd..dbc8bcf22 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -4,11 +4,23 @@ namespace Sentry\Transport; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Sentry\EventType; use Sentry\HttpClient\Response; final class RateLimiter { + /** + * @var string + */ + private const DATA_CATEGORY_ERROR = 'error'; + + /** + * @var string + */ + private const DATA_CATEGORY_METRIC_BUCKET = 'metric_bucket'; + /** * The name of the header to look at to know the rate limits for the events * categories supported by the server. @@ -24,7 +36,7 @@ final class RateLimiter /** * The number of seconds after which an HTTP request can be retried. */ - private const DEFAULT_RETRY_AFTER_DELAY_SECONDS = 60; + private const DEFAULT_RETRY_AFTER_SECONDS = 60; /** * @var array The map of time instants for each event category after @@ -32,28 +44,72 @@ final class RateLimiter */ private $rateLimits = []; + /** + * @var LoggerInterface A PSR-3 logger + */ + private $logger; + + public function __construct(?LoggerInterface $logger = null) + { + $this->logger = $logger ?? new NullLogger(); + } + public function handleResponse(Response $response): bool { $now = time(); if ($response->hasHeader(self::RATE_LIMITS_HEADER)) { foreach (explode(',', $response->getHeaderLine(self::RATE_LIMITS_HEADER)) as $limit) { - $parameters = explode(':', $limit, 3); - $parameters = array_splice($parameters, 0, 2); - $delay = ctype_digit($parameters[0]) ? (int) $parameters[0] : self::DEFAULT_RETRY_AFTER_DELAY_SECONDS; + /** + * $parameters[0] - retry_after + * $parameters[1] - categories + * $parameters[2] - scope (not used) + * $parameters[3] - reason_code (not used) + * $parameters[4] - namespaces (only returned if categories contains "metric_bucket"). + */ + $parameters = explode(':', $limit, 5); + + $retryAfter = $now + (ctype_digit($parameters[0]) ? (int) $parameters[0] : self::DEFAULT_RETRY_AFTER_SECONDS); foreach (explode(';', $parameters[1]) as $category) { - $this->rateLimits[$category ?: 'all'] = $now + $delay; + switch ($category) { + case self::DATA_CATEGORY_METRIC_BUCKET: + $namespaces = []; + if (isset($parameters[4])) { + $namespaces = explode(';', $parameters[4]); + } + + /** + * As we do not support setting any metric namespaces in the SDK, + * checking for the custom namespace suffices. + * In case the namespace was ommited in the response header, + * we'll also back off. + */ + if ($namespaces === [] || \in_array('custom', $namespaces)) { + $this->rateLimits[self::DATA_CATEGORY_METRIC_BUCKET] = $retryAfter; + } + break; + default: + $this->rateLimits[$category ?: 'all'] = $retryAfter; + } + + $this->logger->warning( + sprintf('Rate limited exceeded for category "%s", backing off until "%s".', $category, gmdate(\DATE_ATOM, $retryAfter)) + ); } } - return true; + return $this->rateLimits !== []; } if ($response->hasHeader(self::RETRY_AFTER_HEADER)) { - $delay = $this->parseRetryAfterHeader($now, $response->getHeaderLine(self::RETRY_AFTER_HEADER)); + $retryAfter = $now + $this->parseRetryAfterHeader($now, $response->getHeaderLine(self::RETRY_AFTER_HEADER)); + + $this->rateLimits['all'] = $retryAfter; - $this->rateLimits['all'] = $now + $delay; + $this->logger->warning( + sprintf('Rate limited exceeded for all categories, backing off until "%s".', gmdate(\DATE_ATOM, $retryAfter)) + ); return true; } @@ -73,11 +129,11 @@ public function getDisabledUntil(EventType $eventType): int $category = (string) $eventType; if ($eventType === EventType::event()) { - $category = 'error'; + $category = self::DATA_CATEGORY_ERROR; } if ($eventType === EventType::metrics()) { - $category = 'metric_bucket'; + $category = self::DATA_CATEGORY_METRIC_BUCKET; } return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); @@ -95,6 +151,6 @@ private function parseRetryAfterHeader(int $currentTime, string $header): int return $headerDate->getTimestamp() - $currentTime; } - return self::DEFAULT_RETRY_AFTER_DELAY_SECONDS; + return self::DEFAULT_RETRY_AFTER_SECONDS; } } diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 93790e632..148c3bbb6 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -125,7 +125,7 @@ public static function sendDataProvider(): iterable 'Sent event [%s] to %s [project:%s]. Result: "rate_limit" (status: 429).', ], 'warning' => [ - 'Rate limited exceeded for requests of type "event", backing off until "2022-02-06T00:01:00+00:00".', + 'Rate limited exceeded for all categories, backing off until "2022-02-06T00:01:00+00:00".', ], ], ]; @@ -230,7 +230,7 @@ public function testSendFailsDueToExceedingRateLimits(): void $logger->expects($this->exactly(2)) ->method('warning') ->withConsecutive( - ['Rate limited exceeded for requests of type "event", backing off until "2022-02-06T00:01:00+00:00".', ['event' => $event]], + ['Rate limited exceeded for all categories, backing off until "2022-02-06T00:01:00+00:00".'], ['Rate limit exceeded for sending requests of type "event".', ['event' => $event]] ); diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index f813e9e16..be6c4e24c 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -32,9 +32,7 @@ public function testHandleResponse(Response $response, bool $shouldBeHandled, ar { ClockMock::withClockMock(1644105600); - $rateLimiterResponse = $this->rateLimiter->handleResponse($response); - - $this->assertSame($shouldBeHandled, $rateLimiterResponse); + $this->rateLimiter->handleResponse($response); $this->assertEventTypesAreRateLimited($eventTypesLimited); } @@ -63,6 +61,12 @@ public static function handleResponseDataProvider(): \Generator ], ]; + yield 'Back-off using X-Sentry-Rate-Limits header with missing categories should lock them all' => [ + new Response(429, ['X-Sentry-Rate-Limits' => ['60::org']], ''), + true, + EventType::cases(), + ]; + yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category' => [ new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded:custom']], ''), true, @@ -71,6 +75,14 @@ public static function handleResponseDataProvider(): \Generator ], ]; + yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category, namespace custom and foo' => [ + new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded:custom;foo']], ''), + true, + [ + EventType::metrics(), + ], + ]; + yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category without reason code' => [ new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization::custom']], ''), true, @@ -79,10 +91,18 @@ public static function handleResponseDataProvider(): \Generator ], ]; - yield 'Back-off using X-Sentry-Rate-Limits header with missing categories should lock them all' => [ - new Response(429, ['X-Sentry-Rate-Limits' => ['60::org']], ''), + yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category without metric namespaces' => [ + new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded']], ''), true, - EventType::cases(), + [ + EventType::metrics(), + ], + ]; + + yield 'Do not back-off using X-Sentry-Rate-Limits header with metric_bucket category, namespace foo' => [ + new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded:foo']], ''), + false, + [], ]; yield 'Back-off using Retry-After header with number-based value' => [ From 8ee5a890ecacaaecb50e5130060f10a5c14140de Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 10 Apr 2024 13:41:15 +0200 Subject: [PATCH 1004/1161] Fix deprecation notice when trying to serialize a callable (#1732) --- src/Serializer/AbstractSerializer.php | 2 +- tests/phpt/test_callable_serialization.phpt | 66 +++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/phpt/test_callable_serialization.phpt diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index ac6741a52..ec1389ac8 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -261,7 +261,7 @@ protected function serializeValue($value) } try { - if (\is_callable($value)) { + if (@\is_callable($value)) { return $this->serializeCallable($value); } } catch (\Throwable $exception) { diff --git a/tests/phpt/test_callable_serialization.phpt b/tests/phpt/test_callable_serialization.phpt new file mode 100644 index 000000000..416628c58 --- /dev/null +++ b/tests/phpt/test_callable_serialization.phpt @@ -0,0 +1,66 @@ +--TEST-- +Test that capturing an exception with a callable looking argument does not trigger an deprecation "Deprecated: Use of "static" in callables is deprecated" +--INI-- +error_reporting=E_ALL +zend.exception_ignore_args=0 +--FILE-- + 'http://public@example.com/sentry/1', +]; + +$client = ClientBuilder::create($options) + ->setTransport($transport) + ->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +class Foo { + function __construct(string $bar) { + SentrySdk::getCurrentHub()->captureException(new Exception('doh!')); + } +} + +new Foo('static::sort'); + +echo 'Triggered capture exception' . PHP_EOL; +?> +--EXPECT-- +Transport called +Triggered capture exception From 1f26c26696d90878117256fdbdb4768e2fd27709 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 10 Apr 2024 14:32:51 +0200 Subject: [PATCH 1005/1161] Update metric normalization (#1729) Co-authored-by: Alex Bouma --- src/Serializer/EnvelopItems/MetricsItem.php | 119 ++++++++++++------ .../EnvelopeItems/MetricsItemTest.php | 30 +++++ tests/Serializer/PayloadSerializerTest.php | 14 +-- 3 files changed, 117 insertions(+), 46 deletions(-) create mode 100644 tests/Serializer/EnvelopeItems/MetricsItemTest.php diff --git a/src/Serializer/EnvelopItems/MetricsItem.php b/src/Serializer/EnvelopItems/MetricsItem.php index 92776c989..7109f09b7 100644 --- a/src/Serializer/EnvelopItems/MetricsItem.php +++ b/src/Serializer/EnvelopItems/MetricsItem.php @@ -6,6 +6,7 @@ use Sentry\Event; use Sentry\Metrics\MetricsUnit; +use Sentry\Metrics\Types\AbstractType; use Sentry\Serializer\Traits\StacktraceFrameSeralizerTrait; use Sentry\Util\JSON; @@ -19,12 +20,17 @@ class MetricsItem implements EnvelopeItemInterface /** * @var string */ - private const KEY_PATTERN = '/[^a-zA-Z0-9_\/.-]+/i'; + private const KEY_PATTERN = '/[^\w\-.]+/i'; /** * @var string */ - private const VALUE_PATTERN = '/[^\w\d\s_:\/@\.{}\[\]$-]+/i'; + private const UNIT_PATTERN = '/[^\w]+/i'; + + /** + * @var string + */ + private const TAG_KEY_PATTERN = '/[^\w\-.\/]+/i'; public static function toEnvelopeItem(Event $event): string { @@ -37,43 +43,7 @@ public static function toEnvelopeItem(Event $event): string $metricMetaPayload = []; foreach ($metrics as $metric) { - /** - * In case of us adding support for emitting metrics from other namespaces, - * we have to alter the RateLimiter::class to properly handle these - * namespaces. - */ - - // key - my.metric - $line = preg_replace(self::KEY_PATTERN, '_', $metric->getKey()); - - if ($metric->getUnit() !== MetricsUnit::none()) { - // unit - @second - $line .= '@' . $metric->getunit(); - } - - foreach ($metric->serialize() as $value) { - // value - 2:3:4... - $line .= ':' . $value; - } - - // type - |c|, |d|, ... - $line .= '|' . $metric->getType() . '|'; - - $tags = []; - foreach ($metric->getTags() as $key => $value) { - $tags[] = preg_replace(self::KEY_PATTERN, '_', $key) . - ':' . preg_replace(self::VALUE_PATTERN, '', $value); - } - - if (!empty($tags)) { - // tags - #key:value,key:value... - $line .= '#' . implode(',', $tags) . '|'; - } - - // timestamp - T123456789 - $line .= 'T' . $metric->getTimestamp(); - - $statsdPayload[] = $line; + $statsdPayload[] = self::seralizeMetric($metric); if ($metric->hasCodeLocation()) { $metricMetaPayload[$metric->getMri()][] = array_merge( @@ -116,4 +86,75 @@ public static function toEnvelopeItem(Event $event): string $statsdPayload ); } + + public static function seralizeMetric(AbstractType $metric): string + { + /** + * In case of us adding support for emitting metrics from other namespaces, + * we have to alter the RateLimiter::class to properly handle these + * namespaces. + */ + + // key - my.metric + $line = preg_replace(self::KEY_PATTERN, '_', $metric->getKey()); + + if ($metric->getUnit() !== MetricsUnit::none()) { + // unit - @second + $line .= '@' . preg_replace(self::UNIT_PATTERN, '', (string) $metric->getUnit()); + } + + foreach ($metric->serialize() as $value) { + // value - 2:3:4... + $line .= ':' . $value; + } + + // type - |c|, |d|, ... + $line .= '|' . $metric->getType() . '|'; + + $tags = []; + foreach ($metric->getTags() as $key => $value) { + $tags[] = preg_replace(self::TAG_KEY_PATTERN, '', $key) . + ':' . self::escapeTagValues($value); + } + + if (!empty($tags)) { + // tags - #key:value,key:value... + $line .= '#' . implode(',', $tags) . '|'; + } + + // timestamp - T123456789 + $line .= 'T' . $metric->getTimestamp(); + + return $line; + } + + public static function escapeTagValues(string $tagValue): string + { + $result = ''; + + for ($i = 0; $i < mb_strlen($tagValue); ++$i) { + $character = mb_substr($tagValue, $i, 1); + $result .= str_replace( + [ + "\n", + "\r", + "\t", + '\\', + '|', + ',', + ], + [ + '\n', + '\r', + '\t', + '\\\\', + '\u{7c}', + '\u{2c}', + ], + $character + ); + } + + return $result; + } } diff --git a/tests/Serializer/EnvelopeItems/MetricsItemTest.php b/tests/Serializer/EnvelopeItems/MetricsItemTest.php new file mode 100644 index 000000000..399fcf40b --- /dev/null +++ b/tests/Serializer/EnvelopeItems/MetricsItemTest.php @@ -0,0 +1,30 @@ +assertSame('plain', MetricsItem::escapeTagValues('plain')); + $this->assertSame('plain text', MetricsItem::escapeTagValues('plain text')); + $this->assertSame('plain%text', MetricsItem::escapeTagValues('plain%text')); + + // Escape sequences + $this->assertSame('plain \\\\\\\\ text', MetricsItem::escapeTagValues('plain \\\\ text')); + $this->assertSame('plain\\u{2c}text', MetricsItem::escapeTagValues('plain,text')); + $this->assertSame('plain\\u{7c}text', MetricsItem::escapeTagValues('plain|text')); + $this->assertSame('plain 😅', MetricsItem::escapeTagValues('plain 😅')); + + // Escapable control characters + $this->assertSame('plain\\\\ntext', MetricsItem::escapeTagValues("plain\ntext")); + $this->assertSame('plain\\\\rtext', MetricsItem::escapeTagValues("plain\rtext")); + $this->assertSame('plain\\\\ttext', MetricsItem::escapeTagValues("plain\ttext")); + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 2ebd5e823..0e3cd8a4f 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -415,9 +415,9 @@ public static function serializeAsEnvelopeDataProvider(): iterable ]; $counter = new CounterType('counter', 1.0, MetricsUnit::second(), ['foo' => 'bar', 'route' => 'GET /foo'], 1597790835); - $distribution = new DistributionType('distribution', 1.0, MetricsUnit::second(), ['$foo$' => '%bar%'], 1597790835); - $gauge = new GaugeType('gauge', 1.0, MetricsUnit::second(), ['föö' => 'bär'], 1597790835); - $set = new SetType('set', 1.0, MetricsUnit::second(), ['%{key}' => '$value$'], 1597790835); + $distribution = new DistributionType('distribution', 1.0, MetricsUnit::second(), ['foo' => 'bar'], 1597790835); + $gauge = new GaugeType('gauge', 1.0, MetricsUnit::second(), ['foo' => 'bar', 'bar' => 'baz'], 1597790835); + $set = new SetType('set', 1.0, MetricsUnit::second(), ['key' => 'value'], 1597790835); $noTags = new CounterType('no_tags', 1.0, MetricsUnit::second(), [], 1597790835); $event = Event::createMetrics(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); @@ -433,11 +433,11 @@ public static function serializeAsEnvelopeDataProvider(): iterable $event, << Date: Wed, 10 Apr 2024 15:20:51 +0200 Subject: [PATCH 1006/1161] Prepare 4.7.0 (#1733) Co-authored-by: Alex Bouma --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c22f1ff15..9f10b9a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # CHANGELOG +## 4.7.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.7.0. + +### Features + +- Improve debugging experience by emitting more logs from the SDK [(#1705)](https://github.com/getsentry/sentry-php/pull/1705) +- Handle `metric_bucket` rate limits [(#1726)](https://github.com/getsentry/sentry-php/pull/1726) & [(#1728)](https://github.com/getsentry/sentry-php/pull/1728) + +### Bug Fixes + +- Fix deprecation notice when trying to serialize a callable [(#1732)](https://github.com/getsentry/sentry-php/pull/1732) + +### Misc + +- Deprecated `SpanStatus::resourceExchausted()`. Use `SpanStatus::resourceExhausted()` instead [(#1725)](https://github.com/getsentry/sentry-php/pull/1725) +- Update metric normalization [(#1729)](https://github.com/getsentry/sentry-php/pull/1729) + ## 4.6.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.6.1. From d6769b2a5e6bf19ed3bbfbf52328ceaf8e6fcb1f Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 10 Apr 2024 13:22:13 +0000 Subject: [PATCH 1007/1161] release: 4.7.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 406721f37..b2dd5226b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.6.1'; + public const SDK_VERSION = '4.7.0'; /** * @var Options The client options From 9dcd4ca45ff53abbcfbfe9b054141516b1e6a568 Mon Sep 17 00:00:00 2001 From: PrinsFrank <25006490+PrinsFrank@users.noreply.github.com> Date: Mon, 6 May 2024 16:09:51 +0200 Subject: [PATCH 1008/1161] Add array shape for init function (#1738) --- src/functions.php | 50 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/functions.php b/src/functions.php index 293e6884e..9165a8066 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,6 +4,9 @@ namespace Sentry; +use Psr\Log\LoggerInterface; +use Sentry\HttpClient\HttpClientInterface; +use Sentry\Integration\IntegrationInterface; use Sentry\Metrics\Metrics; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; @@ -14,7 +17,52 @@ /** * Creates a new Client and Hub which will be set as current. * - * @param array $options The client options + * @param array{ + * attach_metric_code_locations?: bool, + * attach_stacktrace?: bool, + * before_breadcrumb?: callable, + * before_send?: callable, + * before_send_check_in?: callable, + * before_send_transaction?: callable, + * capture_silenced_errors?: bool, + * context_lines?: int|null, + * default_integrations?: bool, + * dsn?: string|bool|null|Dsn, + * enable_tracing?: bool|null, + * environment?: string|null, + * error_types?: int|null, + * http_client?: HttpClientInterface|null, + * http_compression?: bool, + * http_connect_timeout?: int|float, + * http_proxy?: string|null, + * http_proxy_authentication?: string|null, + * http_ssl_verify_peer?: bool, + * http_timeout?: int|float, + * ignore_exceptions?: array, + * ignore_transactions?: array, + * in_app_exclude?: array, + * in_app_include?: array, + * integrations?: IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[], + * logger?: LoggerInterface|null, + * max_breadcrumbs?: int, + * max_request_body_size?: "none"|"never"|"small"|"medium"|"always", + * max_value_length?: int, + * prefixes?: array, + * profiler_sample_rate?: int|float|null, + * release?: string|null, + * sample_rate?: float|int, + * send_attempts?: int, + * send_default_pii?: bool, + * server_name?: string, + * server_name?: string, + * spotlight?: bool, + * spotlight_url?: string, + * tags?: array, + * trace_propagation_targets?: array|null, + * traces_sample_rate?: float|int|null, + * traces_sampler?: callable|null, + * transport?: callable, + * } $options The client options */ function init(array $options = []): void { From d12ed37fcfaddaa041f221fe165bd0d435dbef0f Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 10 May 2024 16:26:41 +0200 Subject: [PATCH 1009/1161] Fix missing data on HTTP spans (#1735) Co-authored-by: Michi Hoffmann --- src/Tracing/GuzzleTracingMiddleware.php | 45 +++++++++---------- tests/Tracing/GuzzleTracingMiddlewareTest.php | 29 +++++++++++- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 3d5232db1..08e640359 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -48,14 +48,22 @@ public static function trace(?HubInterface $hub = null): \Closure 'path' => $request->getUri()->getPath(), ]); + $spanAndBreadcrumbData = [ + 'http.request.method' => $request->getMethod(), + 'http.request.body.size' => $request->getBody()->getSize(), + ]; + + if ($request->getUri()->getQuery() !== '') { + $spanAndBreadcrumbData['http.query'] = $request->getUri()->getQuery(); + } + if ($request->getUri()->getFragment() !== '') { + $spanAndBreadcrumbData['http.fragment'] = $request->getUri()->getFragment(); + } + $spanContext = new SpanContext(); $spanContext->setOp('http.client'); - $spanContext->setDescription($request->getMethod() . ' ' . (string) $partialUri); - $spanContext->setData([ - 'http.request.method' => $request->getMethod(), - 'http.query' => $request->getUri()->getQuery(), - 'http.fragment' => $request->getUri()->getFragment(), - ]); + $spanContext->setDescription($request->getMethod() . ' ' . $partialUri); + $spanContext->setData($spanAndBreadcrumbData); $childSpan = $span->startChild($spanContext); @@ -66,7 +74,7 @@ public static function trace(?HubInterface $hub = null): \Closure ->withHeader('baggage', $childSpan->toBaggage()); } - $handlerPromiseCallback = static function ($responseOrException) use ($hub, $request, $childSpan, $partialUri) { + $handlerPromiseCallback = static function ($responseOrException) use ($hub, $spanAndBreadcrumbData, $childSpan, $partialUri) { // We finish the span (which means setting the span end timestamp) first to ensure the measured time // the span spans is as close to only the HTTP request time and do the data collection afterwards $childSpan->finish(); @@ -80,23 +88,12 @@ public static function trace(?HubInterface $hub = null): \Closure $response = $responseOrException->getResponse(); } - $breadcrumbData = [ - 'url' => (string) $partialUri, - 'http.request.method' => $request->getMethod(), - 'http.request.body.size' => $request->getBody()->getSize(), - ]; - if ($request->getUri()->getQuery() !== '') { - $breadcrumbData['http.query'] = $request->getUri()->getQuery(); - } - if ($request->getUri()->getFragment() !== '') { - $breadcrumbData['http.fragment'] = $request->getUri()->getFragment(); - } - if ($response !== null) { - $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); + $spanAndBreadcrumbData['http.response.body.size'] = $response->getBody()->getSize(); + $spanAndBreadcrumbData['http.response.status_code'] = $response->getStatusCode(); - $breadcrumbData['http.response.status_code'] = $response->getStatusCode(); - $breadcrumbData['http.response.body.size'] = $response->getBody()->getSize(); + $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); + $childSpan->setData($spanAndBreadcrumbData); } else { $childSpan->setStatus(SpanStatus::internalError()); } @@ -106,7 +103,9 @@ public static function trace(?HubInterface $hub = null): \Closure Breadcrumb::TYPE_HTTP, 'http', null, - $breadcrumbData + array_merge([ + 'url' => (string) $partialUri, + ], $spanAndBreadcrumbData) )); if ($responseOrException instanceof \Throwable) { diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index ac9aa76c9..ae3c30e30 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -185,7 +185,7 @@ public static function traceHeadersDataProvider(): iterable /** * @dataProvider traceDataProvider */ - public function testTrace(Request $request, $expectedPromiseResult, array $expectedBreadcrumbData): void + public function testTrace(Request $request, $expectedPromiseResult, array $expectedBreadcrumbData, array $expectedSpanData): void { $client = $this->createMock(ClientInterface::class); $client->expects($this->exactly(4)) @@ -201,7 +201,7 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec $client->expects($this->once()) ->method('captureEvent') - ->with($this->callback(function (Event $eventArg) use ($hub, $request, $expectedPromiseResult, $expectedBreadcrumbData): bool { + ->with($this->callback(function (Event $eventArg) use ($hub, $request, $expectedPromiseResult, $expectedBreadcrumbData, $expectedSpanData): bool { $this->assertSame(EventType::transaction(), $eventArg->getType()); $hub->configureScope(static function (Scope $scope) use ($eventArg): void { @@ -233,6 +233,7 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec $this->assertSame(SpanStatus::internalError(), $guzzleSpan->getStatus()); } + $this->assertSame($expectedSpanData, $guzzleSpan->getData()); $this->assertSame($expectedBreadcrumbData, $guzzleBreadcrumb->getMetadata()); return true; @@ -277,8 +278,14 @@ public static function traceDataProvider(): iterable 'url' => 'https://www.example.com', 'http.request.method' => 'GET', 'http.request.body.size' => 0, + 'http.response.body.size' => 0, 'http.response.status_code' => 200, + ], + [ + 'http.request.method' => 'GET', + 'http.request.body.size' => 0, 'http.response.body.size' => 0, + 'http.response.status_code' => 200, ], ]; @@ -291,8 +298,16 @@ public static function traceDataProvider(): iterable 'http.request.body.size' => 0, 'http.query' => 'query=string', 'http.fragment' => 'fragment=1', + 'http.response.body.size' => 0, 'http.response.status_code' => 200, + ], + [ + 'http.request.method' => 'GET', + 'http.request.body.size' => 0, + 'http.query' => 'query=string', + 'http.fragment' => 'fragment=1', 'http.response.body.size' => 0, + 'http.response.status_code' => 200, ], ]; @@ -303,8 +318,14 @@ public static function traceDataProvider(): iterable 'url' => 'https://www.example.com', 'http.request.method' => 'POST', 'http.request.body.size' => 10, + 'http.response.body.size' => 6, 'http.response.status_code' => 403, + ], + [ + 'http.request.method' => 'POST', + 'http.request.body.size' => 10, 'http.response.body.size' => 6, + 'http.response.status_code' => 403, ], ]; @@ -316,6 +337,10 @@ public static function traceDataProvider(): iterable 'http.request.method' => 'GET', 'http.request.body.size' => 0, ], + [ + 'http.request.method' => 'GET', + 'http.request.body.size' => 0, + ], ]; } } From e26ecf8e3c735b752f59d80c6305dc14347187ca Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 24 May 2024 14:23:42 +0200 Subject: [PATCH 1010/1161] Add timing span when emiting a timing metric (#1717) Co-authored-by: Alex Bouma --- src/Metrics/Metrics.php | 42 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php index b218b3749..e8c6a6374 100644 --- a/src/Metrics/Metrics.php +++ b/src/Metrics/Metrics.php @@ -9,6 +9,9 @@ use Sentry\Metrics\Types\DistributionType; use Sentry\Metrics\Types\GaugeType; use Sentry\Metrics\Types\SetType; +use Sentry\Tracing\SpanContext; + +use function Sentry\trace; class Metrics { @@ -142,21 +145,32 @@ public function timing( array $tags = [], int $stackLevel = 0 ) { - $startTimestamp = microtime(true); - - $result = $callback(); - - $this->aggregator->add( - DistributionType::TYPE, - $key, - microtime(true) - $startTimestamp, - MetricsUnit::second(), - $tags, - (int) $startTimestamp, - $stackLevel + return trace( + function () use ($callback, $key, $tags, $stackLevel) { + $startTimestamp = microtime(true); + + $result = $callback(); + + /** + * Emitting the metric here, will attach it to the + * "metric.timing" span. + */ + $this->aggregator->add( + DistributionType::TYPE, + $key, + microtime(true) - $startTimestamp, + MetricsUnit::second(), + $tags, + (int) $startTimestamp, + $stackLevel + 4 // the `trace` helper adds 4 additional stack frames + ); + + return $result; + }, + SpanContext::make() + ->setOp('metric.timing') + ->setDescription($key) ); - - return $result; } public function flush(): ?EventId From 8761545ccfb5df02da92e9967129f910e6d166f7 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 24 May 2024 14:52:33 +0200 Subject: [PATCH 1011/1161] Deprecate `enable_tracing` option (#1743) --- src/Options.php | 4 ++++ src/functions.php | 1 - tests/OptionsTest.php | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Options.php b/src/Options.php index bd04615db..ca7481e91 100644 --- a/src/Options.php +++ b/src/Options.php @@ -130,6 +130,8 @@ public function getTracesSampleRate(): ?float * precedence. * * @param bool|null $enableTracing Boolean if tracing should be enabled or not + * + * @deprecated since version 4.7. To be removed in version 5.0 */ public function setEnableTracing(?bool $enableTracing): self { @@ -144,6 +146,8 @@ public function setEnableTracing(?bool $enableTracing): self * Gets if tracing is enabled or not. * * @return bool|null If the option `enable_tracing` is set or not + * + * @deprecated since version 4.7. To be removed in version 5.0 */ public function getEnableTracing(): ?bool { diff --git a/src/functions.php b/src/functions.php index 9165a8066..4a51da170 100644 --- a/src/functions.php +++ b/src/functions.php @@ -28,7 +28,6 @@ * context_lines?: int|null, * default_integrations?: bool, * dsn?: string|bool|null|Dsn, - * enable_tracing?: bool|null, * environment?: string|null, * error_types?: int|null, * http_client?: HttpClientInterface|null, diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 7f1a409a7..b21b7bed8 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -627,6 +627,8 @@ public function testErrorTypesOptionIsNotDynamiclyReadFromErrorReportingLevelWhe /** * @dataProvider enableTracingDataProvider + * + * @deprecated since version 4.7. To be removed in version 5.0 */ public function testEnableTracing(?bool $enabledTracing, ?float $tracesSampleRate, $expectedResult): void { From 2ceeca41a13e381f327f3910466ff81314052972 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 24 May 2024 14:56:47 +0200 Subject: [PATCH 1012/1161] Use `AWS_LAMBDA_FUNCTION_VERSION` env var for release if available (#1742) --- src/Options.php | 2 +- tests/OptionsTest.php | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Options.php b/src/Options.php index ca7481e91..fe5b38103 100644 --- a/src/Options.php +++ b/src/Options.php @@ -1104,7 +1104,7 @@ private function configureOptions(OptionsResolver $resolver): void 'logger' => null, 'spotlight' => false, 'spotlight_url' => 'http://localhost:8969', - 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, + 'release' => $_SERVER['SENTRY_RELEASE'] ?? $_SERVER['AWS_LAMBDA_FUNCTION_VERSION'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), 'ignore_exceptions' => [], diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index b21b7bed8..9347272f2 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -611,6 +611,27 @@ public function testReleaseOptionDefaultValueIsGotFromEnvironmentVariable(): voi $this->assertSame('0.0.1', (new Options())->getRelease()); } + /** + * @backupGlobals enabled + */ + public function testReleaseOptionDefaultValueIsGotFromLambdaEnvironmentVariable(): void + { + $_SERVER['AWS_LAMBDA_FUNCTION_VERSION'] = '0.0.2'; + + $this->assertSame('0.0.2', (new Options())->getRelease()); + } + + /** + * @backupGlobals enabled + */ + public function testReleaseOptionDefaultValueIsPreferredFromSentryEnvironmentVariable(): void + { + $_SERVER['AWS_LAMBDA_FUNCTION_VERSION'] = '0.0.3'; + $_SERVER['SENTRY_RELEASE'] = '0.0.4'; + + $this->assertSame('0.0.4', (new Options())->getRelease()); + } + public function testErrorTypesOptionIsNotDynamiclyReadFromErrorReportingLevelWhenSet(): void { $errorReportingBeforeTest = error_reporting(\E_ERROR); From 7ed28441a412ca15159e4b4ac3dde2c9f6a55350 Mon Sep 17 00:00:00 2001 From: Typeonce Date: Fri, 24 May 2024 15:59:40 +0300 Subject: [PATCH 1013/1161] Add error message to failed requests (#1722) Co-authored-by: sc Co-authored-by: Alex Bouma Co-authored-by: Michi Hoffmann --- src/HttpClient/HttpClient.php | 5 ++++- tests/HttpClient/HttpClientTest.php | 23 +++++++++++++++++++++++ tests/testserver/index.php | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 779088d25..a5c64847c 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -89,6 +89,7 @@ public function sendRequest(Request $request, Options $options): Response curl_setopt($curlHandle, \CURLOPT_PROXYUSERPWD, $httpProxyAuthentication); } + /** @var string|false $body */ $body = curl_exec($curlHandle); if ($body === false) { @@ -105,6 +106,8 @@ public function sendRequest(Request $request, Options $options): Response curl_close($curlHandle); - return new Response($statusCode, $responseHeaders, ''); + $error = $statusCode >= 400 ? $body : ''; + + return new Response($statusCode, $responseHeaders, $error); } } diff --git a/tests/HttpClient/HttpClientTest.php b/tests/HttpClient/HttpClientTest.php index e6b0d23f0..583ba7eff 100644 --- a/tests/HttpClient/HttpClientTest.php +++ b/tests/HttpClient/HttpClientTest.php @@ -71,9 +71,32 @@ public function testClientMakesUncompressedRequestWhenCompressionDisabled(): voi $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals($response->getStatusCode(), $serverOutput['status']); $this->assertEquals($request->getStringBody(), $serverOutput['body']); + $this->assertEquals($response->getError(), ''); $this->assertEquals(\strlen($request->getStringBody()), $serverOutput['headers']['Content-Length']); } + public function testClientReturnsBodyAsErrorOnNonSuccessStatusCode(): void + { + $testServer = $this->startTestServer(); + + $options = new Options([ + 'dsn' => "http://publicKey@{$testServer}/400", + ]); + + $request = new Request(); + $request->setStringBody('test'); + + $client = new HttpClient('sentry.php', 'testing'); + $response = $client->sendRequest($request, $options); + + $this->stopTestServer(); + + $this->assertFalse($response->isSuccess()); + $this->assertEquals(400, $response->getStatusCode()); + + $this->assertEquals($request->getStringBody(), $response->getError()); + } + public function testThrowsExceptionIfDsnOptionIsNotSet(): void { $this->expectException(\RuntimeException::class); diff --git a/tests/testserver/index.php b/tests/testserver/index.php index b7f0709a4..8b7f3ec08 100644 --- a/tests/testserver/index.php +++ b/tests/testserver/index.php @@ -53,4 +53,4 @@ http_response_code($status); -return 'Processed.'; +echo $body; From 82d959339101281b0ce308870648a97ad819dbae Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 27 May 2024 11:03:21 +0200 Subject: [PATCH 1014/1161] Test span sampled status before creating child spans (#1740) Co-authored-by: Michi Hoffmann --- src/Tracing/GuzzleTracingMiddleware.php | 71 ++++++------ src/functions.php | 8 +- tests/FunctionsTest.php | 27 ++++- tests/Tracing/GuzzleTracingMiddlewareTest.php | 108 +++++++++++++++++- 4 files changed, 166 insertions(+), 48 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 08e640359..6dfde7840 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -28,18 +28,7 @@ public static function trace(?HubInterface $hub = null): \Closure return static function (RequestInterface $request, array $options) use ($hub, $handler) { $hub = $hub ?? SentrySdk::getCurrentHub(); $client = $hub->getClient(); - $span = $hub->getSpan(); - - if ($span === null) { - if (self::shouldAttachTracingHeaders($client, $request)) { - $request = $request - ->withHeader('sentry-trace', getTraceparent()) - ->withHeader('traceparent', getW3CTraceparent()) - ->withHeader('baggage', getBaggage()); - } - - return $handler($request, $options); - } + $parentSpan = $hub->getSpan(); $partialUri = Uri::fromParts([ 'scheme' => $request->getUri()->getScheme(), @@ -60,24 +49,30 @@ public static function trace(?HubInterface $hub = null): \Closure $spanAndBreadcrumbData['http.fragment'] = $request->getUri()->getFragment(); } - $spanContext = new SpanContext(); - $spanContext->setOp('http.client'); - $spanContext->setDescription($request->getMethod() . ' ' . $partialUri); - $spanContext->setData($spanAndBreadcrumbData); + $childSpan = null; + + if ($parentSpan !== null && $parentSpan->getSampled()) { + $spanContext = new SpanContext(); + $spanContext->setOp('http.client'); + $spanContext->setDescription($request->getMethod() . ' ' . $partialUri); + $spanContext->setData($spanAndBreadcrumbData); - $childSpan = $span->startChild($spanContext); + $childSpan = $parentSpan->startChild($spanContext); + } if (self::shouldAttachTracingHeaders($client, $request)) { $request = $request - ->withHeader('sentry-trace', $childSpan->toTraceparent()) - ->withHeader('traceparent', $childSpan->toW3CTraceparent()) - ->withHeader('baggage', $childSpan->toBaggage()); + ->withHeader('sentry-trace', getTraceparent()) + ->withHeader('traceparent', getW3CTraceparent()) + ->withHeader('baggage', getBaggage()); } $handlerPromiseCallback = static function ($responseOrException) use ($hub, $spanAndBreadcrumbData, $childSpan, $partialUri) { - // We finish the span (which means setting the span end timestamp) first to ensure the measured time - // the span spans is as close to only the HTTP request time and do the data collection afterwards - $childSpan->finish(); + if ($childSpan !== null) { + // We finish the span (which means setting the span end timestamp) first to ensure the measured time + // the span spans is as close to only the HTTP request time and do the data collection afterwards + $childSpan->finish(); + } $response = null; @@ -91,11 +86,15 @@ public static function trace(?HubInterface $hub = null): \Closure if ($response !== null) { $spanAndBreadcrumbData['http.response.body.size'] = $response->getBody()->getSize(); $spanAndBreadcrumbData['http.response.status_code'] = $response->getStatusCode(); + } - $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); - $childSpan->setData($spanAndBreadcrumbData); - } else { - $childSpan->setStatus(SpanStatus::internalError()); + if ($childSpan !== null) { + if ($response !== null) { + $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); + $childSpan->setData($spanAndBreadcrumbData); + } else { + $childSpan->setStatus(SpanStatus::internalError()); + } } $hub->addBreadcrumb(new Breadcrumb( @@ -122,18 +121,14 @@ public static function trace(?HubInterface $hub = null): \Closure private static function shouldAttachTracingHeaders(?ClientInterface $client, RequestInterface $request): bool { - if ($client !== null) { - $sdkOptions = $client->getOptions(); - - // Check if the request destination is allow listed in the trace_propagation_targets option. - if ( - $sdkOptions->getTracePropagationTargets() === null - || \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()) - ) { - return true; - } + if ($client === null) { + return false; } - return false; + $sdkOptions = $client->getOptions(); + + // Check if the request destination is allow listed in the trace_propagation_targets option. + return $sdkOptions->getTracePropagationTargets() === null + || \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()); } } diff --git a/src/functions.php b/src/functions.php index 4a51da170..0100cae3e 100644 --- a/src/functions.php +++ b/src/functions.php @@ -249,10 +249,10 @@ function trace(callable $trace, SpanContext $context) return SentrySdk::getCurrentHub()->withScope(function (Scope $scope) use ($context, $trace) { $parentSpan = $scope->getSpan(); - // If there's a span set on the scope there is a transaction - // active currently. If that is the case we create a child span - // and set it on the scope. Otherwise we only execute the callable - if ($parentSpan !== null) { + // If there is a span set on the scope and it's sampled there is an active transaction. + // If that is the case we create the child span and set it on the scope. + // Otherwise we only execute the callable without creating a span. + if ($parentSpan !== null && $parentSpan->getSampled()) { $span = $parentSpan->startChild($context); $scope->setSpan($span); diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 77a5c6ee7..ce892e853 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -364,7 +364,8 @@ public function testTraceCorrectlyReplacesAndRestoresCurrentSpan(): void { $hub = new Hub(); - $transaction = new Transaction(new TransactionContext()); + $transaction = new Transaction(TransactionContext::make()); + $transaction->setSampled(true); $hub->setSpan($transaction); @@ -387,6 +388,30 @@ public function testTraceCorrectlyReplacesAndRestoresCurrentSpan(): void } } + public function testTraceDoesntCreateSpanIfTransactionIsNotSampled(): void + { + $scope = $this->createMock(Scope::class); + + $hub = new Hub(null, $scope); + + $transaction = new Transaction(TransactionContext::make()); + $transaction->setSampled(false); + + $scope->expects($this->never()) + ->method('setSpan'); + $scope->expects($this->exactly(3)) + ->method('getSpan') + ->willReturn($transaction); + + SentrySdk::setCurrentHub($hub); + + trace(function () use ($transaction, $hub) { + $this->assertSame($transaction, $hub->getSpan()); + }, SpanContext::make()); + + $this->assertSame($transaction, $hub->getSpan()); + } + public function testTraceparentWithTracingDisabled(): void { $propagationContext = PropagationContext::fromDefaults(); diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index ae3c30e30..46e9a8325 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -23,15 +23,21 @@ final class GuzzleTracingMiddlewareTest extends TestCase { - public function testTraceDoesNothingIfSpanIsNotSet(): void + public function testTraceCreatesBreadcrumbIfSpanIsNotSet(): void { $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) + $client->expects($this->atLeast(2)) ->method('getOptions') - ->willReturn(new Options()); + ->willReturn(new Options([ + 'traces_sample_rate' => 0, + ])); $hub = new Hub($client); + $transaction = $hub->startTransaction(TransactionContext::make()); + + $this->assertFalse($transaction->getSampled()); + $expectedPromiseResult = new Response(); $middleware = GuzzleTracingMiddleware::trace($hub); @@ -50,12 +56,59 @@ public function testTraceDoesNothingIfSpanIsNotSet(): void $this->assertSame($expectedPromiseResult, $promiseResult); + $this->assertNull($transaction->getSpanRecorder()); + $hub->configureScope(function (Scope $scope): void { $event = Event::createEvent(); $scope->applyToEvent($event); - $this->assertCount(0, $event->getBreadcrumbs()); + $this->assertCount(1, $event->getBreadcrumbs()); + }); + } + + public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeast(2)) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1, + ])); + + $hub = new Hub($client); + + $transaction = $hub->startTransaction(TransactionContext::make()); + + $this->assertTrue($transaction->getSampled()); + + $expectedPromiseResult = new Response(); + + $middleware = GuzzleTracingMiddleware::trace($hub); + $function = $middleware(static function () use ($expectedPromiseResult): PromiseInterface { + return new FulfilledPromise($expectedPromiseResult); + }); + + /** @var PromiseInterface $promise */ + $promise = $function(new Request('GET', 'https://www.example.com'), []); + + try { + $promiseResult = $promise->wait(); + } catch (\Throwable $exception) { + $promiseResult = $exception; + } + + $this->assertSame($expectedPromiseResult, $promiseResult); + + $this->assertNotNull($transaction->getSpanRecorder()); + $this->assertCount(1, $transaction->getSpanRecorder()->getSpans()); + + $hub->configureScope(function (Scope $scope): void { + $event = Event::createEvent(); + + $scope->applyToEvent($event); + + $this->assertCount(1, $event->getBreadcrumbs()); }); } @@ -95,7 +148,7 @@ public function testTraceHeaders(Request $request, Options $options, bool $heade /** * @dataProvider traceHeadersDataProvider */ - public function testTraceHeadersWithTransacttion(Request $request, Options $options, bool $headersShouldBePresent): void + public function testTraceHeadersWithTransaction(Request $request, Options $options, bool $headersShouldBePresent): void { $client = $this->createMock(ClientInterface::class); $client->expects($this->atLeast(2)) @@ -133,6 +186,15 @@ public function testTraceHeadersWithTransacttion(Request $request, Options $opti public static function traceHeadersDataProvider(): iterable { + // Test cases here are duplicated with sampling enabled and disabled because trace headers hould be added regardless of the sample decision + + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 0, + ]), + true, + ]; yield [ new Request('GET', 'https://www.example.com'), new Options([ @@ -141,6 +203,14 @@ public static function traceHeadersDataProvider(): iterable true, ]; + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 0, + 'trace_propagation_targets' => null, + ]), + true, + ]; yield [ new Request('GET', 'https://www.example.com'), new Options([ @@ -150,6 +220,16 @@ public static function traceHeadersDataProvider(): iterable true, ]; + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 0, + 'trace_propagation_targets' => [ + 'www.example.com', + ], + ]), + true, + ]; yield [ new Request('GET', 'https://www.example.com'), new Options([ @@ -161,6 +241,14 @@ public static function traceHeadersDataProvider(): iterable true, ]; + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 0, + 'trace_propagation_targets' => [], + ]), + false, + ]; yield [ new Request('GET', 'https://www.example.com'), new Options([ @@ -170,6 +258,16 @@ public static function traceHeadersDataProvider(): iterable false, ]; + yield [ + new Request('GET', 'https://www.example.com'), + new Options([ + 'traces_sample_rate' => 0, + 'trace_propagation_targets' => [ + 'example.com', + ], + ]), + false, + ]; yield [ new Request('GET', 'https://www.example.com'), new Options([ From 7a51be051902f14156a9ae64f49fa0860b9b6335 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 28 May 2024 18:07:18 +0200 Subject: [PATCH 1015/1161] Type hint `float` on metrics calls (#1745) --- src/Metrics/Metrics.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php index e8c6a6374..2c9d8c504 100644 --- a/src/Metrics/Metrics.php +++ b/src/Metrics/Metrics.php @@ -40,12 +40,11 @@ public static function getInstance(): self } /** - * @param int|float $value * @param array $tags */ public function increment( string $key, - $value, + float $value, ?MetricsUnit $unit = null, array $tags = [], ?int $timestamp = null, @@ -63,12 +62,11 @@ public function increment( } /** - * @param int|float $value * @param array $tags */ public function distribution( string $key, - $value, + float $value, ?MetricsUnit $unit = null, array $tags = [], ?int $timestamp = null, @@ -86,12 +84,11 @@ public function distribution( } /** - * @param int|float $value * @param array $tags */ public function gauge( string $key, - $value, + float $value, ?MetricsUnit $unit = null, array $tags = [], ?int $timestamp = null, From 6051f410a2434a276705c9d429033f634d0afdac Mon Sep 17 00:00:00 2001 From: mark burdett Date: Tue, 28 May 2024 12:07:59 -0700 Subject: [PATCH 1016/1161] Represent callable strings as strings (#1741) Co-authored-by: Alex Bouma Co-authored-by: Michi Hoffmann --- src/Serializer/AbstractSerializer.php | 4 ++-- tests/Serializer/AbstractSerializerTest.php | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index ec1389ac8..766175a9e 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -280,7 +280,7 @@ protected function serializeValue($value) */ protected function serializeCallable($callable): string { - if (\is_string($callable) && !\function_exists($callable)) { + if (\is_string($callable)) { return $callable; } @@ -292,7 +292,7 @@ protected function serializeCallable($callable): string if (\is_array($callable)) { $reflection = new \ReflectionMethod($callable[0], $callable[1]); $class = $reflection->getDeclaringClass(); - } elseif ($callable instanceof \Closure || (\is_string($callable) && \function_exists($callable))) { + } elseif ($callable instanceof \Closure) { $reflection = new \ReflectionFunction($callable); $class = null; } elseif (\is_object($callable) && method_exists($callable, '__invoke')) { diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 43fadc722..025a3921f 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -539,6 +539,11 @@ public function serializableCallableProvider(): array 'callable' => $callableWithoutNamespaces, 'expected' => 'Lambda void {closure} [int|null param1_70ns]', ], + [ + // This is (a example of) a PHP provided function that is technically callable but we want to ignore that because it causes more false positives than it helps + 'callable' => 'header', + 'expected' => 'header', + ], [ 'callable' => __METHOD__, 'expected' => __METHOD__, From 9af5037d277f5a1b49f9691c73fd83cd194060d6 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 4 Jun 2024 15:57:34 +0200 Subject: [PATCH 1017/1161] Add fast path for ignoring errors (#1737) --- src/ErrorHandler.php | 41 ++++++++--- src/Integration/ErrorListenerIntegration.php | 71 +++++++++++++------ src/Integration/IntegrationRegistry.php | 8 ++- .../OptionAwareIntegrationInterface.php | 15 ++++ tests/Integration/IntegrationRegistryTest.php | 20 +++--- 5 files changed, 111 insertions(+), 44 deletions(-) create mode 100644 src/Integration/OptionAwareIntegrationInterface.php diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index ef558d919..37301b407 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -100,6 +100,11 @@ final class ErrorHandler */ private $memoryLimitIncreaseOnOutOfMemoryErrorValue = 5 * 1024 * 1024; // 5 MiB + /** + * @var Options|null The SDK options + */ + private $options; + /** * @var bool Whether the memory limit has been increased */ @@ -153,12 +158,14 @@ private function __construct() /** * Registers the error handler once and returns its instance. */ - public static function registerOnceErrorHandler(): self + public static function registerOnceErrorHandler(?Options $options = null): self { if (self::$handlerInstance === null) { self::$handlerInstance = new self(); } + self::$handlerInstance->options = $options; + if (self::$handlerInstance->isErrorHandlerRegistered) { return self::$handlerInstance; } @@ -326,17 +333,19 @@ private function handleError(int $level, string $message, string $file, int $lin } } - if ($isSilencedError) { - $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); - } else { - $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); - } + if ($this->shouldHandleError($level, $isSilencedError)) { + if ($isSilencedError) { + $errorAsException = new SilencedErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); + } else { + $errorAsException = new \ErrorException(self::ERROR_LEVELS_DESCRIPTION[$level] . ': ' . $message, 0, $level, $file, $line); + } - $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $errorAsException->getFile(), $errorAsException->getLine()); + $backtrace = $this->cleanBacktraceFromErrorHandlerFrames($errorAsException->getTrace(), $errorAsException->getFile(), $errorAsException->getLine()); - $this->exceptionReflection->setValue($errorAsException, $backtrace); + $this->exceptionReflection->setValue($errorAsException, $backtrace); - $this->invokeListeners($this->errorListeners, $errorAsException); + $this->invokeListeners($this->errorListeners, $errorAsException); + } if ($this->previousErrorHandler !== null) { return false !== ($this->previousErrorHandler)($level, $message, $file, $line, $errcontext); @@ -345,6 +354,20 @@ private function handleError(int $level, string $message, string $file, int $lin return false; } + private function shouldHandleError(int $level, bool $silenced): bool + { + // If we were not given any options, we should handle all errors + if ($this->options === null) { + return true; + } + + if ($silenced) { + return $this->options->shouldCaptureSilencedErrors(); + } + + return ($this->options->getErrorTypes() & $level) !== 0; + } + /** * Tries to handle a fatal error if any and relay them to the listeners. * It only tries to do this if we still have some reserved memory at diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index b56d1c00e..5dd75ed60 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -6,40 +6,65 @@ use Sentry\ErrorHandler; use Sentry\Exception\SilencedErrorException; +use Sentry\Options; use Sentry\SentrySdk; /** * This integration hooks into the global error handlers and emits events to * Sentry. */ -final class ErrorListenerIntegration extends AbstractErrorListenerIntegration +final class ErrorListenerIntegration extends AbstractErrorListenerIntegration implements OptionAwareIntegrationInterface { + /** + * @var Options + */ + private $options; + + public function setOptions(Options $options): void + { + $this->options = $options; + } + /** * {@inheritdoc} */ public function setupOnce(): void { - $errorHandler = ErrorHandler::registerOnceErrorHandler(); - $errorHandler->addErrorHandlerListener(static function (\ErrorException $exception): void { - $currentHub = SentrySdk::getCurrentHub(); - $integration = $currentHub->getIntegration(self::class); - $client = $currentHub->getClient(); - - // The client bound to the current hub, if any, could not have this - // integration enabled. If this is the case, bail out - if ($integration === null || $client === null) { - return; - } - - if ($exception instanceof SilencedErrorException && !$client->getOptions()->shouldCaptureSilencedErrors()) { - return; - } - - if (!$exception instanceof SilencedErrorException && !($client->getOptions()->getErrorTypes() & $exception->getSeverity())) { - return; - } - - $integration->captureException($currentHub, $exception); - }); + ErrorHandler::registerOnceErrorHandler($this->options) + ->addErrorHandlerListener( + static function (\ErrorException $exception): void { + $currentHub = SentrySdk::getCurrentHub(); + $integration = $currentHub->getIntegration(self::class); + $client = $currentHub->getClient(); + + // The client bound to the current hub, if any, could not have this + // integration enabled. If this is the case, bail out + if ($integration === null || $client === null) { + return; + } + + if ($exception instanceof SilencedErrorException && !$client->getOptions()->shouldCaptureSilencedErrors()) { + return; + } + + if (!$exception instanceof SilencedErrorException && !($client->getOptions()->getErrorTypes() & $exception->getSeverity())) { + return; + } + + $integration->captureException($currentHub, $exception); + } + ); + } + + /** + * @internal this is a convenience method to create an instance of this integration for tests + */ + public static function make(Options $options): self + { + $integration = new self(); + + $integration->setOptions($options); + + return $integration; } } diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index 40eb9c06b..12c91911f 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -58,7 +58,7 @@ public function setupIntegrations(Options $options, LoggerInterface $logger): ar $integrations[$integrationName] = $integration; - if ($this->setupIntegration($integration)) { + if ($this->setupIntegration($integration, $options)) { $installed[] = $integrationName; } } @@ -70,7 +70,7 @@ public function setupIntegrations(Options $options, LoggerInterface $logger): ar return $integrations; } - private function setupIntegration(IntegrationInterface $integration): bool + private function setupIntegration(IntegrationInterface $integration, Options $options): bool { $integrationName = \get_class($integration); @@ -78,6 +78,10 @@ private function setupIntegration(IntegrationInterface $integration): bool return false; } + if ($integration instanceof OptionAwareIntegrationInterface) { + $integration->setOptions($options); + } + $integration->setupOnce(); $this->integrations[$integrationName] = true; diff --git a/src/Integration/OptionAwareIntegrationInterface.php b/src/Integration/OptionAwareIntegrationInterface.php new file mode 100644 index 000000000..922274726 --- /dev/null +++ b/src/Integration/OptionAwareIntegrationInterface.php @@ -0,0 +1,15 @@ + [ - new Options([ + $options = new Options([ 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, ]), [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), - ErrorListenerIntegration::class => new ErrorListenerIntegration(), + ErrorListenerIntegration::class => ErrorListenerIntegration::make($options), FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), RequestIntegration::class => new RequestIntegration(), TransactionIntegration::class => new TransactionIntegration(), @@ -104,7 +104,7 @@ public function setupOnce(): void ]; yield 'Default integrations and some user integrations' => [ - new Options([ + $options = new Options([ 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, 'integrations' => [ @@ -114,7 +114,7 @@ public function setupOnce(): void ]), [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), - ErrorListenerIntegration::class => new ErrorListenerIntegration(), + ErrorListenerIntegration::class => ErrorListenerIntegration::make($options), FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), RequestIntegration::class => new RequestIntegration(), TransactionIntegration::class => new TransactionIntegration(), @@ -127,7 +127,7 @@ public function setupOnce(): void ]; yield 'Default integrations and some user integrations, one of which is also a default integration' => [ - new Options([ + $options = new Options([ 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, 'integrations' => [ @@ -137,7 +137,7 @@ public function setupOnce(): void ]), [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), - ErrorListenerIntegration::class => new ErrorListenerIntegration(), + ErrorListenerIntegration::class => ErrorListenerIntegration::make($options), FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), RequestIntegration::class => new RequestIntegration(), FrameContextifierIntegration::class => new FrameContextifierIntegration(), @@ -149,7 +149,7 @@ public function setupOnce(): void ]; yield 'Default integrations and one user integration, the ModulesIntegration is also a default integration' => [ - new Options([ + $options = new Options([ 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, 'integrations' => [ @@ -158,7 +158,7 @@ public function setupOnce(): void ]), [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), - ErrorListenerIntegration::class => new ErrorListenerIntegration(), + ErrorListenerIntegration::class => ErrorListenerIntegration::make($options), FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), RequestIntegration::class => new RequestIntegration(), TransactionIntegration::class => new TransactionIntegration(), @@ -192,7 +192,7 @@ public function setupOnce(): void ]; yield 'Default integrations and a callable as user integrations' => [ - new Options([ + $options = new Options([ 'dsn' => 'http://public@example.com/sentry/1', 'default_integrations' => true, 'integrations' => static function (array $defaultIntegrations): array { @@ -201,7 +201,7 @@ public function setupOnce(): void ]), [ ExceptionListenerIntegration::class => new ExceptionListenerIntegration(), - ErrorListenerIntegration::class => new ErrorListenerIntegration(), + ErrorListenerIntegration::class => ErrorListenerIntegration::make($options), FatalErrorListenerIntegration::class => new FatalErrorListenerIntegration(), RequestIntegration::class => new RequestIntegration(), TransactionIntegration::class => new TransactionIntegration(), From b4fd0dbe070af6ca4e88ec7920d008975d4aa862 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 4 Jun 2024 18:59:07 +0200 Subject: [PATCH 1018/1161] Prepare 4.8.0 (#1746) --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f10b9a2f..53673de85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # CHANGELOG +## 4.8.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.8.0. + +### Features + +- Add timing span when emiting a timing metric [(#1717)](https://github.com/getsentry/sentry-php/pull/1717) + + ```php + use function Sentry\metrics; + + // This will now both emit a distribution metric and a span with the "expensive-operation" key + metrics()->timing( + key: 'expensive-operation', + callback: fn() => doExpensiveOperation(), + ); + ``` + +### Bug Fixes + +- Fix missing data on HTTP spans [(#1735)](https://github.com/getsentry/sentry-php/pull/1735) +- Test span sampled status before creating child spans [(#1740)](https://github.com/getsentry/sentry-php/pull/1740) + +### Misc + +- Implement fast path for ignoring errors [(#1737)](https://github.com/getsentry/sentry-php/pull/1737) +- Add array shape for better autocomplete of `Sentry\init` function [(#1738)](https://github.com/getsentry/sentry-php/pull/1738) +- Represent callable strings as strings [(#1741)](https://github.com/getsentry/sentry-php/pull/1741) +- Use `AWS_LAMBDA_FUNCTION_VERSION` environment variable for release if available [(#1742)](https://github.com/getsentry/sentry-php/pull/1742) + ## 4.7.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.7.0. From 3cf5778ff425a23f2d22ed41b423691d36f47163 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 5 Jun 2024 13:18:43 +0000 Subject: [PATCH 1019/1161] release: 4.8.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index b2dd5226b..20a920383 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.7.0'; + public const SDK_VERSION = '4.8.0'; /** * @var Options The client options From dbcf0630ddea2a371e68eddae14aca306e7ed1bd Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 14 Jun 2024 21:33:32 +0200 Subject: [PATCH 1020/1161] Guard against empty `REMOTE_ADDR` (#1751) --- src/Integration/RequestIntegration.php | 2 +- tests/Integration/RequestIntegrationTest.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 4238b457c..d4ab50ce1 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -137,7 +137,7 @@ private function processEvent(Event $event, Options $options): void if ($options->shouldSendDefaultPii()) { $serverParams = $request->getServerParams(); - if (isset($serverParams['REMOTE_ADDR'])) { + if (!empty($serverParams['REMOTE_ADDR'])) { $user = $event->getUser(); $requestData['env']['REMOTE_ADDR'] = $serverParams['REMOTE_ADDR']; diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 266405d90..cea34cb57 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -163,6 +163,24 @@ public static function invokeDataProvider(): iterable UserDataBag::createFromUserIpAddress('127.0.0.1'), ]; + yield [ + [ + 'send_default_pii' => true, + ], + (new ServerRequest('GET', 'http://www.example.com', [], null, '1.1', ['REMOTE_ADDR' => ''])) + ->withHeader('Host', 'www.example.com'), + [ + 'url' => 'http://www.example.com', + 'method' => 'GET', + 'cookies' => [], + 'headers' => [ + 'Host' => ['www.example.com'], + ], + ], + null, + null, + ]; + yield [ [ 'send_default_pii' => false, From 5a95359aa6da93e47318664066e06494fea66db3 Mon Sep 17 00:00:00 2001 From: Stephanie Anderson Date: Mon, 24 Jun 2024 17:26:47 +0200 Subject: [PATCH 1021/1161] Add contributors to README.md (#1754) --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0302ada86..8891ae0f2 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,13 @@ The following integrations are available and maintained by members of the Sentry ## Contributing to the SDK -Please refer to [CONTRIBUTING.md](CONTRIBUTING.md). +Please make sure to read the [CONTRIBUTING.md](CONTRIBUTING.md) before making a pull request. + +Thanks to everyone who has contributed to this project so far. + + + + ## Getting help/support From fd40b93066685bb71af23672f44dca24612d92b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20M=C3=BChlbach?= Date: Thu, 4 Jul 2024 11:29:41 +0200 Subject: [PATCH 1022/1161] docs: Fix MonitorConfig checkinMargin & maxRuntime (#1756) --- src/MonitorConfig.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MonitorConfig.php b/src/MonitorConfig.php index d2954e917..f5e5d9494 100644 --- a/src/MonitorConfig.php +++ b/src/MonitorConfig.php @@ -12,12 +12,12 @@ final class MonitorConfig private $schedule; /** - * @var int|null The check-in margin in seconds + * @var int|null The check-in margin in minutes */ private $checkinMargin; /** - * @var int|null The maximum runtime in seconds + * @var int|null The maximum runtime in minutes */ private $maxRuntime; From 475cedaf59f804eab73d38999e06c1b466ea34c3 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 12 Jul 2024 17:11:28 +0200 Subject: [PATCH 1023/1161] Remove stubs from distributed exports (#1758) --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 71e15013b..5b30cfa97 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,6 +11,7 @@ # Exclude non-essential files from dist /tests export-ignore +/stubs export-ignore /.appveyor.yml export-ignore /.craft.yml export-ignore /.editorconfig export-ignore From de4c734c9cfcb35406959ebd6e663609d8ce8622 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 16 Jul 2024 15:44:56 +0200 Subject: [PATCH 1024/1161] Prepare 4.8.1 (#1759) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53673de85..3b8dcfbc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 4.8.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.8.1. + +### Bug Fixes + +- Guard against empty `REMOTE_ADDR` [(#1751)](https://github.com/getsentry/sentry-php/pull/1751) + ## 4.8.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.8.0. From 61770efd8b7888e0bdd7d234f0ba67b066e47d04 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 16 Jul 2024 13:45:27 +0000 Subject: [PATCH 1025/1161] release: 4.8.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 20a920383..b9506cafd 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.8.0'; + public const SDK_VERSION = '4.8.1'; /** * @var Options The client options From 2c664bb86b8f852119f5b2370d87b27c980653a9 Mon Sep 17 00:00:00 2001 From: Matthew T <20070360+mdtro@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:58:42 -0500 Subject: [PATCH 1026/1161] ci: dependency review action (#1761) --- .github/workflows/dependency-review.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/dependency-review.yml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 000000000..24510de81 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,19 @@ +name: 'Dependency Review' +on: + pull_request: + branches: ['master'] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Dependency Review + uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 + with: + # Possible values: "critical", "high", "moderate", "low" + fail-on-severity: high From d17061989ebd45680023f275d47da7fd9496e664 Mon Sep 17 00:00:00 2001 From: Matthew T <20070360+mdtro@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:58:48 -0500 Subject: [PATCH 1027/1161] Revert "ci: dependency review action (#1761)" (#1762) --- .github/workflows/dependency-review.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/workflows/dependency-review.yml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml deleted file mode 100644 index 24510de81..000000000 --- a/.github/workflows/dependency-review.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: 'Dependency Review' -on: - pull_request: - branches: ['master'] - -permissions: - contents: read - -jobs: - dependency-review: - runs-on: ubuntu-latest - steps: - - name: 'Checkout Repository' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: Dependency Review - uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 - with: - # Possible values: "critical", "high", "moderate", "low" - fail-on-severity: high From 6222d31e22de264f2dba1a26f5e83fd4b36b3d18 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 1 Aug 2024 09:11:26 +0200 Subject: [PATCH 1028/1161] Apply php-cs-fixer changes (#1765) --- src/Client.php | 14 ++++++------- src/Dsn.php | 6 +++--- src/Event.php | 2 +- src/EventHint.php | 8 ++++---- src/FrameBuilder.php | 4 ++-- .../FrameContextifierIntegration.php | 2 +- src/Integration/IntegrationRegistry.php | 4 ++-- src/Integration/RequestIntegration.php | 2 +- src/Logger/DebugFileLogger.php | 2 +- src/Logger/DebugStdOutLogger.php | 2 +- src/Metrics/Types/AbstractType.php | 2 +- src/Monolog/Handler.php | 2 +- src/Profiling/Profile.php | 4 ++-- src/Profiling/Profiler.php | 2 +- src/Serializer/AbstractSerializer.php | 2 +- src/Serializer/EnvelopItems/CheckInItem.php | 2 +- src/Serializer/EnvelopItems/EventItem.php | 2 +- src/Serializer/EnvelopItems/MetricsItem.php | 4 ++-- src/Serializer/EnvelopItems/ProfileItem.php | 2 +- .../EnvelopItems/TransactionItem.php | 2 +- src/Serializer/PayloadSerializer.php | 4 ++-- src/Severity.php | 2 +- src/Stacktrace.php | 4 ++-- src/State/Hub.php | 16 +++++++-------- src/State/Scope.php | 4 ++-- src/Tracing/PropagationContext.php | 4 ++-- src/Tracing/Span.php | 6 +++--- src/Transport/HttpTransport.php | 20 +++++++++---------- src/Transport/RateLimiter.php | 4 ++-- src/UserDataBag.php | 4 ++-- src/Util/JSON.php | 4 ++-- src/Util/SentryUid.php | 2 +- tests/HttpClient/TestServer.php | 2 +- .../FrameContextifierIntegrationTest.php | 2 +- tests/Integration/IntegrationRegistryTest.php | 2 +- 35 files changed, 75 insertions(+), 75 deletions(-) diff --git a/src/Client.php b/src/Client.php index b9506cafd..cc0bc7eb1 100644 --- a/src/Client.php +++ b/src/Client.php @@ -188,7 +188,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scop } } catch (\Throwable $exception) { $this->logger->error( - sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), + \sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), ['exception' => $exception, 'event' => $event] ); } @@ -294,7 +294,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT); } - $eventDescription = sprintf( + $eventDescription = \sprintf( '%s%s [%s]', $event->getLevel() !== null ? $event->getLevel() . ' ' : '', (string) $event->getType(), @@ -306,7 +306,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco // only sample with the `sample_rate` on errors/messages if ($isEvent && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { - $this->logger->info(sprintf('The %s will be discarded because it has been sampled.', $eventDescription), ['event' => $event]); + $this->logger->info(\sprintf('The %s will be discarded because it has been sampled.', $eventDescription), ['event' => $event]); return null; } @@ -323,7 +323,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco if ($event === null) { $this->logger->info( - sprintf('The %s will be discarded because one of the event processors returned "null".', $eventDescription), + \sprintf('The %s will be discarded because one of the event processors returned "null".', $eventDescription), ['event' => $beforeEventProcessors] ); @@ -336,7 +336,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco if ($event === null) { $this->logger->info( - sprintf( + \sprintf( 'The %s will be discarded because the "%s" callback returned "null".', $eventDescription, $this->getBeforeSendCallbackName($beforeSendCallback) @@ -371,7 +371,7 @@ private function applyIgnoreOptions(Event $event, string $eventDescription): ?Ev foreach ($exceptions as $exception) { if ($this->isIgnoredException($exception->getType())) { $this->logger->info( - sprintf('The %s will be discarded because it matches an entry in "ignore_exceptions".', $eventDescription), + \sprintf('The %s will be discarded because it matches an entry in "ignore_exceptions".', $eventDescription), ['event' => $event] ); @@ -389,7 +389,7 @@ private function applyIgnoreOptions(Event $event, string $eventDescription): ?Ev if (\in_array($transactionName, $this->options->getIgnoreTransactions(), true)) { $this->logger->info( - sprintf('The %s will be discarded because it matches a entry in "ignore_transactions".', $eventDescription), + \sprintf('The %s will be discarded because it matches a entry in "ignore_transactions".', $eventDescription), ['event' => $event] ); diff --git a/src/Dsn.php b/src/Dsn.php index 071a6292f..37aaca162 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -72,17 +72,17 @@ public static function createFromString(string $value): self $parsedDsn = parse_url($value); if ($parsedDsn === false) { - throw new \InvalidArgumentException(sprintf('The "%s" DSN is invalid.', $value)); + throw new \InvalidArgumentException(\sprintf('The "%s" DSN is invalid.', $value)); } foreach (['scheme', 'host', 'path', 'user'] as $component) { if (!isset($parsedDsn[$component]) || (isset($parsedDsn[$component]) && empty($parsedDsn[$component]))) { - throw new \InvalidArgumentException(sprintf('The "%s" DSN must contain a scheme, a host, a user and a path component.', $value)); + throw new \InvalidArgumentException(\sprintf('The "%s" DSN must contain a scheme, a host, a user and a path component.', $value)); } } if (!\in_array($parsedDsn['scheme'], ['http', 'https'], true)) { - throw new \InvalidArgumentException(sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value)); + throw new \InvalidArgumentException(\sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value)); } $segmentPaths = explode('/', $parsedDsn['path']); diff --git a/src/Event.php b/src/Event.php index aded2808b..8b096d3cd 100644 --- a/src/Event.php +++ b/src/Event.php @@ -777,7 +777,7 @@ public function setExceptions(array $exceptions): self { foreach ($exceptions as $exception) { if (!$exception instanceof ExceptionDataBag) { - throw new \UnexpectedValueException(sprintf('Expected an instance of the "%s" class. Got: "%s".', ExceptionDataBag::class, get_debug_type($exception))); + throw new \UnexpectedValueException(\sprintf('Expected an instance of the "%s" class. Got: "%s".', ExceptionDataBag::class, get_debug_type($exception))); } } diff --git a/src/EventHint.php b/src/EventHint.php index ceff03315..58b6ffc57 100644 --- a/src/EventHint.php +++ b/src/EventHint.php @@ -56,19 +56,19 @@ public static function fromArray(array $hintData): self $extra = $hintData['extra'] ?? []; if ($exception !== null && !$exception instanceof \Throwable) { - throw new \InvalidArgumentException(sprintf('The value of the "exception" field must be an instance of a class implementing the "%s" interface. Got: "%s".', \Throwable::class, get_debug_type($exception))); + throw new \InvalidArgumentException(\sprintf('The value of the "exception" field must be an instance of a class implementing the "%s" interface. Got: "%s".', \Throwable::class, get_debug_type($exception))); } if ($mechanism !== null && !$mechanism instanceof ExceptionMechanism) { - throw new \InvalidArgumentException(sprintf('The value of the "mechanism" field must be an instance of the "%s" class. Got: "%s".', ExceptionMechanism::class, get_debug_type($mechanism))); + throw new \InvalidArgumentException(\sprintf('The value of the "mechanism" field must be an instance of the "%s" class. Got: "%s".', ExceptionMechanism::class, get_debug_type($mechanism))); } if ($stacktrace !== null && !$stacktrace instanceof Stacktrace) { - throw new \InvalidArgumentException(sprintf('The value of the "stacktrace" field must be an instance of the "%s" class. Got: "%s".', Stacktrace::class, get_debug_type($stacktrace))); + throw new \InvalidArgumentException(\sprintf('The value of the "stacktrace" field must be an instance of the "%s" class. Got: "%s".', Stacktrace::class, get_debug_type($stacktrace))); } if (!\is_array($extra)) { - throw new \InvalidArgumentException(sprintf('The value of the "extra" field must be an array. Got: "%s".', get_debug_type($extra))); + throw new \InvalidArgumentException(\sprintf('The value of the "extra" field must be an array. Got: "%s".', get_debug_type($extra))); } $hint->exception = $exception; diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 143252d57..46aa3461d 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -78,8 +78,8 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac $functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath($this->options, substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX))); } - $rawFunctionName = sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); - $functionName = sprintf('%s::%s', preg_replace('/(?::\d+\$|0x)[a-fA-F0-9]+$/', '', $functionName), $backtraceFrame['function']); + $rawFunctionName = \sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); + $functionName = \sprintf('%s::%s', preg_replace('/(?::\d+\$|0x)[a-fA-F0-9]+$/', '', $functionName), $backtraceFrame['function']); } elseif (isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['function']; } diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 9b1a7b3b6..8935d055d 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -165,7 +165,7 @@ private function getSourceCodeExcerpt(int $maxContextLines, string $filePath, in } } catch (\Throwable $exception) { $this->logger->warning( - sprintf('Failed to get the source code excerpt for the file "%s".', $filePath), + \sprintf('Failed to get the source code excerpt for the file "%s".', $filePath), ['exception' => $exception] ); } diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index 12c91911f..be2df22eb 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -64,7 +64,7 @@ public function setupIntegrations(Options $options, LoggerInterface $logger): ar } if (\count($installed) > 0) { - $logger->debug(sprintf('The "%s" integration(s) have been installed.', implode(', ', $installed))); + $logger->debug(\sprintf('The "%s" integration(s) have been installed.', implode(', ', $installed))); } return $integrations; @@ -123,7 +123,7 @@ private function getIntegrationsToSetup(Options $options): array $integrations = $userIntegrations($defaultIntegrations); if (!\is_array($integrations)) { - throw new \UnexpectedValueException(sprintf('Expected the callback set for the "integrations" option to return a list of integrations. Got: "%s".', get_debug_type($integrations))); + throw new \UnexpectedValueException(\sprintf('Expected the callback set for the "integrations" option to return a list of integrations. Got: "%s".', get_debug_type($integrations))); } } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index d4ab50ce1..72e17ac77 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -267,7 +267,7 @@ private function parseUploadedFiles(array $uploadedFiles): array } elseif (\is_array($item)) { $result[$key] = $this->parseUploadedFiles($item); } else { - throw new \UnexpectedValueException(sprintf('Expected either an object implementing the "%s" interface or an array. Got: "%s".', UploadedFileInterface::class, \is_object($item) ? \get_class($item) : \gettype($item))); + throw new \UnexpectedValueException(\sprintf('Expected either an object implementing the "%s" interface or an array. Got: "%s".', UploadedFileInterface::class, \is_object($item) ? \get_class($item) : \gettype($item))); } } diff --git a/src/Logger/DebugFileLogger.php b/src/Logger/DebugFileLogger.php index 610182f87..abee3948d 100644 --- a/src/Logger/DebugFileLogger.php +++ b/src/Logger/DebugFileLogger.php @@ -24,6 +24,6 @@ public function __construct(string $filePath) */ public function log($level, $message, array $context = []): void { - file_put_contents($this->filePath, sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message), \FILE_APPEND); + file_put_contents($this->filePath, \sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message), \FILE_APPEND); } } diff --git a/src/Logger/DebugStdOutLogger.php b/src/Logger/DebugStdOutLogger.php index eaaba6a10..d83ee31ca 100644 --- a/src/Logger/DebugStdOutLogger.php +++ b/src/Logger/DebugStdOutLogger.php @@ -14,6 +14,6 @@ class DebugStdOutLogger extends AbstractLogger */ public function log($level, $message, array $context = []): void { - file_put_contents('php://stdout', sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message)); + file_put_contents('php://stdout', \sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message)); } } diff --git a/src/Metrics/Types/AbstractType.php b/src/Metrics/Types/AbstractType.php index 1b80bb6c3..ecafca06d 100644 --- a/src/Metrics/Types/AbstractType.php +++ b/src/Metrics/Types/AbstractType.php @@ -119,7 +119,7 @@ public function addCodeLocation(int $stackLevel): void public function getMri(): string { - return sprintf( + return \sprintf( '%s:%s@%s', $this->getType(), $this->getKey(), diff --git a/src/Monolog/Handler.php b/src/Monolog/Handler.php index ddce8f243..3e8d52bba 100644 --- a/src/Monolog/Handler.php +++ b/src/Monolog/Handler.php @@ -55,7 +55,7 @@ protected function doWrite($record): void $event = Event::createEvent(); $event->setLevel(self::getSeverityFromLevel($record['level'])); $event->setMessage($record['message']); - $event->setLogger(sprintf('monolog.%s', $record['channel'])); + $event->setLogger(\sprintf('monolog.%s', $record['channel'])); $hint = new EventHint(); diff --git a/src/Profiling/Profile.php b/src/Profiling/Profile.php index 06d895558..3e88e9cf2 100644 --- a/src/Profiling/Profile.php +++ b/src/Profiling/Profile.php @@ -254,14 +254,14 @@ public function getFormattedData(Event $event): ?array } if (!$this->validateMaxDuration((float) $duration)) { - $this->logger->warning(sprintf('The profile is %ss which is longer than the allowed %ss, the profile will be discarded.', (float) $duration, self::MAX_PROFILE_DURATION)); + $this->logger->warning(\sprintf('The profile is %ss which is longer than the allowed %ss, the profile will be discarded.', (float) $duration, self::MAX_PROFILE_DURATION)); return null; } $startTime = \DateTime::createFromFormat('U.u', number_format($this->startTimeStamp, 4, '.', ''), new \DateTimeZone('UTC')); if ($startTime === false) { - $this->logger->warning(sprintf('The start time (%s) of the profile is not valid, the profile will be discarded.', $this->startTimeStamp)); + $this->logger->warning(\sprintf('The start time (%s) of the profile is not valid, the profile will be discarded.', $this->startTimeStamp)); return null; } diff --git a/src/Profiling/Profiler.php b/src/Profiling/Profiler.php index 7f4eb37c7..f379b53b2 100644 --- a/src/Profiling/Profiler.php +++ b/src/Profiling/Profiler.php @@ -82,7 +82,7 @@ private function initProfiler(): void $this->profiler = new \ExcimerProfiler(); $this->profile->setStartTimeStamp(microtime(true)); - $this->profiler->setEventType(EXCIMER_REAL); + $this->profiler->setEventType(\EXCIMER_REAL); $this->profiler->setPeriod(self::SAMPLE_RATE); $this->profiler->setMaxDepth(self::MAX_STACK_DEPTH); } diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 766175a9e..07bc36118 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -285,7 +285,7 @@ protected function serializeCallable($callable): string } if (!\is_callable($callable)) { - throw new \InvalidArgumentException(sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); + throw new \InvalidArgumentException(\sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); } try { diff --git a/src/Serializer/EnvelopItems/CheckInItem.php b/src/Serializer/EnvelopItems/CheckInItem.php index e3e2778bf..c996db4c3 100644 --- a/src/Serializer/EnvelopItems/CheckInItem.php +++ b/src/Serializer/EnvelopItems/CheckInItem.php @@ -41,6 +41,6 @@ public static function toEnvelopeItem(Event $event): string } } - return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); } } diff --git a/src/Serializer/EnvelopItems/EventItem.php b/src/Serializer/EnvelopItems/EventItem.php index 9b0eb50fe..dd37f70ff 100644 --- a/src/Serializer/EnvelopItems/EventItem.php +++ b/src/Serializer/EnvelopItems/EventItem.php @@ -144,7 +144,7 @@ public static function toEnvelopeItem(Event $event): string ]; } - return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); } /** diff --git a/src/Serializer/EnvelopItems/MetricsItem.php b/src/Serializer/EnvelopItems/MetricsItem.php index 7109f09b7..8b78ccec2 100644 --- a/src/Serializer/EnvelopItems/MetricsItem.php +++ b/src/Serializer/EnvelopItems/MetricsItem.php @@ -71,7 +71,7 @@ public static function toEnvelopeItem(Event $event): string 'length' => mb_strlen($metricMetaPayload), ]; - return sprintf( + return \sprintf( "%s\n%s\n%s\n%s", JSON::encode($statsdHeader), $statsdPayload, @@ -80,7 +80,7 @@ public static function toEnvelopeItem(Event $event): string ); } - return sprintf( + return \sprintf( "%s\n%s", JSON::encode($statsdHeader), $statsdPayload diff --git a/src/Serializer/EnvelopItems/ProfileItem.php b/src/Serializer/EnvelopItems/ProfileItem.php index 90fdca9ec..646506478 100644 --- a/src/Serializer/EnvelopItems/ProfileItem.php +++ b/src/Serializer/EnvelopItems/ProfileItem.php @@ -30,6 +30,6 @@ public static function toEnvelopeItem(Event $event): string return ''; } - return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); } } diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index 91520922d..99ffbc2ae 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -134,7 +134,7 @@ public static function toEnvelopeItem(Event $event): string $payload['transaction_info']['source'] = (string) $transactionMetadata->getSource(); } - return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); } /** diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index e531a9a0e..7dbfd2aa9 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -69,7 +69,7 @@ public function serialize(Event $event): string if ($event->getSdkMetadata('profile') !== null) { $profileItem = ProfileItem::toEnvelopeItem($event); if ($profileItem !== '') { - $items = sprintf("%s\n%s", $transactionItem, $profileItem); + $items = \sprintf("%s\n%s", $transactionItem, $profileItem); break; } } @@ -83,6 +83,6 @@ public function serialize(Event $event): string break; } - return sprintf("%s\n%s", JSON::encode($envelopeHeader), $items); + return \sprintf("%s\n%s", JSON::encode($envelopeHeader), $items); } } diff --git a/src/Severity.php b/src/Severity.php index 8fdf161b3..d0fa899d0 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -73,7 +73,7 @@ final class Severity implements \Stringable public function __construct(string $value = self::INFO) { if (!\in_array($value, self::ALLOWED_SEVERITIES, true)) { - throw new \InvalidArgumentException(sprintf('The "%s" is not a valid enum value.', $value)); + throw new \InvalidArgumentException(\sprintf('The "%s" is not a valid enum value.', $value)); } $this->value = $value; diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 52633602c..f75d73269 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -31,7 +31,7 @@ public function __construct(array $frames) foreach ($frames as $frame) { if (!$frame instanceof Frame) { - throw new \UnexpectedValueException(sprintf('Expected an instance of the "%s" class. Got: "%s".', Frame::class, get_debug_type($frame))); + throw new \UnexpectedValueException(\sprintf('Expected an instance of the "%s" class. Got: "%s".', Frame::class, get_debug_type($frame))); } } @@ -86,7 +86,7 @@ public function addFrame(Frame $frame): self public function removeFrame(int $index): self { if (!isset($this->frames[$index])) { - throw new \OutOfBoundsException(sprintf('Cannot remove the frame at index %d.', $index)); + throw new \OutOfBoundsException(\sprintf('Cannot remove the frame at index %d.', $index)); } if (\count($this->frames) === 1) { diff --git a/src/State/Hub.php b/src/State/Hub.php index a1c01a5e2..4fa9a4382 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -260,7 +260,7 @@ public function startTransaction(TransactionContext $context, array $customSampl if ($options === null || !$options->isTracingEnabled()) { $transaction->setSampled(false); - $logger->warning(sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]); + $logger->warning(\sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]); return $transaction; } @@ -289,7 +289,7 @@ public function startTransaction(TransactionContext $context, array $customSampl if (!$this->isValidSampleRate($sampleRate)) { $transaction->setSampled(false); - $logger->warning(sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + $logger->warning(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); return $transaction; } @@ -299,7 +299,7 @@ public function startTransaction(TransactionContext $context, array $customSampl if ($sampleRate === 0.0) { $transaction->setSampled(false); - $logger->info(sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is %s.', (string) $transaction->getTraceId(), $sampleSource, $sampleRate), ['context' => $context]); + $logger->info(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is %s.', (string) $transaction->getTraceId(), $sampleSource, $sampleRate), ['context' => $context]); return $transaction; } @@ -308,24 +308,24 @@ public function startTransaction(TransactionContext $context, array $customSampl } if (!$transaction->getSampled()) { - $logger->info(sprintf('Transaction [%s] was started but not sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + $logger->info(\sprintf('Transaction [%s] was started but not sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); return $transaction; } - $logger->info(sprintf('Transaction [%s] was started and sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + $logger->info(\sprintf('Transaction [%s] was started and sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); $transaction->initSpanRecorder(); $profilesSampleRate = $options->getProfilesSampleRate(); if ($profilesSampleRate === null) { - $logger->info(sprintf('Transaction [%s] is not profiling because `profiles_sample_rate` option is not set.', (string) $transaction->getTraceId())); + $logger->info(\sprintf('Transaction [%s] is not profiling because `profiles_sample_rate` option is not set.', (string) $transaction->getTraceId())); } elseif ($this->sample($profilesSampleRate)) { - $logger->info(sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId())); + $logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId())); $transaction->initProfiler()->start(); } else { - $logger->info(sprintf('Transaction [%s] is not profiling because it was not sampled.', (string) $transaction->getTraceId())); + $logger->info(\sprintf('Transaction [%s] is not profiling because it was not sampled.', (string) $transaction->getTraceId())); } return $transaction; diff --git a/src/State/Scope.php b/src/State/Scope.php index 546072f4e..e4e054c3c 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -208,7 +208,7 @@ public function getUser(): ?UserDataBag public function setUser($user): self { if (!\is_array($user) && !$user instanceof UserDataBag) { - throw new \TypeError(sprintf('The $user argument must be either an array or an instance of the "%s" class. Got: "%s".', UserDataBag::class, get_debug_type($user))); + throw new \TypeError(\sprintf('The $user argument must be either an array or an instance of the "%s" class. Got: "%s".', UserDataBag::class, get_debug_type($user))); } if (\is_array($user)) { @@ -419,7 +419,7 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op } if (!$event instanceof Event) { - throw new \InvalidArgumentException(sprintf('The event processor must return null or an instance of the %s class', Event::class)); + throw new \InvalidArgumentException(\sprintf('The event processor must return null or an instance of the %s class', Event::class)); } } diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index f63dc4285..fce1b2207 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -64,7 +64,7 @@ public static function fromEnvironment(string $sentryTrace, string $baggage): se */ public function toTraceparent(): string { - return sprintf('%s-%s', (string) $this->traceId, (string) $this->spanId); + return \sprintf('%s-%s', (string) $this->traceId, (string) $this->spanId); } /** @@ -72,7 +72,7 @@ public function toTraceparent(): string */ public function toW3CTraceparent(): string { - return sprintf('00-%s-%s-00', (string) $this->traceId, (string) $this->spanId); + return \sprintf('00-%s-%s-00', (string) $this->traceId, (string) $this->spanId); } /** diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 17d4ea25f..649d8e7cb 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -510,7 +510,7 @@ public function setMetricsSummary( MetricsUnit $unit, array $tags ): void { - $mri = sprintf('%s:%s@%s', $type, $key, (string) $unit); + $mri = \sprintf('%s:%s@%s', $type, $key, (string) $unit); $bucketKey = $mri . serialize($tags); if ( @@ -567,7 +567,7 @@ public function toTraceparent(): string $sampled = $this->sampled ? '-1' : '-0'; } - return sprintf('%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled); + return \sprintf('%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled); } /** @@ -584,7 +584,7 @@ public function toW3CTraceparent(): string $sampled = '00'; } - return sprintf('00-%s-%s-%s', (string) $this->traceId, (string) $this->spanId, $sampled); + return \sprintf('00-%s-%s-%s', (string) $this->traceId, (string) $this->spanId, $sampled); } /** diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 6fab31833..db9ef1046 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -69,7 +69,7 @@ public function send(Event $event): Result { $this->sendRequestToSpotlight($event); - $eventDescription = sprintf( + $eventDescription = \sprintf( '%s%s [%s]', $event->getLevel() !== null ? $event->getLevel() . ' ' : '', (string) $event->getType(), @@ -77,23 +77,23 @@ public function send(Event $event): Result ); if ($this->options->getDsn() === null) { - $this->logger->info(sprintf('Skipping %s, because no DSN is set.', $eventDescription), ['event' => $event]); + $this->logger->info(\sprintf('Skipping %s, because no DSN is set.', $eventDescription), ['event' => $event]); return new Result(ResultStatus::skipped(), $event); } - $targetDescription = sprintf( + $targetDescription = \sprintf( '%s [project:%s]', $this->options->getDsn()->getHost(), $this->options->getDsn()->getProjectId() ); - $this->logger->info(sprintf('Sending %s to %s.', $eventDescription, $targetDescription), ['event' => $event]); + $this->logger->info(\sprintf('Sending %s to %s.', $eventDescription, $targetDescription), ['event' => $event]); $eventType = $event->getType(); if ($this->rateLimiter->isRateLimited($eventType)) { $this->logger->warning( - sprintf('Rate limit exceeded for sending requests of type "%s".', (string) $eventType), + \sprintf('Rate limit exceeded for sending requests of type "%s".', (string) $eventType), ['event' => $event] ); @@ -107,7 +107,7 @@ public function send(Event $event): Result $response = $this->httpClient->sendRequest($request, $this->options); } catch (\Throwable $exception) { $this->logger->error( - sprintf('Failed to send %s to %s. Reason: "%s".', $eventDescription, $targetDescription, $exception->getMessage()), + \sprintf('Failed to send %s to %s. Reason: "%s".', $eventDescription, $targetDescription, $exception->getMessage()), ['exception' => $exception, 'event' => $event] ); @@ -116,7 +116,7 @@ public function send(Event $event): Result if ($response->hasError()) { $this->logger->error( - sprintf('Failed to send %s to %s. Reason: "%s".', $eventDescription, $targetDescription, $response->getError()), + \sprintf('Failed to send %s to %s. Reason: "%s".', $eventDescription, $targetDescription, $response->getError()), ['event' => $event] ); @@ -128,7 +128,7 @@ public function send(Event $event): Result $resultStatus = ResultStatus::createFromHttpStatusCode($response->getStatusCode()); $this->logger->info( - sprintf('Sent %s to %s. Result: "%s" (status: %s).', $eventDescription, $targetDescription, strtolower((string) $resultStatus), $response->getStatusCode()), + \sprintf('Sent %s to %s. Result: "%s" (status: %s).', $eventDescription, $targetDescription, strtolower((string) $resultStatus), $response->getStatusCode()), ['response' => $response, 'event' => $event] ); @@ -168,13 +168,13 @@ private function sendRequestToSpotlight(Event $event): void if ($spotLightResponse->hasError()) { $this->logger->info( - sprintf('Failed to send the event to Spotlight. Reason: "%s".', $spotLightResponse->getError()), + \sprintf('Failed to send the event to Spotlight. Reason: "%s".', $spotLightResponse->getError()), ['event' => $event] ); } } catch (\Throwable $exception) { $this->logger->info( - sprintf('Failed to send the event to Spotlight. Reason: "%s".', $exception->getMessage()), + \sprintf('Failed to send the event to Spotlight. Reason: "%s".', $exception->getMessage()), ['exception' => $exception, 'event' => $event] ); } diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index dbc8bcf22..6a4f89bf8 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -94,7 +94,7 @@ public function handleResponse(Response $response): bool } $this->logger->warning( - sprintf('Rate limited exceeded for category "%s", backing off until "%s".', $category, gmdate(\DATE_ATOM, $retryAfter)) + \sprintf('Rate limited exceeded for category "%s", backing off until "%s".', $category, gmdate(\DATE_ATOM, $retryAfter)) ); } } @@ -108,7 +108,7 @@ public function handleResponse(Response $response): bool $this->rateLimits['all'] = $retryAfter; $this->logger->warning( - sprintf('Rate limited exceeded for all categories, backing off until "%s".', gmdate(\DATE_ATOM, $retryAfter)) + \sprintf('Rate limited exceeded for all categories, backing off until "%s".', gmdate(\DATE_ATOM, $retryAfter)) ); return true; diff --git a/src/UserDataBag.php b/src/UserDataBag.php index ec536faef..dcc374f1f 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -133,7 +133,7 @@ public function getId() public function setId($id): self { if ($id !== null && !\is_string($id) && !\is_int($id)) { - throw new \UnexpectedValueException(sprintf('Expected an integer or string value for the $id argument. Got: "%s".', get_debug_type($id))); + throw new \UnexpectedValueException(\sprintf('Expected an integer or string value for the $id argument. Got: "%s".', get_debug_type($id))); } $this->id = $id; @@ -221,7 +221,7 @@ public function getIpAddress(): ?string public function setIpAddress(?string $ipAddress): self { if ($ipAddress !== null && filter_var($ipAddress, \FILTER_VALIDATE_IP) === false) { - throw new \InvalidArgumentException(sprintf('The "%s" value is not a valid IP address.', $ipAddress)); + throw new \InvalidArgumentException(\sprintf('The "%s" value is not a valid IP address.', $ipAddress)); } $this->ipAddress = $ipAddress; diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 9824cbd74..48b843423 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -42,7 +42,7 @@ public static function encode($data, int $options = 0, int $maxDepth = 512): str $encounteredAnyError = json_last_error() !== \JSON_ERROR_NONE; if (($encounteredAnyError && ($encodedData === 'null' || $encodedData === false)) || !\in_array(json_last_error(), $allowedErrors, true)) { - throw new JsonException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); + throw new JsonException(\sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); } return $encodedData; @@ -62,7 +62,7 @@ public static function decode(string $data) $decodedData = json_decode($data, true); if (json_last_error() !== \JSON_ERROR_NONE) { - throw new JsonException(sprintf('Could not decode value from JSON format. Error was: "%s".', json_last_error_msg())); + throw new JsonException(\sprintf('Could not decode value from JSON format. Error was: "%s".', json_last_error_msg())); } return $decodedData; diff --git a/src/Util/SentryUid.php b/src/Util/SentryUid.php index 7adbc1cca..253952572 100644 --- a/src/Util/SentryUid.php +++ b/src/Util/SentryUid.php @@ -22,7 +22,7 @@ public static function generate(): string $uuid = bin2hex(random_bytes(16)); - return sprintf('%08s%04s4%03s%04x%012s', + return \sprintf('%08s%04s4%03s%04x%012s', // 32 bits for "time_low" substr($uuid, 0, 8), // 16 bits for "time_mid" diff --git a/tests/HttpClient/TestServer.php b/tests/HttpClient/TestServer.php index 901dc26f6..d915187ef 100644 --- a/tests/HttpClient/TestServer.php +++ b/tests/HttpClient/TestServer.php @@ -49,7 +49,7 @@ public function startTestServer(): string $pipes = []; $this->serverProcess = proc_open( - $command = sprintf( + $command = \sprintf( 'php -S localhost:%d -t %s', $this->serverPort, realpath(__DIR__ . '/../testserver') diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index e4458be4c..5667826b0 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -161,7 +161,7 @@ private function getFixtureFileContent(string $file): string $fileContent = file_get_contents($file); if ($fileContent === false) { - throw new \RuntimeException(sprintf('The fixture file at path "%s" could not be read.', $file)); + throw new \RuntimeException(\sprintf('The fixture file at path "%s" could not be read.', $file)); } return $fileContent; diff --git a/tests/Integration/IntegrationRegistryTest.php b/tests/Integration/IntegrationRegistryTest.php index 63e46b29a..fefa624e7 100644 --- a/tests/Integration/IntegrationRegistryTest.php +++ b/tests/Integration/IntegrationRegistryTest.php @@ -38,7 +38,7 @@ public function testSetupIntegrations(Options $options, array $expectedIntegrati if (\count($expectedIntegrations) > 0) { $logger->expects($this->once()) ->method('debug') - ->with(sprintf('The "%s" integration(s) have been installed.', implode(', ', array_keys($expectedIntegrations))), []); + ->with(\sprintf('The "%s" integration(s) have been installed.', implode(', ', array_keys($expectedIntegrations))), []); } else { $logger->expects($this->never()) ->method('debug'); From 86ddd655307728b43ac752182cb4b0c1a545615c Mon Sep 17 00:00:00 2001 From: BenjaminEllisSo <129332754+BenjaminEllisSo@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:40:05 +0100 Subject: [PATCH 1029/1161] Update functions.php to correct options (#1768) --- src/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functions.php b/src/functions.php index 0100cae3e..1865d397c 100644 --- a/src/functions.php +++ b/src/functions.php @@ -47,7 +47,7 @@ * max_request_body_size?: "none"|"never"|"small"|"medium"|"always", * max_value_length?: int, * prefixes?: array, - * profiler_sample_rate?: int|float|null, + * profiles_sample_rate?: int|float|null, * release?: string|null, * sample_rate?: float|int, * send_attempts?: int, From ec033e3c22fda3ef408dcfa8efa91c0a8d3a6994 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 5 Aug 2024 13:44:21 +0200 Subject: [PATCH 1030/1161] =?UTF-8?q?Allow=20retrieving=20a=20single=20pie?= =?UTF-8?q?ce=20of=20data=20from=20the=20span=20by=20it=E2=80=99s=20key=20?= =?UTF-8?q?(#1767)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michi Hoffmann --- src/Tracing/Span.php | 18 ++++++++++++------ tests/Tracing/SpanTest.php | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 649d8e7cb..382c921a0 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -357,18 +357,24 @@ public function setSampled(?bool $sampled) } /** - * Gets a map of arbitrary data. + * Gets a map of arbitrary data or a specific key from the map of data attached to this span. * - * @return array + * @param string|null $key Select a specific key from the data to return the value of + * @param mixed $default When the $key is not found, return this value + * + * @return ($key is null ? array : mixed|null) */ - public function getData(): array + public function getData(?string $key = null, $default = null) { - return $this->data; + if ($key === null) { + return $this->data; + } + + return $this->data[$key] ?? $default; } /** - * Sets a map of arbitrary data. This method will merge the given data with - * the existing one. + * Sets a map of arbitrary data. This method will merge the given data with the existing one. * * @param array $data The data * diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index 0f851a092..96ada1065 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -272,4 +272,41 @@ public function testMetricsSummary(): void ], ], $span->getMetricsSummary()); } + + public function testDataGetter(): void + { + $span = new Span(); + + $initialData = [ + 'foo' => 'bar', + 'baz' => 1, + ]; + + $span->setData($initialData); + + $this->assertSame($initialData, $span->getData()); + $this->assertSame('bar', $span->getData('foo')); + $this->assertSame(1, $span->getData('baz')); + } + + public function testDataIsMergedWhenSet(): void + { + $span = new Span(); + + $span->setData([ + 'foo' => 'bar', + 'baz' => 1, + ]); + + $span->setData([ + 'baz' => 2, + ]); + + $this->assertSame(2, $span->getData('baz')); + $this->assertSame('bar', $span->getData('foo')); + $this->assertSame([ + 'foo' => 'bar', + 'baz' => 2, + ], $span->getData()); + } } From 677ffabecb1795d88470726f0998c1e7c139bc2c Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 6 Aug 2024 11:30:39 +0200 Subject: [PATCH 1031/1161] Add span trace origin (#1769) --- src/Metrics/Metrics.php | 1 + .../EnvelopItems/TransactionItem.php | 1 + src/Tracing/GuzzleTracingMiddleware.php | 3 +- src/Tracing/Span.php | 32 ++++++++++++++++++- src/Tracing/SpanContext.php | 20 ++++++++++++ tests/Serializer/PayloadSerializerTest.php | 4 +-- tests/State/ScopeTest.php | 1 + tests/Tracing/SpanTest.php | 10 ++++++ 8 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php index 2c9d8c504..868944fa9 100644 --- a/src/Metrics/Metrics.php +++ b/src/Metrics/Metrics.php @@ -166,6 +166,7 @@ function () use ($callback, $key, $tags, $stackLevel) { }, SpanContext::make() ->setOp('metric.timing') + ->setOrigin('auto.measure.metrics.timing') ->setDescription($key) ); } diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index 99ffbc2ae..9d102f3b4 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -160,6 +160,7 @@ protected static function serializeSpan(Span $span): array 'span_id' => (string) $span->getSpanId(), 'trace_id' => (string) $span->getTraceId(), 'start_timestamp' => $span->getStartTimestamp(), + 'origin' => $span->getOrigin() ?? 'manual', ]; if ($span->getParentSpanId() !== null) { diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 6dfde7840..454923c5d 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -54,8 +54,9 @@ public static function trace(?HubInterface $hub = null): \Closure if ($parentSpan !== null && $parentSpan->getSampled()) { $spanContext = new SpanContext(); $spanContext->setOp('http.client'); - $spanContext->setDescription($request->getMethod() . ' ' . $partialUri); $spanContext->setData($spanAndBreadcrumbData); + $spanContext->setOrigin('auto.http.guzzle'); + $spanContext->setDescription($request->getMethod() . ' ' . $partialUri); $childSpan = $parentSpan->startChild($spanContext); } diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 382c921a0..8810edb13 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -93,6 +93,13 @@ class Span */ protected $metricsSummary = []; + /** + * @var string|null The trace origin of the span. If no origin is set, the span is considered to be "manual". + * + * @see https://develop.sentry.dev/sdk/performance/trace-origin/ + */ + protected $origin; + /** * Constructor. * @@ -121,6 +128,7 @@ public function __construct(?SpanContext $context = null) $this->tags = $context->getTags(); $this->data = $context->getData(); $this->endTimestamp = $context->getEndTimestamp(); + $this->origin = $context->getOrigin(); } /** @@ -400,7 +408,8 @@ public function setData(array $data) * span_id: string, * status?: string, * tags?: array, - * trace_id: string + * trace_id: string, + * origin: string, * } */ public function getTraceContext(): array @@ -408,6 +417,7 @@ public function getTraceContext(): array $result = [ 'span_id' => (string) $this->spanId, 'trace_id' => (string) $this->traceId, + 'origin' => $this->origin ?? 'manual', ]; if ($this->parentSpanId !== null) { @@ -554,6 +564,26 @@ public function setMetricsSummary( } } + /** + * Sets the trace origin for this span. + */ + public function getOrigin(): ?string + { + return $this->origin; + } + + /** + * Sets the trace origin of the span. + * + * @return $this + */ + public function setOrigin(?string $origin) + { + $this->origin = $origin; + + return $this; + } + /** * Returns the transaction containing this span. */ diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index a4a22e2e2..e00208742 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -61,6 +61,11 @@ class SpanContext */ private $endTimestamp; + /** + * @var string|null the trace origin of the span + */ + private $origin; + /** * @return self */ @@ -243,4 +248,19 @@ public function setEndTimestamp(?float $endTimestamp) return $this; } + + public function getOrigin(): ?string + { + return $this->origin; + } + + /** + * @return $this + */ + public function setOrigin(?string $origin) + { + $this->origin = $origin; + + return $this; + } } diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 0e3cd8a4f..1d528ca8d 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -302,7 +302,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable << [ 'span_id' => '566e3688a61d4bc8', 'trace_id' => '566e3688a61d4bc888951642d6f14a19', + 'origin' => 'manual', 'parent_span_id' => '8c2df92a922b4efe', ], 'barcontext' => [ diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index 96ada1065..ae9f9ec1d 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -309,4 +309,14 @@ public function testDataIsMergedWhenSet(): void 'baz' => 2, ], $span->getData()); } + + public function testOriginIsCopiedFromContext(): void + { + $context = SpanContext::make()->setOrigin('auto.testing'); + + $span = new Span($context); + + $this->assertSame($context->getOrigin(), $span->getOrigin()); + $this->assertSame($context->getOrigin(), $span->getTraceContext()['origin']); + } } From 82ef46db8bb60f9f337d0cdac2020edc7b9535d8 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 6 Aug 2024 15:20:37 +0200 Subject: [PATCH 1032/1161] Update README banner (#1770) --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8891ae0f2..8d8c62c97 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -

- - Sentry - -

+ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_ From 6b6cde7717d949ff86ab5a783c3c0f98a8e5ec8b Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 8 Aug 2024 16:33:16 +0200 Subject: [PATCH 1033/1161] Prepare 4.9.0 (#1771) --- CHANGELOG.md | 16 ++++++++++++++++ tests/Integration/IntegrationRegistryTest.php | 4 ++-- tests/Serializer/SerializerTest.php | 2 +- tests/StacktraceTest.php | 2 +- tests/Tracing/SpanRecorderTest.php | 2 +- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8dcfbc3..7a2908bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # CHANGELOG +## 4.9.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.9.0. + +### Features + +- Allow retrieving a single piece of data from the span by it’s key [(#1767)](https://github.com/getsentry/sentry-php/pull/1767) + + ```php + \Sentry\SentrySdk::getCurrentHub()->getSpan()?->setData([ + 'failure' => $span->getData('failure', 0) + 1, + ]); + ``` + +- Add span trace origin [(#1769)](https://github.com/getsentry/sentry-php/pull/1769) + ## 4.8.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.8.1. diff --git a/tests/Integration/IntegrationRegistryTest.php b/tests/Integration/IntegrationRegistryTest.php index fefa624e7..43caf2872 100644 --- a/tests/Integration/IntegrationRegistryTest.php +++ b/tests/Integration/IntegrationRegistryTest.php @@ -49,13 +49,13 @@ public function testSetupIntegrations(Options $options, array $expectedIntegrati public static function setupIntegrationsDataProvider(): iterable { - $integration1 = new class() implements IntegrationInterface { + $integration1 = new class implements IntegrationInterface { public function setupOnce(): void { } }; - $integration2 = new class() implements IntegrationInterface { + $integration2 = new class implements IntegrationInterface { public function setupOnce(): void { } diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index 43dccf209..39329e665 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -99,7 +99,7 @@ public function testNull(bool $serializeAllObjects): void public function testRegisteredObjectSerializers(): void { - $object = new class() { + $object = new class { public function getPurpose(): string { return 'To be tested!'; diff --git a/tests/StacktraceTest.php b/tests/StacktraceTest.php index 27704821c..be0ae1ee0 100644 --- a/tests/StacktraceTest.php +++ b/tests/StacktraceTest.php @@ -48,7 +48,7 @@ public static function constructorThrowsIfFramesListContainsUnexpectedValueDataP ]; yield [ - [new class() { + [new class { }], '/^Expected an instance of the "Sentry\\\\Frame" class\. Got: "class@anonymous.*"\.$/', ]; diff --git a/tests/Tracing/SpanRecorderTest.php b/tests/Tracing/SpanRecorderTest.php index 8a8d085be..64c676ddf 100644 --- a/tests/Tracing/SpanRecorderTest.php +++ b/tests/Tracing/SpanRecorderTest.php @@ -13,7 +13,7 @@ final class SpanRecorderTest extends TestCase { public function testAdd(): void { - $span1 = new class() extends Span { + $span1 = new class extends Span { public function __construct() { parent::__construct(); From 788ec170f51ebb22f2809a1e3f78b19ccd39b70d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 8 Aug 2024 14:40:50 +0000 Subject: [PATCH 1034/1161] release: 4.9.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index cc0bc7eb1..b97a19c0b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.8.1'; + public const SDK_VERSION = '4.9.0'; /** * @var Options The client options From aa207f50793d43a2bca2f51a1db47a863bad9138 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 4 Sep 2024 09:40:10 +0200 Subject: [PATCH 1035/1161] Set `http` breadcrumb level based on response code (#1773) --- src/Tracing/GuzzleTracingMiddleware.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 454923c5d..4fb1b45f7 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -84,9 +84,17 @@ public static function trace(?HubInterface $hub = null): \Closure $response = $responseOrException->getResponse(); } + $breadcrumbLevel = Breadcrumb::LEVEL_INFO; + if ($response !== null) { $spanAndBreadcrumbData['http.response.body.size'] = $response->getBody()->getSize(); $spanAndBreadcrumbData['http.response.status_code'] = $response->getStatusCode(); + + if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) { + $breadcrumbLevel = Breadcrumb::LEVEL_WARNING; + } elseif ($response->getStatusCode() >= 500) { + $breadcrumbLevel = Breadcrumb::LEVEL_ERROR; + } } if ($childSpan !== null) { @@ -99,7 +107,7 @@ public static function trace(?HubInterface $hub = null): \Closure } $hub->addBreadcrumb(new Breadcrumb( - Breadcrumb::LEVEL_INFO, + $breadcrumbLevel, Breadcrumb::TYPE_HTTP, 'http', null, From 0ecfac57990b6907425f9fb6018595a8b897c9c1 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 5 Sep 2024 11:08:27 +0200 Subject: [PATCH 1036/1161] Fix CS --- .php-cs-fixer.dist.php | 7 ++++--- src/Logger/DebugFileLogger.php | 5 +++-- src/Logger/DebugStdOutLogger.php | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 3975a591f..6a94c4c5c 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -18,9 +18,6 @@ ], 'self_accessor' => false, 'modernize_strpos' => false, - 'nullable_type_declaration_for_default_null_value' => [ - 'use_nullable_type_declaration' => true, - ], 'no_superfluous_phpdoc_tags' => [ 'allow_mixed' => true, ], @@ -33,6 +30,10 @@ 'method' => 'multi', 'property' => 'multi', ], + 'trailing_comma_in_multiline' => [ + 'after_heredoc' => false, + 'elements' => ['arrays'], + ], ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/src/Logger/DebugFileLogger.php b/src/Logger/DebugFileLogger.php index abee3948d..4875baa75 100644 --- a/src/Logger/DebugFileLogger.php +++ b/src/Logger/DebugFileLogger.php @@ -19,8 +19,9 @@ public function __construct(string $filePath) } /** - * @param mixed $level - * @param mixed[] $context + * @param mixed $level + * @param string|\Stringable $message + * @param mixed[] $context */ public function log($level, $message, array $context = []): void { diff --git a/src/Logger/DebugStdOutLogger.php b/src/Logger/DebugStdOutLogger.php index d83ee31ca..5b2da8faf 100644 --- a/src/Logger/DebugStdOutLogger.php +++ b/src/Logger/DebugStdOutLogger.php @@ -9,8 +9,9 @@ class DebugStdOutLogger extends AbstractLogger { /** - * @param mixed $level - * @param mixed[] $context + * @param mixed $level + * @param string|\Stringable $message + * @param mixed[] $context */ public function log($level, $message, array $context = []): void { From 383514bb4326e1e229d9dff0c97ccfba55289225 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 5 Sep 2024 11:27:54 +0200 Subject: [PATCH 1037/1161] Expose setting `CURLSSLOPT_NATIVE_CA` as an option (#1776) --- phpstan-baseline.neon | 5 +++++ src/HttpClient/HttpClient.php | 11 +++++++++++ src/Options.php | 15 +++++++++++++++ tests/OptionsTest.php | 7 +++++++ 4 files changed, 38 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e981a4f43..e3c69ed59 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -125,6 +125,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpSslNativeCa\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getHttpSslVerifyPeer\\(\\) should return bool but returns mixed\\.$#" count: 1 diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index a5c64847c..e94452a14 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -78,6 +78,17 @@ public function sendRequest(Request $request, Options $options): Response curl_setopt($curlHandle, \CURLOPT_SSL_VERIFYPEER, false); } + $httpSslNativeCa = $options->getHttpSslNativeCa(); + if ($httpSslNativeCa) { + if ( + \defined('CURLSSLOPT_NATIVE_CA') + && isset(curl_version()['version']) + && version_compare(curl_version()['version'], '7.71', '>=') + ) { + curl_setopt($curlHandle, \CURLOPT_SSL_OPTIONS, \CURLSSLOPT_NATIVE_CA); + } + } + $httpProxy = $options->getHttpProxy(); if ($httpProxy !== null) { curl_setopt($curlHandle, \CURLOPT_PROXY, $httpProxy); diff --git a/src/Options.php b/src/Options.php index fe5b38103..34ebd5a7c 100644 --- a/src/Options.php +++ b/src/Options.php @@ -942,6 +942,20 @@ public function setHttpSslVerifyPeer(bool $httpSslVerifyPeer): self return $this; } + public function getHttpSslNativeCa(): bool + { + return $this->options['http_ssl_native_ca']; + } + + public function setHttpSslNativeCa(bool $httpSslNativeCa): self + { + $options = array_merge($this->options, ['http_ssl_native_ca' => $httpSslNativeCa]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Returns whether the requests should be compressed using GZIP or not. */ @@ -1139,6 +1153,7 @@ private function configureOptions(OptionsResolver $resolver): void 'http_connect_timeout' => self::DEFAULT_HTTP_CONNECT_TIMEOUT, 'http_timeout' => self::DEFAULT_HTTP_TIMEOUT, 'http_ssl_verify_peer' => true, + 'http_ssl_native_ca' => false, 'http_compression' => true, 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 9347272f2..ddae2f913 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -362,6 +362,13 @@ static function (): void {}, 'setHttpSslVerifyPeer', ]; + yield [ + 'http_ssl_native_ca', + true, + 'getHttpSslNativeCa', + 'setHttpSslNativeCa', + ]; + yield [ 'http_compression', false, From af7955a3d70f4232df8989b54ee8abb05ec67d04 Mon Sep 17 00:00:00 2001 From: Aleksa Nikolic <50905985+nikolicaleksa@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:21:56 +0100 Subject: [PATCH 1038/1161] feat: Add support timeouts in milliseconds for HTTP client (#1783) --- src/HttpClient/HttpClient.php | 16 ++++++++++++++-- tests/OptionsTest.php | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index e94452a14..879e570e0 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -64,8 +64,6 @@ public function sendRequest(Request $request, Options $options): Response curl_setopt($curlHandle, \CURLOPT_URL, $dsn->getEnvelopeApiEndpointUrl()); curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, $requestHeaders); curl_setopt($curlHandle, \CURLOPT_USERAGENT, $this->sdkIdentifier . '/' . $this->sdkVersion); - curl_setopt($curlHandle, \CURLOPT_TIMEOUT, $options->getHttpTimeout()); - curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, $options->getHttpConnectTimeout()); curl_setopt($curlHandle, \CURLOPT_ENCODING, ''); curl_setopt($curlHandle, \CURLOPT_POST, true); curl_setopt($curlHandle, \CURLOPT_POSTFIELDS, $requestData); @@ -73,6 +71,20 @@ public function sendRequest(Request $request, Options $options): Response curl_setopt($curlHandle, \CURLOPT_HEADERFUNCTION, $responseHeaderCallback); curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1); + $httpTimeout = $options->getHttpTimeout(); + if ($httpTimeout < 1.0) { + curl_setopt($curlHandle, \CURLOPT_TIMEOUT_MS, $httpTimeout * 1000); + } else { + curl_setopt($curlHandle, \CURLOPT_TIMEOUT, $httpTimeout); + } + + $connectTimeout = $options->getHttpConnectTimeout(); + if ($connectTimeout < 1.0) { + curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT_MS, $connectTimeout * 1000); + } else { + curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, $connectTimeout); + } + $httpSslVerifyPeer = $options->getHttpSslVerifyPeer(); if (!$httpSslVerifyPeer) { curl_setopt($curlHandle, \CURLOPT_SSL_VERIFYPEER, false); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index ddae2f913..3bbc3c5ea 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -341,6 +341,13 @@ static function (): void {}, 'setHttpTimeout', ]; + yield [ + 'http_timeout', + 0.2, + 'getHttpTimeout', + 'setHttpTimeout', + ]; + yield [ 'http_connect_timeout', 1, @@ -355,6 +362,13 @@ static function (): void {}, 'setHttpConnectTimeout', ]; + yield [ + 'http_connect_timeout', + 0.2, + 'getHttpConnectTimeout', + 'setHttpConnectTimeout', + ]; + yield [ 'http_ssl_verify_peer', false, From 93f0673989fb5267e2163edc517c8aa24613fa49 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 5 Nov 2024 14:29:10 +0100 Subject: [PATCH 1039/1161] Always use `ms` scaled timeouts (#1785) --- src/HttpClient/HttpClient.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 879e570e0..aa5873df3 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -64,6 +64,8 @@ public function sendRequest(Request $request, Options $options): Response curl_setopt($curlHandle, \CURLOPT_URL, $dsn->getEnvelopeApiEndpointUrl()); curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, $requestHeaders); curl_setopt($curlHandle, \CURLOPT_USERAGENT, $this->sdkIdentifier . '/' . $this->sdkVersion); + curl_setopt($curlHandle, \CURLOPT_TIMEOUT_MS, $options->getHttpTimeout() * 1000); + curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT_MS, $options->getHttpConnectTimeout() * 1000); curl_setopt($curlHandle, \CURLOPT_ENCODING, ''); curl_setopt($curlHandle, \CURLOPT_POST, true); curl_setopt($curlHandle, \CURLOPT_POSTFIELDS, $requestData); @@ -71,20 +73,6 @@ public function sendRequest(Request $request, Options $options): Response curl_setopt($curlHandle, \CURLOPT_HEADERFUNCTION, $responseHeaderCallback); curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1); - $httpTimeout = $options->getHttpTimeout(); - if ($httpTimeout < 1.0) { - curl_setopt($curlHandle, \CURLOPT_TIMEOUT_MS, $httpTimeout * 1000); - } else { - curl_setopt($curlHandle, \CURLOPT_TIMEOUT, $httpTimeout); - } - - $connectTimeout = $options->getHttpConnectTimeout(); - if ($connectTimeout < 1.0) { - curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT_MS, $connectTimeout * 1000); - } else { - curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, $connectTimeout); - } - $httpSslVerifyPeer = $options->getHttpSslVerifyPeer(); if (!$httpSslVerifyPeer) { curl_setopt($curlHandle, \CURLOPT_SSL_VERIFYPEER, false); From 452fc34bf5a1a90dbcc7af5c3e17c79fe1d280c8 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 5 Nov 2024 14:32:25 +0100 Subject: [PATCH 1040/1161] Test on PHP 8.4 (#1760) --- .github/workflows/ci.yml | 32 ++++++---- .github/workflows/static-analysis.yaml | 6 +- composer.json | 4 +- src/ErrorHandler.php | 3 +- src/FrameBuilder.php | 2 +- src/Severity.php | 2 +- tests/Serializer/AbstractSerializerTest.php | 58 ++++++++++++++----- tests/SeverityTest.php | 3 +- ..._option_regardless_of_error_reporting.phpt | 8 ++- 9 files changed, 81 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6017bab07..c0a1985cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ concurrency: jobs: tests: - name: Tests + name: Tests (${{ matrix.os }}, ${{ matrix.php.version }}, ${{ matrix.dependencies }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -27,13 +27,14 @@ jobs: - ubuntu-latest - windows-latest php: - - '7.2' - - '7.3' - - '7.4' - - '8.0' - - '8.1' - - '8.2' - - '8.3' + - { version: '7.2', phpunit: '^8.5.40' } + - { version: '7.3', phpunit: '^9.6.21' } + - { version: '7.4', phpunit: '^9.6.21' } + - { version: '8.0', phpunit: '^9.6.21' } + - { version: '8.1', phpunit: '^9.6.21' } + - { version: '8.2', phpunit: '^9.6.21' } + - { version: '8.3', phpunit: '^9.6.21' } + - { version: '8.4', phpunit: '^9.6.21' } dependencies: - lowest - highest @@ -47,7 +48,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php }} + php-version: ${{ matrix.php.version }} coverage: xdebug - name: Setup Problem Matchers for PHPUnit @@ -62,8 +63,15 @@ jobs: uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.directory }} - key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.dependencies }}-composer- + key: ${{ runner.os }}-${{ matrix.php.version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php.version }}-${{ matrix.dependencies }}-composer- + + # These dependencies are not used running the tests but can cause deprecation warnings so we remove them before running the tests + - name: Remove unused dependencies + run: composer remove vimeo/psalm phpstan/phpstan friendsofphp/php-cs-fixer --dev --no-interaction --no-update + + - name: Set phpunit/phpunit version constraint + run: composer require phpunit/phpunit:'${{ matrix.php.phpunit }}' --dev --no-interaction --no-update - name: Install highest dependencies run: composer update --no-progress --no-interaction --prefer-dist @@ -84,4 +92,4 @@ jobs: - name: Check benchmarks run: vendor/bin/phpbench run --revs=1 --iterations=1 - if: ${{ matrix.dependencies == 'highest' && matrix.php == '8.2' }} + if: ${{ matrix.dependencies == 'highest' && matrix.php.version == '8.3' }} diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index d7e3c5124..69de38c90 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -21,7 +21,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist @@ -39,7 +39,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist @@ -59,7 +59,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist diff --git a/composer.json b/composer.json index 798b05541..0be4a6531 100644 --- a/composer.json +++ b/composer.json @@ -33,12 +33,12 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.4", - "guzzlehttp/promises": "^1.0|^2.0", + "guzzlehttp/promises": "^2.0.3", "guzzlehttp/psr7": "^1.8.4|^2.1.1", "monolog/monolog": "^1.6|^2.0|^3.0", "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^8.5.14|^9.4", + "phpunit/phpunit": "^8.5|^9.6", "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", "vimeo/psalm": "^4.17" }, diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 37301b407..a34ec5893 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -130,7 +130,8 @@ final class ErrorHandler \E_USER_DEPRECATED => 'User Deprecated', \E_NOTICE => 'Notice', \E_USER_NOTICE => 'User Notice', - \E_STRICT => 'Runtime Notice', + // This is \E_STRICT which has been deprecated in PHP 8.4 so we should not reference it directly to prevent deprecation notices + 2048 => 'Runtime Notice', \E_WARNING => 'Warning', \E_USER_WARNING => 'User Warning', \E_COMPILE_WARNING => 'Compile Warning', diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 46aa3461d..1eec712d4 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -161,7 +161,7 @@ private function getFunctionArguments(array $backtraceFrame): array } else { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); } - } elseif (!\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { + } elseif ($backtraceFrame['function'] !== '__lambda_func' && !str_starts_with($backtraceFrame['function'], '{closure') && \function_exists($backtraceFrame['function'])) { $reflectionFunction = new \ReflectionFunction($backtraceFrame['function']); } } catch (\ReflectionException $e) { diff --git a/src/Severity.php b/src/Severity.php index d0fa899d0..89dcabc7d 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -104,7 +104,7 @@ public static function fromError(int $severity): self return self::error(); case \E_NOTICE: case \E_USER_NOTICE: - case \E_STRICT: + case 2048: // This is \E_STRICT which has been deprecated in PHP 8.4 so we should not reference it directly to prevent deprecation notices return self::info(); default: return self::error(); diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 025a3921f..8f6104fbc 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -442,48 +442,64 @@ public function serializableCallableProvider(): array $this->assertFileExists($filename); $callableWithoutNamespaces = require $filename; + $prettyClosureNames = \PHP_VERSION_ID >= 80400; + return [ [ 'callable' => function (array $param1) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array param1]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [array param1]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [array param1]', ], [ 'callable' => function ($param1a) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [mixed|null param1a]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [mixed|null param1a]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [mixed|null param1a]', ], [ 'callable' => function (callable $param1c) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [callable param1c]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [callable param1c]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [callable param1c]', ], [ 'callable' => function (\stdClass $param1d) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass param1d]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [stdClass param1d]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass param1d]', ], [ 'callable' => function (?\stdClass $param1e = null) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass|null [param1e]]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [stdClass|null [param1e]]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass|null [param1e]]', ], [ 'callable' => function (array &$param1f) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array ¶m1f]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [array ¶m1f]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [array ¶m1f]', ], [ 'callable' => function (?array &$param1g = null) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array|null [¶m1g]]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [array|null [¶m1g]]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [array|null [¶m1g]]', ], [ 'callable' => [$this, 'serializableCallableProvider'], @@ -509,35 +525,47 @@ public function serializableCallableProvider(): array 'callable' => function (int $param1_70a) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [int param1_70a]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [int param1_70a]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [int param1_70a]', ], [ 'callable' => function (&$param): int { return (int) $param; }, - 'expected' => 'Lambda int ' . __NAMESPACE__ . '\\{closure} [mixed|null ¶m]', + 'expected' => $prettyClosureNames + ? 'Lambda int {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [mixed|null ¶m]' + : 'Lambda int ' . __NAMESPACE__ . '\\{closure} [mixed|null ¶m]', ], [ 'callable' => function (int $param): ?int { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda int ' . __NAMESPACE__ . '\\{closure} [int param]', + 'expected' => $prettyClosureNames + ? 'Lambda int {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [int param]' + : 'Lambda int ' . __NAMESPACE__ . '\\{closure} [int param]', ], [ 'callable' => function (?int $param1_70b) { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [int|null param1_70b]', + 'expected' => $prettyClosureNames + ? 'Lambda {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [int|null param1_70b]' + : 'Lambda ' . __NAMESPACE__ . '\\{closure} [int|null param1_70b]', ], [ 'callable' => function (?int $param1_70c): void { throw new \Exception('Don\'t even think about invoke me'); }, - 'expected' => 'Lambda void ' . __NAMESPACE__ . '\\{closure} [int|null param1_70c]', + 'expected' => $prettyClosureNames + ? 'Lambda void {closure:' . __CLASS__ . '::' . __FUNCTION__ . '():%d} [int|null param1_70c]' + : 'Lambda void ' . __NAMESPACE__ . '\\{closure} [int|null param1_70c]', ], [ 'callable' => $callableWithoutNamespaces, - 'expected' => 'Lambda void {closure} [int|null param1_70ns]', + 'expected' => $prettyClosureNames + ? 'Lambda void {closure:%s:%d} [int|null param1_70ns]' + : 'Lambda void {closure} [int|null param1_70ns]', ], [ // This is (a example of) a PHP provided function that is technically callable but we want to ignore that because it causes more false positives than it helps @@ -559,11 +587,11 @@ public function testSerializeCallable($callable, string $expected): void $serializer = $this->createSerializer(); $actual = $this->invokeSerialization($serializer, $callable); - $this->assertSame($expected, $actual); + $this->assertStringMatchesFormat($expected, $actual); $actual = $this->invokeSerialization($serializer, [$callable]); - $this->assertSame([$expected], $actual); + $this->assertStringMatchesFormat($expected, $actual[0]); } /** diff --git a/tests/SeverityTest.php b/tests/SeverityTest.php index 79ade6add..63d2184f7 100644 --- a/tests/SeverityTest.php +++ b/tests/SeverityTest.php @@ -83,7 +83,8 @@ public static function levelsDataProvider(): array // Info [\E_NOTICE, 'info'], [\E_USER_NOTICE, 'info'], - [\E_STRICT, 'info'], + // This is \E_STRICT which has been deprecated in PHP 8.4 so we should not reference it directly to prevent deprecation notices + [2048, 'info'], ]; } } diff --git a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt index 2ab9a423e..35deedee9 100644 --- a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt @@ -62,7 +62,13 @@ trigger_error('Error thrown', E_USER_WARNING); echo 'Triggering E_USER_ERROR error (unsilenceable on PHP8)' . PHP_EOL; -trigger_error('Error thrown', E_USER_ERROR); +if (PHP_VERSION_ID >= 80400) { + // Silence a deprecation notice on PHP 8.4 + // https://wiki.php.net/rfc/deprecations_php_8_4#deprecate_passing_e_user_error_to_trigger_error + @trigger_error('Error thrown', E_USER_ERROR); +} else { + trigger_error('Error thrown', E_USER_ERROR); +} ?> --EXPECT-- Triggering E_USER_NOTICE error From 21a77e7eeb8aef0288283b5f31c40e0039193f38 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 6 Nov 2024 08:26:23 +0100 Subject: [PATCH 1041/1161] Remove metrics plumbing and deprecate/no-op public API (#1786) --- phpstan-baseline.neon | 40 +++- src/Client.php | 4 - src/Event.php | 30 +-- src/EventType.php | 3 + .../FrameContextifierIntegration.php | 7 - src/Metrics/Metrics.php | 87 ++------- src/Metrics/MetricsAggregator.php | 157 --------------- src/Metrics/MetricsUnit.php | 3 + src/Metrics/Types/AbstractType.php | 129 ------------- src/Metrics/Types/CounterType.php | 51 ----- src/Metrics/Types/DistributionType.php | 51 ----- src/Metrics/Types/GaugeType.php | 92 --------- src/Metrics/Types/SetType.php | 57 ------ src/Options.php | 18 +- src/Serializer/EnvelopItems/MetricsItem.php | 160 ---------------- .../EnvelopItems/TransactionItem.php | 26 --- src/Serializer/PayloadSerializer.php | 4 - src/Tracing/Span.php | 49 +---- src/Tracing/Transaction.php | 4 - src/Transport/RateLimiter.php | 30 +-- src/functions.php | 3 + tests/ClientTest.php | 31 --- tests/Metrics/MetricsTest.php | 179 ++---------------- .../EnvelopeItems/MetricsItemTest.php | 30 --- tests/Serializer/PayloadSerializerTest.php | 87 --------- tests/Tracing/SpanTest.php | 132 ------------- tests/Transport/RateLimiterTest.php | 33 ---- 27 files changed, 101 insertions(+), 1396 deletions(-) delete mode 100644 src/Metrics/MetricsAggregator.php delete mode 100644 src/Metrics/Types/AbstractType.php delete mode 100644 src/Metrics/Types/CounterType.php delete mode 100644 src/Metrics/Types/DistributionType.php delete mode 100644 src/Metrics/Types/GaugeType.php delete mode 100644 src/Metrics/Types/SetType.php delete mode 100644 src/Serializer/EnvelopItems/MetricsItem.php delete mode 100644 tests/Serializer/EnvelopeItems/MetricsItemTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e3c69ed59..ae65ebb11 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -25,6 +25,26 @@ parameters: count: 1 path: src/Dsn.php + - + message: "#^Method Sentry\\\\Event\\:\\:getMetrics\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Event.php + + - + message: "#^Method Sentry\\\\Event\\:\\:getMetricsSummary\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Event.php + + - + message: "#^Method Sentry\\\\Event\\:\\:setMetrics\\(\\) has parameter \\$metrics with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Event.php + + - + message: "#^Method Sentry\\\\Event\\:\\:setMetricsSummary\\(\\) has parameter \\$metricsSummary with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Event.php + - message: "#^Property Sentry\\\\Integration\\\\RequestIntegration\\:\\:\\$options \\(array\\{pii_sanitize_headers\\: array\\\\}\\) does not accept array\\.$#" count: 1 @@ -40,11 +60,6 @@ parameters: count: 1 path: src/Logger/DebugStdOutLogger.php - - - message: "#^Method Sentry\\\\Metrics\\\\Types\\\\AbstractType\\:\\:add\\(\\) has parameter \\$value with no type specified\\.$#" - count: 1 - path: src/Metrics/Types/AbstractType.php - - message: "#^Parameter \\#1 \\$level of method Monolog\\\\Handler\\\\AbstractHandler\\:\\:__construct\\(\\) expects 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|'ALERT'\\|'alert'\\|'CRITICAL'\\|'critical'\\|'DEBUG'\\|'debug'\\|'EMERGENCY'\\|'emergency'\\|'ERROR'\\|'error'\\|'INFO'\\|'info'\\|'NOTICE'\\|'notice'\\|'WARNING'\\|'warning'\\|Monolog\\\\Level, int\\|Monolog\\\\Level\\|string given\\.$#" count: 1 @@ -290,6 +305,21 @@ parameters: count: 1 path: src/Tracing/GuzzleTracingMiddleware.php + - + message: "#^Method Sentry\\\\Tracing\\\\Span\\:\\:getMetricsSummary\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Tracing/Span.php + + - + message: "#^Method Sentry\\\\Tracing\\\\Span\\:\\:setMetricsSummary\\(\\) has parameter \\$tags with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Tracing/Span.php + + - + message: "#^Method Sentry\\\\Tracing\\\\Span\\:\\:setMetricsSummary\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/Tracing/Span.php + - message: "#^Parameter \\#1 \\$email of method Sentry\\\\UserDataBag\\:\\:setEmail\\(\\) expects string\\|null, mixed given\\.$#" count: 1 diff --git a/src/Client.php b/src/Client.php index b97a19c0b..02757beeb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -409,8 +409,6 @@ private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event return ($this->options->getBeforeSendTransactionCallback())($event, $hint); case EventType::checkIn(): return ($this->options->getBeforeSendCheckInCallback())($event, $hint); - case EventType::metrics(): - return ($this->options->getBeforeSendMetricsCallback())($event, $hint); default: return $event; } @@ -423,8 +421,6 @@ private function getBeforeSendCallbackName(Event $event): string return 'before_send_transaction'; case EventType::checkIn(): return 'before_send_check_in'; - case EventType::metrics(): - return 'before_send_metrics'; default: return 'before_send'; } diff --git a/src/Event.php b/src/Event.php index 8b096d3cd..0148069ac 100644 --- a/src/Event.php +++ b/src/Event.php @@ -6,7 +6,6 @@ use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; -use Sentry\Metrics\Types\AbstractType; use Sentry\Profiling\Profile; use Sentry\Tracing\Span; @@ -62,16 +61,6 @@ final class Event */ private $checkIn; - /** - * @var array The metrics data - */ - private $metrics = []; - - /** - * @var array> - */ - private $metricsSummary = []; - /** * @var string|null The name of the server (e.g. the host name) */ @@ -227,6 +216,9 @@ public static function createCheckIn(?EventId $eventId = null): self return new self($eventId, EventType::checkIn()); } + /** + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + */ public static function createMetrics(?EventId $eventId = null): self { return new self($eventId, EventType::metrics()); @@ -377,38 +369,34 @@ public function setCheckIn(?CheckIn $checkIn): self } /** - * @return array + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function getMetrics(): array { - return $this->metrics; + return []; } /** - * @param array $metrics + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function setMetrics(array $metrics): self { - $this->metrics = $metrics; - return $this; } /** - * @return array> + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function getMetricsSummary(): array { - return $this->metricsSummary; + return []; } /** - * @param array> $metricsSummary + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function setMetricsSummary(array $metricsSummary): self { - $this->metricsSummary = $metricsSummary; - return $this; } diff --git a/src/EventType.php b/src/EventType.php index 7e34cefc1..b8ffdef94 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -42,6 +42,9 @@ public static function checkIn(): self return self::getInstance('check_in'); } + /** + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + */ public static function metrics(): self { return self::getInstance('metrics'); diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 8935d055d..8a541d13d 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -66,13 +66,6 @@ public function setupOnce(): void } } - foreach ($event->getMetrics() as $metric) { - if ($metric->hasCodeLocation()) { - $frame = $metric->getCodeLocation(); - $integration->addContextToStacktraceFrame($maxContextLines, $frame); - } - } - return $event; }); } diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php index 868944fa9..6f0fa6764 100644 --- a/src/Metrics/Metrics.php +++ b/src/Metrics/Metrics.php @@ -5,10 +5,6 @@ namespace Sentry\Metrics; use Sentry\EventId; -use Sentry\Metrics\Types\CounterType; -use Sentry\Metrics\Types\DistributionType; -use Sentry\Metrics\Types\GaugeType; -use Sentry\Metrics\Types\SetType; use Sentry\Tracing\SpanContext; use function Sentry\trace; @@ -20,16 +16,6 @@ class Metrics */ private static $instance; - /** - * @var MetricsAggregator - */ - private $aggregator; - - private function __construct() - { - $this->aggregator = new MetricsAggregator(); - } - public static function getInstance(): self { if (self::$instance === null) { @@ -41,6 +27,8 @@ public static function getInstance(): self /** * @param array $tags + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function increment( string $key, @@ -50,19 +38,12 @@ public function increment( ?int $timestamp = null, int $stackLevel = 0 ): void { - $this->aggregator->add( - CounterType::TYPE, - $key, - $value, - $unit, - $tags, - $timestamp, - $stackLevel - ); } /** * @param array $tags + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function distribution( string $key, @@ -72,19 +53,12 @@ public function distribution( ?int $timestamp = null, int $stackLevel = 0 ): void { - $this->aggregator->add( - DistributionType::TYPE, - $key, - $value, - $unit, - $tags, - $timestamp, - $stackLevel - ); } /** * @param array $tags + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function gauge( string $key, @@ -94,20 +68,13 @@ public function gauge( ?int $timestamp = null, int $stackLevel = 0 ): void { - $this->aggregator->add( - GaugeType::TYPE, - $key, - $value, - $unit, - $tags, - $timestamp, - $stackLevel - ); } /** * @param int|string $value * @param array $tags + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function set( string $key, @@ -117,15 +84,6 @@ public function set( ?int $timestamp = null, int $stackLevel = 0 ): void { - $this->aggregator->add( - SetType::TYPE, - $key, - $value, - $unit, - $tags, - $timestamp, - $stackLevel - ); } /** @@ -135,6 +93,8 @@ public function set( * @param array $tags * * @return T + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function timing( string $key, @@ -143,26 +103,8 @@ public function timing( int $stackLevel = 0 ) { return trace( - function () use ($callback, $key, $tags, $stackLevel) { - $startTimestamp = microtime(true); - - $result = $callback(); - - /** - * Emitting the metric here, will attach it to the - * "metric.timing" span. - */ - $this->aggregator->add( - DistributionType::TYPE, - $key, - microtime(true) - $startTimestamp, - MetricsUnit::second(), - $tags, - (int) $startTimestamp, - $stackLevel + 4 // the `trace` helper adds 4 additional stack frames - ); - - return $result; + function () use ($callback) { + return $callback(); }, SpanContext::make() ->setOp('metric.timing') @@ -171,8 +113,11 @@ function () use ($callback, $key, $tags, $stackLevel) { ); } + /** + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + */ public function flush(): ?EventId { - return $this->aggregator->flush(); + return null; } } diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php deleted file mode 100644 index 5014c97fe..000000000 --- a/src/Metrics/MetricsAggregator.php +++ /dev/null @@ -1,157 +0,0 @@ - - */ - private $buckets = []; - - private const METRIC_TYPES = [ - CounterType::TYPE => CounterType::class, - DistributionType::TYPE => DistributionType::class, - GaugeType::TYPE => GaugeType::class, - SetType::TYPE => SetType::class, - ]; - - /** - * @param array $tags - * @param int|float|string $value - */ - public function add( - string $type, - string $key, - $value, - ?MetricsUnit $unit, - array $tags, - ?int $timestamp, - int $stackLevel - ): void { - if ($timestamp === null) { - $timestamp = time(); - } - if ($unit === null) { - $unit = MetricsUnit::none(); - } - - $tags = $this->serializeTags($tags); - - $bucketTimestamp = floor($timestamp / self::ROLLUP_IN_SECONDS); - $bucketKey = md5( - $type . - $key . - $unit . - serialize($tags) . - $bucketTimestamp - ); - - if (\array_key_exists($bucketKey, $this->buckets)) { - $metric = $this->buckets[$bucketKey]; - $metric->add($value); - } else { - $metricTypeClass = self::METRIC_TYPES[$type]; - /** @var AbstractType $metric */ - /** @phpstan-ignore-next-line SetType accepts int|float|string, others only int|float */ - $metric = new $metricTypeClass($key, $value, $unit, $tags, $timestamp); - $this->buckets[$bucketKey] = $metric; - } - - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); - - if ($client !== null) { - $options = $client->getOptions(); - - if ( - $options->shouldAttachMetricCodeLocations() - && !$metric->hasCodeLocation() - ) { - $metric->addCodeLocation($stackLevel); - } - } - - $span = $hub->getSpan(); - if ($span !== null) { - $span->setMetricsSummary($type, $key, $value, $unit, $tags); - } - } - - public function flush(): ?EventId - { - if ($this->buckets === []) { - return null; - } - - $hub = SentrySdk::getCurrentHub(); - $event = Event::createMetrics()->setMetrics($this->buckets); - - $this->buckets = []; - - return $hub->captureEvent($event); - } - - /** - * @param array $tags - * - * @return array - */ - private function serializeTags(array $tags): array - { - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); - - if ($client !== null) { - $options = $client->getOptions(); - - $defaultTags = [ - 'environment' => $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, - ]; - - $release = $options->getRelease(); - if ($release !== null) { - $defaultTags['release'] = $release; - } - - $hub->configureScope(function (Scope $scope) use (&$defaultTags) { - $transaction = $scope->getTransaction(); - if ( - $transaction !== null - // Only include the transaction name if it has good quality - && $transaction->getMetadata()->getSource() !== TransactionSource::url() - ) { - $defaultTags['transaction'] = $transaction->getName(); - } - }); - - $tags = array_merge($defaultTags, $tags); - } - - // It's very important to sort the tags in order to obtain the same bucket key. - ksort($tags); - - return $tags; - } -} diff --git a/src/Metrics/MetricsUnit.php b/src/Metrics/MetricsUnit.php index 3e39d56fe..e94951fe3 100644 --- a/src/Metrics/MetricsUnit.php +++ b/src/Metrics/MetricsUnit.php @@ -4,6 +4,9 @@ namespace Sentry\Metrics; +/** + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + */ final class MetricsUnit implements \Stringable { /** diff --git a/src/Metrics/Types/AbstractType.php b/src/Metrics/Types/AbstractType.php deleted file mode 100644 index ecafca06d..000000000 --- a/src/Metrics/Types/AbstractType.php +++ /dev/null @@ -1,129 +0,0 @@ - - */ - private $tags; - - /** - * @var int - */ - private $timestamp; - - /** - * @var Frame - */ - private $codeLocation; - - /** - * @param array $tags - */ - public function __construct(string $key, MetricsUnit $unit, array $tags, int $timestamp) - { - $this->key = $key; - $this->unit = $unit; - $this->tags = $tags; - $this->timestamp = $timestamp; - } - - abstract public function add($value): void; - - /** - * @return array - */ - abstract public function serialize(): array; - - abstract public function getType(): string; - - public function getKey(): string - { - return $this->key; - } - - public function getUnit(): MetricsUnit - { - return $this->unit; - } - - /** - * @return array - */ - public function getTags(): array - { - return $this->tags; - } - - public function getTimestamp(): int - { - return $this->timestamp; - } - - /** - * @phpstan-assert-if-true !null $this->getCodeLocation() - */ - public function hasCodeLocation(): bool - { - return $this->codeLocation !== null; - } - - public function getCodeLocation(): ?Frame - { - return $this->codeLocation; - } - - public function addCodeLocation(int $stackLevel): void - { - $client = SentrySdk::getCurrentHub()->getClient(); - if ($client === null) { - return; - } - - $options = $client->getOptions(); - - $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3 + $stackLevel); - $frame = end($backtrace); - - // If we don't have a valid frame there is no code location to resolve - if ($frame === false || empty($frame['file']) || empty($frame['line'])) { - return; - } - - $frameBuilder = new FrameBuilder($options, new RepresentationSerializer($options)); - $this->codeLocation = $frameBuilder->buildFromBacktraceFrame($frame['file'], $frame['line'], $frame); - } - - public function getMri(): string - { - return \sprintf( - '%s:%s@%s', - $this->getType(), - $this->getKey(), - (string) $this->getUnit() - ); - } -} diff --git a/src/Metrics/Types/CounterType.php b/src/Metrics/Types/CounterType.php deleted file mode 100644 index 6f54b3f9c..000000000 --- a/src/Metrics/Types/CounterType.php +++ /dev/null @@ -1,51 +0,0 @@ -value = (float) $value; - } - - /** - * @param int|float $value - */ - public function add($value): void - { - $this->value += (float) $value; - } - - public function serialize(): array - { - return [$this->value]; - } - - public function getType(): string - { - return self::TYPE; - } -} diff --git a/src/Metrics/Types/DistributionType.php b/src/Metrics/Types/DistributionType.php deleted file mode 100644 index fa43e4f48..000000000 --- a/src/Metrics/Types/DistributionType.php +++ /dev/null @@ -1,51 +0,0 @@ - - */ - private $values; - - /** - * @param int|float $value - */ - public function __construct(string $key, $value, MetricsUnit $unit, array $tags, int $timestamp) - { - parent::__construct($key, $unit, $tags, $timestamp); - - $this->add($value); - } - - /** - * @param int|float $value - */ - public function add($value): void - { - $this->values[] = (float) $value; - } - - public function serialize(): array - { - return $this->values; - } - - public function getType(): string - { - return self::TYPE; - } -} diff --git a/src/Metrics/Types/GaugeType.php b/src/Metrics/Types/GaugeType.php deleted file mode 100644 index 1803365a9..000000000 --- a/src/Metrics/Types/GaugeType.php +++ /dev/null @@ -1,92 +0,0 @@ -last = $value; - $this->min = $value; - $this->max = $value; - $this->sum = $value; - $this->count = 1; - } - - /** - * @param int|float $value - */ - public function add($value): void - { - $value = (float) $value; - - $this->last = $value; - $this->min = min($this->min, $value); - $this->max = max($this->min, $value); - $this->sum += $value; - ++$this->count; - } - - /** - * @return array - */ - public function serialize(): array - { - return [ - $this->last, - $this->min, - $this->max, - $this->sum, - $this->count, - ]; - } - - public function getType(): string - { - return self::TYPE; - } -} diff --git a/src/Metrics/Types/SetType.php b/src/Metrics/Types/SetType.php deleted file mode 100644 index d761bc264..000000000 --- a/src/Metrics/Types/SetType.php +++ /dev/null @@ -1,57 +0,0 @@ - - */ - private $values; - - /** - * @param int|string $value - */ - public function __construct(string $key, $value, MetricsUnit $unit, array $tags, int $timestamp) - { - parent::__construct($key, $unit, $tags, $timestamp); - - $this->add($value); - } - - /** - * @param int|string $value - */ - public function add($value): void - { - $this->values[] = $value; - } - - public function serialize(): array - { - foreach ($this->values as $key => $value) { - if (\is_string($value)) { - $this->values[$key] = crc32($value); - } - } - - return $this->values; - } - - public function getType(): string - { - return self::TYPE; - } -} diff --git a/src/Options.php b/src/Options.php index 34ebd5a7c..dd2930547 100644 --- a/src/Options.php +++ b/src/Options.php @@ -224,6 +224,8 @@ public function setAttachStacktrace(bool $enable): self /** * Gets whether a metric has their code location attached. + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function shouldAttachMetricCodeLocations(): bool { @@ -232,6 +234,8 @@ public function shouldAttachMetricCodeLocations(): bool /** * Sets whether a metric will have their code location attached. + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function setAttachMetricCodeLocations(bool $enable): self { @@ -581,6 +585,8 @@ public function setBeforeSendCheckInCallback(callable $callback): self * If `null` is returned it won't be sent. * * @psalm-return callable(Event, ?EventHint): ?Event + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function getBeforeSendMetricsCallback(): callable { @@ -594,6 +600,8 @@ public function getBeforeSendMetricsCallback(): callable * @param callable $callback The callable * * @psalm-param callable(Event, ?EventHint): ?Event $callback + * + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function setBeforeSendMetricsCallback(callable $callback): self { @@ -1112,6 +1120,9 @@ private function configureOptions(OptionsResolver $resolver): void 'traces_sampler' => null, 'profiles_sample_rate' => null, 'attach_stacktrace' => false, + /** + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + */ 'attach_metric_code_locations' => false, 'context_lines' => 5, 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, @@ -1132,8 +1143,11 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send_check_in' => static function (Event $checkIn): Event { return $checkIn; }, - 'before_send_metrics' => static function (Event $metrics): Event { - return $metrics; + /** + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + */ + 'before_send_metrics' => static function (Event $metrics): ?Event { + return null; }, 'trace_propagation_targets' => null, 'tags' => [], diff --git a/src/Serializer/EnvelopItems/MetricsItem.php b/src/Serializer/EnvelopItems/MetricsItem.php deleted file mode 100644 index 8b78ccec2..000000000 --- a/src/Serializer/EnvelopItems/MetricsItem.php +++ /dev/null @@ -1,160 +0,0 @@ -getMetrics(); - if (empty($metrics)) { - return ''; - } - - $statsdPayload = []; - $metricMetaPayload = []; - - foreach ($metrics as $metric) { - $statsdPayload[] = self::seralizeMetric($metric); - - if ($metric->hasCodeLocation()) { - $metricMetaPayload[$metric->getMri()][] = array_merge( - ['type' => 'location'], - self::serializeStacktraceFrame($metric->getCodeLocation()) - ); - } - } - - $statsdPayload = implode("\n", $statsdPayload); - - $statsdHeader = [ - 'type' => 'statsd', - 'length' => mb_strlen($statsdPayload), - ]; - - if (!empty($metricMetaPayload)) { - $metricMetaPayload = JSON::encode([ - 'timestamp' => time(), - 'mapping' => $metricMetaPayload, - ]); - - $metricMetaHeader = [ - 'type' => 'metric_meta', - 'length' => mb_strlen($metricMetaPayload), - ]; - - return \sprintf( - "%s\n%s\n%s\n%s", - JSON::encode($statsdHeader), - $statsdPayload, - JSON::encode($metricMetaHeader), - $metricMetaPayload - ); - } - - return \sprintf( - "%s\n%s", - JSON::encode($statsdHeader), - $statsdPayload - ); - } - - public static function seralizeMetric(AbstractType $metric): string - { - /** - * In case of us adding support for emitting metrics from other namespaces, - * we have to alter the RateLimiter::class to properly handle these - * namespaces. - */ - - // key - my.metric - $line = preg_replace(self::KEY_PATTERN, '_', $metric->getKey()); - - if ($metric->getUnit() !== MetricsUnit::none()) { - // unit - @second - $line .= '@' . preg_replace(self::UNIT_PATTERN, '', (string) $metric->getUnit()); - } - - foreach ($metric->serialize() as $value) { - // value - 2:3:4... - $line .= ':' . $value; - } - - // type - |c|, |d|, ... - $line .= '|' . $metric->getType() . '|'; - - $tags = []; - foreach ($metric->getTags() as $key => $value) { - $tags[] = preg_replace(self::TAG_KEY_PATTERN, '', $key) . - ':' . self::escapeTagValues($value); - } - - if (!empty($tags)) { - // tags - #key:value,key:value... - $line .= '#' . implode(',', $tags) . '|'; - } - - // timestamp - T123456789 - $line .= 'T' . $metric->getTimestamp(); - - return $line; - } - - public static function escapeTagValues(string $tagValue): string - { - $result = ''; - - for ($i = 0; $i < mb_strlen($tagValue); ++$i) { - $character = mb_substr($tagValue, $i, 1); - $result .= str_replace( - [ - "\n", - "\r", - "\t", - '\\', - '|', - ',', - ], - [ - '\n', - '\r', - '\t', - '\\\\', - '\u{7c}', - '\u{2c}', - ], - $character - ); - } - - return $result; - } -} diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index 9d102f3b4..de6fd624a 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -125,10 +125,6 @@ public static function toEnvelopeItem(Event $event): string $payload['spans'] = array_values(array_map([self::class, 'serializeSpan'], $event->getSpans())); - if (!empty($event->getMetricsSummary())) { - $payload['_metrics_summary'] = self::serializeMetricsSummary($event->getMetricsSummary()); - } - $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); if ($transactionMetadata instanceof TransactionMetadata) { $payload['transaction_info']['source'] = (string) $transactionMetadata->getSource(); @@ -191,28 +187,6 @@ protected static function serializeSpan(Span $span): array $result['tags'] = $span->getTags(); } - if (!empty($span->getMetricsSummary())) { - $result['_metrics_summary'] = self::serializeMetricsSummary($span->getMetricsSummary()); - } - return $result; } - - /** - * @param array> $metricsSummary - * - * @return array - */ - protected static function serializeMetricsSummary(array $metricsSummary): array - { - $formattedSummary = []; - - foreach ($metricsSummary as $mri => $metrics) { - foreach ($metrics as $metric) { - $formattedSummary[$mri][] = $metric; - } - } - - return $formattedSummary; - } } diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 7dbfd2aa9..c109e0336 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -9,7 +9,6 @@ use Sentry\Options; use Sentry\Serializer\EnvelopItems\CheckInItem; use Sentry\Serializer\EnvelopItems\EventItem; -use Sentry\Serializer\EnvelopItems\MetricsItem; use Sentry\Serializer\EnvelopItems\ProfileItem; use Sentry\Serializer\EnvelopItems\TransactionItem; use Sentry\Tracing\DynamicSamplingContext; @@ -78,9 +77,6 @@ public function serialize(Event $event): string case EventType::checkIn(): $items = CheckInItem::toEnvelopeItem($event); break; - case EventType::metrics(): - $items = MetricsItem::toEnvelopeItem($event); - break; } return \sprintf("%s\n%s", JSON::encode($envelopeHeader), $items); diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 8810edb13..fdbba775c 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -6,7 +6,6 @@ use Sentry\EventId; use Sentry\Metrics\MetricsUnit; -use Sentry\Metrics\Types\SetType; use Sentry\SentrySdk; use Sentry\State\Scope; @@ -88,11 +87,6 @@ class Span */ protected $transaction; - /** - * @var array> - */ - protected $metricsSummary = []; - /** * @var string|null The trace origin of the span. If no origin is set, the span is considered to be "manual". * @@ -508,16 +502,15 @@ public function detachSpanRecorder() } /** - * @return array> + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function getMetricsSummary(): array { - return $this->metricsSummary; + return []; } /** - * @param string|int|float $value - * @param string[] $tags + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ public function setMetricsSummary( string $type, @@ -526,42 +519,6 @@ public function setMetricsSummary( MetricsUnit $unit, array $tags ): void { - $mri = \sprintf('%s:%s@%s', $type, $key, (string) $unit); - $bucketKey = $mri . serialize($tags); - - if ( - isset($this->metricsSummary[$mri]) - && \array_key_exists($bucketKey, $this->metricsSummary[$mri]) - ) { - if ($type === SetType::TYPE) { - $value = 1.0; - } else { - $value = (float) $value; - } - - $summary = $this->metricsSummary[$mri][$bucketKey]; - $this->metricsSummary[$mri][$bucketKey] = [ - 'min' => min($summary['min'], $value), - 'max' => max($summary['max'], $value), - 'sum' => $summary['sum'] + $value, - 'count' => $summary['count'] + 1, - 'tags' => $tags, - ]; - } else { - if ($type === SetType::TYPE) { - $value = 0.0; - } else { - $value = (float) $value; - } - - $this->metricsSummary[$mri][$bucketKey] = [ - 'min' => $value, - 'max' => $value, - 'sum' => $value, - 'count' => 1, - 'tags' => $tags, - ]; - } } /** diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index 735768c57..5e4d727d2 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -190,10 +190,6 @@ public function finish(?float $endTimestamp = null): ?EventId } } - if (!empty($this->getMetricsSummary())) { - $event->setMetricsSummary($this->getMetricsSummary()); - } - return $this->hub->captureEvent($event); } } diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index 6a4f89bf8..9a3d6d0f5 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -16,11 +16,6 @@ final class RateLimiter */ private const DATA_CATEGORY_ERROR = 'error'; - /** - * @var string - */ - private const DATA_CATEGORY_METRIC_BUCKET = 'metric_bucket'; - /** * The name of the header to look at to know the rate limits for the events * categories supported by the server. @@ -72,26 +67,7 @@ public function handleResponse(Response $response): bool $retryAfter = $now + (ctype_digit($parameters[0]) ? (int) $parameters[0] : self::DEFAULT_RETRY_AFTER_SECONDS); foreach (explode(';', $parameters[1]) as $category) { - switch ($category) { - case self::DATA_CATEGORY_METRIC_BUCKET: - $namespaces = []; - if (isset($parameters[4])) { - $namespaces = explode(';', $parameters[4]); - } - - /** - * As we do not support setting any metric namespaces in the SDK, - * checking for the custom namespace suffices. - * In case the namespace was ommited in the response header, - * we'll also back off. - */ - if ($namespaces === [] || \in_array('custom', $namespaces)) { - $this->rateLimits[self::DATA_CATEGORY_METRIC_BUCKET] = $retryAfter; - } - break; - default: - $this->rateLimits[$category ?: 'all'] = $retryAfter; - } + $this->rateLimits[$category ?: 'all'] = $retryAfter; $this->logger->warning( \sprintf('Rate limited exceeded for category "%s", backing off until "%s".', $category, gmdate(\DATE_ATOM, $retryAfter)) @@ -132,10 +108,6 @@ public function getDisabledUntil(EventType $eventType): int $category = self::DATA_CATEGORY_ERROR; } - if ($eventType === EventType::metrics()) { - $category = self::DATA_CATEGORY_METRIC_BUCKET; - } - return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); } diff --git a/src/functions.php b/src/functions.php index 1865d397c..46a3d99e0 100644 --- a/src/functions.php +++ b/src/functions.php @@ -377,6 +377,9 @@ function continueTrace(string $sentryTrace, string $baggage): TransactionContext return TransactionContext::fromHeaders($sentryTrace, $baggage); } +/** + * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + */ function metrics(): Metrics { return Metrics::getInstance(); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index ddc3dc6c9..2aa111e64 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -629,11 +629,6 @@ public static function processEventChecksBeforeSendMetricsOptionDataProvider(): Event::createCheckIn(), false, ]; - - yield [ - Event::createMetrics(), - true, - ]; } public function testProcessEventDiscardsEventWhenSampleRateOptionIsZero(): void @@ -822,32 +817,6 @@ public function testProcessEventDiscardsEventWhenBeforeSendCheckInCallbackReturn $client->captureEvent(Event::createCheckIn()); } - public function testProcessEventDiscardsEventWhenBeforeSendMetricsCallbackReturnsNull(): void - { - /** @var LoggerInterface&MockObject $logger */ - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once()) - ->method('info') - ->with( - new StringMatchesFormatDescription('The metrics [%s] will be discarded because the "before_send_metrics" callback returned "null".'), - $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - }) - ); - - $options = [ - 'before_send_metrics' => static function () { - return null; - }, - ]; - - $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); - - $client->captureEvent(Event::createMetrics()); - } - public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): void { /** @var LoggerInterface&MockObject $logger */ diff --git a/tests/Metrics/MetricsTest.php b/tests/Metrics/MetricsTest.php index 5e19d9750..654156c5c 100644 --- a/tests/Metrics/MetricsTest.php +++ b/tests/Metrics/MetricsTest.php @@ -8,10 +8,6 @@ use Sentry\ClientInterface; use Sentry\Event; use Sentry\Metrics\MetricsUnit; -use Sentry\Metrics\Types\CounterType; -use Sentry\Metrics\Types\DistributionType; -use Sentry\Metrics\Types\GaugeType; -use Sentry\Metrics\Types\SetType; use Sentry\Options; use Sentry\SentrySdk; use Sentry\State\Hub; @@ -39,31 +35,8 @@ public function testIncrement(): void $self = $this; - $client->expects($this->once()) - ->method('captureEvent') - ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['2794a118fd879e10a3a97836df803872']; - - $self->assertSame(CounterType::TYPE, $metric->getType()); - $self->assertSame('foo', $metric->getKey()); - $self->assertSame([3.0], $metric->serialize()); - $self->assertSame(MetricsUnit::second(), $metric->getUnit()); - $self->assertSame( - [ - 'environment' => 'development', - 'foo' => 'bar', - 'release' => '1.0.0', - ], - $metric->getTags() - ); - $self->assertSame(1699412953, $metric->getTimestamp()); - - $codeLocation = $metric->getCodeLocation(); - - $self->assertSame('Sentry\Metrics\Metrics::increment', $codeLocation->getFunctionName()); - - return true; - })); + $client->expects($this->never()) + ->method('captureEvent'); $hub = new Hub($client); SentrySdk::setCurrentHub($hub); @@ -101,31 +74,8 @@ public function testDistribution(): void $self = $this; - $client->expects($this->once()) - ->method('captureEvent') - ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['edb74f95b4572e82dc4600cfeea76181']; - - $self->assertSame(DistributionType::TYPE, $metric->getType()); - $self->assertSame('foo', $metric->getKey()); - $self->assertSame([1.0, 2.0], $metric->serialize()); - $self->assertSame(MetricsUnit::second(), $metric->getUnit()); - $self->assertSame( - [ - 'environment' => 'development', - 'foo' => 'bar', - 'release' => '1.0.0', - ], - $metric->getTags() - ); - $self->assertSame(1699412953, $metric->getTimestamp()); - - $codeLocation = $metric->getCodeLocation(); - - $self->assertSame('Sentry\Metrics\Metrics::distribution', $codeLocation->getFunctionName()); - - return true; - })); + $client->expects($this->never()) + ->method('captureEvent'); $hub = new Hub($client); SentrySdk::setCurrentHub($hub); @@ -163,31 +113,8 @@ public function testTiming(): void $self = $this; - $client->expects($this->once()) - ->method('captureEvent') - ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['edb74f95b4572e82dc4600cfeea76181']; - - $self->assertSame(DistributionType::TYPE, $metric->getType()); - $self->assertSame('foo', $metric->getKey()); - $self->assertSame([1.0, 2.0], $metric->serialize()); - $self->assertSame(MetricsUnit::second(), $metric->getUnit()); - $self->assertSame( - [ - 'environment' => 'development', - 'foo' => 'bar', - 'release' => '1.0.0', - ], - $metric->getTags() - ); - $self->assertSame(1699412953, $metric->getTimestamp()); - - $codeLocation = $metric->getCodeLocation(); - - $self->assertSame('Sentry\Metrics\Metrics::timing', $codeLocation->getFunctionName()); - - return true; - })); + $client->expects($this->never()) + ->method('captureEvent'); $hub = new Hub($client); SentrySdk::setCurrentHub($hub); @@ -237,37 +164,8 @@ public function testGauge(): void $self = $this; - $client->expects($this->once()) - ->method('captureEvent') - ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['f39c23c73897d4f006fd617f76664571']; - - $self->assertSame(GaugeType::TYPE, $metric->getType()); - $self->assertSame('foo', $metric->getKey()); - $self->assertSame([ - 2.0, // last - 1.0, // min - 2.0, // max - 3.0, // sum, - 2, // count, - ], $metric->serialize()); - $self->assertSame(MetricsUnit::second(), $metric->getUnit()); - $self->assertSame( - [ - 'environment' => 'development', - 'foo' => 'bar', - 'release' => '1.0.0', - ], - $metric->getTags() - ); - $self->assertSame(1699412953, $metric->getTimestamp()); - - $codeLocation = $metric->getCodeLocation(); - - $self->assertSame('Sentry\Metrics\Metrics::gauge', $codeLocation->getFunctionName()); - - return true; - })); + $client->expects($this->never()) + ->method('captureEvent'); $hub = new Hub($client); SentrySdk::setCurrentHub($hub); @@ -305,31 +203,8 @@ public function testSet(): void $self = $this; - $client->expects($this->once()) - ->method('captureEvent') - ->with($this->callback(static function (Event $event) use ($self): bool { - $metric = $event->getMetrics()['868b190d923bbd619570328d7ba3e4cd']; - - $self->assertSame(SetType::TYPE, $metric->getType()); - $self->assertSame('foo', $metric->getKey()); - $self->assertSame([1, 1, 2356372769], $metric->serialize()); - $self->assertSame(MetricsUnit::second(), $metric->getUnit()); - $self->assertSame( - [ - 'environment' => 'development', - 'foo' => 'bar', - 'release' => '1.0.0', - ], - $metric->getTags() - ); - $self->assertSame(1699412953, $metric->getTimestamp()); - - $codeLocation = $metric->getCodeLocation(); - - $self->assertSame('Sentry\Metrics\Metrics::set', $codeLocation->getFunctionName()); - - return true; - })); + $client->expects($this->never()) + ->method('captureEvent'); $hub = new Hub($client); SentrySdk::setCurrentHub($hub); @@ -378,42 +253,12 @@ public function testMetricsSummary(): void ->method('captureEvent') ->with($this->callback(static function (Event $event) use ($self): bool { $self->assertSame( - [ - 'c:foo@second' => [ - 'c:foo@seconda:4:{s:11:"environment";s:11:"development";s:3:"foo";s:3:"bar";s:7:"release";s:5:"1.0.0";s:11:"transaction";s:12:"GET /metrics";}' => [ - 'min' => 1.0, - 'max' => 1.0, - 'sum' => 1.0, - 'count' => 1, - 'tags' => [ - 'environment' => 'development', - 'foo' => 'bar', - 'release' => '1.0.0', - 'transaction' => 'GET /metrics', - ], - ], - ], - ], + [], $event->getMetricsSummary() ); $self->assertSame( - [ - 'c:foo@second' => [ - 'c:foo@seconda:4:{s:11:"environment";s:11:"development";s:3:"foo";s:3:"bar";s:7:"release";s:5:"1.0.0";s:11:"transaction";s:12:"GET /metrics";}' => [ - 'min' => 1.0, - 'max' => 1.0, - 'sum' => 2.0, - 'count' => 2, - 'tags' => [ - 'environment' => 'development', - 'foo' => 'bar', - 'release' => '1.0.0', - 'transaction' => 'GET /metrics', - ], - ], - ], - ], + [], $event->getSpans()[0]->getMetricsSummary() ); diff --git a/tests/Serializer/EnvelopeItems/MetricsItemTest.php b/tests/Serializer/EnvelopeItems/MetricsItemTest.php deleted file mode 100644 index 399fcf40b..000000000 --- a/tests/Serializer/EnvelopeItems/MetricsItemTest.php +++ /dev/null @@ -1,30 +0,0 @@ -assertSame('plain', MetricsItem::escapeTagValues('plain')); - $this->assertSame('plain text', MetricsItem::escapeTagValues('plain text')); - $this->assertSame('plain%text', MetricsItem::escapeTagValues('plain%text')); - - // Escape sequences - $this->assertSame('plain \\\\\\\\ text', MetricsItem::escapeTagValues('plain \\\\ text')); - $this->assertSame('plain\\u{2c}text', MetricsItem::escapeTagValues('plain,text')); - $this->assertSame('plain\\u{7c}text', MetricsItem::escapeTagValues('plain|text')); - $this->assertSame('plain 😅', MetricsItem::escapeTagValues('plain 😅')); - - // Escapable control characters - $this->assertSame('plain\\\\ntext', MetricsItem::escapeTagValues("plain\ntext")); - $this->assertSame('plain\\\\rtext', MetricsItem::escapeTagValues("plain\rtext")); - $this->assertSame('plain\\\\ttext', MetricsItem::escapeTagValues("plain\ttext")); - } -} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 1d528ca8d..c9283825d 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -16,11 +16,6 @@ use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; -use Sentry\Metrics\MetricsUnit; -use Sentry\Metrics\Types\CounterType; -use Sentry\Metrics\Types\DistributionType; -use Sentry\Metrics\Types\GaugeType; -use Sentry\Metrics\Types\SetType; use Sentry\MonitorConfig; use Sentry\MonitorSchedule; use Sentry\Options; @@ -410,88 +405,6 @@ public static function serializeAsEnvelopeDataProvider(): iterable {"event_id":"fc9442f5aef34234bb22b9a615e30ccd","sent_at":"2020-08-18T22:47:15Z","dsn":"http:\/\/public@example.com\/sentry\/1","sdk":{"name":"sentry.php","version":"$sdkVersion"}} {"type":"check_in","content_type":"application\/json"} {"check_in_id":"$checkinId","monitor_slug":"my-monitor","status":"ok","duration":10,"release":"1.0.0","environment":"dev","monitor_config":{"schedule":{"type":"crontab","value":"0 0 * * *","unit":""},"checkin_margin":10,"max_runtime":12,"timezone":"Europe\/Amsterdam","failure_issue_threshold":5,"recovery_threshold":10},"contexts":{"trace":{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"5dd538dc297544cc"}}} -TEXT - , - ]; - - $counter = new CounterType('counter', 1.0, MetricsUnit::second(), ['foo' => 'bar', 'route' => 'GET /foo'], 1597790835); - $distribution = new DistributionType('distribution', 1.0, MetricsUnit::second(), ['foo' => 'bar'], 1597790835); - $gauge = new GaugeType('gauge', 1.0, MetricsUnit::second(), ['foo' => 'bar', 'bar' => 'baz'], 1597790835); - $set = new SetType('set', 1.0, MetricsUnit::second(), ['key' => 'value'], 1597790835); - $noTags = new CounterType('no_tags', 1.0, MetricsUnit::second(), [], 1597790835); - - $event = Event::createMetrics(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); - $event->setMetrics([ - $counter, - $distribution, - $gauge, - $set, - $noTags, - ]); - - yield [ - $event, - <<setSpanId(new SpanId('5dd538dc297544cc')); - $span->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); - $span->setMetricsSummary( - CounterType::TYPE, - 'counter', - 10, - MetricsUnit::custom('star'), - [ - 'repository' => 'client', - ] - ); - $span->setMetricsSummary( - CounterType::TYPE, - 'counter', - 50, - MetricsUnit::custom('star'), - [ - 'repository' => 'client', - ] - ); - - $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); - $event->setSpans([$span]); - $event->setTransaction('GET /'); - $event->setContext('trace', [ - 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', - 'span_id' => '5dd538dc297544cc', - ]); - $event->setMetricsSummary([ - 'c:counter@star' => [ - 'c:counter@stara:0:{s:10:"repository";s:6:"client";}' => [ - 'min' => 1, - 'max' => 1, - 'sum' => 1, - 'count' => 1, - 'tags' => [ - 'repository' => 'client', - ], - ], - ], - ]); - - yield [ - $event, - <<setMetricsSummary( - CounterType::TYPE, - 'counter', - 10, - MetricsUnit::custom('star'), - [ - 'repository' => 'client', - ] - ); - $span->setMetricsSummary( - CounterType::TYPE, - 'counter', - 50, - MetricsUnit::custom('star'), - [ - 'repository' => 'client', - ] - ); - $span->setMetricsSummary( - CounterType::TYPE, - 'counter', - 10, - MetricsUnit::custom('star'), - [ - 'repository' => 'server', - ] - ); - - $span->setMetricsSummary( - DistributionType::TYPE, - 'distribution', - 10.2, - MetricsUnit::millisecond(), - [] - ); - $span->setMetricsSummary( - DistributionType::TYPE, - 'distribution', - 5.7, - MetricsUnit::millisecond(), - [] - ); - - $span->setMetricsSummary( - GaugeType::TYPE, - 'gauge', - 10, - MetricsUnit::none(), - [] - ); - $span->setMetricsSummary( - GaugeType::TYPE, - 'gauge', - 20, - MetricsUnit::none(), - [] - ); - - $span->setMetricsSummary( - SetType::TYPE, - 'set', - 'jane@doe@example.com', - MetricsUnit::custom('user'), - [] - ); - $span->setMetricsSummary( - SetType::TYPE, - 'set', - 'jon@doe@example.com', - MetricsUnit::custom('user'), - [] - ); - - $this->assertSame([ - 'c:counter@star' => [ - 'c:counter@stara:1:{s:10:"repository";s:6:"client";}' => [ - 'min' => 10.0, - 'max' => 50.0, - 'sum' => 60.0, - 'count' => 2, - 'tags' => [ - 'repository' => 'client', - ], - ], - 'c:counter@stara:1:{s:10:"repository";s:6:"server";}' => [ - 'min' => 10.0, - 'max' => 10.0, - 'sum' => 10.0, - 'count' => 1, - 'tags' => [ - 'repository' => 'server', - ], - ], - ], - 'd:distribution@millisecond' => [ - 'd:distribution@milliseconda:0:{}' => [ - 'min' => 5.7, - 'max' => 10.2, - 'sum' => 15.899999999999999, - 'count' => 2, - 'tags' => [], - ], - ], - 'g:gauge@none' => [ - 'g:gauge@nonea:0:{}' => [ - 'min' => 10.0, - 'max' => 20.0, - 'sum' => 30.0, - 'count' => 2, - 'tags' => [], - ], - ], - 's:set@user' => [ - 's:set@usera:0:{}' => [ - 'min' => 0.0, - 'max' => 1.0, - 'sum' => 1.0, - 'count' => 2, - 'tags' => [], - ], - ], - ], $span->getMetricsSummary()); - } - public function testDataGetter(): void { $span = new Span(); diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index be6c4e24c..19f9a9f89 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -57,7 +57,6 @@ public static function handleResponseDataProvider(): \Generator [ EventType::event(), EventType::transaction(), - EventType::metrics(), ], ]; @@ -67,38 +66,6 @@ public static function handleResponseDataProvider(): \Generator EventType::cases(), ]; - yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category' => [ - new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded:custom']], ''), - true, - [ - EventType::metrics(), - ], - ]; - - yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category, namespace custom and foo' => [ - new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded:custom;foo']], ''), - true, - [ - EventType::metrics(), - ], - ]; - - yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category without reason code' => [ - new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization::custom']], ''), - true, - [ - EventType::metrics(), - ], - ]; - - yield 'Back-off using X-Sentry-Rate-Limits header with metric_bucket category without metric namespaces' => [ - new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded']], ''), - true, - [ - EventType::metrics(), - ], - ]; - yield 'Do not back-off using X-Sentry-Rate-Limits header with metric_bucket category, namespace foo' => [ new Response(429, ['X-Sentry-Rate-Limits' => ['60:metric_bucket:organization:quota_exceeded:foo']], ''), false, From 8e81be12cc571a52e9fe40649d8d384ee984ad8f Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 6 Nov 2024 08:43:39 +0100 Subject: [PATCH 1042/1161] Prepare 4.10.0 (#1787) --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a2908bf9..8724d8ffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # CHANGELOG +## 4.10.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.10.0. + +### Features + +- The SDK was updated to support PHP 8.4 [(#1760)](https://github.com/getsentry/sentry-php/pull/1760) +- Expose a new `http_ssl_native_ca` option to tell the HTTP client to use the operating system's native CA store for certificate verification [(#1766)](https://github.com/getsentry/sentry-php/pull/1766) + +### Bug Fixes + +- Fix the `http_timeout` & `http_connect_timeout` options, which now also work with sub second values [(#1785)](https://github.com/getsentry/sentry-php/pull/1785) + +### Misc + +- HTTP breadcrumbs created by the `GuzzleTracingMiddleware` are now set to a warning status for `4xx` responses and an error status for `5xx` responses [(#1773)](https://github.com/getsentry/sentry-php/pull/1773) +- All public Metrics APIs are now no-op, intneral APIs were removed [(#1786)](https://github.com/getsentry/sentry-php/pull/1786) + ## 4.9.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.9.0. From 2af937d47d8aadb8dab0b1d7b9557e495dd12856 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 6 Nov 2024 07:44:19 +0000 Subject: [PATCH 1043/1161] release: 4.10.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 02757beeb..0fb96d986 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.9.0'; + public const SDK_VERSION = '4.10.0'; /** * @var Options The client options From d7936484b42d38a1d1d5869f51e6ad04a000aa10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 08:54:21 +0000 Subject: [PATCH 1044/1161] chore(deps): bump codecov/codecov-action from 3 to 5 (#1790) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0a1985cd..3f95de17b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,8 +87,10 @@ jobs: - name: Run out of memory tests (without coverage) run: vendor/bin/phpunit --testsuite oom --no-coverage - - name: Upload code coverage - uses: codecov/codecov-action@v3 + - name: Upload code coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} - name: Check benchmarks run: vendor/bin/phpbench run --revs=1 --iterations=1 From 9b773f5dfebbb7051800a81476582c752c77cb62 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 10 Dec 2024 13:50:53 +0100 Subject: [PATCH 1045/1161] Use `SENTRY_SPOTLIGHT` as default value for Spotlight options (#1789) Co-authored-by: Michi Hoffmann --- phpstan-baseline.neon | 5 ----- src/Options.php | 45 +++++++++++++++++++++++++++++++++++++------ tests/OptionsTest.php | 33 ++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ae65ebb11..f5a9fe9f8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -260,11 +260,6 @@ parameters: count: 1 path: src/Options.php - - - message: "#^Method Sentry\\\\Options\\:\\:isSpotlightEnabled\\(\\) should return bool but returns mixed\\.$#" - count: 1 - path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:shouldAttachMetricCodeLocations\\(\\) should return bool but returns mixed\\.$#" count: 1 diff --git a/src/Options.php b/src/Options.php index dd2930547..eb4ffb1f1 100644 --- a/src/Options.php +++ b/src/Options.php @@ -368,10 +368,13 @@ public function setLogger(LoggerInterface $logger): self public function isSpotlightEnabled(): bool { - return $this->options['spotlight']; + return \is_string($this->options['spotlight']) || $this->options['spotlight']; } - public function enableSpotlight(bool $enable): self + /** + * @param bool|string $enable can be passed a boolean or the Spotlight URL (which will also enable Spotlight) + */ + public function enableSpotlight($enable): self { $options = array_merge($this->options, ['spotlight' => $enable]); @@ -382,9 +385,18 @@ public function enableSpotlight(bool $enable): self public function getSpotlightUrl(): string { + if (\is_string($this->options['spotlight'])) { + return $this->options['spotlight']; + } + return $this->options['spotlight_url']; } + /** + * @return $this + * + * @deprecated since version 4.11. To be removed in 5.x. You may use `enableSpotlight` instead. + */ public function setSpotlightUrl(string $url): self { $options = array_merge($this->options, ['spotlight_url' => $url]); @@ -1076,7 +1088,7 @@ public function setClassSerializers(array $serializers): self /** * Gets a callback that will be invoked when we sample a Transaction. * - * @psalm-return null|callable(\Sentry\Tracing\SamplingContext): float + * @psalm-return null|callable(Tracing\SamplingContext): float */ public function getTracesSampler(): ?callable { @@ -1089,7 +1101,7 @@ public function getTracesSampler(): ?callable * * @param ?callable $sampler The sampler * - * @psalm-param null|callable(\Sentry\Tracing\SamplingContext): float $sampler + * @psalm-param null|callable(Tracing\SamplingContext): float $sampler */ public function setTracesSampler(?callable $sampler): self { @@ -1127,7 +1139,10 @@ private function configureOptions(OptionsResolver $resolver): void 'context_lines' => 5, 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, 'logger' => null, - 'spotlight' => false, + 'spotlight' => $_SERVER['SENTRY_SPOTLIGHT'] ?? null, + /** + * @deprecated since version 4.11. To be removed in 5.0. You may use `spotlight` instead. + */ 'spotlight_url' => 'http://localhost:8969', 'release' => $_SERVER['SENTRY_RELEASE'] ?? $_SERVER['AWS_LAMBDA_FUNCTION_VERSION'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, @@ -1187,7 +1202,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('in_app_exclude', 'string[]'); $resolver->setAllowedTypes('in_app_include', 'string[]'); $resolver->setAllowedTypes('logger', ['null', LoggerInterface::class]); - $resolver->setAllowedTypes('spotlight', 'bool'); + $resolver->setAllowedTypes('spotlight', ['bool', 'string', 'null']); $resolver->setAllowedTypes('spotlight_url', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); @@ -1229,6 +1244,8 @@ private function configureOptions(OptionsResolver $resolver): void return array_map([$this, 'normalizeAbsolutePath'], $value); }); + $resolver->setNormalizer('spotlight', \Closure::fromCallable([$this, 'normalizeBooleanOrUrl'])); + $resolver->setNormalizer('in_app_exclude', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); @@ -1254,6 +1271,22 @@ private function normalizeAbsolutePath(string $value): string return $path; } + /** + * @return bool|string + */ + private function normalizeBooleanOrUrl(SymfonyOptions $options, ?string $booleanOrUrl) + { + if (empty($booleanOrUrl)) { + return false; + } + + if (filter_var($booleanOrUrl, \FILTER_VALIDATE_URL)) { + return $booleanOrUrl; + } + + return filter_var($booleanOrUrl, \FILTER_VALIDATE_BOOLEAN); + } + /** * Normalizes the DSN option by parsing the host, public and secret keys and * an optional path. diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 3bbc3c5ea..9ef4662ea 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -177,7 +177,7 @@ static function (): void {}, 'spotlight', true, 'isSpotlightEnabled', - 'EnableSpotlight', + 'enableSpotlight', ]; yield [ @@ -653,6 +653,37 @@ public function testReleaseOptionDefaultValueIsPreferredFromSentryEnvironmentVar $this->assertSame('0.0.4', (new Options())->getRelease()); } + /** + * @backupGlobals enabled + * + * @dataProvider spotlightEnvironmentValueDataProvider + */ + public function testSpotlightOptionDefaultValueIsControlledFromEnvironmentVariable(string $environmentVariableValue, bool $expectedSpotlightEnabled, string $expectedSpotlightUrl): void + { + $_SERVER['SENTRY_SPOTLIGHT'] = $environmentVariableValue; + + $options = new Options(); + + $this->assertEquals($expectedSpotlightEnabled, $options->isSpotlightEnabled()); + $this->assertEquals($expectedSpotlightUrl, $options->getSpotlightUrl()); + } + + public static function spotlightEnvironmentValueDataProvider(): array + { + $defaultSpotlightUrl = 'http://localhost:8969'; + + return [ + ['', false, $defaultSpotlightUrl], + ['true', true, $defaultSpotlightUrl], + ['1', true, $defaultSpotlightUrl], + ['false', false, $defaultSpotlightUrl], + ['0', false, $defaultSpotlightUrl], + ['null', false, $defaultSpotlightUrl], + ['http://localhost:1234', true, 'http://localhost:1234'], + ['some invalid looking value', false, $defaultSpotlightUrl], + ]; + } + public function testErrorTypesOptionIsNotDynamiclyReadFromErrorReportingLevelWhenSet(): void { $errorReportingBeforeTest = error_reporting(\E_ERROR); From 675b22cf7ea344d6f20592f52994652a31978989 Mon Sep 17 00:00:00 2001 From: Stephanie Anderson Date: Tue, 10 Dec 2024 17:16:03 +0100 Subject: [PATCH 1046/1161] chore(meta): update issue templates to use issue types (#1795) --- .../{feature.yml => 01-feature.yml} | 6 ++-- .github/ISSUE_TEMPLATE/02-improvement.yml | 30 +++++++++++++++++++ .../ISSUE_TEMPLATE/{bug.yml => 03-bug.yml} | 1 + .github/ISSUE_TEMPLATE/config.yml | 1 - 4 files changed, 34 insertions(+), 4 deletions(-) rename .github/ISSUE_TEMPLATE/{feature.yml => 01-feature.yml} (89%) create mode 100644 .github/ISSUE_TEMPLATE/02-improvement.yml rename .github/ISSUE_TEMPLATE/{bug.yml => 03-bug.yml} (99%) diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/01-feature.yml similarity index 89% rename from .github/ISSUE_TEMPLATE/feature.yml rename to .github/ISSUE_TEMPLATE/01-feature.yml index 615bbc9f7..164acf1b1 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/01-feature.yml @@ -1,6 +1,6 @@ name: 💡 Feature Request -description: Create a feature request for sentry-php SDK. -labels: 'enhancement' +description: Propose new functionality for the SDK +type: Feature body: - type: markdown attributes: @@ -27,4 +27,4 @@ body: attributes: value: |- ## Thanks 🙠- Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. \ No newline at end of file + Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/ISSUE_TEMPLATE/02-improvement.yml b/.github/ISSUE_TEMPLATE/02-improvement.yml new file mode 100644 index 000000000..ae828cd71 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-improvement.yml @@ -0,0 +1,30 @@ +name: 💡 Improvement +description: Propose an improvement for existing functionality of the SDK +type: Improvement +body: + - type: markdown + attributes: + value: Thanks for taking the time to file a request! Please fill out this form as completely as possible. + - type: textarea + id: problem + attributes: + label: Problem Statement + description: A clear and concise description of what you want and what your use case is. + placeholder: |- + I want to make whirled peas, but Sentry doesn't blend. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Solution Brainstorm + description: We know you have bright ideas to share ... share away, friend. + placeholder: |- + Add a blender to Sentry. + validations: + required: true + - type: markdown + attributes: + value: |- + ## Thanks 🙠+ Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/03-bug.yml similarity index 99% rename from .github/ISSUE_TEMPLATE/bug.yml rename to .github/ISSUE_TEMPLATE/03-bug.yml index 1f0e71f73..75a3f6531 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/03-bug.yml @@ -1,5 +1,6 @@ name: 🞠Bug Report description: Tell us about something that's not working the way we (probably) intend. +type: Bug body: - type: dropdown id: type diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a6467ab0a..17d8a34dc 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,4 +3,3 @@ contact_links: - name: Support Request url: https://sentry.io/support about: Use our dedicated support channel for paid accounts. - \ No newline at end of file From bdcb19cb93f3aaa39004afdee30d161504d98fec Mon Sep 17 00:00:00 2001 From: Jeffrey Hung <17494876+Jeffreyhung@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:23:59 -0800 Subject: [PATCH 1047/1161] feat(release): Replace release bot with GH app (#1796) --- .github/workflows/publish-release.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index b3453ccb5..cc35d71a2 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -22,15 +22,22 @@ jobs: runs-on: ubuntu-latest name: Release version steps: + - name: Get auth token + id: token + uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + with: + app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} + private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} + - uses: actions/checkout@v4 with: - token: ${{ secrets.GH_RELEASE_PAT }} + token: ${{ steps.token.outputs.token }} fetch-depth: 0 - name: Prepare release uses: getsentry/action-prepare-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }} + GITHUB_TOKEN: ${{ steps.token.outputs.token }} with: version: ${{ github.event.inputs.version }} force: ${{ github.event.inputs.force }} From e43cf00d22ebdefcad23a4d91449fc6e38a6be25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:57:33 +0100 Subject: [PATCH 1048/1161] chore(deps): bump actions/create-github-app-token from 1.11.0 to 1.11.1 (#1797) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index cc35d71a2..0c9bba06b 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From 3519924e5fb1699c43a255755222099fb3387cdc Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 24 Jan 2025 14:45:06 +0100 Subject: [PATCH 1049/1161] Reference LOGAF Scale from develop docs (#1800) --- CONTRIBUTING.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8607f8012..a3eeab7a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,11 +25,7 @@ Thank you for contributing! ## PR reviews -For feedback in PRs, we use the [LOGAF scale](https://blog.danlew.net/2020/04/15/the-logaf-scale/) to specify how important a comment is: - -* `l`: low - nitpick. You may address this comment, but you don't have to. -* `m`: medium - normal comment. Worth addressing and fixing. -* `h`: high - Very important. We must not merge this PR without addressing this issue. +For feedback in PRs, we use the [LOGAF scale](https://develop.sentry.dev/engineering-practices/code-review/#logaf-scale) to specify how important a comment is. You only need one approval from a maintainer to be able to merge. For some PRs, asking specific or multiple people for review might be adequate. From 87d06784293b7c5f0ab3f1885110a5166fff041a Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 28 Jan 2025 13:04:47 +0100 Subject: [PATCH 1050/1161] Set the current span on the hub in Guzzle middleware (#1801) --- src/Tracing/GuzzleTracingMiddleware.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 4fb1b45f7..499f5bb97 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -59,6 +59,8 @@ public static function trace(?HubInterface $hub = null): \Closure $spanContext->setDescription($request->getMethod() . ' ' . $partialUri); $childSpan = $parentSpan->startChild($spanContext); + + $hub->setSpan($childSpan); } if (self::shouldAttachTracingHeaders($client, $request)) { @@ -68,11 +70,13 @@ public static function trace(?HubInterface $hub = null): \Closure ->withHeader('baggage', getBaggage()); } - $handlerPromiseCallback = static function ($responseOrException) use ($hub, $spanAndBreadcrumbData, $childSpan, $partialUri) { + $handlerPromiseCallback = static function ($responseOrException) use ($hub, $spanAndBreadcrumbData, $childSpan, $parentSpan, $partialUri) { if ($childSpan !== null) { // We finish the span (which means setting the span end timestamp) first to ensure the measured time // the span spans is as close to only the HTTP request time and do the data collection afterwards $childSpan->finish(); + + $hub->setSpan($parentSpan); } $response = null; From 880e328f15445df4483c4f19ef0bcd0bafd698c6 Mon Sep 17 00:00:00 2001 From: Grohotun Date: Tue, 18 Feb 2025 00:42:29 +0300 Subject: [PATCH 1051/1161] List Magento 2 3rd party integration (#1802) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8d8c62c97..37411dd77 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ The following integrations are available and maintained by members of the Sentry - [Drupal](https://www.drupal.org/project/raven) - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) +- [Magento 2](https://github.com/mygento/module-sentry) - ... feel free to be famous, create a port to your favourite platform! ## 3rd party integrations using the old SDK 3.x From 9792392c3c7a3e567dbcbb64761e24db2a135705 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 00:17:49 +0100 Subject: [PATCH 1052/1161] chore(deps): bump actions/create-github-app-token from 1.11.1 to 1.11.5 (#1808) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 0c9bba06b..949da6e57 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From c2c5c3ac4545cf5e835061eff7568c746bd1524c Mon Sep 17 00:00:00 2001 From: Martin Seitz Date: Tue, 18 Feb 2025 14:59:44 +0100 Subject: [PATCH 1053/1161] Serialize date objects (#1803) --- src/Serializer/AbstractSerializer.php | 18 ++++++++++++++ tests/Serializer/SerializerTest.php | 35 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 07bc36118..5393bb7aa 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -135,6 +135,10 @@ protected function serializeRecursively($value, int $_depth = 0) } } + if ($value instanceof \DateTimeInterface) { + return $this->formatDate($value); + } + if ($this->serializeAllObjects || ($value instanceof \stdClass)) { return $this->serializeObject($value, $_depth); } @@ -275,6 +279,20 @@ protected function serializeValue($value) return $this->serializeString($value); } + private function formatDate(\DateTimeInterface $date): string + { + $hasMicroseconds = $date->format('u') !== '000000'; + $formatted = $hasMicroseconds ? $date->format('Y-m-d H:i:s.u') : $date->format('Y-m-d H:i:s'); + $className = \get_class($date); + + $timezone = $date->getTimezone(); + if ($timezone && $timezone->getName() !== 'UTC') { + $formatted .= ' ' . $date->format('eP'); + } + + return "$className($formatted)"; + } + /** * @param callable|mixed $callable */ diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index 39329e665..d402f210f 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -144,6 +144,41 @@ public function testSerializableObject(): void ], $this->invokeSerialization($serializer, $object)); } + /** + * @dataProvider serializeDateTimeDataProvider + */ + public function testSerializeDateTime(\DateTimeInterface $date, string $expected): void + { + $serializer = $this->createSerializer(); + + $result = $this->invokeSerialization($serializer, $date); + + $this->assertSame($expected, $result); + } + + public function serializeDateTimeDataProvider(): \Generator + { + yield 'DateTime' => [ + new \DateTime('2001-02-03 13:37:42'), + 'DateTime(2001-02-03 13:37:42)', + ]; + + yield 'Microseconds' => [ + new \DateTimeImmutable('2001-02-03 13:37:42.123456'), + 'DateTimeImmutable(2001-02-03 13:37:42.123456)', + ]; + + yield 'Timezone' => [ + new \DateTime('2001-02-03 13:37:42', new \DateTimeZone('Europe/Paris')), + 'DateTime(2001-02-03 13:37:42 Europe/Paris+01:00)', + ]; + + yield 'Abbreviated timezone' => [ + new \DateTime('2021-03-28 13:37:42 CET'), + 'DateTime(2021-03-28 13:37:42 CET+01:00)', + ]; + } + public function testSerializableThatReturnsNull(): void { $serializer = $this->createSerializer(); From 792f349e6147da45a424c5bfd7864fca7923df32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 23:28:55 +0100 Subject: [PATCH 1054/1161] chore(deps): bump actions/create-github-app-token from 1.11.5 to 1.11.6 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 949da6e57..4ee78805e 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5 + uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1.11.6 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From 38f3c243114078d57f580239d2933dd99292e66d Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 4 Mar 2025 14:38:11 +0100 Subject: [PATCH 1055/1161] Propagated Sampling Rates (#1793) --- src/State/Hub.php | 24 +++++---- src/Tracing/DynamicSamplingContext.php | 5 ++ src/Tracing/PropagationContext.php | 53 ++++++++++++++++++++ src/Tracing/SamplingContext.php | 11 ++++ src/Tracing/TransactionContext.php | 23 +++++++++ src/Tracing/TransactionMetadata.php | 42 +++++++++++++++- tests/FunctionsTest.php | 6 ++- tests/Tracing/DynamicSamplingContextTest.php | 13 ++++- 8 files changed, 163 insertions(+), 14 deletions(-) diff --git a/src/State/Hub.php b/src/State/Hub.php index 4fa9a4382..79573a2e2 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -269,6 +269,7 @@ public function startTransaction(TransactionContext $context, array $customSampl $samplingContext->setAdditionalContext($customSamplingContext); $sampleSource = 'context'; + $sampleRand = $context->getMetadata()->getSampleRand(); if ($transaction->getSampled() === null) { $tracesSampler = $options->getTracesSampler(); @@ -278,12 +279,17 @@ public function startTransaction(TransactionContext $context, array $customSampl $sampleSource = 'config:traces_sampler'; } else { - $sampleRate = $this->getSampleRate( - $samplingContext->getParentSampled(), - $options->getTracesSampleRate() ?? 0 - ); - - $sampleSource = $samplingContext->getParentSampled() ? 'parent' : 'config:traces_sample_rate'; + $parentSampleRate = $context->getMetadata()->getParentSamplingRate(); + if ($parentSampleRate !== null) { + $sampleRate = $parentSampleRate; + $sampleSource = 'parent:sample_rate'; + } else { + $sampleRate = $this->getSampleRate( + $samplingContext->getParentSampled(), + $options->getTracesSampleRate() ?? 0 + ); + $sampleSource = $samplingContext->getParentSampled() ? 'parent:sampling_decision' : 'config:traces_sample_rate'; + } } if (!$this->isValidSampleRate($sampleRate)) { @@ -304,7 +310,7 @@ public function startTransaction(TransactionContext $context, array $customSampl return $transaction; } - $transaction->setSampled($this->sample($sampleRate)); + $transaction->setSampled($sampleRand < $sampleRate); } if (!$transaction->getSampled()) { @@ -376,11 +382,11 @@ private function getStackTop(): Layer private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float { if ($hasParentBeenSampled === true) { - return 1; + return 1.0; } if ($hasParentBeenSampled === false) { - return 0; + return 0.0; } return $fallbackSampleRate; diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index cf159ed59..55dafa5a4 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -186,6 +186,10 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h $samplingContext->set('sampled', $transaction->getSampled() ? 'true' : 'false'); } + if ($transaction->getMetadata()->getSampleRand() !== null) { + $samplingContext->set('sample_rand', (string) $transaction->getMetadata()->getSampleRand()); + } + $samplingContext->freeze(); return $samplingContext; @@ -195,6 +199,7 @@ public static function fromOptions(Options $options, Scope $scope): self { $samplingContext = new self(); $samplingContext->set('trace_id', (string) $scope->getPropagationContext()->getTraceId()); + $samplingContext->set('sample_rand', (string) $scope->getPropagationContext()->getSampleRand()); if ($options->getTracesSampleRate() !== null) { $samplingContext->set('sample_rate', (string) $options->getTracesSampleRate()); diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index fce1b2207..c05d9eb4c 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -28,6 +28,16 @@ final class PropagationContext */ private $parentSpanId; + /** + * @var bool|null The parent's sampling decision + */ + private $parentSampled; + + /** + * @var float|null + */ + private $sampleRand; + /** * @var DynamicSamplingContext|null The dynamic sampling context */ @@ -44,6 +54,8 @@ public static function fromDefaults(): self $context->traceId = TraceId::generate(); $context->spanId = SpanId::generate(); $context->parentSpanId = null; + $context->parentSampled = null; + $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6); $context->dynamicSamplingContext = null; return $context; @@ -159,6 +171,19 @@ public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplin return $this; } + public function getSampleRand(): ?float + { + return $this->sampleRand; + } + + public function setSampleRand(?float $sampleRand): self + { + $this->sampleRand = $sampleRand; + + return $this; + } + + // TODO add same logic as in TransactionContext private static function parseTraceparentAndBaggage(string $traceparent, string $baggage): self { $context = self::fromDefaults(); @@ -174,6 +199,11 @@ private static function parseTraceparentAndBaggage(string $traceparent, string $ $context->parentSpanId = new SpanId($matches['span_id']); $hasSentryTrace = true; } + + if (isset($matches['sampled'])) { + $context->parentSampled = $matches['sampled'] === '1'; + $hasSentryTrace = true; + } } elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) { if (!empty($matches['trace_id'])) { $context->traceId = new TraceId($matches['trace_id']); @@ -184,6 +214,11 @@ private static function parseTraceparentAndBaggage(string $traceparent, string $ $context->parentSpanId = new SpanId($matches['span_id']); $hasSentryTrace = true; } + + if (isset($matches['sampled'])) { + $context->parentSampled = $matches['sampled'] === '01'; + $hasSentryTrace = true; + } } $samplingContext = DynamicSamplingContext::fromHeader($baggage); @@ -201,6 +236,24 @@ private static function parseTraceparentAndBaggage(string $traceparent, string $ $context->dynamicSamplingContext = $samplingContext; } + // Store the propagated trace sample rand or generate a new one + if ($samplingContext->has('sample_rand')) { + $context->sampleRand = (float) $samplingContext->get('sample_rand'); + } else { + if ($samplingContext->has('sample_rate') && $context->parentSampled !== null) { + if ($context->parentSampled === true) { + // [0, rate) + $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6); + } else { + // [rate, 1) + $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample-rate'), 6); + } + } elseif ($context->parentSampled !== null) { + // [0, 1) + $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6); + } + } + return $context; } } diff --git a/src/Tracing/SamplingContext.php b/src/Tracing/SamplingContext.php index bee540bfb..24a393cbc 100644 --- a/src/Tracing/SamplingContext.php +++ b/src/Tracing/SamplingContext.php @@ -16,6 +16,11 @@ final class SamplingContext */ private $parentSampled; + /** + * @var float|null The parent sample rate + */ + private $sampleRand; + /** * @var array|null Additional context, depending on where the SDK runs */ @@ -29,6 +34,7 @@ public static function getDefault(TransactionContext $transactionContext): self $context = new self(); $context->transactionContext = $transactionContext; $context->parentSampled = $transactionContext->getParentSampled(); + $context->sampleRand = $transactionContext->getMetadata()->getSampleRand(); return $context; } @@ -46,6 +52,11 @@ public function getParentSampled(): ?bool return $this->parentSampled; } + public function getSampleRand(): ?float + { + return $this->sampleRand; + } + /** * Sets the sampling decision from the parent transaction, if any. */ diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index e804a1bd9..e18e55900 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -198,6 +198,29 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag $context->getMetadata()->setDynamicSamplingContext($samplingContext); } + // Store the propagated traces sample rate + if ($samplingContext->has('sample_rate')) { + $context->getMetadata()->setParentSamplingRate((float) $samplingContext->get('sample_rate')); + } + + // Store the propagated trace sample rand or generate a new one + if ($samplingContext->has('sample_rand')) { + $context->getMetadata()->setSampleRand((float) $samplingContext->get('sample_rand')); + } else { + if ($samplingContext->has('sample_rate') && $context->parentSampled !== null) { + if ($context->parentSampled === true) { + // [0, rate) + $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6)); + } else { + // [rate, 1) + $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample-rate'), 6)); + } + } elseif ($context->parentSampled !== null) { + // [0, 1) + $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6)); + } + } + return $context; } } diff --git a/src/Tracing/TransactionMetadata.php b/src/Tracing/TransactionMetadata.php index 2a10923bc..01ba3c579 100644 --- a/src/Tracing/TransactionMetadata.php +++ b/src/Tracing/TransactionMetadata.php @@ -21,21 +21,37 @@ final class TransactionMetadata */ private $source; + /** + * @var float|int|null + */ + private $parentSamplingRate; + + /** + * @var float|int|null + */ + private $sampleRand; + /** * Constructor. * * @param float|int|null $samplingRate The sampling rate * @param DynamicSamplingContext|null $dynamicSamplingContext The Dynamic Sampling Context * @param TransactionSource|null $source The transaction source + * @param float|null $parentSamplingRate The parent sampling rate + * @param float|null $sampleRand The trace sample rand */ public function __construct( $samplingRate = null, ?DynamicSamplingContext $dynamicSamplingContext = null, - ?TransactionSource $source = null + ?TransactionSource $source = null, + ?float $parentSamplingRate = null, + ?float $sampleRand = null ) { $this->samplingRate = $samplingRate; $this->dynamicSamplingContext = $dynamicSamplingContext; $this->source = $source ?? TransactionSource::custom(); + $this->parentSamplingRate = $parentSamplingRate; + $this->sampleRand = $sampleRand ?? round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6); } /** @@ -56,6 +72,30 @@ public function setSamplingRate($samplingRate): self return $this; } + public function getParentSamplingRate(): ?float + { + return $this->parentSamplingRate; + } + + public function setParentSamplingRate(?float $parentSamplingRate): self + { + $this->parentSamplingRate = $parentSamplingRate; + + return $this; + } + + public function getSampleRand(): ?float + { + return $this->sampleRand; + } + + public function setSampleRand(?float $sampleRand): self + { + $this->sampleRand = $sampleRand; + + return $this; + } + public function getDynamicSamplingContext(): ?DynamicSamplingContext { return $this->dynamicSamplingContext; diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index ce892e853..3e48cb95e 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -503,6 +503,7 @@ public function testBaggageWithTracingDisabled(): void { $propagationContext = PropagationContext::fromDefaults(); $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $propagationContext->setSampleRand(0.25); $scope = new Scope($propagationContext); @@ -520,7 +521,7 @@ public function testBaggageWithTracingDisabled(): void $baggage = getBaggage(); - $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-release=1.0.0,sentry-environment=development', $baggage); + $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rand=0.25,sentry-release=1.0.0,sentry-environment=development', $baggage); } public function testBaggageWithTracingEnabled(): void @@ -541,6 +542,7 @@ public function testBaggageWithTracingEnabled(): void $transactionContext = new TransactionContext(); $transactionContext->setName('Test'); $transactionContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $transactionContext->getMetadata()->setSampleRand(0.25); $transaction = startTransaction($transactionContext); @@ -552,7 +554,7 @@ public function testBaggageWithTracingEnabled(): void $baggage = getBaggage(); - $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1,sentry-transaction=Test,sentry-release=1.0.0,sentry-environment=development,sentry-sampled=true', $baggage); + $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1,sentry-transaction=Test,sentry-release=1.0.0,sentry-environment=development,sentry-sampled=true,sentry-sample_rand=0.25', $baggage); } public function testContinueTrace(): void diff --git a/tests/Tracing/DynamicSamplingContextTest.php b/tests/Tracing/DynamicSamplingContextTest.php index 73ecb5052..90ded1369 100644 --- a/tests/Tracing/DynamicSamplingContextTest.php +++ b/tests/Tracing/DynamicSamplingContextTest.php @@ -28,7 +28,8 @@ public function testFromHeader( ?string $expectedSampleRate, ?string $expectedRelease, ?string $expectedEnvironment, - ?string $expectedTransaction + ?string $expectedTransaction, + ?string $expectedSampleRand ): void { $samplingContext = DynamicSamplingContext::fromHeader($header); @@ -38,6 +39,7 @@ public function testFromHeader( $this->assertSame($expectedRelease, $samplingContext->get('release')); $this->assertSame($expectedEnvironment, $samplingContext->get('environment')); $this->assertSame($expectedTransaction, $samplingContext->get('transaction')); + $this->assertSame($expectedSampleRand, $samplingContext->get('sample_rand')); } public static function fromHeaderDataProvider(): \Generator @@ -51,6 +53,7 @@ public static function fromHeaderDataProvider(): \Generator null, null, null, + null, ]; yield [ @@ -61,16 +64,18 @@ public static function fromHeaderDataProvider(): \Generator null, null, null, + null, ]; yield [ - 'sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1,sentry-release=1.0.0,sentry-environment=test,sentry-user_segment=my_segment,sentry-transaction=', + 'sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1,sentry-release=1.0.0,sentry-environment=test,sentry-user_segment=my_segment,sentry-transaction=,sentry-sample_rand=0.5', 'd49d9bf66f13450b81f65bc51cf49c03', 'public', '1', '1.0.0', 'test', '', + '0.5', ]; } @@ -92,6 +97,7 @@ public function testFromTransaction(): void $transaction = new Transaction($transactionContext, $hub); $transaction->getMetadata()->setSamplingRate(1.0); + $transaction->getMetadata()->setSampleRand(0.5); $samplingContext = DynamicSamplingContext::fromTransaction($transaction, $hub); @@ -101,6 +107,7 @@ public function testFromTransaction(): void $this->assertSame('public', $samplingContext->get('public_key')); $this->assertSame('1.0.0', $samplingContext->get('release')); $this->assertSame('test', $samplingContext->get('environment')); + $this->assertSame('0.5', $samplingContext->get('sample_rand')); $this->assertTrue($samplingContext->isFrozen()); } @@ -130,6 +137,7 @@ public function testFromOptions(): void $propagationContext = PropagationContext::fromDefaults(); $propagationContext->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); + $propagationContext->setSampleRand(0.5); $scope = new Scope(); $scope->setPropagationContext($propagationContext); @@ -141,6 +149,7 @@ public function testFromOptions(): void $this->assertSame('public', $samplingContext->get('public_key')); $this->assertSame('1.0.0', $samplingContext->get('release')); $this->assertSame('test', $samplingContext->get('environment')); + $this->assertSame('0.5', $samplingContext->get('sample_rand')); $this->assertTrue($samplingContext->isFrozen()); } From 6341d842ea8bde2e44bdd58b9a5a95931bac3b93 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 10 Apr 2025 11:23:00 +0200 Subject: [PATCH 1056/1161] Improve handling of prefix stripping for anonymous frames (#1820) --- src/Frame.php | 3 +++ src/FrameBuilder.php | 11 +++++++++-- tests/FrameBuilderTest.php | 13 +++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Frame.php b/src/Frame.php index fa5b3e169..11a632120 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -13,6 +13,9 @@ final class Frame { public const INTERNAL_FRAME_FILENAME = '[internal]'; + /** + * @deprecated This constant is deprecated and will be removed in 5.x. + */ public const ANONYMOUS_CLASS_PREFIX = "class@anonymous\x00"; /** diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 1eec712d4..71f82024f 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -74,8 +74,15 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['class']; - if (mb_substr($functionName, 0, mb_strlen(Frame::ANONYMOUS_CLASS_PREFIX)) === Frame::ANONYMOUS_CLASS_PREFIX) { - $functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath($this->options, substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX))); + // Optimization: skip doing regex if we don't have prefixes to strip + if ($this->options->getPrefixes()) { + $prefixStrippedFunctionName = preg_replace_callback('/@anonymous\\x00([^:]+)(:.*)?/', function (array $matches) { + return "@anonymous\x00" . $this->stripPrefixFromFilePath($this->options, $matches[1]) . ($matches[2] ?? ''); + }, $functionName); + + if ($prefixStrippedFunctionName) { + $functionName = $prefixStrippedFunctionName; + } } $rawFunctionName = \sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']); diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php index ebaa58df8..c9393f5b1 100644 --- a/tests/FrameBuilderTest.php +++ b/tests/FrameBuilderTest.php @@ -149,6 +149,19 @@ public static function buildFromBacktraceFrameDataProvider(): \Generator ], new Frame(null, 'path/not/of/app/to/file', 10, null, 'path/not/of/app/to/file'), ]; + + yield [ + new Options([ + 'prefixes' => ['/path/to'], + ]), + [ + 'file' => '/path/to/file', + 'line' => 10, + 'function' => 'test_function', + 'class' => "App\\ClassName@anonymous\0/path/to/file:85$29e", + ], + new Frame("App\\ClassName@anonymous\0/file::test_function", '/file', 10, "App\\ClassName@anonymous\0/path/to/file:85$29e::test_function", '/path/to/file'), + ]; } /** From fffa68a90584d456d6c4fb2da326dadaa3d310c1 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 11 Apr 2025 13:40:38 +0200 Subject: [PATCH 1057/1161] Prepare 4.11.0 (#1821) --- CHANGELOG.md | 15 +++++++++++++++ src/FrameBuilder.php | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8724d8ffa..d401ef0a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # CHANGELOG +## 4.11.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.11.0. + +### Features + +- Serialize `\DateTimeInterface` objects by default [(#1803)](https://github.com/getsentry/sentry-php/pull/1803) +- Add support for [Propagated Ramdom Value](https://develop.sentry.dev/sdk/telemetry/traces/#propagated-random-value) [(#1793)](https://github.com/getsentry/sentry-php/pull/1793) +- Use the `SENTRY_SPOTLIGHT` environment variable as the input for the `spotlight` configuration option [(#1789)](https://github.com/getsentry/sentry-php/pull/1789) + +### Bug Fixes + +- Fix cases where anonymous stacktrace frames did not get their prefixes stripped [(#1820)](https://github.com/getsentry/sentry-php/pull/1820) +- Fix Guzzle middleware not setting the span it created as the current span, which resulted in nesting issues in the trace view [(#1801)](https://github.com/getsentry/sentry-php/pull/1801) + ## 4.10.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.10.0. diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 71f82024f..328ba5119 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -74,7 +74,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['class']; - // Optimization: skip doing regex if we don't have prefixes to strip + // Skip if no prefixes are set if ($this->options->getPrefixes()) { $prefixStrippedFunctionName = preg_replace_callback('/@anonymous\\x00([^:]+)(:.*)?/', function (array $matches) { return "@anonymous\x00" . $this->stripPrefixFromFilePath($this->options, $matches[1]) . ($matches[2] ?? ''); From ebf67deb9902b6da58a4b3383cbd12fed3f4f555 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 14 Apr 2025 09:04:23 +0000 Subject: [PATCH 1058/1161] release: 4.11.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 0fb96d986..23c551846 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.10.0'; + public const SDK_VERSION = '4.11.0'; /** * @var Options The client options From d1a1f1305bec67b01d9d54b4836fe5567559c445 Mon Sep 17 00:00:00 2001 From: Stephanie Anderson Date: Fri, 25 Apr 2025 16:36:03 +0200 Subject: [PATCH 1059/1161] Update GH issue templates for Linear compatibility (#1827) --- .github/ISSUE_TEMPLATE/01-feature.yml | 2 +- .github/ISSUE_TEMPLATE/02-improvement.yml | 2 +- .github/ISSUE_TEMPLATE/03-bug.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/01-feature.yml b/.github/ISSUE_TEMPLATE/01-feature.yml index 164acf1b1..f96ce60c0 100644 --- a/.github/ISSUE_TEMPLATE/01-feature.yml +++ b/.github/ISSUE_TEMPLATE/01-feature.yml @@ -1,6 +1,6 @@ name: 💡 Feature Request description: Propose new functionality for the SDK -type: Feature +labels: ["PHP", "Feature"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/02-improvement.yml b/.github/ISSUE_TEMPLATE/02-improvement.yml index ae828cd71..8df3bb748 100644 --- a/.github/ISSUE_TEMPLATE/02-improvement.yml +++ b/.github/ISSUE_TEMPLATE/02-improvement.yml @@ -1,6 +1,6 @@ name: 💡 Improvement description: Propose an improvement for existing functionality of the SDK -type: Improvement +labels: ["PHP", "Improvement"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/03-bug.yml b/.github/ISSUE_TEMPLATE/03-bug.yml index 75a3f6531..bf2f0058f 100644 --- a/.github/ISSUE_TEMPLATE/03-bug.yml +++ b/.github/ISSUE_TEMPLATE/03-bug.yml @@ -1,6 +1,6 @@ name: 🞠Bug Report description: Tell us about something that's not working the way we (probably) intend. -type: Bug +labels: ["PHP", "Bug"] body: - type: dropdown id: type From c9596f078fd15a44406828fc1bb9b01cc7133047 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 12 May 2025 08:24:07 +0200 Subject: [PATCH 1060/1161] Fix stripping prefixes from closure frames (#1828) --- src/FrameBuilder.php | 11 +++++++++++ tests/FrameBuilderTest.php | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 328ba5119..6384279fd 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -91,6 +91,17 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac $functionName = $backtraceFrame['function']; } + // Starting with PHP 8.4 a closure function call is reported as "{closure:filename:line}" instead of just "{closure}", properly strip the prefixes from that format + if (\PHP_VERSION_ID >= 80400 && $functionName !== null && $this->options->getPrefixes()) { + $prefixStrippedFunctionName = preg_replace_callback('/^\{closure:(.*?):(\d+)}$/', function (array $matches) { + return '{closure:' . $this->stripPrefixFromFilePath($this->options, $matches[1]) . ':' . $matches[2] . '}'; + }, $functionName); + + if ($prefixStrippedFunctionName) { + $functionName = $prefixStrippedFunctionName; + } + } + return new Frame( $functionName, $strippedFilePath, diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php index c9393f5b1..b43efcff8 100644 --- a/tests/FrameBuilderTest.php +++ b/tests/FrameBuilderTest.php @@ -162,6 +162,20 @@ public static function buildFromBacktraceFrameDataProvider(): \Generator ], new Frame("App\\ClassName@anonymous\0/file::test_function", '/file', 10, "App\\ClassName@anonymous\0/path/to/file:85$29e::test_function", '/path/to/file'), ]; + + if (\PHP_VERSION_ID >= 80400) { + yield [ + new Options([ + 'prefixes' => ['/path/to'], + ]), + [ + 'file' => '/path/to/file', + 'line' => 18, + 'function' => '{closure:/path/to/file:18}', + ], + new Frame('{closure:/file:18}', '/file', 18, null, '/path/to/file'), + ]; + } } /** From f9acbce2d70ee11bafd76b9e0d4647ac57f3d8c5 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 12 May 2025 08:25:07 +0200 Subject: [PATCH 1061/1161] Allow passing arbitrary event types to the rate limiter (#1831) --- src/Transport/HttpTransport.php | 2 +- src/Transport/RateLimiter.php | 18 ++++++++++++------ tests/Transport/RateLimiterTest.php | 4 ++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index db9ef1046..f47867fe8 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -91,7 +91,7 @@ public function send(Event $event): Result $this->logger->info(\sprintf('Sending %s to %s.', $eventDescription, $targetDescription), ['event' => $event]); $eventType = $event->getType(); - if ($this->rateLimiter->isRateLimited($eventType)) { + if ($this->rateLimiter->isRateLimited((string) $eventType)) { $this->logger->warning( \sprintf('Rate limit exceeded for sending requests of type "%s".', (string) $eventType), ['event' => $event] diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index 9a3d6d0f5..ce0becfd1 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -93,22 +93,28 @@ public function handleResponse(Response $response): bool return false; } - public function isRateLimited(EventType $eventType): bool + /** + * @param string|EventType $eventType + */ + public function isRateLimited($eventType): bool { $disabledUntil = $this->getDisabledUntil($eventType); return $disabledUntil > time(); } - public function getDisabledUntil(EventType $eventType): int + /** + * @param string|EventType $eventType + */ + public function getDisabledUntil($eventType): int { - $category = (string) $eventType; + $eventType = $eventType instanceof EventType ? (string) $eventType : $eventType; - if ($eventType === EventType::event()) { - $category = self::DATA_CATEGORY_ERROR; + if ($eventType === 'event') { + $eventType = self::DATA_CATEGORY_ERROR; } - return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); + return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$eventType] ?? 0); } private function parseRetryAfterHeader(int $currentTime, string $header): int diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index 19f9a9f89..441aa2eff 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -116,13 +116,13 @@ public function testIsRateLimited(): void private function assertEventTypesAreRateLimited(array $eventTypesLimited): void { foreach ($eventTypesLimited as $eventType) { - $this->assertTrue($this->rateLimiter->isRateLimited($eventType)); + $this->assertTrue($this->rateLimiter->isRateLimited((string) $eventType)); } $eventTypesNotLimited = array_diff(EventType::cases(), $eventTypesLimited); foreach ($eventTypesNotLimited as $eventType) { - $this->assertFalse($this->rateLimiter->isRateLimited($eventType)); + $this->assertFalse($this->rateLimiter->isRateLimited((string) $eventType)); } } } From 914a207f3b58d1c38d80dc7c15827a07cd605790 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 12 May 2025 08:25:34 +0200 Subject: [PATCH 1062/1161] Add package info to SDK payload (#1823) --- src/Event.php | 48 +++++++++++++++++++ src/Serializer/EnvelopItems/EventItem.php | 5 +- .../EnvelopItems/TransactionItem.php | 5 +- src/Serializer/PayloadSerializer.php | 5 +- tests/Serializer/PayloadSerializerTest.php | 38 +++++++-------- 5 files changed, 70 insertions(+), 31 deletions(-) diff --git a/src/Event.php b/src/Event.php index 0148069ac..51f4563b2 100644 --- a/src/Event.php +++ b/src/Event.php @@ -19,6 +19,10 @@ * count: int, * tags: array, * } + * @phpstan-type SdkPackageEntry array{ + * name: string, + * version: string, + * } */ final class Event { @@ -174,6 +178,16 @@ final class Event */ private $sdkVersion = Client::SDK_VERSION; + /** + * @var SdkPackageEntry[] The Sentry SDK packages + */ + private $sdkPackages = [ + [ + 'name' => 'composer:sentry/sentry', + 'version' => Client::SDK_VERSION, + ], + ]; + /** * @var EventType The type of the Event */ @@ -276,6 +290,40 @@ public function setSdkVersion(string $sdkVersion): self return $this; } + /** + * Append a package to the list of SDK packages. + * + * @param SdkPackageEntry $package The package to append + * + * @return $this + * + * @internal + */ + public function appendSdkPackage(array $package): self + { + $this->sdkPackages[] = $package; + + return $this; + } + + /** + * Gets the SDK playload that will be sent to Sentry. + * + * @see https://develop.sentry.dev/sdk/data-model/event-payloads/sdk/ + * + * @return array{name: string, version: string, packages: SdkPackageEntry[]} + * + * @internal + */ + public function getSdkPayload(): array + { + return [ + 'name' => $this->sdkIdentifier, + 'version' => $this->sdkVersion, + 'packages' => $this->sdkPackages, + ]; + } + /** * Gets the timestamp of when this event was generated. */ diff --git a/src/Serializer/EnvelopItems/EventItem.php b/src/Serializer/EnvelopItems/EventItem.php index dd37f70ff..ca6d1d38b 100644 --- a/src/Serializer/EnvelopItems/EventItem.php +++ b/src/Serializer/EnvelopItems/EventItem.php @@ -28,10 +28,7 @@ public static function toEnvelopeItem(Event $event): string $payload = [ 'timestamp' => $event->getTimestamp(), 'platform' => 'php', - 'sdk' => [ - 'name' => $event->getSdkIdentifier(), - 'version' => $event->getSdkVersion(), - ], + 'sdk' => $event->getSdkPayload(), ]; if ($event->getStartTimestamp() !== null) { diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index de6fd624a..679bd6bcd 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -35,10 +35,7 @@ public static function toEnvelopeItem(Event $event): string $payload = [ 'timestamp' => $event->getTimestamp(), 'platform' => 'php', - 'sdk' => [ - 'name' => $event->getSdkIdentifier(), - 'version' => $event->getSdkVersion(), - ], + 'sdk' => $event->getSdkPayload(), ]; if ($event->getStartTimestamp() !== null) { diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index c109e0336..b836fe044 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -42,10 +42,7 @@ public function serialize(Event $event): string 'event_id' => (string) $event->getId(), 'sent_at' => gmdate('Y-m-d\TH:i:s\Z'), 'dsn' => (string) $this->options->getDsn(), - 'sdk' => [ - 'name' => $event->getSdkIdentifier(), - 'version' => $event->getSdkVersion(), - ], + 'sdk' => $event->getSdkPayload(), ]; $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index c9283825d..1e8eca0fe 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -63,9 +63,9 @@ public static function serializeAsEnvelopeDataProvider(): iterable yield [ Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","sapi":"cli","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}},{"type":"default","category":"log","level":"info","timestamp":1597790835,"data":{"0":"foo","1":"bar"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} +{"timestamp":1597790835,"platform":"php","sdk":{"name":"sentry.php","version":"$sdkVersion","packages":[{"name":"composer:sentry\/sentry","version":"$sdkVersion"}]},"start_timestamp":1597790835,"level":"error","logger":"app.php","transaction":"\/users\/\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","sapi":"cli","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}},{"type":"default","category":"log","level":"info","timestamp":1597790835,"data":{"0":"foo","1":"bar"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} TEXT ]; @@ -181,9 +181,9 @@ public static function serializeAsEnvelopeDataProvider(): iterable yield [ $event, << Date: Mon, 12 May 2025 06:27:39 +0000 Subject: [PATCH 1063/1161] chore(deps): bump actions/create-github-app-token from 1.11.6 to 2.0.6 (#1829) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Michi Hoffmann --- .github/workflows/publish-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 4ee78805e..5488ac5b0 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1.11.6 + uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From 7e91a3c69f4c3882518c65192f063cb0609458c1 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 12 May 2025 13:29:55 +0200 Subject: [PATCH 1064/1161] Prepare 4.11.1 (#1832) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d401ef0a9..dae925bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 4.11.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.11.1. + +### Bug Fixes + +- Fix stripping prefixes from closure frames for PHP 8.4 and up [(#1828)](https://github.com/getsentry/sentry-php/pull/1828) + ## 4.11.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.11.0. From 53dc0bcb6a667cac5b760b46f98d5380e63e02ca Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 12 May 2025 11:30:33 +0000 Subject: [PATCH 1065/1161] release: 4.11.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 23c551846..5048343ae 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.11.0'; + public const SDK_VERSION = '4.11.1'; /** * @var Options The client options From 9e3662d44ed664505b46c00a1adcaa77996bb498 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 13 May 2025 12:40:46 +0200 Subject: [PATCH 1066/1161] Remove support for `traceparent` (#1833) --- src/Tracing/GuzzleTracingMiddleware.php | 2 - src/Tracing/PropagationContext.php | 21 ++------- src/Tracing/Span.php | 13 ++---- src/Tracing/TransactionContext.php | 17 ------- src/functions.php | 23 ++-------- tests/FunctionsTest.php | 45 ------------------- tests/Tracing/GuzzleTracingMiddlewareTest.php | 5 --- tests/Tracing/PropagationContextTest.php | 17 ------- tests/Tracing/TransactionContextTest.php | 20 --------- 9 files changed, 9 insertions(+), 154 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 499f5bb97..ee2ce0f0f 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -15,7 +15,6 @@ use function Sentry\getBaggage; use function Sentry\getTraceparent; -use function Sentry\getW3CTraceparent; /** * This handler traces each outgoing HTTP request by recording performance data. @@ -66,7 +65,6 @@ public static function trace(?HubInterface $hub = null): \Closure if (self::shouldAttachTracingHeaders($client, $request)) { $request = $request ->withHeader('sentry-trace', getTraceparent()) - ->withHeader('traceparent', getW3CTraceparent()) ->withHeader('baggage', getBaggage()); } diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index c05d9eb4c..cb150fe64 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -11,8 +11,6 @@ final class PropagationContext { private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; - private const W3C_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0]{2})?-?(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01]{2})?[ \\t]*$/i'; - /** * @var TraceId The trace id */ @@ -81,10 +79,12 @@ public function toTraceparent(): string /** * Returns a string that can be used for the W3C `traceparent` header & meta tag. + * + * @deprecated since version 4.12. To be removed in version 5.0. */ public function toW3CTraceparent(): string { - return \sprintf('00-%s-%s-00', (string) $this->traceId, (string) $this->spanId); + return ''; } /** @@ -204,21 +204,6 @@ private static function parseTraceparentAndBaggage(string $traceparent, string $ $context->parentSampled = $matches['sampled'] === '1'; $hasSentryTrace = true; } - } elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) { - if (!empty($matches['trace_id'])) { - $context->traceId = new TraceId($matches['trace_id']); - $hasSentryTrace = true; - } - - if (!empty($matches['span_id'])) { - $context->parentSpanId = new SpanId($matches['span_id']); - $hasSentryTrace = true; - } - - if (isset($matches['sampled'])) { - $context->parentSampled = $matches['sampled'] === '01'; - $hasSentryTrace = true; - } } $samplingContext = DynamicSamplingContext::fromHeader($baggage); diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index fdbba775c..51308b06e 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -565,19 +565,12 @@ public function toTraceparent(): string /** * Returns a string that can be used for the W3C `traceparent` header & meta tag. + * + * @deprecated since version 4.12. To be removed in version 5.0. */ public function toW3CTraceparent(): string { - $sampled = ''; - - if ($this->sampled !== null) { - $sampled = $this->sampled ? '01' : '00'; - } else { - // If no sampling decision was made, set the flag to 00 - $sampled = '00'; - } - - return \sprintf('00-%s-%s-%s', (string) $this->traceId, (string) $this->spanId, $sampled); + return ''; } /** diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index e18e55900..e7774a3f0 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -8,8 +8,6 @@ final class TransactionContext extends SpanContext { private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; - private const W3C_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0]{2})?-?(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01]{2})?[ \\t]*$/i'; - public const DEFAULT_NAME = ''; /** @@ -166,21 +164,6 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag $context->parentSampled = $matches['sampled'] === '1'; $hasSentryTrace = true; } - } elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) { - if (!empty($matches['trace_id'])) { - $context->traceId = new TraceId($matches['trace_id']); - $hasSentryTrace = true; - } - - if (!empty($matches['span_id'])) { - $context->parentSpanId = new SpanId($matches['span_id']); - $hasSentryTrace = true; - } - - if (isset($matches['sampled'])) { - $context->parentSampled = $matches['sampled'] === '01'; - $hasSentryTrace = true; - } } $samplingContext = DynamicSamplingContext::fromHeader($baggage); diff --git a/src/functions.php b/src/functions.php index 46a3d99e0..227d47bab 100644 --- a/src/functions.php +++ b/src/functions.php @@ -305,29 +305,12 @@ function getTraceparent(): string * or HTML meta tag value. * This function is context aware, as in it either returns the traceparent based * on the current span, or the scope's propagation context. + * + * @deprecated since version 4.12. To be removed in version 5.0. */ function getW3CTraceparent(): string { - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); - - if ($client !== null) { - $options = $client->getOptions(); - - if ($options !== null && $options->isTracingEnabled()) { - $span = SentrySdk::getCurrentHub()->getSpan(); - if ($span !== null) { - return $span->toW3CTraceparent(); - } - } - } - - $traceParent = ''; - $hub->configureScope(function (Scope $scope) use (&$traceParent) { - $traceParent = $scope->getPropagationContext()->toW3CTraceparent(); - }); - - return $traceParent; + return ''; } /** diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 3e48cb95e..949f9fadd 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -39,7 +39,6 @@ use function Sentry\continueTrace; use function Sentry\getBaggage; use function Sentry\getTraceparent; -use function Sentry\getW3CTraceparent; use function Sentry\init; use function Sentry\startTransaction; use function Sentry\trace; @@ -455,50 +454,6 @@ public function testTraceparentWithTracingEnabled(): void $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent); } - public function testW3CTraceparentWithTracingDisabled(): void - { - $propagationContext = PropagationContext::fromDefaults(); - $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); - $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); - - $scope = new Scope($propagationContext); - - $hub = new Hub(null, $scope); - - SentrySdk::setCurrentHub($hub); - - $traceParent = getW3CTraceparent(); - - $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-00', $traceParent); - } - - public function testW3CTraceparentWithTracingEnabled(): void - { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1.0, - ])); - - $hub = new Hub($client); - - SentrySdk::setCurrentHub($hub); - - $spanContext = (new SpanContext()) - ->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')) - ->setSpanId(new SpanId('566e3688a61d4bc8')) - ->setSampled(true); - - $span = new Span($spanContext); - - $hub->setSpan($span); - - $traceParent = getW3CTraceparent(); - - $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01', $traceParent); - } - public function testBaggageWithTracingDisabled(): void { $propagationContext = PropagationContext::fromDefaults(); diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 46e9a8325..f43b7fcf2 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -130,11 +130,9 @@ public function testTraceHeaders(Request $request, Options $options, bool $heade $function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface { if ($headersShouldBePresent) { $this->assertNotEmpty($request->getHeader('sentry-trace')); - $this->assertNotEmpty($request->getHeader('traceparent')); $this->assertNotEmpty($request->getHeader('baggage')); } else { $this->assertEmpty($request->getHeader('sentry-trace')); - $this->assertEmpty($request->getHeader('traceparent')); $this->assertEmpty($request->getHeader('baggage')); } @@ -167,11 +165,9 @@ public function testTraceHeadersWithTransaction(Request $request, Options $optio $function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface { if ($headersShouldBePresent) { $this->assertNotEmpty($request->getHeader('sentry-trace')); - $this->assertNotEmpty($request->getHeader('traceparent')); $this->assertNotEmpty($request->getHeader('baggage')); } else { $this->assertEmpty($request->getHeader('sentry-trace')); - $this->assertEmpty($request->getHeader('traceparent')); $this->assertEmpty($request->getHeader('baggage')); } @@ -344,7 +340,6 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec $middleware = GuzzleTracingMiddleware::trace($hub); $function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface { $this->assertNotEmpty($request->getHeader('sentry-trace')); - $this->assertNotEmpty($request->getHeader('traceparent')); $this->assertNotEmpty($request->getHeader('baggage')); if ($expectedPromiseResult instanceof \Throwable) { return new RejectedPromise($expectedPromiseResult); diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index b60fbc701..a80d9d17f 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -78,14 +78,6 @@ public static function tracingDataProvider(): iterable true, ]; - yield [ - '00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01', - '', - new TraceId('566e3688a61d4bc888951642d6f14a19'), - new SpanId('566e3688a61d4bc8'), - true, - ]; - yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', @@ -112,15 +104,6 @@ public function testToTraceparent() $this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toTraceparent()); } - public function testToW3CTraceparent() - { - $propagationContext = PropagationContext::fromDefaults(); - $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); - $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); - - $this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-00', $propagationContext->toW3CTraceparent()); - } - public function testToBaggage() { $dynamicSamplingContext = DynamicSamplingContext::fromHeader('sentry-trace_id=566e3688a61d4bc888951642d6f14a19'); diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index 67bc5ced6..c4c1176e2 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -117,26 +117,6 @@ public static function tracingDataProvider(): iterable true, ]; - yield [ - '00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-00', - '', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - false, - DynamicSamplingContext::class, - true, - ]; - - yield [ - '00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01', - '', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - true, - DynamicSamplingContext::class, - true, - ]; - yield [ '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', 'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1', From 33a3011f4d6175b1a35b12268463d4581d15a06a Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 15 May 2025 16:07:29 +0200 Subject: [PATCH 1067/1161] Fix new phpstan errors (#1835) --- phpstan-baseline.neon | 5 ----- psalm-baseline.xml | 5 +++++ src/Serializer/AbstractSerializer.php | 22 ++++++++++++---------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f5a9fe9f8..51779526a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -285,11 +285,6 @@ parameters: count: 1 path: src/Serializer/AbstractSerializer.php - - - message: "#^Cannot cast mixed to string\\.$#" - count: 1 - path: src/Serializer/AbstractSerializer.php - - message: "#^Call to method getResponse\\(\\) on an unknown class GuzzleHttp\\\\Exception\\\\RequestException\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index e8bdabe84..1294a8bc5 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -66,6 +66,11 @@ SentryProfile|null + + + (string) $value + + $value diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 5393bb7aa..b26cd2ac2 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -212,24 +212,22 @@ protected function serializeObject($object, int $_depth = 0, array $hashes = []) /** * Serializes the given value to a string. * - * @param mixed $value The value to serialize + * @param string $value The value to serialize */ - protected function serializeString($value): string + protected function serializeString(string $value): string { - $value = (string) $value; - // we always guarantee this is coerced, even if we can't detect encoding if ($currentEncoding = mb_detect_encoding($value, $this->mbDetectOrder)) { - $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); + $encoded = mb_convert_encoding($value, 'UTF-8', $currentEncoding) ?: ''; } else { - $value = mb_convert_encoding($value, 'UTF-8'); + $encoded = mb_convert_encoding($value, 'UTF-8') ?: ''; } - if (mb_strlen($value) > $this->options->getMaxValueLength()) { - $value = mb_substr($value, 0, $this->options->getMaxValueLength() - 10, 'UTF-8') . ' {clipped}'; + if (mb_strlen($encoded) > $this->options->getMaxValueLength()) { + $encoded = mb_substr($encoded, 0, $this->options->getMaxValueLength() - 10, 'UTF-8') . ' {clipped}'; } - return $value; + return $encoded; } /** @@ -276,7 +274,11 @@ protected function serializeValue($value) return 'Array of length ' . \count($value); } - return $this->serializeString($value); + if (\is_string($value) || (\is_object($value) && method_exists($value, '__toString'))) { + return $this->serializeString((string) $value); + } + + return null; } private function formatDate(\DateTimeInterface $date): string From 795006bb226da61810e4d2e4836ca7459c7ba28e Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 22 May 2025 08:16:32 +0200 Subject: [PATCH 1068/1161] Add new `orgId` option (#1794) --- phpstan-baseline.neon | 7 +++++- src/Dsn.php | 32 ++++++++++++++++++++++---- src/Options.php | 22 ++++++++++++++++++ src/Tracing/DynamicSamplingContext.php | 7 ++++++ src/functions.php | 1 + tests/DsnTest.php | 24 ++++++++++++++++++- tests/OptionsTest.php | 7 ++++++ 7 files changed, 94 insertions(+), 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 51779526a..955eebcdf 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7,7 +7,7 @@ parameters: - message: "#^Offset 'host' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" - count: 1 + count: 2 path: src/Dsn.php - @@ -200,6 +200,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getOrgId\\(\\) should return int\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getPrefixes\\(\\) should return array\\ but returns mixed\\.$#" count: 1 diff --git a/src/Dsn.php b/src/Dsn.php index 37aaca162..b6f952297 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -12,6 +12,12 @@ */ final class Dsn implements \Stringable { + /** + * @var string Regex to match the organization ID in the host. + * This only applies to Sentry SaaS DSNs that contain the organization ID. + */ + private const SENTRY_ORG_ID_REGEX = '/^o(\d+)\./'; + /** * @var string The protocol to be used to access the resource */ @@ -42,6 +48,11 @@ final class Dsn implements \Stringable */ private $path; + /** + * @var int|null + */ + private $orgId; + /** * Class constructor. * @@ -51,15 +62,17 @@ final class Dsn implements \Stringable * @param string $projectId The ID of the resource to access * @param string $path The specific resource that the web client wants to access * @param string $publicKey The public key to authenticate the SDK + * @param ?int $orgId The org ID */ - private function __construct(string $scheme, string $host, int $port, string $projectId, string $path, string $publicKey) + private function __construct(string $scheme, string $host, int $port, string $projectId, string $path, string $publicKey, ?int $orgId = null) { $this->scheme = $scheme; $this->host = $host; $this->port = $port; - $this->publicKey = $publicKey; - $this->path = $path; $this->projectId = $projectId; + $this->path = $path; + $this->publicKey = $publicKey; + $this->orgId = $orgId; } /** @@ -94,13 +107,19 @@ public static function createFromString(string $value): self $path = substr($parsedDsn['path'], 0, $lastSlashPosition); } + $orgId = null; + if (preg_match(self::SENTRY_ORG_ID_REGEX, $parsedDsn['host'], $matches) == 1) { + $orgId = (int) $matches[1]; + } + return new self( $parsedDsn['scheme'], $parsedDsn['host'], $parsedDsn['port'] ?? ($parsedDsn['scheme'] === 'http' ? 80 : 443), $projectId, $path, - $parsedDsn['user'] + $parsedDsn['user'], + $orgId ); } @@ -152,6 +171,11 @@ public function getPublicKey(): string return $this->publicKey; } + public function getOrgId(): ?int + { + return $this->orgId; + } + /** * Returns the URL of the API for the envelope endpoint. */ diff --git a/src/Options.php b/src/Options.php index eb4ffb1f1..3808643a0 100644 --- a/src/Options.php +++ b/src/Options.php @@ -436,6 +436,26 @@ public function getDsn(): ?Dsn return $this->options['dsn']; } + /** + * Gets the Org ID. + */ + public function getOrgId(): ?int + { + return $this->options['org_id']; + } + + /** + * Sets the Org ID. + */ + public function setOrgId(int $orgId): self + { + $options = array_merge($this->options, ['org_id' => $orgId]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Gets the name of the server the SDK is running on (e.g. the hostname). */ @@ -1146,6 +1166,7 @@ private function configureOptions(OptionsResolver $resolver): void 'spotlight_url' => 'http://localhost:8969', 'release' => $_SERVER['SENTRY_RELEASE'] ?? $_SERVER['AWS_LAMBDA_FUNCTION_VERSION'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, + 'org_id' => null, 'server_name' => gethostname(), 'ignore_exceptions' => [], 'ignore_transactions' => [], @@ -1206,6 +1227,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('spotlight_url', 'string'); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); + $resolver->setAllowedTypes('org_id', ['null', 'int']); $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('before_send_transaction', ['callable']); diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index 55dafa5a4..bcdf6c959 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -172,6 +172,9 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h if ($options->getDsn() !== null && $options->getDsn()->getPublicKey() !== null) { $samplingContext->set('public_key', $options->getDsn()->getPublicKey()); } + if ($options->getDsn() !== null && $options->getDsn()->getOrgId() !== null) { + $samplingContext->set('org_id', (string) $options->getDsn()->getOrgId()); + } if ($options->getRelease() !== null) { $samplingContext->set('release', $options->getRelease()); @@ -209,6 +212,10 @@ public static function fromOptions(Options $options, Scope $scope): self $samplingContext->set('public_key', $options->getDsn()->getPublicKey()); } + if ($options->getDsn() !== null && $options->getDsn()->getOrgId() !== null) { + $samplingContext->set('org_id', (string) $options->getDsn()->getOrgId()); + } + if ($options->getRelease() !== null) { $samplingContext->set('release', $options->getRelease()); } diff --git a/src/functions.php b/src/functions.php index 227d47bab..e53e48687 100644 --- a/src/functions.php +++ b/src/functions.php @@ -46,6 +46,7 @@ * max_breadcrumbs?: int, * max_request_body_size?: "none"|"never"|"small"|"medium"|"always", * max_value_length?: int, + * org_id?: int|null, * prefixes?: array, * profiles_sample_rate?: int|float|null, * release?: string|null, diff --git a/tests/DsnTest.php b/tests/DsnTest.php index 724d00538..ee091a0f2 100644 --- a/tests/DsnTest.php +++ b/tests/DsnTest.php @@ -22,7 +22,8 @@ public function testCreateFromString( int $expectedPort, string $expectedPublicKey, string $expectedProjectId, - string $expectedPath + string $expectedPath, + ?int $expectedOrgId ): void { $dsn = Dsn::createFromString($value); @@ -32,6 +33,7 @@ public function testCreateFromString( $this->assertSame($expectedPublicKey, $dsn->getPublicKey()); $this->assertSame($expectedProjectId, $dsn->getProjectId(true)); $this->assertSame($expectedPath, $dsn->getPath()); + $this->assertSame($expectedOrgId, $dsn->getOrgId()); } public static function createFromStringDataProvider(): \Generator @@ -44,6 +46,7 @@ public static function createFromStringDataProvider(): \Generator 'public', '1', '/sentry', + null, ]; yield [ @@ -54,6 +57,18 @@ public static function createFromStringDataProvider(): \Generator 'public', '1', '', + null, + ]; + + yield [ + 'http://public@o1.example.com/1', + 'http', + 'o1.example.com', + 80, + 'public', + '1', + '', + 1, ]; yield [ @@ -64,6 +79,7 @@ public static function createFromStringDataProvider(): \Generator 'public', '1', '', + null, ]; yield [ @@ -74,6 +90,7 @@ public static function createFromStringDataProvider(): \Generator 'public', '1', '', + null, ]; yield [ @@ -84,6 +101,7 @@ public static function createFromStringDataProvider(): \Generator 'public', '1', '', + null, ]; yield [ @@ -94,6 +112,7 @@ public static function createFromStringDataProvider(): \Generator 'public', '1', '', + null, ]; yield [ @@ -104,6 +123,7 @@ public static function createFromStringDataProvider(): \Generator 'public', '1', '', + null, ]; yield [ @@ -114,6 +134,7 @@ public static function createFromStringDataProvider(): \Generator 'public', '1', '', + null, ]; } @@ -240,6 +261,7 @@ public static function toStringDataProvider(): array return [ ['http://public@example.com/sentry/1'], ['http://public@example.com/1'], + ['http://public@o1.example.com/1'], ['http://public@example.com:8080/sentry/1'], ['https://public@example.com/sentry/1'], ['https://public@example.com:4343/sentry/1'], diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 9ef4662ea..8725ac912 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -75,6 +75,13 @@ public function testGettersAndSetters( public static function optionsDataProvider(): \Generator { + yield [ + 'org_id', + 1, + 'getOrgId', + 'setOrgId', + ]; + yield [ 'prefixes', ['foo', 'bar'], From d779b0099272d0746b98f8522774c91786b94faf Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 22 May 2025 09:04:15 +0200 Subject: [PATCH 1069/1161] Add new `strictTracePropagation` option (#1834) --- phpstan-baseline.neon | 5 +++++ src/Options.php | 22 ++++++++++++++++++++++ src/functions.php | 1 + tests/OptionsTest.php | 7 +++++++ 4 files changed, 35 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 955eebcdf..e8f6d849c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -265,6 +265,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:isStrictTracePropagationEnabled\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:shouldAttachMetricCodeLocations\\(\\) should return bool but returns mixed\\.$#" count: 1 diff --git a/src/Options.php b/src/Options.php index 3808643a0..45dcbefaf 100644 --- a/src/Options.php +++ b/src/Options.php @@ -668,6 +668,26 @@ public function setTracePropagationTargets(array $tracePropagationTargets): self return $this; } + /** + * Returns whether strict trace propagation is enabled or not. + */ + public function isStrictTracePropagationEnabled(): bool + { + return $this->options['strict_trace_propagation']; + } + + /** + * Sets if strict trace propagation should be enabled or not. + */ + public function enableStrictTracePropagation(bool $strictTracePropagation): self + { + $options = array_merge($this->options, ['strict_trace_propagation' => $strictTracePropagation]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Gets a list of default tags for events. * @@ -1186,6 +1206,7 @@ private function configureOptions(OptionsResolver $resolver): void return null; }, 'trace_propagation_targets' => null, + 'strict_trace_propagation' => false, 'tags' => [], 'error_types' => null, 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, @@ -1234,6 +1255,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('ignore_exceptions', 'string[]'); $resolver->setAllowedTypes('ignore_transactions', 'string[]'); $resolver->setAllowedTypes('trace_propagation_targets', ['null', 'string[]']); + $resolver->setAllowedTypes('strict_trace_propagation', 'bool'); $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['null', 'int']); $resolver->setAllowedTypes('max_breadcrumbs', 'int'); diff --git a/src/functions.php b/src/functions.php index e53e48687..28b0c3009 100644 --- a/src/functions.php +++ b/src/functions.php @@ -57,6 +57,7 @@ * server_name?: string, * spotlight?: bool, * spotlight_url?: string, + * strict_trace_propagation?: bool, * tags?: array, * trace_propagation_targets?: array|null, * traces_sample_rate?: float|int|null, diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 8725ac912..dd47ca778 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -278,6 +278,13 @@ static function (): void {}, 'setTracePropagationTargets', ]; + yield [ + 'strict_trace_propagation', + true, + 'isStrictTracePropagationEnabled', + 'enableStrictTracePropagation', + ]; + yield [ 'before_breadcrumb', static function (): void {}, From 3f99e198539b558f472ed5453a619f78d12441cb Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 22 May 2025 10:57:39 +0200 Subject: [PATCH 1070/1161] Log correct source of sampling decision (#1836) --- src/State/Hub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/State/Hub.php b/src/State/Hub.php index 79573a2e2..bd51b440b 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -288,7 +288,7 @@ public function startTransaction(TransactionContext $context, array $customSampl $samplingContext->getParentSampled(), $options->getTracesSampleRate() ?? 0 ); - $sampleSource = $samplingContext->getParentSampled() ? 'parent:sampling_decision' : 'config:traces_sample_rate'; + $sampleSource = $samplingContext->getParentSampled() !== null ? 'parent:sampling_decision' : 'config:traces_sample_rate'; } } From 18b57e05d62fef93e0dd2bcbcc189b36f6045b80 Mon Sep 17 00:00:00 2001 From: Fabien Salathe Date: Thu, 22 May 2025 18:28:39 +0900 Subject: [PATCH 1071/1161] Add removal of `Dsn::getStoreApiEndpointUrl()` to `UPGRADE-4.0.md` (#1837) --- UPGRADE-4.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index fd4dffce7..81c0b2d95 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -58,6 +58,7 @@ - Removed `NullTransport`. - Removed `Dsn::getSecretKey()`. - Removed `Dsn::setSecretKey()`. +- Removed `Dsn::getStoreApiEndpointUrl()`. - Removed `EventType::default()`. - Removed adding the value of the `logger` option as a tag on the event. If you rely on this behaviour, add the tag manually. From bd09259fe9c6913cc5fe2b7b7d6846751e6225ae Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 22 May 2025 14:49:05 +0200 Subject: [PATCH 1072/1161] Add Sentry logs (#1813) Co-authored-by: Alex Bouma --- phpstan-baseline.neon | 17 +- src/Attributes/Attribute.php | 126 ++++++++++++++ src/Attributes/AttributeBag.php | 74 +++++++++ src/Client.php | 11 ++ src/Event.php | 29 ++++ src/EventType.php | 6 + src/Logger/DebugFileLogger.php | 13 +- src/Logger/DebugLogger.php | 26 +++ src/Logger/DebugStdOutLogger.php | 13 +- src/Logs/Log.php | 109 +++++++++++++ src/Logs/LogLevel.php | 70 ++++++++ src/Logs/Logs.php | 112 +++++++++++++ src/Logs/LogsAggregator.php | 145 +++++++++++++++++ src/Options.php | 40 +++++ .../EnvelopItems/EnvelopeItemInterface.php | 2 +- src/Serializer/EnvelopItems/LogsItem.php | 34 ++++ src/Serializer/EnvelopItems/ProfileItem.php | 6 +- .../EnvelopItems/TransactionItem.php | 3 +- src/Serializer/PayloadSerializer.php | 21 ++- src/Transport/RateLimiter.php | 7 + src/Util/Arr.php | 66 ++++++++ src/functions.php | 11 ++ tests/Attributes/AttributeBagTest.php | 68 ++++++++ tests/Attributes/AttributeTest.php | 147 +++++++++++++++++ tests/Logs/LogTest.php | 43 +++++ tests/Logs/LogsTest.php | 154 ++++++++++++++++++ tests/Serializer/PayloadSerializerTest.php | 17 ++ tests/Util/ArrTest.php | 101 ++++++++++++ 28 files changed, 1429 insertions(+), 42 deletions(-) create mode 100644 src/Attributes/Attribute.php create mode 100644 src/Attributes/AttributeBag.php create mode 100644 src/Logger/DebugLogger.php create mode 100644 src/Logs/Log.php create mode 100644 src/Logs/LogLevel.php create mode 100644 src/Logs/Logs.php create mode 100644 src/Logs/LogsAggregator.php create mode 100644 src/Serializer/EnvelopItems/LogsItem.php create mode 100644 src/Util/Arr.php create mode 100644 tests/Attributes/AttributeBagTest.php create mode 100644 tests/Attributes/AttributeTest.php create mode 100644 tests/Logs/LogTest.php create mode 100644 tests/Logs/LogsTest.php create mode 100644 tests/Util/ArrTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e8f6d849c..5d7f1c733 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -53,12 +53,7 @@ parameters: - message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" count: 1 - path: src/Logger/DebugFileLogger.php - - - - message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" - count: 1 - path: src/Logger/DebugStdOutLogger.php + path: src/Logger/DebugLogger.php - message: "#^Parameter \\#1 \\$level of method Monolog\\\\Handler\\\\AbstractHandler\\:\\:__construct\\(\\) expects 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|'ALERT'\\|'alert'\\|'CRITICAL'\\|'critical'\\|'DEBUG'\\|'debug'\\|'EMERGENCY'\\|'emergency'\\|'ERROR'\\|'error'\\|'INFO'\\|'info'\\|'NOTICE'\\|'notice'\\|'WARNING'\\|'warning'\\|Monolog\\\\Level, int\\|Monolog\\\\Level\\|string given\\.$#" @@ -80,6 +75,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendLogCallback\\(\\) should return callable\\(Sentry\\\\Logs\\\\Log\\)\\: \\(Sentry\\\\Logs\\\\Log\\|null\\) but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendMetricsCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: \\(Sentry\\\\Event\\|null\\) but returns mixed\\.$#" count: 1 @@ -105,6 +105,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getEnableLogs\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getEnableTracing\\(\\) should return bool\\|null but returns mixed\\.$#" count: 1 diff --git a/src/Attributes/Attribute.php b/src/Attributes/Attribute.php new file mode 100644 index 000000000..c3ff48355 --- /dev/null +++ b/src/Attributes/Attribute.php @@ -0,0 +1,126 @@ +value = $value; + $this->type = $type; + } + + /** + * @return AttributeType + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return AttributeValue + */ + public function getValue() + { + return $this->value; + } + + /** + * @param mixed $value + * + * @throws \InvalidArgumentException thrown when the value cannot be serialized as an attribute + */ + public static function fromValue($value): self + { + $attribute = self::tryFromValue($value); + + if ($attribute === null) { + throw new \InvalidArgumentException(\sprintf('Invalid attribute value, %s cannot be serialized', \gettype($value))); + } + + return $attribute; + } + + /** + * @param mixed $value + */ + public static function tryFromValue($value): ?self + { + if ($value === null) { + return null; + } + + if (\is_bool($value)) { + return new self($value, 'boolean'); + } + + if (\is_int($value)) { + return new self($value, 'integer'); + } + + if (\is_float($value)) { + return new self($value, 'double'); + } + + if (\is_string($value) || (\is_object($value) && method_exists($value, '__toString'))) { + $stringValue = (string) $value; + + if (empty($stringValue)) { + return null; + } + + return new self($stringValue, 'string'); + } + + return null; + } + + /** + * @return AttributeSerialized + */ + public function toArray(): array + { + return [ + 'type' => $this->type, + 'value' => $this->value, + ]; + } + + /** + * @return AttributeSerialized + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + public function __toString(): string + { + return "{$this->value} ({$this->type})"; + } +} diff --git a/src/Attributes/AttributeBag.php b/src/Attributes/AttributeBag.php new file mode 100644 index 000000000..00cb45dc5 --- /dev/null +++ b/src/Attributes/AttributeBag.php @@ -0,0 +1,74 @@ + + */ + private $attributes = []; + + /** + * @param mixed $value + */ + public function set(string $key, $value): self + { + $attribute = $value instanceof Attribute + ? $value + : Attribute::tryFromValue($value); + + if ($attribute !== null) { + $this->attributes[$key] = $attribute; + } + + return $this; + } + + public function get(string $key): ?Attribute + { + return $this->attributes[$key] ?? null; + } + + /** + * @return array + */ + public function all(): array + { + return $this->attributes; + } + + /** + * @return array + */ + public function toArray(): array + { + return array_map(static function (Attribute $attribute) { + return $attribute->jsonSerialize(); + }, $this->attributes); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * @return array + */ + public function toSimpleArray(): array + { + return array_map(static function (Attribute $attribute) { + return $attribute->getValue(); + }, $this->attributes); + } +} diff --git a/src/Client.php b/src/Client.php index 5048343ae..36ee6a1ce 100644 --- a/src/Client.php +++ b/src/Client.php @@ -255,6 +255,16 @@ public function getTransport(): TransportInterface return $this->transport; } + public function getSdkIdentifier(): string + { + return $this->sdkIdentifier; + } + + public function getSdkVersion(): string + { + return $this->sdkVersion; + } + /** * Assembles an event and prepares it to be sent of to Sentry. * @@ -280,6 +290,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setSdkIdentifier($this->sdkIdentifier); $event->setSdkVersion($this->sdkVersion); + $event->setTags(array_merge($this->options->getTags(), $event->getTags())); if ($event->getServerName() === null) { diff --git a/src/Event.php b/src/Event.php index 51f4563b2..1ac6cc6af 100644 --- a/src/Event.php +++ b/src/Event.php @@ -6,6 +6,7 @@ use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; +use Sentry\Logs\Log; use Sentry\Profiling\Profile; use Sentry\Tracing\Span; @@ -65,6 +66,11 @@ final class Event */ private $checkIn; + /** + * @var Log[] + */ + private $logs = []; + /** * @var string|null The name of the server (e.g. the host name) */ @@ -230,6 +236,11 @@ public static function createCheckIn(?EventId $eventId = null): self return new self($eventId, EventType::checkIn()); } + public static function createLogs(?EventId $eventId = null): self + { + return new self($eventId, EventType::logs()); + } + /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ @@ -416,6 +427,24 @@ public function setCheckIn(?CheckIn $checkIn): self return $this; } + /** + * @return Log[] + */ + public function getLogs(): array + { + return $this->logs; + } + + /** + * @param Log[] $logs + */ + public function setLogs(array $logs): self + { + $this->logs = $logs; + + return $this; + } + /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ diff --git a/src/EventType.php b/src/EventType.php index b8ffdef94..3c2d13fb3 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -42,6 +42,11 @@ public static function checkIn(): self return self::getInstance('check_in'); } + public static function logs(): self + { + return self::getInstance('log'); + } + /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ @@ -61,6 +66,7 @@ public static function cases(): array self::event(), self::transaction(), self::checkIn(), + self::logs(), self::metrics(), ]; } diff --git a/src/Logger/DebugFileLogger.php b/src/Logger/DebugFileLogger.php index 4875baa75..e096b4bba 100644 --- a/src/Logger/DebugFileLogger.php +++ b/src/Logger/DebugFileLogger.php @@ -4,9 +4,7 @@ namespace Sentry\Logger; -use Psr\Log\AbstractLogger; - -class DebugFileLogger extends AbstractLogger +class DebugFileLogger extends DebugLogger { /** * @var string @@ -18,13 +16,8 @@ public function __construct(string $filePath) $this->filePath = $filePath; } - /** - * @param mixed $level - * @param string|\Stringable $message - * @param mixed[] $context - */ - public function log($level, $message, array $context = []): void + public function write(string $message): void { - file_put_contents($this->filePath, \sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message), \FILE_APPEND); + file_put_contents($this->filePath, $message, \FILE_APPEND); } } diff --git a/src/Logger/DebugLogger.php b/src/Logger/DebugLogger.php new file mode 100644 index 000000000..7ebe02115 --- /dev/null +++ b/src/Logger/DebugLogger.php @@ -0,0 +1,26 @@ +write( + \sprintf("sentry/sentry: [%s] %s\n", $level, $formattedMessageAndContext) + ); + } + + abstract public function write(string $message): void; +} diff --git a/src/Logger/DebugStdOutLogger.php b/src/Logger/DebugStdOutLogger.php index 5b2da8faf..8e31845d2 100644 --- a/src/Logger/DebugStdOutLogger.php +++ b/src/Logger/DebugStdOutLogger.php @@ -4,17 +4,10 @@ namespace Sentry\Logger; -use Psr\Log\AbstractLogger; - -class DebugStdOutLogger extends AbstractLogger +class DebugStdOutLogger extends DebugLogger { - /** - * @param mixed $level - * @param string|\Stringable $message - * @param mixed[] $context - */ - public function log($level, $message, array $context = []): void + public function write(string $message): void { - file_put_contents('php://stdout', \sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message)); + file_put_contents('php://stdout', $message); } } diff --git a/src/Logs/Log.php b/src/Logs/Log.php new file mode 100644 index 000000000..4e29885de --- /dev/null +++ b/src/Logs/Log.php @@ -0,0 +1,109 @@ + + * } + */ +class Log implements \JsonSerializable +{ + /** + * @var float + */ + private $timestamp; + + /** + * @var string + */ + private $traceId; + + /** + * @var LogLevel + */ + private $level; + + /** + * @var string + */ + private $body; + + /** + * @var AttributeBag + */ + private $attributes; + + public function __construct( + float $timestamp, + string $traceId, + LogLevel $level, + string $body + ) { + $this->timestamp = $timestamp; + $this->traceId = $traceId; + $this->level = $level; + $this->body = $body; + $this->attributes = new AttributeBag(); + } + + public function getTimestamp(): float + { + return $this->timestamp; + } + + public function getTraceId(): string + { + return $this->traceId; + } + + public function getLevel(): LogLevel + { + return $this->level; + } + + public function getBody(): string + { + return $this->body; + } + + public function attributes(): AttributeBag + { + return $this->attributes; + } + + /** + * @param mixed $value + */ + public function setAttribute(string $key, $value): self + { + $this->attributes->set($key, $value); + + return $this; + } + + /** + * @return LogEnvelopeItem + */ + public function jsonSerialize(): array + { + return [ + 'timestamp' => $this->timestamp, + 'trace_id' => $this->traceId, + 'level' => (string) $this->level, + 'body' => $this->body, + 'attributes' => $this->attributes->toArray(), + ]; + } +} diff --git a/src/Logs/LogLevel.php b/src/Logs/LogLevel.php new file mode 100644 index 000000000..5b12dc3d0 --- /dev/null +++ b/src/Logs/LogLevel.php @@ -0,0 +1,70 @@ + A list of cached enum instances + */ + private static $instances = []; + + private function __construct(string $value) + { + $this->value = $value; + } + + public static function trace(): self + { + return self::getInstance('trace'); + } + + public static function debug(): self + { + return self::getInstance('debug'); + } + + public static function info(): self + { + return self::getInstance('info'); + } + + public static function warn(): self + { + return self::getInstance('warn'); + } + + public static function error(): self + { + return self::getInstance('error'); + } + + public static function fatal(): self + { + return self::getInstance('fatal'); + } + + public function __toString(): string + { + return $this->value; + } + + private static function getInstance(string $value): self + { + if (!isset(self::$instances[$value])) { + self::$instances[$value] = new self($value); + } + + return self::$instances[$value]; + } +} diff --git a/src/Logs/Logs.php b/src/Logs/Logs.php new file mode 100644 index 000000000..99bc34e43 --- /dev/null +++ b/src/Logs/Logs.php @@ -0,0 +1,112 @@ +aggregator = new LogsAggregator(); + } + + public static function getInstance(): self + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * @param string $message see sprintf for a description of format + * @param array $values see sprintf for a description of values + * @param array $attributes additional attributes to add to the log + */ + public function trace(string $message, array $values = [], array $attributes = []): void + { + $this->aggregator->add(LogLevel::trace(), $message, $values, $attributes); + } + + /** + * @param string $message see sprintf for a description of format + * @param array $values see sprintf for a description of values + * @param array $attributes additional attributes to add to the log + */ + public function debug(string $message, array $values = [], array $attributes = []): void + { + $this->aggregator->add(LogLevel::debug(), $message, $values, $attributes); + } + + /** + * @param string $message see sprintf for a description of format + * @param array $values see sprintf for a description of values + * @param array $attributes additional attributes to add to the log + */ + public function info(string $message, array $values = [], array $attributes = []): void + { + $this->aggregator->add(LogLevel::info(), $message, $values, $attributes); + } + + /** + * @param string $message see sprintf for a description of format + * @param array $values see sprintf for a description of values + * @param array $attributes additional attributes to add to the log + */ + public function warn(string $message, array $values = [], array $attributes = []): void + { + $this->aggregator->add(LogLevel::warn(), $message, $values, $attributes); + } + + /** + * @param string $message see sprintf for a description of format + * @param array $values see sprintf for a description of values + * @param array $attributes additional attributes to add to the log + */ + public function error(string $message, array $values = [], array $attributes = []): void + { + $this->aggregator->add(LogLevel::error(), $message, $values, $attributes); + } + + /** + * @param string $message see sprintf for a description of format + * @param array $values see sprintf for a description of values + * @param array $attributes additional attributes to add to the log + */ + public function fatal(string $message, array $values = [], array $attributes = []): void + { + $this->aggregator->add(LogLevel::fatal(), $message, $values, $attributes); + } + + /** + * Flush the captured logs and send them to Sentry. + */ + public function flush(): ?EventId + { + return $this->aggregator->flush(); + } + + /** + * Get the logs aggregator. + * + * @internal + */ + public function aggregator(): LogsAggregator + { + return $this->aggregator; + } +} diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php new file mode 100644 index 000000000..7fa9bc231 --- /dev/null +++ b/src/Logs/LogsAggregator.php @@ -0,0 +1,145 @@ + $values see sprintf for a description of values + * @param array $attributes additional attributes to add to the log + */ + public function add( + LogLevel $level, + string $message, + array $values = [], + array $attributes = [] + ): void { + $timestamp = microtime(true); + + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + // There is no need to continue if there is no client + if ($client === null) { + return; + } + + $options = $client->getOptions(); + $sdkLogger = $options->getLogger(); + + if (!$options->getEnableLogs()) { + if ($sdkLogger !== null) { + $sdkLogger->info( + 'Log will be discarded because "enable_logs" is "false".' + ); + } + + return; + } + + $log = (new Log($timestamp, $this->getTraceId($hub), $level, vsprintf($message, $values))) + ->setAttribute('sentry.release', $options->getRelease()) + ->setAttribute('sentry.environment', $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT) + ->setAttribute('sentry.server.address', $options->getServerName()) + ->setAttribute('sentry.message.template', $message) + ->setAttribute('sentry.trace.parent_span_id', $hub->getSpan() ? $hub->getSpan()->getSpanId() : null); + + if ($client instanceof Client) { + $log->setAttribute('sentry.sdk.name', $client->getSdkIdentifier()); + $log->setAttribute('sentry.sdk.version', $client->getSdkVersion()); + } + + foreach ($values as $key => $value) { + $log->setAttribute("sentry.message.parameter.{$key}", $value); + } + + $attributes = Arr::simpleDot($attributes); + + foreach ($attributes as $key => $value) { + $attribute = Attribute::tryFromValue($value); + + if ($attribute === null) { + if ($sdkLogger !== null) { + $sdkLogger->info( + \sprintf("Dropping log attribute {$key} with value of type '%s' because it is not serializable or an unsupported type.", \gettype($value)) + ); + } + } else { + $log->setAttribute($key, $attribute); + } + } + + $log = ($options->getBeforeSendLogCallback())($log); + + if ($log === null) { + if ($sdkLogger !== null) { + $sdkLogger->info( + 'Log will be discarded because the "before_send_log" callback returned "null".', + ['log' => $log] + ); + } + + return; + } + + // We check if it's a `LogsLogger` to avoid a infinite loop where the logger is logging the logs it's writing + if ($sdkLogger !== null) { + $sdkLogger->log((string) $log->getLevel(), "Logs item: {$log->getBody()}", $log->attributes()->toSimpleArray()); + } + + $this->logs[] = $log; + } + + public function flush(): ?EventId + { + if (empty($this->logs)) { + return null; + } + + $hub = SentrySdk::getCurrentHub(); + $event = Event::createLogs()->setLogs($this->logs); + + $this->logs = []; + + return $hub->captureEvent($event); + } + + private function getTraceId(HubInterface $hub): string + { + $span = $hub->getSpan(); + + if ($span !== null) { + return (string) $span->getTraceId(); + } + + $traceId = ''; + + $hub->configureScope(function (Scope $scope) use (&$traceId) { + $traceId = (string) $scope->getPropagationContext()->getTraceId(); + }); + + return $traceId; + } +} diff --git a/src/Options.php b/src/Options.php index 45dcbefaf..b4f9ba3ed 100644 --- a/src/Options.php +++ b/src/Options.php @@ -9,6 +9,7 @@ use Sentry\HttpClient\HttpClientInterface; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\IntegrationInterface; +use Sentry\Logs\Log; use Sentry\Transport\TransportInterface; use Symfony\Component\OptionsResolver\Options as SymfonyOptions; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -1152,6 +1153,39 @@ public function setTracesSampler(?callable $sampler): self return $this; } + /** + * Sets if logs should be enabled or not. + * + * @param bool|null $enableLogs Boolean if logs should be enabled or not + */ + public function setEnableLogs(?bool $enableLogs): self + { + $options = array_merge($this->options, ['enable_tracing' => $enableLogs]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + + /** + * Gets if logs is enabled or not. + */ + public function getEnableLogs(): bool + { + return $this->options['enable_logs'] ?? false; + } + + /** + * Gets a callback that will be invoked before an log is sent to the server. + * If `null` is returned it won't be sent. + * + * @psalm-return callable(Log): ?Log + */ + public function getBeforeSendLogCallback(): callable + { + return $this->options['before_send_log']; + } + /** * Configures the options of the client. * @@ -1229,6 +1263,10 @@ private function configureOptions(OptionsResolver $resolver): void 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', 'class_serializers' => [], + 'enable_logs' => false, + 'before_send_log' => static function (Log $log): Log { + return $log; + }, ]); $resolver->setAllowedTypes('prefixes', 'string[]'); @@ -1275,6 +1313,8 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); $resolver->setAllowedTypes('class_serializers', 'array'); + $resolver->setAllowedTypes('enable_logs', 'bool'); + $resolver->setAllowedTypes('before_send_log', 'callable'); $resolver->setAllowedValues('max_request_body_size', ['none', 'never', 'small', 'medium', 'always']); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); diff --git a/src/Serializer/EnvelopItems/EnvelopeItemInterface.php b/src/Serializer/EnvelopItems/EnvelopeItemInterface.php index d2b7d3712..bf95f6e45 100644 --- a/src/Serializer/EnvelopItems/EnvelopeItemInterface.php +++ b/src/Serializer/EnvelopItems/EnvelopeItemInterface.php @@ -11,5 +11,5 @@ */ interface EnvelopeItemInterface { - public static function toEnvelopeItem(Event $event): string; + public static function toEnvelopeItem(Event $event): ?string; } diff --git a/src/Serializer/EnvelopItems/LogsItem.php b/src/Serializer/EnvelopItems/LogsItem.php new file mode 100644 index 000000000..431e67f9f --- /dev/null +++ b/src/Serializer/EnvelopItems/LogsItem.php @@ -0,0 +1,34 @@ +getLogs(); + + $header = [ + 'type' => (string) EventType::logs(), + 'item_count' => \count($logs), + 'content_type' => 'application/vnd.sentry.items.log+json', + ]; + + return \sprintf( + "%s\n%s", + JSON::encode($header), + JSON::encode([ + 'items' => $logs, + ]) + ); + } +} diff --git a/src/Serializer/EnvelopItems/ProfileItem.php b/src/Serializer/EnvelopItems/ProfileItem.php index 646506478..512eae80f 100644 --- a/src/Serializer/EnvelopItems/ProfileItem.php +++ b/src/Serializer/EnvelopItems/ProfileItem.php @@ -13,7 +13,7 @@ */ class ProfileItem implements EnvelopeItemInterface { - public static function toEnvelopeItem(Event $event): string + public static function toEnvelopeItem(Event $event): ?string { $header = [ 'type' => 'profile', @@ -22,12 +22,12 @@ public static function toEnvelopeItem(Event $event): string $profile = $event->getSdkMetadata('profile'); if (!$profile instanceof Profile) { - return ''; + return null; } $payload = $profile->getFormattedData($event); if ($payload === null) { - return ''; + return null; } return \sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php index 679bd6bcd..9eb8eab22 100644 --- a/src/Serializer/EnvelopItems/TransactionItem.php +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -5,6 +5,7 @@ namespace Sentry\Serializer\EnvelopItems; use Sentry\Event; +use Sentry\EventType; use Sentry\Serializer\Traits\BreadcrumbSeralizerTrait; use Sentry\Tracing\Span; use Sentry\Tracing\TransactionMetadata; @@ -28,7 +29,7 @@ class TransactionItem implements EnvelopeItemInterface public static function toEnvelopeItem(Event $event): string { $header = [ - 'type' => (string) $event->getType(), + 'type' => (string) EventType::transaction(), 'content_type' => 'application/json', ]; diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index b836fe044..4878cc767 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -9,6 +9,7 @@ use Sentry\Options; use Sentry\Serializer\EnvelopItems\CheckInItem; use Sentry\Serializer\EnvelopItems\EventItem; +use Sentry\Serializer\EnvelopItems\LogsItem; use Sentry\Serializer\EnvelopItems\ProfileItem; use Sentry\Serializer\EnvelopItems\TransactionItem; use Sentry\Tracing\DynamicSamplingContext; @@ -54,28 +55,26 @@ public function serialize(Event $event): string } } - $items = ''; + $items = []; switch ($event->getType()) { case EventType::event(): - $items = EventItem::toEnvelopeItem($event); + $items[] = EventItem::toEnvelopeItem($event); break; case EventType::transaction(): - $transactionItem = TransactionItem::toEnvelopeItem($event); + $items[] = TransactionItem::toEnvelopeItem($event); if ($event->getSdkMetadata('profile') !== null) { - $profileItem = ProfileItem::toEnvelopeItem($event); - if ($profileItem !== '') { - $items = \sprintf("%s\n%s", $transactionItem, $profileItem); - break; - } + $items[] = ProfileItem::toEnvelopeItem($event); } - $items = $transactionItem; break; case EventType::checkIn(): - $items = CheckInItem::toEnvelopeItem($event); + $items[] = CheckInItem::toEnvelopeItem($event); + break; + case EventType::logs(): + $items[] = LogsItem::toEnvelopeItem($event); break; } - return \sprintf("%s\n%s", JSON::encode($envelopeHeader), $items); + return \sprintf("%s\n%s", JSON::encode($envelopeHeader), implode("\n", array_filter($items))); } } diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index ce0becfd1..dbb8deccf 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -16,6 +16,11 @@ final class RateLimiter */ private const DATA_CATEGORY_ERROR = 'error'; + /** + * @var string + */ + private const DATA_CATEGORY_LOG_ITEM = 'log_item'; + /** * The name of the header to look at to know the rate limits for the events * categories supported by the server. @@ -112,6 +117,8 @@ public function getDisabledUntil($eventType): int if ($eventType === 'event') { $eventType = self::DATA_CATEGORY_ERROR; + } elseif ($eventType === 'log') { + $eventType = self::DATA_CATEGORY_LOG_ITEM; } return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$eventType] ?? 0); diff --git a/src/Util/Arr.php b/src/Util/Arr.php new file mode 100644 index 000000000..8773b6c07 --- /dev/null +++ b/src/Util/Arr.php @@ -0,0 +1,66 @@ + $array + * + * @return array + */ + public static function simpleDot(array $array): array + { + $results = []; + + $flatten = static function ($data, $prefix = '') use (&$results, &$flatten): void { + foreach ($data as $key => $value) { + $newKey = $prefix . $key; + + if (\is_array($value) && !empty($value) && !self::isList($value)) { + $flatten($value, $newKey . '.'); + } else { + $results[$newKey] = $value; + } + } + }; + + $flatten($array); + + return $results; + } + + /** + * Checks whether a given array is a list. + * + * `array_is_list` is introduced in PHP 8.1, so we have a polyfill for it. + * + * @see https://www.php.net/manual/en/function.array-is-list.php#126794 + * + * @param array $array + */ + public static function isList(array $array): bool + { + $i = 0; + + foreach ($array as $k => $v) { + if ($k !== $i++) { + return false; + } + } + + return true; + } +} diff --git a/src/functions.php b/src/functions.php index 28b0c3009..b944eb965 100644 --- a/src/functions.php +++ b/src/functions.php @@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientInterface; use Sentry\Integration\IntegrationInterface; +use Sentry\Logs\Logs; use Sentry\Metrics\Metrics; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; @@ -63,6 +64,8 @@ * traces_sample_rate?: float|int|null, * traces_sampler?: callable|null, * transport?: callable, + * enable_logs?: bool, + * before_send_log?: callable, * } $options The client options */ function init(array $options = []): void @@ -362,6 +365,14 @@ function continueTrace(string $sentryTrace, string $baggage): TransactionContext return TransactionContext::fromHeaders($sentryTrace, $baggage); } +/** + * Get the Sentry Logs client. + */ +function logger(): Logs +{ + return Logs::getInstance(); +} + /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ diff --git a/tests/Attributes/AttributeBagTest.php b/tests/Attributes/AttributeBagTest.php new file mode 100644 index 000000000..397fc8cf5 --- /dev/null +++ b/tests/Attributes/AttributeBagTest.php @@ -0,0 +1,68 @@ +assertCount(0, $bag->all()); + + $bag->set('foo', 'bar'); + + $this->assertCount(1, $bag->all()); + $this->assertInstanceOf(Attribute::class, $bag->get('foo')); + + $this->assertNull($bag->get('non-existing')); + } + + public function testSerializeAsJson(): void + { + $bag = new AttributeBag(); + $bag->set('foo', 'bar'); + + $this->assertEquals( + ['foo' => ['type' => 'string', 'value' => 'bar']], + $bag->jsonSerialize() + ); + + $this->assertEquals( + '{"foo":{"type":"string","value":"bar"}}', + json_encode($bag) + ); + } + + public function testSerializeAsArray(): void + { + $bag = new AttributeBag(); + $bag->set('foo', 'bar'); + + $this->assertEquals( + ['foo' => ['type' => 'string', 'value' => 'bar']], + $bag->toArray() + ); + } + + public function testSerializeAsSimpleArray(): void + { + $bag = new AttributeBag(); + $bag->set('foo', 'bar'); + + $this->assertEquals( + ['foo' => 'bar'], + $bag->toSimpleArray() + ); + } +} diff --git a/tests/Attributes/AttributeTest.php b/tests/Attributes/AttributeTest.php new file mode 100644 index 000000000..3919e1ce0 --- /dev/null +++ b/tests/Attributes/AttributeTest.php @@ -0,0 +1,147 @@ +assertNull($attribute); + + return; + } + + $this->assertEquals($expected, $attribute->toArray()); + $this->assertEquals($expected['type'], $attribute->getType()); + $this->assertEquals($expected['value'], $attribute->getValue()); + } + + public static function fromValueDataProvider(): \Generator + { + yield [ + 'foo', + [ + 'type' => 'string', + 'value' => 'foo', + ], + ]; + + yield [ + 123, + [ + 'type' => 'integer', + 'value' => 123, + ], + ]; + + yield [ + 123.33, + [ + 'type' => 'double', + 'value' => 123.33, + ], + ]; + + yield [ + true, + [ + 'type' => 'boolean', + 'value' => true, + ], + ]; + + yield [ + new class { + public function __toString(): string + { + return 'foo'; + } + }, + [ + 'type' => 'string', + 'value' => 'foo', + ], + ]; + + yield [ + new class {}, + null, + ]; + + yield [ + new \stdClass(), + null, + ]; + + yield [ + [], + null, + ]; + } + + public function testSerializeAsJson(): void + { + $attribute = Attribute::tryFromValue('foo'); + + $this->assertInstanceOf(Attribute::class, $attribute); + + $this->assertEquals( + ['type' => 'string', 'value' => 'foo'], + $attribute->jsonSerialize() + ); + + $this->assertEquals( + '{"type":"string","value":"foo"}', + json_encode($attribute) + ); + } + + public function testSerializeAsArray(): void + { + $attribute = Attribute::tryFromValue('foo'); + + $this->assertInstanceOf(Attribute::class, $attribute); + + $this->assertEquals( + ['type' => 'string', 'value' => 'foo'], + $attribute->toArray() + ); + } + + public function testSerializeAsString(): void + { + $attribute = Attribute::tryFromValue('foo'); + + $this->assertInstanceOf(Attribute::class, $attribute); + + $this->assertEquals( + 'foo (string)', + (string) $attribute + ); + } + + public function testFromValueFactoryMethod(): void + { + $this->expectException(\InvalidArgumentException::class); + + Attribute::fromValue([]); + } +} diff --git a/tests/Logs/LogTest.php b/tests/Logs/LogTest.php new file mode 100644 index 000000000..0b5a844c8 --- /dev/null +++ b/tests/Logs/LogTest.php @@ -0,0 +1,43 @@ +setAttribute('foo', 'bar'); + $log->setAttribute('should-be-missing', ['foo' => 'bar']); + + $this->assertEquals( + [ + 'timestamp' => $timestamp, + 'trace_id' => '123', + 'level' => 'debug', + 'body' => 'foo', + 'attributes' => [ + 'foo' => [ + 'type' => 'string', + 'value' => 'bar', + ], + ], + ], + $log->jsonSerialize() + ); + } +} diff --git a/tests/Logs/LogsTest.php b/tests/Logs/LogsTest.php new file mode 100644 index 000000000..05931b1c2 --- /dev/null +++ b/tests/Logs/LogsTest.php @@ -0,0 +1,154 @@ +createMock(ClientInterface::class); + $client->expects($this->any()) + ->method('getOptions') + ->willReturn(new Options([ + 'dsn' => 'https://public@example.com/1', + 'enable_logs' => false, + ])); + + $client->expects($this->never()) + ->method('captureEvent'); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + logger()->info('Some info message'); + + $this->assertNull(logger()->flush()); + } + + public function testLogSentWhenEnabled(): void + { + $this->assertEvent(function (Event $event) { + $this->assertCount(1, $event->getLogs()); + + $logItem = $event->getLogs()[0]->jsonSerialize(); + + $this->assertEquals(LogLevel::info(), $logItem['level']); + $this->assertEquals('Some info message', $logItem['body']); + }); + + logger()->info('Some info message'); + + $this->assertNotNull(logger()->flush()); + } + + public function testLogWithTemplate(): void + { + $this->assertEvent(function (Event $event) { + $this->assertCount(1, $event->getLogs()); + + $logItem = $event->getLogs()[0]->jsonSerialize(); + + $this->assertEquals(LogLevel::info(), $logItem['level']); + $this->assertEquals('Some info message', $logItem['body']); + }); + + logger()->info('Some %s message', ['info']); + + $this->assertNotNull(logger()->flush()); + } + + public function testLogWithNestedAttributes(): void + { + $this->assertEvent(function (Event $event) { + $this->assertCount(1, $event->getLogs()); + + $logItem = $event->getLogs()[0]->jsonSerialize(); + + $this->assertArrayHasKey('nested.foo', $logItem['attributes']); + $this->assertArrayNotHasKey('nested.should-be-missing', $logItem['attributes']); + + $this->assertEquals('bar', $logItem['attributes']['nested.foo']['value']); + }); + + logger()->info('Some message', [], [ + 'nested' => [ + 'foo' => 'bar', + 'should-be-missing' => [1, 2, 3], + ], + ]); + + $this->assertNotNull(logger()->flush()); + } + + /** + * @dataProvider logLevelDataProvider + */ + public function testLoggerSetsCorrectLevel(LogLevel $level): void + { + $this->assertEvent(function (Event $event) use ($level) { + $this->assertCount(1, $event->getLogs()); + + $this->assertEquals($level, $event->getLogs()[0]->getLevel()); + }); + + logger()->{(string) $level}('Some message'); + + $this->assertNotNull(logger()->flush()); + } + + public static function logLevelDataProvider(): \Generator + { + yield [LogLevel::trace()]; + yield [LogLevel::debug()]; + yield [LogLevel::info()]; + yield [LogLevel::warn()]; + yield [LogLevel::error()]; + yield [LogLevel::fatal()]; + } + + /** + * @param callable(Event): void $assert + */ + private function assertEvent(callable $assert): ClientInterface + { + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + $transport->expects($this->once()) + ->method('send') + ->with($this->callback(function (Event $event) use ($assert): bool { + $assert($event); + + return true; + })) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); + + $client = ClientBuilder::create([ + 'enable_logs' => true, + ])->setTransport($transport)->getClient(); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + return $client; + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 1e8eca0fe..8cf6d16ec 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -16,6 +16,8 @@ use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; +use Sentry\Logs\Log; +use Sentry\Logs\LogLevel; use Sentry\MonitorConfig; use Sentry\MonitorSchedule; use Sentry\Options; @@ -405,6 +407,21 @@ public static function serializeAsEnvelopeDataProvider(): iterable {"event_id":"fc9442f5aef34234bb22b9a615e30ccd","sent_at":"2020-08-18T22:47:15Z","dsn":"http:\/\/public@example.com\/sentry\/1","sdk":{"name":"sentry.php","version":"$sdkVersion","packages":[{"name":"composer:sentry\/sentry","version":"$sdkVersion"}]}} {"type":"check_in","content_type":"application\/json"} {"check_in_id":"$checkinId","monitor_slug":"my-monitor","status":"ok","duration":10,"release":"1.0.0","environment":"dev","monitor_config":{"schedule":{"type":"crontab","value":"0 0 * * *","unit":""},"checkin_margin":10,"max_runtime":12,"timezone":"Europe\/Amsterdam","failure_issue_threshold":5,"recovery_threshold":10},"contexts":{"trace":{"trace_id":"21160e9b836d479f81611368b2aa3d2c","span_id":"5dd538dc297544cc"}}} +TEXT + , + ]; + + $event = Event::createLogs(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); + $event->setLogs([ + new Log(ClockMock::microtime(true), '21160e9b836d479f81611368b2aa3d2c', LogLevel::info(), 'A log message'), + ]); + + yield [ + $event, + <<assertSame($expectedResult, Arr::simpleDot($value)); + } + + public static function simpleDotDataProvider(): \Generator + { + yield [ + [1, 2, 3], + [1, 2, 3], + ]; + + yield [ + [ + 'key' => 'value', + ], + [ + 'key' => 'value', + ], + ]; + + yield [ + [ + 'key' => [ + 'key2' => 'value', + ], + ], + [ + 'key.key2' => 'value', + ], + ]; + + yield [ + [ + 'key' => ['foo', 'bar'], + ], + [ + 'key' => ['foo', 'bar'], + ], + ]; + + yield [ + [ + 'key' => [ + 'key2' => ['foo', 'bar'], + ], + ], + [ + 'key.key2' => ['foo', 'bar'], + ], + ]; + + $someClass = new \stdClass(); + + yield [ + [ + 'key' => $someClass, + ], + [ + 'key' => $someClass, + ], + ]; + } + + /** + * @dataProvider isListDataProvider + */ + public function testIsList(array $value, bool $expectedResult): void + { + $this->assertSame($expectedResult, Arr::isList($value)); + } + + public static function isListDataProvider(): \Generator + { + yield [ + [1, 2, 3], + true, + ]; + + yield [ + [ + 'key' => 'value', + ], + false, + ]; + } +} From 1d45188abb44954f9706f21bc89869942edf0850 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 3 Jun 2025 11:56:34 +0200 Subject: [PATCH 1073/1161] Order init options doc block alphabetically (#1839) --- src/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/functions.php b/src/functions.php index b944eb965..01705f631 100644 --- a/src/functions.php +++ b/src/functions.php @@ -24,11 +24,13 @@ * before_breadcrumb?: callable, * before_send?: callable, * before_send_check_in?: callable, + * before_send_log?: callable, * before_send_transaction?: callable, * capture_silenced_errors?: bool, * context_lines?: int|null, * default_integrations?: bool, * dsn?: string|bool|null|Dsn, + * enable_logs?: bool, * environment?: string|null, * error_types?: int|null, * http_client?: HttpClientInterface|null, @@ -64,8 +66,6 @@ * traces_sample_rate?: float|int|null, * traces_sampler?: callable|null, * transport?: callable, - * enable_logs?: bool, - * before_send_log?: callable, * } $options The client options */ function init(array $options = []): void From e8f9bca952edfb05771fb8ba64bc65c8fd106b71 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 5 Jun 2025 12:40:44 +0200 Subject: [PATCH 1074/1161] Logs improvements (#1848) --- src/Attributes/Attribute.php | 25 +-------- src/Attributes/AttributeBag.php | 28 ++++------ src/Logs/Log.php | 56 ++++++++++---------- src/Logs/LogsAggregator.php | 2 - src/Serializer/EnvelopItems/LogsItem.php | 17 +++++- tests/Attributes/AttributeBagTest.php | 33 ++---------- tests/Attributes/AttributeTest.php | 38 ++------------ tests/Logs/LogTest.php | 43 +++++++-------- tests/Logs/LogsTest.php | 61 +++++++++++++++++----- tests/Serializer/PayloadSerializerTest.php | 5 +- 10 files changed, 134 insertions(+), 174 deletions(-) diff --git a/src/Attributes/Attribute.php b/src/Attributes/Attribute.php index c3ff48355..ce792220e 100644 --- a/src/Attributes/Attribute.php +++ b/src/Attributes/Attribute.php @@ -7,12 +7,8 @@ /** * @phpstan-type AttributeType 'string'|'boolean'|'integer'|'double' * @phpstan-type AttributeValue string|bool|int|float - * @phpstan-type AttributeSerialized array{ - * type: AttributeType, - * value: AttributeValue - * } */ -class Attribute implements \JsonSerializable +class Attribute { /** * @var AttributeType @@ -100,25 +96,6 @@ public static function tryFromValue($value): ?self return null; } - /** - * @return AttributeSerialized - */ - public function toArray(): array - { - return [ - 'type' => $this->type, - 'value' => $this->value, - ]; - } - - /** - * @return AttributeSerialized - */ - public function jsonSerialize(): array - { - return $this->toArray(); - } - public function __toString(): string { return "{$this->value} ({$this->type})"; diff --git a/src/Attributes/AttributeBag.php b/src/Attributes/AttributeBag.php index 00cb45dc5..0d5b3018b 100644 --- a/src/Attributes/AttributeBag.php +++ b/src/Attributes/AttributeBag.php @@ -6,9 +6,8 @@ /** * @phpstan-import-type AttributeValue from Attribute - * @phpstan-import-type AttributeSerialized from Attribute */ -class AttributeBag implements \JsonSerializable +class AttributeBag { /** * @var array @@ -36,33 +35,24 @@ public function get(string $key): ?Attribute return $this->attributes[$key] ?? null; } - /** - * @return array - */ - public function all(): array + public function forget(string $key): self { - return $this->attributes; - } + unset($this->attributes[$key]); - /** - * @return array - */ - public function toArray(): array - { - return array_map(static function (Attribute $attribute) { - return $attribute->jsonSerialize(); - }, $this->attributes); + return $this; } /** - * @return array + * @return array */ - public function jsonSerialize(): array + public function all(): array { - return $this->toArray(); + return $this->attributes; } /** + * Get a simplified representation of the attributes as a key-value array, main purpose is for logging output. + * * @return array */ public function toSimpleArray(): array diff --git a/src/Logs/Log.php b/src/Logs/Log.php index 4e29885de..7c5b5b23b 100644 --- a/src/Logs/Log.php +++ b/src/Logs/Log.php @@ -4,21 +4,9 @@ namespace Sentry\Logs; -use Sentry\Attributes\Attribute; use Sentry\Attributes\AttributeBag; -/** - * @phpstan-import-type AttributeSerialized from Attribute - * - * @phpstan-type LogEnvelopeItem array{ - * timestamp: int|float, - * trace_id: string, - * level: string, - * body: string, - * attributes: array - * } - */ -class Log implements \JsonSerializable +class Log { /** * @var float @@ -63,21 +51,49 @@ public function getTimestamp(): float return $this->timestamp; } + public function setTimestamp(float $timestamp): self + { + $this->timestamp = $timestamp; + + return $this; + } + public function getTraceId(): string { return $this->traceId; } + public function setTraceId(string $traceId): self + { + $this->traceId = $traceId; + + return $this; + } + public function getLevel(): LogLevel { return $this->level; } + public function setLevel(LogLevel $level): self + { + $this->level = $level; + + return $this; + } + public function getBody(): string { return $this->body; } + public function setBody(string $body): self + { + $this->body = $body; + + return $this; + } + public function attributes(): AttributeBag { return $this->attributes; @@ -92,18 +108,4 @@ public function setAttribute(string $key, $value): self return $this; } - - /** - * @return LogEnvelopeItem - */ - public function jsonSerialize(): array - { - return [ - 'timestamp' => $this->timestamp, - 'trace_id' => $this->traceId, - 'level' => (string) $this->level, - 'body' => $this->body, - 'attributes' => $this->attributes->toArray(), - ]; - } } diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index 7fa9bc231..754048e41 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -14,8 +14,6 @@ use Sentry\Util\Arr; /** - * @phpstan-import-type AttributeValue from Attribute - * * @internal */ final class LogsAggregator diff --git a/src/Serializer/EnvelopItems/LogsItem.php b/src/Serializer/EnvelopItems/LogsItem.php index 431e67f9f..8a201a768 100644 --- a/src/Serializer/EnvelopItems/LogsItem.php +++ b/src/Serializer/EnvelopItems/LogsItem.php @@ -4,8 +4,10 @@ namespace Sentry\Serializer\EnvelopItems; +use Sentry\Attributes\Attribute; use Sentry\Event; use Sentry\EventType; +use Sentry\Logs\Log; use Sentry\Util\JSON; /** @@ -27,7 +29,20 @@ public static function toEnvelopeItem(Event $event): string "%s\n%s", JSON::encode($header), JSON::encode([ - 'items' => $logs, + 'items' => array_map(static function (Log $log): array { + return [ + 'timestamp' => $log->getTimestamp(), + 'trace_id' => $log->getTraceId(), + 'level' => (string) $log->getLevel(), + 'body' => $log->getBody(), + 'attributes' => array_map(static function (Attribute $attribute): array { + return [ + 'type' => $attribute->getType(), + 'value' => $attribute->getValue(), + ]; + }, $log->attributes()->all()), + ]; + }, $logs), ]) ); } diff --git a/tests/Attributes/AttributeBagTest.php b/tests/Attributes/AttributeBagTest.php index 397fc8cf5..da01e067c 100644 --- a/tests/Attributes/AttributeBagTest.php +++ b/tests/Attributes/AttributeBagTest.php @@ -8,10 +8,6 @@ use Sentry\Attributes\Attribute; use Sentry\Attributes\AttributeBag; -/** - * @phpstan-import-type AttributeValue from Attribute - * @phpstan-import-type AttributeSerialized from Attribute - */ final class AttributeBagTest extends TestCase { public function testGettersAndSetters(): void @@ -25,34 +21,13 @@ public function testGettersAndSetters(): void $this->assertCount(1, $bag->all()); $this->assertInstanceOf(Attribute::class, $bag->get('foo')); - $this->assertNull($bag->get('non-existing')); - } - - public function testSerializeAsJson(): void - { - $bag = new AttributeBag(); - $bag->set('foo', 'bar'); - - $this->assertEquals( - ['foo' => ['type' => 'string', 'value' => 'bar']], - $bag->jsonSerialize() - ); + $bag->set('will-be-removed', 'baz'); - $this->assertEquals( - '{"foo":{"type":"string","value":"bar"}}', - json_encode($bag) - ); - } + $this->assertNotNull($bag->get('will-be-removed')); - public function testSerializeAsArray(): void - { - $bag = new AttributeBag(); - $bag->set('foo', 'bar'); + $bag->forget('will-be-removed'); - $this->assertEquals( - ['foo' => ['type' => 'string', 'value' => 'bar']], - $bag->toArray() - ); + $this->assertNull($bag->get('will-be-removed')); } public function testSerializeAsSimpleArray(): void diff --git a/tests/Attributes/AttributeTest.php b/tests/Attributes/AttributeTest.php index 3919e1ce0..030f628bd 100644 --- a/tests/Attributes/AttributeTest.php +++ b/tests/Attributes/AttributeTest.php @@ -8,18 +8,18 @@ use Sentry\Attributes\Attribute; /** + * @phpstan-import-type AttributeType from Attribute * @phpstan-import-type AttributeValue from Attribute - * @phpstan-import-type AttributeSerialized from Attribute */ final class AttributeTest extends TestCase { /** - * @param AttributeValue $value - * @param AttributeSerialized|null $expected + * @param AttributeValue $value + * @param array{type: AttributeType, value: AttributeValue}|null $expected * * @dataProvider fromValueDataProvider */ - public function testFromValue($value, $expected): void + public function testFromValue($value, ?array $expected): void { $attribute = Attribute::tryFromValue($value); @@ -29,7 +29,6 @@ public function testFromValue($value, $expected): void return; } - $this->assertEquals($expected, $attribute->toArray()); $this->assertEquals($expected['type'], $attribute->getType()); $this->assertEquals($expected['value'], $attribute->getValue()); } @@ -97,35 +96,6 @@ public function __toString(): string ]; } - public function testSerializeAsJson(): void - { - $attribute = Attribute::tryFromValue('foo'); - - $this->assertInstanceOf(Attribute::class, $attribute); - - $this->assertEquals( - ['type' => 'string', 'value' => 'foo'], - $attribute->jsonSerialize() - ); - - $this->assertEquals( - '{"type":"string","value":"foo"}', - json_encode($attribute) - ); - } - - public function testSerializeAsArray(): void - { - $attribute = Attribute::tryFromValue('foo'); - - $this->assertInstanceOf(Attribute::class, $attribute); - - $this->assertEquals( - ['type' => 'string', 'value' => 'foo'], - $attribute->toArray() - ); - } - public function testSerializeAsString(): void { $attribute = Attribute::tryFromValue('foo'); diff --git a/tests/Logs/LogTest.php b/tests/Logs/LogTest.php index 0b5a844c8..cb3c94426 100644 --- a/tests/Logs/LogTest.php +++ b/tests/Logs/LogTest.php @@ -15,29 +15,26 @@ */ final class LogTest extends TestCase { - public function testJsonSerializesToExpected(): void + public function testGettersAndSetters(): void { - $timestamp = microtime(true); - - $log = new Log($timestamp, '123', LogLevel::debug(), 'foo'); - - $log->setAttribute('foo', 'bar'); - $log->setAttribute('should-be-missing', ['foo' => 'bar']); - - $this->assertEquals( - [ - 'timestamp' => $timestamp, - 'trace_id' => '123', - 'level' => 'debug', - 'body' => 'foo', - 'attributes' => [ - 'foo' => [ - 'type' => 'string', - 'value' => 'bar', - ], - ], - ], - $log->jsonSerialize() - ); + $log = new Log(1.0, '123', LogLevel::debug(), 'foo'); + + $this->assertSame(1.0, $log->getTimestamp()); + $this->assertSame('123', $log->getTraceId()); + $this->assertSame(LogLevel::debug(), $log->getLevel()); + $this->assertSame('foo', $log->getBody()); + $this->assertSame([], $log->attributes()->all()); + + $log->setTimestamp(2.0); + $this->assertSame(2.0, $log->getTimestamp()); + + $log->setTraceId('456'); + $this->assertSame('456', $log->getTraceId()); + + $log->setLevel(LogLevel::warn()); + $this->assertSame(LogLevel::warn(), $log->getLevel()); + + $log->setBody('bar'); + $this->assertSame('bar', $log->getBody()); } } diff --git a/tests/Logs/LogsTest.php b/tests/Logs/LogsTest.php index 05931b1c2..1917bd48b 100644 --- a/tests/Logs/LogsTest.php +++ b/tests/Logs/LogsTest.php @@ -9,6 +9,7 @@ use Sentry\ClientBuilder; use Sentry\ClientInterface; use Sentry\Event; +use Sentry\Logs\Log; use Sentry\Logs\LogLevel; use Sentry\Options; use Sentry\SentrySdk; @@ -48,10 +49,10 @@ public function testLogSentWhenEnabled(): void $this->assertEvent(function (Event $event) { $this->assertCount(1, $event->getLogs()); - $logItem = $event->getLogs()[0]->jsonSerialize(); + $logItem = $event->getLogs()[0]; - $this->assertEquals(LogLevel::info(), $logItem['level']); - $this->assertEquals('Some info message', $logItem['body']); + $this->assertEquals(LogLevel::info(), $logItem->getLevel()); + $this->assertEquals('Some info message', $logItem->getBody()); }); logger()->info('Some info message'); @@ -59,15 +60,45 @@ public function testLogSentWhenEnabled(): void $this->assertNotNull(logger()->flush()); } + public function testLogNotSentWithBeforeSendLogOption(): void + { + $this->assertEvent( + function (Event $event) { + $this->assertCount(1, $event->getLogs()); + + $logItem = $event->getLogs()[0]; + + $this->assertEquals(LogLevel::fatal(), $logItem->getLevel()); + $this->assertEquals('Some test message', $logItem->getBody()); + }, + [ + 'before_send_log' => static function (Log $log): ?Log { + if ($log->getLevel() === LogLevel::info()) { + // Returning null will prevent the log from being sent + return null; + } + + // Return the log while changing the level to fatal + return $log->setLevel(LogLevel::fatal()); + }, + ] + ); + + logger()->info('Some info message'); + logger()->warn('Some test message'); + + $this->assertNotNull(logger()->flush()); + } + public function testLogWithTemplate(): void { $this->assertEvent(function (Event $event) { $this->assertCount(1, $event->getLogs()); - $logItem = $event->getLogs()[0]->jsonSerialize(); + $logItem = $event->getLogs()[0]; - $this->assertEquals(LogLevel::info(), $logItem['level']); - $this->assertEquals('Some info message', $logItem['body']); + $this->assertEquals(LogLevel::info(), $logItem->getLevel()); + $this->assertEquals('Some info message', $logItem->getBody()); }); logger()->info('Some %s message', ['info']); @@ -80,12 +111,14 @@ public function testLogWithNestedAttributes(): void $this->assertEvent(function (Event $event) { $this->assertCount(1, $event->getLogs()); - $logItem = $event->getLogs()[0]->jsonSerialize(); + $logItem = $event->getLogs()[0]; - $this->assertArrayHasKey('nested.foo', $logItem['attributes']); - $this->assertArrayNotHasKey('nested.should-be-missing', $logItem['attributes']); + $this->assertNull($logItem->attributes()->get('nested.should-be-missing')); - $this->assertEquals('bar', $logItem['attributes']['nested.foo']['value']); + $attribute = $logItem->attributes()->get('nested.foo'); + + $this->assertNotNull($attribute); + $this->assertEquals('bar', $attribute->getValue()); }); logger()->info('Some message', [], [ @@ -127,7 +160,7 @@ public static function logLevelDataProvider(): \Generator /** * @param callable(Event): void $assert */ - private function assertEvent(callable $assert): ClientInterface + private function assertEvent(callable $assert, array $options = []): ClientInterface { /** @var TransportInterface&MockObject $transport */ $transport = $this->createMock(TransportInterface::class); @@ -142,9 +175,11 @@ private function assertEvent(callable $assert): ClientInterface return new Result(ResultStatus::success(), $event); }); - $client = ClientBuilder::create([ + $clientOptions = array_merge([ 'enable_logs' => true, - ])->setTransport($transport)->getClient(); + ], $options); + + $client = ClientBuilder::create($clientOptions)->setTransport($transport)->getClient(); $hub = new Hub($client); SentrySdk::setCurrentHub($hub); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 8cf6d16ec..e3a8be1b7 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -413,7 +413,8 @@ public static function serializeAsEnvelopeDataProvider(): iterable $event = Event::createLogs(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); $event->setLogs([ - new Log(ClockMock::microtime(true), '21160e9b836d479f81611368b2aa3d2c', LogLevel::info(), 'A log message'), + (new Log(ClockMock::microtime(true), '21160e9b836d479f81611368b2aa3d2c', LogLevel::info(), 'A log message')) + ->setAttribute('foo', 'bar'), ]); yield [ @@ -421,7 +422,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable << Date: Sat, 7 Jun 2025 09:46:23 +0200 Subject: [PATCH 1075/1161] Prepare 4.12.0 (#1851) --- CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dae925bdc..c22c83cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # CHANGELOG +## 4.12.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.12.0. + +### Features + +- Add support for Sentry Structured Logs [(#1813)](https://github.com/getsentry/sentry-php/pull/1813) + + You can now send logs directly to Sentry using the new logging API: + + ```php + Sentry\init([ + // Enable logs to be sent to Sentry + 'enable_logs' => true, + ]); + ``` + + ```php + use function Sentry\logger; + + // Log messages at different levels + logger()->info('User logged in', ['user_id' => 123]); + logger()->warn('Deprecated function used', ['function' => 'old_function']); + logger()->error('Database connection failed', ['host' => 'db.example.com']); + logger()->fatal('Critical system failure: %s', ['Out of memory'], ['component' => 'database']); + + // Flush logs to Sentry + logger()->flush(); + + // We recommend registering the flushing in a shutdown function + register_shutdown_function(static fn () => logger()->flush()); + ``` + + To learn more, head over to our [docs](https://docs.sentry.io/platforms/php/logs/). + +### Bug Fixes + +- Log correct source of sampling decision [(#1836)](https://github.com/getsentry/sentry-php/pull/1836) + ## 4.11.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.11.1. From f595aabab2255856caaf6c8a9f0cb3f275a62fac Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Sat, 7 Jun 2025 08:19:34 +0000 Subject: [PATCH 1076/1161] release: 4.12.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 36ee6a1ce..0b420e787 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.11.1'; + public const SDK_VERSION = '4.12.0'; /** * @var Options The client options From 0173702ffcbe36ce7638f07f090271294866a7a0 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Sat, 7 Jun 2025 10:37:56 +0200 Subject: [PATCH 1077/1161] Fix PayloadSerializerTest.php --- tests/Serializer/PayloadSerializerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index e3a8be1b7..141fde32f 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -420,7 +420,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable yield [ $event, << Date: Sat, 7 Jun 2025 22:06:26 +0200 Subject: [PATCH 1078/1161] Fix `Options::setEnableLogs` and add `Options:: setBeforeSendLogCallback()` (#1852) --- src/Options.php | 95 +++++++++++++++++++++++++------------------ tests/OptionsTest.php | 14 +++++++ 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/src/Options.php b/src/Options.php index b4f9ba3ed..f1e0bffc0 100644 --- a/src/Options.php +++ b/src/Options.php @@ -155,6 +155,28 @@ public function getEnableTracing(): ?bool return $this->options['enable_tracing']; } + /** + * Sets if logs should be enabled or not. + * + * @param bool|null $enableLogs Boolean if logs should be enabled or not + */ + public function setEnableLogs(?bool $enableLogs): self + { + $options = array_merge($this->options, ['enable_logs' => $enableLogs]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + + /** + * Gets if logs is enabled or not. + */ + public function getEnableLogs(): bool + { + return $this->options['enable_logs'] ?? false; + } + /** * Sets the sampling factor to apply to transactions. A value of 0 will deny * sending any transactions, and a value of 1 will send 100% of transactions. @@ -613,6 +635,34 @@ public function setBeforeSendCheckInCallback(callable $callback): self return $this; } + /** + * Gets a callback that will be invoked before an log is sent to the server. + * If `null` is returned it won't be sent. + * + * @psalm-return callable(Log): ?Log + */ + public function getBeforeSendLogCallback(): callable + { + return $this->options['before_send_log']; + } + + /** + * Sets a callable to be called to decide whether a log should + * be captured or not. + * + * @param callable $callback The callable + * + * @psalm-param callable(Log): ?Log $callback + */ + public function setBeforeSendLogCallback(callable $callback): self + { + $options = array_merge($this->options, ['before_send_log' => $callback]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Gets a callback that will be invoked before metrics are sent to the server. * If `null` is returned it won't be sent. @@ -1153,39 +1203,6 @@ public function setTracesSampler(?callable $sampler): self return $this; } - /** - * Sets if logs should be enabled or not. - * - * @param bool|null $enableLogs Boolean if logs should be enabled or not - */ - public function setEnableLogs(?bool $enableLogs): self - { - $options = array_merge($this->options, ['enable_tracing' => $enableLogs]); - - $this->options = $this->resolver->resolve($options); - - return $this; - } - - /** - * Gets if logs is enabled or not. - */ - public function getEnableLogs(): bool - { - return $this->options['enable_logs'] ?? false; - } - - /** - * Gets a callback that will be invoked before an log is sent to the server. - * If `null` is returned it won't be sent. - * - * @psalm-return callable(Log): ?Log - */ - public function getBeforeSendLogCallback(): callable - { - return $this->options['before_send_log']; - } - /** * Configures the options of the client. * @@ -1202,6 +1219,7 @@ private function configureOptions(OptionsResolver $resolver): void 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, 'enable_tracing' => null, + 'enable_logs' => false, 'traces_sample_rate' => null, 'traces_sampler' => null, 'profiles_sample_rate' => null, @@ -1233,6 +1251,9 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send_check_in' => static function (Event $checkIn): Event { return $checkIn; }, + 'before_send_log' => static function (Log $log): Log { + return $log; + }, /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ @@ -1263,15 +1284,12 @@ private function configureOptions(OptionsResolver $resolver): void 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', 'class_serializers' => [], - 'enable_logs' => false, - 'before_send_log' => static function (Log $log): Log { - return $log; - }, ]); $resolver->setAllowedTypes('prefixes', 'string[]'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('enable_tracing', ['null', 'bool']); + $resolver->setAllowedTypes('enable_logs', 'bool'); $resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); $resolver->setAllowedTypes('profiles_sample_rate', ['null', 'int', 'float']); @@ -1290,6 +1308,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('before_send_transaction', ['callable']); + $resolver->setAllowedTypes('before_send_log', 'callable'); $resolver->setAllowedTypes('ignore_exceptions', 'string[]'); $resolver->setAllowedTypes('ignore_transactions', 'string[]'); $resolver->setAllowedTypes('trace_propagation_targets', ['null', 'string[]']); @@ -1313,8 +1332,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); $resolver->setAllowedTypes('class_serializers', 'array'); - $resolver->setAllowedTypes('enable_logs', 'bool'); - $resolver->setAllowedTypes('before_send_log', 'callable'); $resolver->setAllowedValues('max_request_body_size', ['none', 'never', 'small', 'medium', 'always']); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index dd47ca778..4192781a0 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -96,6 +96,13 @@ public static function optionsDataProvider(): \Generator 'setSampleRate', ]; + yield [ + 'enable_logs', + true, + 'getEnableLogs', + 'setEnableLogs', + ]; + yield [ 'traces_sample_rate', 0.5, @@ -264,6 +271,13 @@ static function (): void {}, 'setBeforeSendCheckInCallback', ]; + yield [ + 'before_send_log', + static function (): void {}, + 'getBeforeSendLogCallback', + 'setBeforeSendLogCallback', + ]; + yield [ 'before_send_metrics', static function (): void {}, From c15838a8735f3219e3c6e89ab2ba89269a7f1f6e Mon Sep 17 00:00:00 2001 From: Denitz <197527+Denitz@users.noreply.github.com> Date: Sun, 8 Jun 2025 07:21:54 +0300 Subject: [PATCH 1079/1161] Remove duplicate option name (#1853) --- src/functions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/functions.php b/src/functions.php index 01705f631..573ef7be8 100644 --- a/src/functions.php +++ b/src/functions.php @@ -57,7 +57,6 @@ * send_attempts?: int, * send_default_pii?: bool, * server_name?: string, - * server_name?: string, * spotlight?: bool, * spotlight_url?: string, * strict_trace_propagation?: bool, From 04dd53fe3adfa4c791094692535ea3788babafa2 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 9 Jun 2025 12:18:23 -0400 Subject: [PATCH 1080/1161] Add regex support for `ignore_exceptions` and `ignore_transactions` (#1850) --- src/Client.php | 73 ++++++- tests/ClientTest.php | 250 +++++++++++++++++++++--- tests/Fixtures/code/CustomException.php | 9 - 3 files changed, 285 insertions(+), 47 deletions(-) delete mode 100644 tests/Fixtures/code/CustomException.php diff --git a/src/Client.php b/src/Client.php index 0b420e787..669221329 100644 --- a/src/Client.php +++ b/src/Client.php @@ -34,6 +34,12 @@ class Client implements ClientInterface */ public const SDK_VERSION = '4.12.0'; + /** + * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). + * Supported flags: i (case-insensitive), m (multiline), s (dotall), u (unicode). + */ + private const REGEX_PATTERN_DETECTION = '/^\/.*\/[imsu]*$/'; + /** * @var Options The client options */ @@ -149,7 +155,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId { $className = \get_class($exception); - if ($this->isIgnoredException($className)) { + if ($this->shouldIgnoreException($className)) { $this->logger->info( 'The exception will be discarded because it matches an entry in "ignore_exceptions".', ['className' => $className] @@ -359,11 +365,64 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco return $event; } - private function isIgnoredException(string $className): bool + /** + * Checks if an exception should be ignored based on configured patterns. + * Supports both class hierarchy matching and regex patterns. + * Patterns starting and ending with '/' are treated as regex patterns. + */ + private function shouldIgnoreException(string $className): bool + { + foreach ($this->options->getIgnoreExceptions() as $pattern) { + // Check for regex pattern (starts with / and ends with / optionally followed by flags) + if (preg_match(self::REGEX_PATTERN_DETECTION, $pattern)) { + try { + if (preg_match($pattern, $className)) { + return true; + } + } catch (\Throwable $e) { + // Invalid regex pattern, log and skip + $this->logger->warning( + \sprintf('Invalid regex pattern in ignore_exceptions: "%s". Error: %s', $pattern, $e->getMessage()) + ); + continue; + } + } else { + // Class hierarchy check + if (is_a($className, $pattern, true)) { + return true; + } + } + } + + return false; + } + + /** + * Checks if a transaction should be ignored based on configured patterns. + * Supports both exact string matching and regex patterns. + * Patterns starting and ending with '/' are treated as regex patterns. + */ + private function shouldIgnoreTransaction(string $transactionName): bool { - foreach ($this->options->getIgnoreExceptions() as $ignoredException) { - if (is_a($className, $ignoredException, true)) { - return true; + foreach ($this->options->getIgnoreTransactions() as $pattern) { + // Check for regex pattern (starts with / and ends with / optionally followed by flags) + if (preg_match(self::REGEX_PATTERN_DETECTION, $pattern)) { + try { + if (preg_match($pattern, $transactionName)) { + return true; + } + } catch (\Throwable $e) { + // Invalid regex pattern, log and skip + $this->logger->warning( + \sprintf('Invalid regex pattern in ignore_transactions: "%s". Error: %s', $pattern, $e->getMessage()) + ); + continue; + } + } else { + // Exact string match + if ($transactionName === $pattern) { + return true; + } } } @@ -380,7 +439,7 @@ private function applyIgnoreOptions(Event $event, string $eventDescription): ?Ev } foreach ($exceptions as $exception) { - if ($this->isIgnoredException($exception->getType())) { + if ($this->shouldIgnoreException($exception->getType())) { $this->logger->info( \sprintf('The %s will be discarded because it matches an entry in "ignore_exceptions".', $eventDescription), ['event' => $event] @@ -398,7 +457,7 @@ private function applyIgnoreOptions(Event $event, string $eventDescription): ?Ev return $event; } - if (\in_array($transactionName, $this->options->getIgnoreTransactions(), true)) { + if ($this->shouldIgnoreTransaction($transactionName)) { $this->logger->info( \sprintf('The %s will be discarded because it matches a entry in "ignore_transactions".', $eventDescription), ['event' => $event] diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 2aa111e64..5bc005fb1 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -21,7 +21,6 @@ use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; -use Sentry\Tests\Fixtures\code\CustomException; use Sentry\Transport\Result; use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; @@ -670,75 +669,264 @@ public function testProcessEventCapturesEventWhenSampleRateOptionIsAboveZero(): $client->captureEvent(Event::createEvent()); } - public function testProcessEventDiscardsEventWhenIgnoreExceptionsMatches(): void + /** + * @dataProvider ignoreExceptionsDataProvider + */ + public function testProcessEventDiscardsEventWhenIgnoreExceptionMatches(string $exceptionClass, array $ignorePatterns, bool $shouldIgnore): void { - $exception = new \Exception('Some foo error'); + if (class_exists($exceptionClass)) { + $exception = new $exceptionClass('Test exception message'); + } else { + $exception = new \Exception('Test exception message'); + } /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once()) - ->method('info') - ->with('The exception will be discarded because it matches an entry in "ignore_exceptions".'); + + if ($shouldIgnore) { + $logger->expects($this->once()) + ->method('info') + ->with('The exception will be discarded because it matches an entry in "ignore_exceptions".'); + } else { + $logger->expects($this->never()) + ->method('info'); + } + + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + + if ($shouldIgnore) { + $transport->expects($this->never()) + ->method('send'); + } else { + $transport->expects($this->once()) + ->method('send') + ->with($this->anything()) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); + } $options = [ - 'ignore_exceptions' => [\Exception::class], + 'ignore_exceptions' => $ignorePatterns, ]; $client = ClientBuilder::create($options) + ->setTransport($transport) ->setLogger($logger) ->getClient(); $client->captureException($exception); } - public function testProcessEventDiscardsEventWhenParentHierarchyOfIgnoreExceptionsMatches(): void + public static function ignoreExceptionsDataProvider(): \Generator { - $exception = new CustomException('Some foo error'); + yield 'Exact class name match' => [ + \Exception::class, + [\Exception::class], + true, + ]; - /** @var LoggerInterface&MockObject $logger */ - $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once()) - ->method('info') - ->with('The exception will be discarded because it matches an entry in "ignore_exceptions".'); + yield 'Class hierarchy match' => [ + \RuntimeException::class, + [\Exception::class], + true, + ]; - $options = [ - 'ignore_exceptions' => [\RuntimeException::class], + yield 'No class match' => [ + \Exception::class, + [\RuntimeException::class], + false, ]; - $client = ClientBuilder::create($options) - ->setLogger($logger) - ->getClient(); + yield 'Regex pattern matches exception class' => [ + \InvalidArgumentException::class, + ['/.*ArgumentException$/'], + true, + ]; - $client->captureException($exception); + yield 'Regex pattern no match' => [ + \Exception::class, + ['/.*RuntimeException$/'], + false, + ]; + + yield 'Multiple patterns, first matches (class hierarchy)' => [ + \RuntimeException::class, + [\Exception::class, '/.*NotFound.*/'], + true, + ]; + + yield 'Multiple patterns, second matches (regex)' => [ + \InvalidArgumentException::class, + [\RuntimeException::class, '/.*Argument.*/'], + true, + ]; + + yield 'Multiple patterns, none match' => [ + \Exception::class, + [\RuntimeException::class, '/.*NotFound.*/'], + false, + ]; + + yield 'Regex with namespace matching' => [ + \InvalidArgumentException::class, + ['/^InvalidArgumentException$/'], + true, + ]; + + yield 'Regex with full namespace' => [ + \InvalidArgumentException::class, + ['/^InvalidArgumentException$/'], + true, + ]; + + yield 'Case sensitive regex' => [ + \Exception::class, + ['/^exception$/'], + false, + ]; + + yield 'Case insensitive regex' => [ + \Exception::class, + ['/^exception$/i'], + true, + ]; + + yield 'Regex with wildcards for any exception' => [ + \LogicException::class, + ['/.*Exception$/'], + true, + ]; + + yield 'Mixed class and regex patterns' => [ + \InvalidArgumentException::class, + [\RuntimeException::class, '/.*Argument.*/'], + true, + ]; } - public function testProcessEventDiscardsEventWhenIgnoreTransactionsMatches(): void + /** + * @dataProvider ignoreTransactionsDataProvider + */ + public function testProcessEventDiscardsEventWhenIgnoreTransactionsMatches(string $transactionName, array $ignorePatterns, bool $shouldIgnore): void { $event = Event::createTransaction(); - $event->setTransaction('GET /foo'); + $event->setTransaction($transactionName); /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); - $logger->expects($this->once()) - ->method('info') - ->with( - new StringMatchesFormatDescription('The transaction [%s] will be discarded because it matches a entry in "ignore_transactions".'), - $this->callback(static function (array $context): bool { - return isset($context['event']) && $context['event'] instanceof Event; - }) - ); + + if ($shouldIgnore) { + $logger->expects($this->once()) + ->method('info') + ->with( + new StringMatchesFormatDescription('The transaction [%s] will be discarded because it matches a entry in "ignore_transactions".'), + $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + }) + ); + } else { + $logger->expects($this->never()) + ->method('info'); + } + + /** @var TransportInterface&MockObject $transport */ + $transport = $this->createMock(TransportInterface::class); + + if ($shouldIgnore) { + $transport->expects($this->never()) + ->method('send'); + } else { + $transport->expects($this->once()) + ->method('send') + ->with($event) + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); + }); + } $options = [ - 'ignore_transactions' => ['GET /foo'], + 'ignore_transactions' => $ignorePatterns, ]; $client = ClientBuilder::create($options) + ->setTransport($transport) ->setLogger($logger) ->getClient(); $client->captureEvent($event); } + public static function ignoreTransactionsDataProvider(): \Generator + { + yield 'Exact string match' => [ + 'GET /api/users', + ['GET /api/users'], + true, + ]; + + yield 'Exact string no match' => [ + 'GET /api/posts', + ['GET /api/users'], + false, + ]; + + yield 'Regex pattern matches' => [ + 'GET /api/users/123', + ['/^GET \/api\/users\/\d+$/'], + true, + ]; + + yield 'Regex pattern no match' => [ + 'POST /api/users/123', + ['/^GET \/api\/users\/\d+$/'], + false, + ]; + + yield 'Multiple patterns, first matches' => [ + 'GET /health', + ['GET /health', '/^POST \/api\/.*$/'], + true, + ]; + + yield 'Multiple patterns, second matches' => [ + 'POST /api/data', + ['GET /health', '/^POST \/api\/.*$/'], + true, + ]; + + yield 'Multiple patterns, none match' => [ + 'PUT /api/data', + ['GET /health', '/^POST \/api\/.*$/'], + false, + ]; + + yield 'Regex with wildcards' => [ + 'GET /api/v1/users/active', + ['/\/api\/v\d+\/users\/.*$/'], + true, + ]; + + yield 'Case sensitive regex' => [ + 'get /api/users', + ['/^GET \/api\/users$/'], + false, + ]; + + yield 'Case insensitive regex' => [ + 'get /api/users', + ['/^get \/api\/users$/i'], + true, + ]; + + yield 'Mixed exact and regex patterns' => [ + 'DELETE /api/cache', + ['GET /health', '/^DELETE \/api\/.*$/'], + true, + ]; + } + public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull(): void { /** @var LoggerInterface&MockObject $logger */ diff --git a/tests/Fixtures/code/CustomException.php b/tests/Fixtures/code/CustomException.php deleted file mode 100644 index f47296725..000000000 --- a/tests/Fixtures/code/CustomException.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Mon, 9 Jun 2025 12:18:44 -0400 Subject: [PATCH 1081/1161] Add support for variadic parameters and null values (#1849) --- src/FrameBuilder.php | 13 ++++- tests/FrameBuilderTest.php | 111 +++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 6384279fd..4aa598347 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -219,7 +219,18 @@ private function getFunctionArgumentValues(\ReflectionFunctionAbstract $reflecti foreach ($reflectionFunction->getParameters() as $reflectionParameter) { $parameterPosition = $reflectionParameter->getPosition(); - if (!isset($backtraceFrameArgs[$parameterPosition])) { + if ($reflectionParameter->isVariadic()) { + // For variadic parameters, collect all remaining arguments into an array + $variadicArgs = []; + for ($i = $parameterPosition; $i < \count($backtraceFrameArgs); ++$i) { + $variadicArgs[] = $backtraceFrameArgs[$i]; + } + $argumentValues[$reflectionParameter->getName()] = $variadicArgs; + // Variadic parameter is always the last one, so we can break + break; + } + + if (!\array_key_exists($parameterPosition, $backtraceFrameArgs)) { continue; } diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php index b43efcff8..56aa4f3b0 100644 --- a/tests/FrameBuilderTest.php +++ b/tests/FrameBuilderTest.php @@ -284,4 +284,115 @@ public static function addFrameSetsInAppFlagCorrectlyDataProvider(): \Generator false, ]; } + + public function testGetFunctionArgumentsWithVariadicParameters(): void + { + $options = new Options([]); + $frameBuilder = new FrameBuilder($options, new RepresentationSerializer($options)); + + $testFunction = function (string $first, int $second, ...$rest) { + }; + + $backtraceFrame = [ + 'function' => 'testVariadicFunction', + 'args' => ['hello', 42, 'extra1', 'extra2', 'extra3'], + ]; + + $reflectionClass = new \ReflectionClass($frameBuilder); + $getFunctionArgumentsMethod = $reflectionClass->getMethod('getFunctionArguments'); + $getFunctionArgumentsMethod->setAccessible(true); + + $reflectionFunction = new \ReflectionFunction($testFunction); + + $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); + $getFunctionArgumentValuesMethod->setAccessible(true); + + $result = $getFunctionArgumentValuesMethod->invoke($frameBuilder, $reflectionFunction, $backtraceFrame['args']); + + $this->assertSame('hello', $result['first']); + $this->assertSame(42, $result['second']); + + $this->assertArrayHasKey('rest', $result); + $this->assertSame(['extra1', 'extra2', 'extra3'], $result['rest']); + } + + public function testGetFunctionArgumentsWithOnlyVariadicParameters(): void + { + $options = new Options([]); + $frameBuilder = new FrameBuilder($options, new RepresentationSerializer($options)); + + $testFunction = function (...$args) { + }; + + $backtraceFrame = [ + 'function' => 'testOnlyVariadicFunction', + 'args' => ['arg1', 'arg2', 'arg3'], + ]; + + $reflectionClass = new \ReflectionClass($frameBuilder); + $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); + $getFunctionArgumentValuesMethod->setAccessible(true); + + $reflectionFunction = new \ReflectionFunction($testFunction); + + $result = $getFunctionArgumentValuesMethod->invoke($frameBuilder, $reflectionFunction, $backtraceFrame['args']); + + $this->assertArrayHasKey('args', $result); + $this->assertSame(['arg1', 'arg2', 'arg3'], $result['args']); + } + + public function testGetFunctionArgumentsWithEmptyVariadicParameters(): void + { + $options = new Options([]); + $frameBuilder = new FrameBuilder($options, new RepresentationSerializer($options)); + + $testFunction = function (string $first, ...$rest) { + }; + + $backtraceFrame = [ + 'function' => 'testEmptyVariadicFunction', + 'args' => ['hello'], + ]; + + $reflectionClass = new \ReflectionClass($frameBuilder); + $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); + $getFunctionArgumentValuesMethod->setAccessible(true); + + $reflectionFunction = new \ReflectionFunction($testFunction); + + $result = $getFunctionArgumentValuesMethod->invoke($frameBuilder, $reflectionFunction, $backtraceFrame['args']); + + $this->assertSame('hello', $result['first']); + + $this->assertArrayHasKey('rest', $result); + $this->assertSame([], $result['rest']); + } + + public function testGetFunctionArgumentsWithNullValues(): void + { + $options = new Options([]); + $frameBuilder = new FrameBuilder($options, new RepresentationSerializer($options)); + + $testFunction = function (string $first, $second, ...$rest) { + }; + + $backtraceFrame = [ + 'function' => 'testNullFunction', + 'args' => ['hello', null, 'extra1', null, 'extra3'], + ]; + + $reflectionClass = new \ReflectionClass($frameBuilder); + $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); + $getFunctionArgumentValuesMethod->setAccessible(true); + + $reflectionFunction = new \ReflectionFunction($testFunction); + + $result = $getFunctionArgumentValuesMethod->invoke($frameBuilder, $reflectionFunction, $backtraceFrame['args']); + + $this->assertSame('hello', $result['first']); + $this->assertNull($result['second']); + + $this->assertArrayHasKey('rest', $result); + $this->assertSame(['extra1', null, 'extra3'], $result['rest']); + } } From 1055a66b4bca2863124efd3c1a212d56325619f9 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 10 Jun 2025 17:28:50 +0200 Subject: [PATCH 1082/1161] Fix `vsprintf` not handling errors (#1855) --- src/Logs/LogsAggregator.php | 25 ++++++- src/Serializer/EnvelopItems/EventItem.php | 3 +- src/Util/Str.php | 45 ++++++++++++ tests/Logs/LogTest.php | 5 -- tests/Logs/LogsAggregatorTest.php | 85 +++++++++++++++++++++++ tests/Util/StrTest.php | 58 ++++++++++++++++ 6 files changed, 214 insertions(+), 7 deletions(-) create mode 100644 src/Util/Str.php create mode 100644 tests/Logs/LogsAggregatorTest.php create mode 100644 tests/Util/StrTest.php diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index 754048e41..6c0bde51a 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -12,6 +12,7 @@ use Sentry\State\HubInterface; use Sentry\State\Scope; use Sentry\Util\Arr; +use Sentry\Util\Str; /** * @internal @@ -57,7 +58,21 @@ public function add( return; } - $log = (new Log($timestamp, $this->getTraceId($hub), $level, vsprintf($message, $values))) + $formattedMessage = Str::vsprintfOrNull($message, $values); + + if ($formattedMessage === null) { + // If formatting fails we don't format the message and log the error + if ($sdkLogger !== null) { + $sdkLogger->warning('Failed to format log message with values.', [ + 'message' => $message, + 'values' => $values, + ]); + } + + $formattedMessage = $message; + } + + $log = (new Log($timestamp, $this->getTraceId($hub), $level, $formattedMessage)) ->setAttribute('sentry.release', $options->getRelease()) ->setAttribute('sentry.environment', $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT) ->setAttribute('sentry.server.address', $options->getServerName()) @@ -124,6 +139,14 @@ public function flush(): ?EventId return $hub->captureEvent($event); } + /** + * @return Log[] + */ + public function all(): array + { + return $this->logs; + } + private function getTraceId(HubInterface $hub): string { $span = $hub->getSpan(); diff --git a/src/Serializer/EnvelopItems/EventItem.php b/src/Serializer/EnvelopItems/EventItem.php index ca6d1d38b..49d58cabc 100644 --- a/src/Serializer/EnvelopItems/EventItem.php +++ b/src/Serializer/EnvelopItems/EventItem.php @@ -9,6 +9,7 @@ use Sentry\Serializer\Traits\BreadcrumbSeralizerTrait; use Sentry\Serializer\Traits\StacktraceFrameSeralizerTrait; use Sentry\Util\JSON; +use Sentry\Util\Str; /** * @internal @@ -124,7 +125,7 @@ public static function toEnvelopeItem(Event $event): string $payload['message'] = [ 'message' => $event->getMessage(), 'params' => $event->getMessageParams(), - 'formatted' => $event->getMessageFormatted() ?? vsprintf($event->getMessage(), $event->getMessageParams()), + 'formatted' => $event->getMessageFormatted() ?? Str::vsprintfOrNull($event->getMessage(), $event->getMessageParams()) ?? $event->getMessage(), ]; } } diff --git a/src/Util/Str.php b/src/Util/Str.php new file mode 100644 index 000000000..5dcfed9be --- /dev/null +++ b/src/Util/Str.php @@ -0,0 +1,45 @@ + $values + */ + public static function vsprintfOrNull(string $message, array $values): ?string + { + if (empty($values)) { + return $message; + } + + foreach ($values as $value) { + // If the value is not a scalar or null, we cannot safely format it + if (!\is_scalar($value) && $value !== null) { + return null; + } + } + + try { + $result = @vsprintf($message, $values); + + // @phpstan-ignore-next-line on PHP 7 `vsprintf` does not throw an exception but can return `false` + return $result === false ? null : $result; + } catch (\Error $e) { // This is technically a `ValueError` in PHP 8.0+ but this works in PHP 7 as well + return null; + } + } +} diff --git a/tests/Logs/LogTest.php b/tests/Logs/LogTest.php index cb3c94426..1cfba9c83 100644 --- a/tests/Logs/LogTest.php +++ b/tests/Logs/LogTest.php @@ -5,14 +5,9 @@ namespace Sentry\Tests\Logs; use PHPUnit\Framework\TestCase; -use Sentry\Attributes\Attribute; use Sentry\Logs\Log; use Sentry\Logs\LogLevel; -/** - * @phpstan-import-type AttributeValue from Attribute - * @phpstan-import-type AttributeSerialized from Attribute - */ final class LogTest extends TestCase { public function testGettersAndSetters(): void diff --git a/tests/Logs/LogsAggregatorTest.php b/tests/Logs/LogsAggregatorTest.php new file mode 100644 index 000000000..fbb3e8b70 --- /dev/null +++ b/tests/Logs/LogsAggregatorTest.php @@ -0,0 +1,85 @@ + true, + ])->getClient(); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + $aggregator = new LogsAggregator(); + + $aggregator->add(LogLevel::info(), $message, $values); + + $logs = $aggregator->all(); + + $this->assertCount(1, $logs); + + $log = $logs[0]; + + $this->assertEquals($expected, $log->getBody()); + } + + public static function messageFormattingDataProvider(): \Generator + { + yield [ + 'Simple message without values', + [], + 'Simple message without values', + ]; + + yield [ + 'Message with a value: %s', + ['value'], + 'Message with a value: value', + ]; + + yield [ + 'Message with placeholders but no values: %s', + [], + 'Message with placeholders but no values: %s', + ]; + + yield [ + 'Message with placeholders but incorrect number of values: %s, %s', + ['value'], + 'Message with placeholders but incorrect number of values: %s, %s', + ]; + + yield [ + 'Message with a percentage: 42%', + [], + 'Message with a percentage: 42%', + ]; + + // This test case is a bit of an odd one, you would not expect this to happen in practice unless the user intended + // to format the message but did not add the proper placeholder. On PHP 8+ this will return the message as is, but + // on PHP 7 it will return the message without the percentage sign because some processing is done by `vsprintf`. + // You would however more likely expect the previous test case where no values are provided when no placeholders are present + yield [ + 'Message with a percentage: 42%', + ['value'], + \PHP_VERSION_ID >= 80000 + ? 'Message with a percentage: 42%' + : 'Message with a percentage: 42', + ]; + } +} diff --git a/tests/Util/StrTest.php b/tests/Util/StrTest.php new file mode 100644 index 000000000..0a3a64572 --- /dev/null +++ b/tests/Util/StrTest.php @@ -0,0 +1,58 @@ +assertSame($expected, Str::vsprintfOrNull($message, $values)); + } + + public static function vsprintfOrNullDataProvider(): \Generator + { + yield [ + 'Simple message without values', + [], + 'Simple message without values', + ]; + + yield [ + 'Message with a value: %s', + ['value'], + 'Message with a value: value', + ]; + + yield [ + 'Message with placeholders but no values: %s', + [], + 'Message with placeholders but no values: %s', + ]; + + yield [ + 'Message with placeholders but incorrect number of values: %s, %s', + ['value'], + null, + ]; + + yield [ + 'Message with placeholder: %s', + [[1, 2, 3]], + null, + ]; + + yield [ + 'Message with placeholder: %s', + [new \stdClass()], + null, + ]; + } +} From 7758c222948a10332920086838049d123c45d872 Mon Sep 17 00:00:00 2001 From: kafkiansky Date: Tue, 10 Jun 2025 18:29:03 +0300 Subject: [PATCH 1083/1161] Remove @internal phpdoc from HttpClient (#1841) Co-authored-by: Michi Hoffmann --- src/HttpClient/Request.php | 3 --- src/HttpClient/Response.php | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/HttpClient/Request.php b/src/HttpClient/Request.php index eb3aefd50..4fde1592d 100644 --- a/src/HttpClient/Request.php +++ b/src/HttpClient/Request.php @@ -4,9 +4,6 @@ namespace Sentry\HttpClient; -/** - * @internal - */ final class Request { /** diff --git a/src/HttpClient/Response.php b/src/HttpClient/Response.php index 76748271a..f82b09e94 100644 --- a/src/HttpClient/Response.php +++ b/src/HttpClient/Response.php @@ -4,9 +4,6 @@ namespace Sentry\HttpClient; -/** - * @internal - */ final class Response { /** From 434e60a065d5d01fc1892eaf6a5be7864b595f20 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 10 Jun 2025 11:39:01 -0400 Subject: [PATCH 1084/1161] Prepare 4.13.0 (#1856) --- CHANGELOG.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c22c83cdc..d69f50084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # CHANGELOG +## 4.13.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.13.0. + +### Features + +- Add regex support for `ignore_exceptions` and `ignore_transactions` [(#1850)](https://github.com/getsentry/sentry-php/pull/1850) + + You can now use regular expressions to ignore exceptions and transactions: + + ```php + Sentry\init([ + 'ignore_exceptions' => [ + '/.*ArgumentException$/', + ], + 'ignore_transactions' => [ + '/^GET \/api\/users\/\d+$/', + ], + ]); + ``` + +- Add support for variadic parameters and null values [(#1849)](https://github.com/getsentry/sentry-php/pull/1849) + +### Bug Fixes + +- Fix `Options::setEnableLogs` [(#1852)](https://github.com/getsentry/sentry-php/pull/1852) +- Fix `vsprintf` not handling errors [(#1855)](https://github.com/getsentry/sentry-php/pull/1855) + ## 4.12.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.12.0. @@ -86,7 +114,7 @@ The Sentry SDK team is happy to announce the immediate availability of Sentry PH ### Features -- Allow retrieving a single piece of data from the span by it’s key [(#1767)](https://github.com/getsentry/sentry-php/pull/1767) +- Allow retrieving a single piece of data from the span by it's key [(#1767)](https://github.com/getsentry/sentry-php/pull/1767) ```php \Sentry\SentrySdk::getCurrentHub()->getSpan()?->setData([ From b54a0eaedfc27fc2da587e64455a66cd29cd3c4d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 10 Jun 2025 15:39:27 +0000 Subject: [PATCH 1085/1161] release: 4.13.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 669221329..9a44c339e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.12.0'; + public const SDK_VERSION = '4.13.0'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 5fc99eb58efd708c7de20a2d1c943f63c18436a3 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 10 Jun 2025 15:46:25 -0400 Subject: [PATCH 1086/1161] Set allowed types for `http_ssl_native_ca` (#1858) Co-authored-by: Cursor Agent --- src/Options.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Options.php b/src/Options.php index f1e0bffc0..ea5e04787 100644 --- a/src/Options.php +++ b/src/Options.php @@ -1328,6 +1328,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('http_connect_timeout', ['int', 'float']); $resolver->setAllowedTypes('http_timeout', ['int', 'float']); $resolver->setAllowedTypes('http_ssl_verify_peer', 'bool'); + $resolver->setAllowedTypes('http_ssl_native_ca', 'bool'); $resolver->setAllowedTypes('http_compression', 'bool'); $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); From 6a1adc2be320b94025c013162ca3f44a3e880367 Mon Sep 17 00:00:00 2001 From: Peter Maatman Date: Thu, 12 Jun 2025 19:20:38 +0200 Subject: [PATCH 1087/1161] Serialize Enum variants with the variant name (#1860) --- src/Serializer/AbstractSerializer.php | 6 ++++++ tests/Serializer/AbstractSerializerTest.php | 12 ++++++++++++ tests/Serializer/SerializerTestEnum.php | 10 ++++++++++ 3 files changed, 28 insertions(+) create mode 100644 tests/Serializer/SerializerTestEnum.php diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index b26cd2ac2..42946bf8d 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -241,6 +241,12 @@ protected function serializeValue($value) return $value; } + if ($value instanceof \UnitEnum) { + $reflection = new \ReflectionObject($value); + + return 'Enum ' . $reflection->getName() . '::' . $value->name; + } + if (\is_object($value)) { $reflection = new \ReflectionObject($value); diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 8f6104fbc..36f753c73 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -54,6 +54,18 @@ public function testObjectsAreStrings(): void $this->assertSame('Object Sentry\Tests\Serializer\SerializerTestObject', $result); } + /** + * @requires PHP >= 8.1 + */ + public function testEnumsAreNames(): void + { + $serializer = $this->createSerializer(); + $input = SerializerTestEnum::CASE_NAME; + $result = $this->invokeSerialization($serializer, $input); + + $this->assertSame('Enum Sentry\Tests\Serializer\SerializerTestEnum::CASE_NAME', $result); + } + public static function objectsWithIdPropertyDataProvider(): array { return [ diff --git a/tests/Serializer/SerializerTestEnum.php b/tests/Serializer/SerializerTestEnum.php new file mode 100644 index 000000000..05e38b88c --- /dev/null +++ b/tests/Serializer/SerializerTestEnum.php @@ -0,0 +1,10 @@ + Date: Fri, 13 Jun 2025 11:24:17 -0400 Subject: [PATCH 1088/1161] Fix handling of backtrace frames (#1862) Co-authored-by: Cursor Agent --- src/FrameBuilder.php | 7 ++----- tests/FrameBuilderTest.php | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 4aa598347..7808ea962 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -221,11 +221,8 @@ private function getFunctionArgumentValues(\ReflectionFunctionAbstract $reflecti if ($reflectionParameter->isVariadic()) { // For variadic parameters, collect all remaining arguments into an array - $variadicArgs = []; - for ($i = $parameterPosition; $i < \count($backtraceFrameArgs); ++$i) { - $variadicArgs[] = $backtraceFrameArgs[$i]; - } - $argumentValues[$reflectionParameter->getName()] = $variadicArgs; + $variadicArgs = \array_slice($backtraceFrameArgs, $parameterPosition); + $argumentValues[$reflectionParameter->getName()] = array_values($variadicArgs); // Variadic parameter is always the last one, so we can break break; } diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php index 56aa4f3b0..e102996e8 100644 --- a/tests/FrameBuilderTest.php +++ b/tests/FrameBuilderTest.php @@ -395,4 +395,39 @@ public function testGetFunctionArgumentsWithNullValues(): void $this->assertArrayHasKey('rest', $result); $this->assertSame(['extra1', null, 'extra3'], $result['rest']); } + + public function testGetFunctionArgumentsWithGapsInBacktraceArrayIndices(): void + { + $options = new Options([]); + $frameBuilder = new FrameBuilder($options, new RepresentationSerializer($options)); + + $testFunction = function (string $first, int $second, ...$rest) { + }; + + $backtraceFrameArgs = []; + $backtraceFrameArgs[0] = 'hello'; // first parameter + $backtraceFrameArgs[1] = 42; // second parameter + $backtraceFrameArgs[3] = 'extra1'; // gap at index 2, starts variadic args + $backtraceFrameArgs[5] = 'extra2'; // gap at index 4 + $backtraceFrameArgs[7] = 'extra3'; // gap at index 6 + + $backtraceFrame = [ + 'function' => 'testGapsFunction', + 'args' => $backtraceFrameArgs, + ]; + + $reflectionClass = new \ReflectionClass($frameBuilder); + $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); + $getFunctionArgumentValuesMethod->setAccessible(true); + + $reflectionFunction = new \ReflectionFunction($testFunction); + + $result = $getFunctionArgumentValuesMethod->invoke($frameBuilder, $reflectionFunction, $backtraceFrame['args']); + + $this->assertSame('hello', $result['first']); + $this->assertSame(42, $result['second']); + + $this->assertArrayHasKey('rest', $result); + $this->assertSame(['extra1', 'extra2', 'extra3'], $result['rest']); + } } From 7c3a5cdf9f156b5e9803792ae0b86788c6a81bcc Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 13 Jun 2025 13:22:14 -0400 Subject: [PATCH 1089/1161] Prepare 4.14.0 (#1863) --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d69f50084..cc309095d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # CHANGELOG +## 4.14.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.14.0. + +### Features + +- Serialize enum variants with the variant name [(#1860)](https://github.com/getsentry/sentry-php/pull/1860) + +### Bug Fixes + +- Fix handling of backtrace frames [(#1862)](https://github.com/getsentry/sentry-php/pull/1862) +- Set allowed types for `http_ssl_native_ca` [(#1858)](https://github.com/getsentry/sentry-php/pull/1858) + ## 4.13.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.13.0. From f8c50304bd2f1640704345274bdd5af150b6945d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Fri, 13 Jun 2025 17:22:57 +0000 Subject: [PATCH 1090/1161] release: 4.14.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 9a44c339e..d87e61c8f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.13.0'; + public const SDK_VERSION = '4.14.0'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 28bf0ceb37384f617def4eb1125dabe4a509bad3 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 20 Jun 2025 12:17:35 +0200 Subject: [PATCH 1091/1161] Fix missing user attributes on logs (#1864) --- src/Logs/LogsAggregator.php | 15 ++++++++ tests/Logs/LogsAggregatorTest.php | 59 ++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index 6c0bde51a..e4587b124 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -84,6 +84,21 @@ public function add( $log->setAttribute('sentry.sdk.version', $client->getSdkVersion()); } + $hub->configureScope(function (Scope $scope) use ($log) { + $user = $scope->getUser(); + if ($user !== null) { + if ($user->getId() !== null) { + $log->setAttribute('user.id', $user->getId()); + } + if ($user->getEmail() !== null) { + $log->setAttribute('user.email', $user->getEmail()); + } + if ($user->getUsername() !== null) { + $log->setAttribute('user.name', $user->getUsername()); + } + } + }); + foreach ($values as $key => $value) { $log->setAttribute("sentry.message.parameter.{$key}", $value); } diff --git a/tests/Logs/LogsAggregatorTest.php b/tests/Logs/LogsAggregatorTest.php index fbb3e8b70..7e5e38b98 100644 --- a/tests/Logs/LogsAggregatorTest.php +++ b/tests/Logs/LogsAggregatorTest.php @@ -5,11 +5,18 @@ namespace Sentry\Tests\Logs; use PHPUnit\Framework\TestCase; +use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Logs\LogLevel; use Sentry\Logs\LogsAggregator; use Sentry\SentrySdk; use Sentry\State\Hub; +use Sentry\State\Scope; +use Sentry\Tracing\Span; +use Sentry\Tracing\SpanContext; +use Sentry\Tracing\SpanId; +use Sentry\Tracing\TraceId; +use Sentry\UserDataBag; final class LogsAggregatorTest extends TestCase { @@ -35,7 +42,7 @@ public function testMessageFormatting(string $message, array $values, string $ex $log = $logs[0]; - $this->assertEquals($expected, $log->getBody()); + $this->assertSame($expected, $log->getBody()); } public static function messageFormattingDataProvider(): \Generator @@ -82,4 +89,54 @@ public static function messageFormattingDataProvider(): \Generator : 'Message with a percentage: 42', ]; } + + public function testAttributesAreAddedToLogMessage(): void + { + $client = ClientBuilder::create([ + 'enable_logs' => true, + 'release' => '1.0.0', + 'environment' => 'production', + 'server_name' => 'web-server-01', + ])->getClient(); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + $hub->configureScope(function (Scope $scope) { + $userDataBag = new UserDataBag(); + $userDataBag->setId('unique_id'); + $userDataBag->setEmail('foo@example.com'); + $userDataBag->setUsername('my_user'); + $scope->setUser($userDataBag); + }); + + $spanContext = new SpanContext(); + $spanContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $spanContext->setSpanId(new SpanId('566e3688a61d4bc8')); + $span = new Span($spanContext); + $hub->setSpan($span); + + $aggregator = new LogsAggregator(); + + $aggregator->add(LogLevel::info(), 'User %s performed action %s', [ + 'testuser', 'login', + ]); + + $logs = $aggregator->all(); + $this->assertCount(1, $logs); + + $log = $logs[0]; + $attributes = $log->attributes(); + + $this->assertSame('1.0.0', $attributes->get('sentry.release')->getValue()); + $this->assertSame('production', $attributes->get('sentry.environment')->getValue()); + $this->assertSame('web-server-01', $attributes->get('sentry.server.address')->getValue()); + $this->assertSame('User %s performed action %s', $attributes->get('sentry.message.template')->getValue()); + $this->assertSame('566e3688a61d4bc8', $attributes->get('sentry.trace.parent_span_id')->getValue()); + $this->assertSame('sentry.php', $attributes->get('sentry.sdk.name')->getValue()); + $this->assertSame(Client::SDK_VERSION, $attributes->get('sentry.sdk.version')->getValue()); + $this->assertSame('unique_id', $attributes->get('user.id')->getValue()); + $this->assertSame('foo@example.com', $attributes->get('user.email')->getValue()); + $this->assertSame('my_user', $attributes->get('user.name')->getValue()); + } } From 7761d2b15f7f13b060631af66bcf0db085f44d7e Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:49:44 +0200 Subject: [PATCH 1092/1161] Updated readme with both Magento plugins (#1859) Co-authored-by: Alex Bouma --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37411dd77..2372380c3 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The following integrations are available and maintained by members of the Sentry - [Drupal](https://www.drupal.org/project/raven) - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) -- [Magento 2](https://github.com/mygento/module-sentry) +- Magento 2 by [JustBetter](https://github.com/justbetter/magento2-sentry) or by [Mygento](https://github.com/mygento/module-sentry) - ... feel free to be famous, create a port to your favourite platform! ## 3rd party integrations using the old SDK 3.x From 2d8542ba1b0c03e0ac93e72615f768a92f107b98 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 23 Jun 2025 15:13:36 +0200 Subject: [PATCH 1093/1161] Prepare 4.14.1 (#1865) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc309095d..35736de6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 4.14.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.14.1. + +### Bug Fixes + +- Fix missing user attributes on logs [(#1864)](https://github.com/getsentry/sentry-php/pull/1864) + ## 4.14.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.14.0. From a28c4a6f5fda2bf730789a638501d7a737a64eda Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 23 Jun 2025 15:25:52 +0000 Subject: [PATCH 1094/1161] release: 4.14.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index d87e61c8f..296591891 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.14.0'; + public const SDK_VERSION = '4.14.1'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 4a5ed24e9985cc568e292aca0b7d025bebeade85 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 21 Jul 2025 09:08:58 +0200 Subject: [PATCH 1095/1161] Apply PHP CS fixer changes (#1869) --- src/ErrorHandler.php | 4 ++-- src/Event.php | 4 ++-- src/FrameBuilder.php | 4 ++-- src/Monolog/BreadcrumbHandler.php | 4 ++-- src/State/HubInterface.php | 4 ++-- src/functions.php | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index a34ec5893..0ba6cc32e 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -458,9 +458,9 @@ private function handleException(\Throwable $exception): void * @param string $file The filename the backtrace was raised in * @param int $line The line number the backtrace was raised at * - * @return array - * * @psalm-param list $backtrace + * + * @return array */ private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array { diff --git a/src/Event.php b/src/Event.php index 1ac6cc6af..5244f945c 100644 --- a/src/Event.php +++ b/src/Event.php @@ -892,12 +892,12 @@ public function setSdkMetadata(string $name, $data): self /** * Gets the SDK metadata. * - * @return mixed - * * @psalm-template T of string|null * * @psalm-param T $name * + * @return mixed + * * @psalm-return (T is string ? mixed : array|null) */ public function getSdkMetadata(?string $name = null) diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 7808ea962..8c102ef28 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -158,9 +158,9 @@ private function isFrameInApp(string $file, ?string $functionName): bool * * @param array $backtraceFrame The frame data * - * @return array - * * @psalm-param StacktraceFrame $backtraceFrame + * + * @return array */ private function getFunctionArguments(array $backtraceFrame): array { diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php index 8c5e03b0a..bb2b60ea0 100644 --- a/src/Monolog/BreadcrumbHandler.php +++ b/src/Monolog/BreadcrumbHandler.php @@ -26,13 +26,13 @@ final class BreadcrumbHandler extends AbstractProcessingHandler private $hub; /** - * @phpstan-param int|string|Level|LogLevel::* $level - * * @param HubInterface $hub The hub to which errors are reported * @param int|string $level The minimum logging level at which this * handler will be triggered * @param bool $bubble Whether the messages that are handled can * bubble up the stack or not + * + * @phpstan-param int|string|Level|LogLevel::* $level */ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true) { diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 44d9e70e3..227a8451e 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -51,12 +51,12 @@ public function popScope(): bool; * * @param callable $callback The callback to be executed * - * @return mixed|void The callback's return value, upon successful execution - * * @psalm-template T * * @psalm-param callable(Scope): T $callback * + * @return mixed|void The callback's return value, upon successful execution + * * @psalm-return T */ public function withScope(callable $callback); diff --git a/src/functions.php b/src/functions.php index 573ef7be8..286cab45c 100644 --- a/src/functions.php +++ b/src/functions.php @@ -201,12 +201,12 @@ function configureScope(callable $callback): void * * @param callable $callback The callback to be executed * - * @return mixed|void The callback's return value, upon successful execution - * * @psalm-template T * * @psalm-param callable(Scope): T $callback * + * @return mixed|void The callback's return value, upon successful execution + * * @psalm-return T */ function withScope(callable $callback) From dbdbac4f77b4979e3e4ef1d6d51f4c93eeab1b1d Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 21 Jul 2025 10:14:55 +0200 Subject: [PATCH 1096/1161] =?UTF-8?q?Update=20the=20DSC=E2=80=99s=20sample?= =?UTF-8?q?=5Frate=20if=20a=20traces=5Fsampler=20is=20used=20(#1870)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/State/Hub.php | 7 ++++++- src/Tracing/DynamicSamplingContext.php | 4 ++-- tests/State/HubTest.php | 23 +++++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/State/Hub.php b/src/State/Hub.php index bd51b440b..eeaf7bb28 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -276,7 +276,6 @@ public function startTransaction(TransactionContext $context, array $customSampl if ($tracesSampler !== null) { $sampleRate = $tracesSampler($samplingContext); - $sampleSource = 'config:traces_sampler'; } else { $parentSampleRate = $context->getMetadata()->getParentSamplingRate(); @@ -302,6 +301,12 @@ public function startTransaction(TransactionContext $context, array $customSampl $transaction->getMetadata()->setSamplingRate($sampleRate); + // Always overwrite the sample_rate in the DSC + $dynamicSamplingContext = $context->getMetadata()->getDynamicSamplingContext(); + if ($dynamicSamplingContext !== null) { + $dynamicSamplingContext->set('sample_rate', (string) $sampleRate, true); + } + if ($sampleRate === 0.0) { $transaction->setSampled(false); diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index bcdf6c959..0304504ce 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -40,9 +40,9 @@ private function __construct() * @param string $key the list member key * @param string $value the list member value */ - public function set(string $key, string $value): self + public function set(string $key, string $value, bool $forceOverwrite = false): self { - if ($this->isFrozen) { + if ($this->isFrozen && !$forceOverwrite) { return $this; } diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index a2d014648..bb735cb96 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -20,9 +20,11 @@ use Sentry\Severity; use Sentry\State\Hub; use Sentry\State\Scope; +use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SamplingContext; use Sentry\Tracing\TransactionContext; +use Sentry\Tracing\TransactionMetadata; use Sentry\Util\SentryUid; final class HubTest extends TestCase @@ -805,6 +807,27 @@ public function testStartTransactionWithCustomSamplingContext(): void $hub->startTransaction(new TransactionContext(), $customSamplingContext); } + public function testStartTransactionUpdatesTheDscSampleRate(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sampler' => function (SamplingContext $samplingContext): float { + return 1.0; + }, + ])); + + $hub = new Hub($client); + + $dsc = DynamicSamplingContext::fromHeader('sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public'); + $transactionMetaData = new TransactionMetadata(null, $dsc); + $transactionContext = new TransactionContext(TransactionContext::DEFAULT_NAME, null, $transactionMetaData); + + $transaction = $hub->startTransaction($transactionContext); + $this->assertSame('1', $transaction->getMetadata()->getDynamicSamplingContext()->get('sample_rate')); + } + public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsNotSampled(): void { $client = $this->createMock(ClientInterface::class); From 1cb2d506817eb73aef26001e5737288eae6fb014 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 21 Jul 2025 10:26:45 +0200 Subject: [PATCH 1097/1161] Prepare 4.14.2 (#1871) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35736de6c..d604d9e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 4.14.2 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.14.2. + +### Bug Fixes + +- Add missing sample rates in the envelope header [(#1870)](https://github.com/getsentry/sentry-php/pull/1870) + ## 4.14.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.14.1. From bfeec74303d60d3f8bc33701ab3e86f8a8729f17 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 21 Jul 2025 08:28:29 +0000 Subject: [PATCH 1098/1161] release: 4.14.2 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 296591891..51d87bcb4 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.14.1'; + public const SDK_VERSION = '4.14.2'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 33a7838442d93ebb6bd0f17b64fcc52eb62576c2 Mon Sep 17 00:00:00 2001 From: AlterBrains Date: Mon, 21 Jul 2025 14:08:18 +0300 Subject: [PATCH 1099/1161] Add link to Joomla! integration (#1868) Co-authored-by: Michi Hoffmann --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2372380c3..0b8c54e21 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ The following integrations are available and maintained by members of the Sentry - [Drupal](https://www.drupal.org/project/raven) - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - Magento 2 by [JustBetter](https://github.com/justbetter/magento2-sentry) or by [Mygento](https://github.com/mygento/module-sentry) +- [Joomla!](https://github.com/AlterBrains/sentry-joomla) - ... feel free to be famous, create a port to your favourite platform! ## 3rd party integrations using the old SDK 3.x From 23f25f1d0bfdacc7eed7398d3a7ed9f63c1eb4e3 Mon Sep 17 00:00:00 2001 From: Dieter Beck Date: Tue, 5 Aug 2025 15:08:50 +0200 Subject: [PATCH 1100/1161] Do not call Reflection*::setAccessible() in PHP >= 8.1 (#1872) --- src/ErrorHandler.php | 4 +++- src/functions.php | 2 +- tests/FrameBuilderTest.php | 24 ++++++++++++++++++------ tests/SentrySdkExtension.php | 24 ++++++++++++++++++------ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 0ba6cc32e..ba44df0c6 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -153,7 +153,9 @@ final class ErrorHandler private function __construct() { $this->exceptionReflection = new \ReflectionProperty(\Exception::class, 'trace'); - $this->exceptionReflection->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $this->exceptionReflection->setAccessible(true); + } } /** diff --git a/src/functions.php b/src/functions.php index 286cab45c..23f6eb1e1 100644 --- a/src/functions.php +++ b/src/functions.php @@ -29,7 +29,7 @@ * capture_silenced_errors?: bool, * context_lines?: int|null, * default_integrations?: bool, - * dsn?: string|bool|null|Dsn, + * dsn?: string|bool|Dsn|null, * enable_logs?: bool, * environment?: string|null, * error_types?: int|null, diff --git a/tests/FrameBuilderTest.php b/tests/FrameBuilderTest.php index e102996e8..667b85a18 100644 --- a/tests/FrameBuilderTest.php +++ b/tests/FrameBuilderTest.php @@ -300,12 +300,16 @@ public function testGetFunctionArgumentsWithVariadicParameters(): void $reflectionClass = new \ReflectionClass($frameBuilder); $getFunctionArgumentsMethod = $reflectionClass->getMethod('getFunctionArguments'); - $getFunctionArgumentsMethod->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $getFunctionArgumentsMethod->setAccessible(true); + } $reflectionFunction = new \ReflectionFunction($testFunction); $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); - $getFunctionArgumentValuesMethod->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $getFunctionArgumentValuesMethod->setAccessible(true); + } $result = $getFunctionArgumentValuesMethod->invoke($frameBuilder, $reflectionFunction, $backtraceFrame['args']); @@ -331,7 +335,9 @@ public function testGetFunctionArgumentsWithOnlyVariadicParameters(): void $reflectionClass = new \ReflectionClass($frameBuilder); $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); - $getFunctionArgumentValuesMethod->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $getFunctionArgumentValuesMethod->setAccessible(true); + } $reflectionFunction = new \ReflectionFunction($testFunction); @@ -356,7 +362,9 @@ public function testGetFunctionArgumentsWithEmptyVariadicParameters(): void $reflectionClass = new \ReflectionClass($frameBuilder); $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); - $getFunctionArgumentValuesMethod->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $getFunctionArgumentValuesMethod->setAccessible(true); + } $reflectionFunction = new \ReflectionFunction($testFunction); @@ -383,7 +391,9 @@ public function testGetFunctionArgumentsWithNullValues(): void $reflectionClass = new \ReflectionClass($frameBuilder); $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); - $getFunctionArgumentValuesMethod->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $getFunctionArgumentValuesMethod->setAccessible(true); + } $reflectionFunction = new \ReflectionFunction($testFunction); @@ -418,7 +428,9 @@ public function testGetFunctionArgumentsWithGapsInBacktraceArrayIndices(): void $reflectionClass = new \ReflectionClass($frameBuilder); $getFunctionArgumentValuesMethod = $reflectionClass->getMethod('getFunctionArgumentValues'); - $getFunctionArgumentValuesMethod->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $getFunctionArgumentValuesMethod->setAccessible(true); + } $reflectionFunction = new \ReflectionFunction($testFunction); diff --git a/tests/SentrySdkExtension.php b/tests/SentrySdkExtension.php index ee6bef9dc..8637499ee 100644 --- a/tests/SentrySdkExtension.php +++ b/tests/SentrySdkExtension.php @@ -14,18 +14,30 @@ final class SentrySdkExtension implements BeforeTestHookInterface public function executeBeforeTest(string $test): void { $reflectionProperty = new \ReflectionProperty(SentrySdk::class, 'currentHub'); - $reflectionProperty->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $reflectionProperty->setAccessible(true); + } $reflectionProperty->setValue(null, null); - $reflectionProperty->setAccessible(false); + if (\PHP_VERSION_ID < 80100) { + $reflectionProperty->setAccessible(false); + } $reflectionProperty = new \ReflectionProperty(Scope::class, 'globalEventProcessors'); - $reflectionProperty->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $reflectionProperty->setAccessible(true); + } $reflectionProperty->setValue(null, []); - $reflectionProperty->setAccessible(false); + if (\PHP_VERSION_ID < 80100) { + $reflectionProperty->setAccessible(false); + } $reflectionProperty = new \ReflectionProperty(IntegrationRegistry::class, 'integrations'); - $reflectionProperty->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $reflectionProperty->setAccessible(true); + } $reflectionProperty->setValue(IntegrationRegistry::getInstance(), []); - $reflectionProperty->setAccessible(false); + if (\PHP_VERSION_ID < 80100) { + $reflectionProperty->setAccessible(false); + } } } From db21532fa4493ed3270cdcb689d74f40f58348b1 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 11 Aug 2025 07:05:51 +0200 Subject: [PATCH 1101/1161] Use correct 'sample_rate' key when deriving sampleRand (#1874) --- src/Tracing/PropagationContext.php | 2 +- src/Tracing/TransactionContext.php | 2 +- tests/Tracing/PropagationContextTest.php | 15 +++++++++++++++ tests/Tracing/TransactionContextTest.php | 15 +++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index cb150fe64..363dc71d1 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -231,7 +231,7 @@ private static function parseTraceparentAndBaggage(string $traceparent, string $ $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6); } else { // [rate, 1) - $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample-rate'), 6); + $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6); } } elseif ($context->parentSampled !== null) { // [0, 1) diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index e7774a3f0..95ba78979 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -196,7 +196,7 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6)); } else { // [rate, 1) - $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample-rate'), 6)); + $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6)); } } elseif ($context->parentSampled !== null) { // [0, 1) diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index a80d9d17f..2b6c4600b 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -136,6 +136,21 @@ public function testGetTraceContext() ], $propagationContext->getTraceContext()); } + public function testSampleRandRangeWhenParentNotSampledAndSampleRateProvided(): void + { + $propagationContext = PropagationContext::fromHeaders( + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', + 'sentry-sample_rate=0.4' + ); + + $sampleRand = $propagationContext->getSampleRand(); + + $this->assertNotNull($sampleRand); + // Should be within [rate, 1) and rounded to 6 decimals + $this->assertGreaterThanOrEqual(0.4, $sampleRand); + $this->assertLessThanOrEqual(0.999999, $sampleRand); + } + /** * @dataProvider gettersAndSettersDataProvider */ diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index c4c1176e2..eb5e12cbc 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -137,4 +137,19 @@ public static function tracingDataProvider(): iterable true, ]; } + + public function testSampleRandRangeWhenParentNotSampledAndSampleRateProvided(): void + { + $context = TransactionContext::fromHeaders( + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', + 'sentry-sample_rate=0.4' + ); + + $sampleRand = $context->getMetadata()->getSampleRand(); + + $this->assertNotNull($sampleRand); + // Should be within [rate, 1) and rounded to 6 decimals + $this->assertGreaterThanOrEqual(0.4, $sampleRand); + $this->assertLessThanOrEqual(0.999999, $sampleRand); + } } From aff42b51e02f1b5479b1d0d13201a4563bbbfefd Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Sun, 10 Aug 2025 23:08:10 -0600 Subject: [PATCH 1102/1161] Add Monolog Sentry Logs handler (#1867) Co-authored-by: Michi Hoffmann --- psalm-baseline.xml | 59 +++--- src/Logs/LogLevel.php | 29 ++- src/Monolog/CompatibilityLogLevelTrait.php | 77 ++++++++ src/Monolog/LogsHandler.php | 114 ++++++++++++ tests/Monolog/LogsHandlerTest.php | 200 +++++++++++++++++++++ 5 files changed, 433 insertions(+), 46 deletions(-) create mode 100644 src/Monolog/CompatibilityLogLevelTrait.php create mode 100644 src/Monolog/LogsHandler.php create mode 100644 tests/Monolog/LogsHandlerTest.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 1294a8bc5..8dbbbbe27 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -8,15 +8,6 @@ $parsedDsn['user'] - - - Guzzle6HttpClient - GuzzleHttpClientOptions - GuzzleHttpClientOptions - GuzzleHttpClientOptions - SymfonyHttpClient - - $userIntegration @@ -38,6 +29,14 @@ int|string|Level|LogLevel::* + + + CompatibilityLogLevelTrait + + + Level + + CompatibilityProcessingHandlerTrait @@ -60,6 +59,20 @@ $record['context'] + + + $record['level'] + $record['level'] + $record['message'] + + + $record['context']['exception'] + + + $record['context'] + $record['context'] + + @@ -79,37 +92,9 @@ representationSerialize - - - captureException - captureLastError - captureMessage - - - - - captureException - captureLastError - captureMessage - startTransaction - - - - - new static() - - $transaction - - - captureException - captureLastError - captureMessage - startTransaction - - diff --git a/src/Logs/LogLevel.php b/src/Logs/LogLevel.php index 5b12dc3d0..4e096daa3 100644 --- a/src/Logs/LogLevel.php +++ b/src/Logs/LogLevel.php @@ -14,44 +14,50 @@ class LogLevel */ private $value; + /** + * @var int The priority of the log level, used for sorting + */ + private $priority; + /** * @var array A list of cached enum instances */ private static $instances = []; - private function __construct(string $value) + private function __construct(string $value, int $priority) { $this->value = $value; + $this->priority = $priority; } public static function trace(): self { - return self::getInstance('trace'); + return self::getInstance('trace', 10); } public static function debug(): self { - return self::getInstance('debug'); + return self::getInstance('debug', 20); } public static function info(): self { - return self::getInstance('info'); + return self::getInstance('info', 30); } public static function warn(): self { - return self::getInstance('warn'); + return self::getInstance('warn', 40); } public static function error(): self { - return self::getInstance('error'); + return self::getInstance('error', 50); } public static function fatal(): self { - return self::getInstance('fatal'); + return self::getInstance('fatal', 60); } public function __toString(): string @@ -59,10 +65,15 @@ public function __toString(): string return $this->value; } - private static function getInstance(string $value): self + public function getPriority(): int + { + return $this->priority; + } + + private static function getInstance(string $value, int $priority): self { if (!isset(self::$instances[$value])) { - self::$instances[$value] = new self($value); + self::$instances[$value] = new self($value, $priority); } return self::$instances[$value]; diff --git a/src/Monolog/CompatibilityLogLevelTrait.php b/src/Monolog/CompatibilityLogLevelTrait.php new file mode 100644 index 000000000..39a5b4fd7 --- /dev/null +++ b/src/Monolog/CompatibilityLogLevelTrait.php @@ -0,0 +1,77 @@ += 3) { + /** + * Logic which is used if monolog >= 3 is installed. + * + * @internal + */ + trait CompatibilityLogLevelTrait + { + /** + * Translates the Monolog level into the Sentry LogLevel. + */ + private static function getSentryLogLevelFromMonologLevel(int $level): LogLevel + { + $level = Level::from($level); + + switch ($level) { + case Level::Debug: + return LogLevel::debug(); + case Level::Warning: + return LogLevel::warn(); + case Level::Error: + return LogLevel::error(); + case Level::Critical: + case Level::Alert: + case Level::Emergency: + return LogLevel::fatal(); + case Level::Info: + case Level::Notice: + default: + return LogLevel::info(); + } + } + } +} else { + /** + * Logic which is used if monolog < 3 is installed. + * + * @internal + */ + trait CompatibilityLogLevelTrait + { + /** + * Translates the Monolog level into the Sentry LogLevel. + * + * @param Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY $level The Monolog log level + */ + private static function getSentryLogLevelFromMonologLevel(int $level): LogLevel + { + switch ($level) { + case Logger::DEBUG: + return LogLevel::debug(); + case Logger::WARNING: + return LogLevel::warn(); + case Logger::ERROR: + return LogLevel::error(); + case Logger::CRITICAL: + case Logger::ALERT: + case Logger::EMERGENCY: + return LogLevel::fatal(); + case Logger::INFO: + case Logger::NOTICE: + default: + return LogLevel::info(); + } + } + } +} diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php new file mode 100644 index 000000000..1d47fa90b --- /dev/null +++ b/src/Monolog/LogsHandler.php @@ -0,0 +1,114 @@ +logLevel = $logLevel ?? LogLevel::debug(); + $this->bubble = $bubble; + } + + /** + * @param array|LogRecord $record + */ + public function isHandling($record): bool + { + return self::getSentryLogLevelFromMonologLevel($record['level'])->getPriority() >= $this->logLevel->getPriority(); + } + + /** + * @param array|LogRecord $record + */ + public function handle($record): bool + { + // Do not collect logs for exceptions, they should be handled seperately by the `Handler` or `captureException` + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { + return false; + } + + Logs::getInstance()->aggregator()->add( + self::getSentryLogLevelFromMonologLevel($record['level']), + $record['message'], + [], + array_merge($record['context'], $record['extra']) + ); + + return $this->bubble === false; + } + + /** + * @param array|LogRecord> $records + */ + public function handleBatch(array $records): void + { + foreach ($records as $record) { + $this->handle($record); + } + } + + public function close(): void + { + Logs::getInstance()->flush(); + } + + /** + * @param callable $callback + */ + public function pushProcessor($callback): void + { + // noop, this handler does not support processors + } + + /** + * @return callable + */ + public function popProcessor() + { + // Since we do not support processors, we throw an exception if this method is called + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + public function setFormatter(FormatterInterface $formatter): void + { + // noop, this handler does not support formatters + } + + public function getFormatter(): FormatterInterface + { + // To adhere to the interface we need to return a formatter so we return a default one + return new LineFormatter(); + } +} diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php new file mode 100644 index 000000000..4abda1919 --- /dev/null +++ b/tests/Monolog/LogsHandlerTest.php @@ -0,0 +1,200 @@ + true, + 'before_send' => static function () { + return null; // we don't need to send the event, we are just testing the Monolog handler + }, + ])->getClient(); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + $handler = new LogsHandler(); + $handler->handle($record); + + $logs = Logs::getInstance()->aggregator()->all(); + + // Clear the logs aggregator to avoid side effects in other tests + Logs::getInstance()->aggregator()->flush(); + + $this->assertCount(1, $logs); + + $log = $logs[0]; + + $this->assertEquals($expectedLog->getBody(), $log->getBody()); + $this->assertEquals($expectedLog->getLevel(), $log->getLevel()); + $this->assertEquals( + $expectedLog->attributes()->toSimpleArray(), + array_filter( + $log->attributes()->toSimpleArray(), + static function (string $key) { + // We are not testing Sentry's own attributes here, only the ones the user supplied so filter them out of the expected attributes + return !str_starts_with($key, 'sentry.'); + }, + \ARRAY_FILTER_USE_KEY + ) + ); + } + + public static function handleDataProvider(): iterable + { + yield [ + RecordFactory::create( + 'foo bar', + Logger::DEBUG, + 'channel.foo', + [], + [] + ), + new Log(123, 'abc', LogLevel::debug(), 'foo bar'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::INFO, + 'channel.foo', + [], + [] + ), + new Log(123, 'abc', LogLevel::info(), 'foo bar'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::NOTICE, + 'channel.foo', + [], + [] + ), + new Log(123, 'abc', LogLevel::info(), 'foo bar'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [], + [] + ), + new Log(123, 'abc', LogLevel::warn(), 'foo bar'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::ERROR, + 'channel.foo', + [], + [] + ), + new Log(123, 'abc', LogLevel::error(), 'foo bar'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::CRITICAL, + 'channel.foo', + [], + [] + ), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::ALERT, + 'channel.foo', + [], + [] + ), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::EMERGENCY, + 'channel.foo', + [], + [] + ), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::INFO, + 'channel.foo', + [ + 'foo' => 'bar', + 'bar' => 'baz', + ], + [] + ), + (new Log(123, 'abc', LogLevel::info(), 'foo bar')) + ->setAttribute('foo', 'bar') + ->setAttribute('bar', 'baz'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::INFO, + 'channel.foo', + [], + [ + 'foo' => 'bar', + 'bar' => 'baz', + ] + ), + (new Log(123, 'abc', LogLevel::info(), 'foo bar')) + ->setAttribute('foo', 'bar') + ->setAttribute('bar', 'baz'), + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::INFO, + 'channel.foo', + [ + 'foo' => 'bar', + ], + [ + 'bar' => 'baz', + ] + ), + (new Log(123, 'abc', LogLevel::info(), 'foo bar')) + ->setAttribute('foo', 'bar') + ->setAttribute('bar', 'baz'), + ]; + } +} From a9ada243719987a2e191c84fd892671db01d42d9 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 11 Aug 2025 13:27:55 +0200 Subject: [PATCH 1103/1161] Refactor trace header parsing logic (#1876) --- phpstan-baseline.neon | 5 + src/Tracing/PropagationContext.php | 58 +++------- src/Tracing/Traits/TraceHeaderParserTrait.php | 106 ++++++++++++++++++ src/Tracing/TransactionContext.php | 65 +++-------- 4 files changed, 143 insertions(+), 91 deletions(-) create mode 100644 src/Tracing/Traits/TraceHeaderParserTrait.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5d7f1c733..3ff509ab0 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -310,6 +310,11 @@ parameters: count: 1 path: src/Tracing/GuzzleTracingMiddleware.php + - + message: "#^Property Sentry\\\\Tracing\\\\PropagationContext\\:\\:\\$parentSampled is never read, only written\\.$#" + count: 1 + path: src/Tracing/PropagationContext.php + - message: "#^Method Sentry\\\\Tracing\\\\Span\\:\\:getMetricsSummary\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index 363dc71d1..a4e855176 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -6,10 +6,11 @@ use Sentry\SentrySdk; use Sentry\State\Scope; +use Sentry\Tracing\Traits\TraceHeaderParserTrait; final class PropagationContext { - private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + use TraceHeaderParserTrait; /** * @var TraceId The trace id @@ -183,60 +184,29 @@ public function setSampleRand(?float $sampleRand): self return $this; } - // TODO add same logic as in TransactionContext private static function parseTraceparentAndBaggage(string $traceparent, string $baggage): self { $context = self::fromDefaults(); - $hasSentryTrace = false; + $parsedData = self::parseTraceAndBaggageHeaders($traceparent, $baggage); - if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) { - if (!empty($matches['trace_id'])) { - $context->traceId = new TraceId($matches['trace_id']); - $hasSentryTrace = true; - } - - if (!empty($matches['span_id'])) { - $context->parentSpanId = new SpanId($matches['span_id']); - $hasSentryTrace = true; - } - - if (isset($matches['sampled'])) { - $context->parentSampled = $matches['sampled'] === '1'; - $hasSentryTrace = true; - } + if ($parsedData['traceId'] !== null) { + $context->traceId = $parsedData['traceId']; } - $samplingContext = DynamicSamplingContext::fromHeader($baggage); + if ($parsedData['parentSpanId'] !== null) { + $context->parentSpanId = $parsedData['parentSpanId']; + } - if ($hasSentryTrace && !$samplingContext->hasEntries()) { - // The request comes from an old SDK which does not support Dynamic Sampling. - // Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries. - $samplingContext->freeze(); - $context->dynamicSamplingContext = $samplingContext; + if ($parsedData['parentSampled'] !== null) { + $context->parentSampled = $parsedData['parentSampled']; } - if ($hasSentryTrace && $samplingContext->hasEntries()) { - // The baggage header contains Dynamic Sampling Context data from an upstream SDK. - // Propagate this Dynamic Sampling Context. - $context->dynamicSamplingContext = $samplingContext; + if ($parsedData['dynamicSamplingContext'] !== null) { + $context->dynamicSamplingContext = $parsedData['dynamicSamplingContext']; } - // Store the propagated trace sample rand or generate a new one - if ($samplingContext->has('sample_rand')) { - $context->sampleRand = (float) $samplingContext->get('sample_rand'); - } else { - if ($samplingContext->has('sample_rate') && $context->parentSampled !== null) { - if ($context->parentSampled === true) { - // [0, rate) - $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6); - } else { - // [rate, 1) - $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6); - } - } elseif ($context->parentSampled !== null) { - // [0, 1) - $context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6); - } + if ($parsedData['sampleRand'] !== null) { + $context->sampleRand = $parsedData['sampleRand']; } return $context; diff --git a/src/Tracing/Traits/TraceHeaderParserTrait.php b/src/Tracing/Traits/TraceHeaderParserTrait.php new file mode 100644 index 000000000..56e002d68 --- /dev/null +++ b/src/Tracing/Traits/TraceHeaderParserTrait.php @@ -0,0 +1,106 @@ +[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + + /** + * Parses the sentry-trace and baggage headers and returns the extracted data. + * + * @param string $sentryTrace The sentry-trace header value + * @param string $baggage The baggage header value + * + * @return array{ + * traceId: TraceId|null, + * parentSpanId: SpanId|null, + * parentSampled: bool|null, + * dynamicSamplingContext: DynamicSamplingContext|null, + * sampleRand: float|null, + * parentSamplingRate: float|null + * } + */ + protected static function parseTraceAndBaggageHeaders(string $sentryTrace, string $baggage): array + { + $result = [ + 'traceId' => null, + 'parentSpanId' => null, + 'parentSampled' => null, + 'dynamicSamplingContext' => null, + 'sampleRand' => null, + 'parentSamplingRate' => null, + ]; + + $hasSentryTrace = false; + + if (preg_match(self::$sentryTraceparentHeaderRegex, $sentryTrace, $matches)) { + if (!empty($matches['trace_id'])) { + $result['traceId'] = new TraceId($matches['trace_id']); + $hasSentryTrace = true; + } + + if (!empty($matches['span_id'])) { + $result['parentSpanId'] = new SpanId($matches['span_id']); + $hasSentryTrace = true; + } + + if (isset($matches['sampled'])) { + $result['parentSampled'] = $matches['sampled'] === '1'; + $hasSentryTrace = true; + } + } + + $samplingContext = DynamicSamplingContext::fromHeader($baggage); + + if ($hasSentryTrace && !$samplingContext->hasEntries()) { + // The request comes from an old SDK which does not support Dynamic Sampling. + // Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries. + $samplingContext->freeze(); + $result['dynamicSamplingContext'] = $samplingContext; + } + + if ($hasSentryTrace && $samplingContext->hasEntries()) { + // The baggage header contains Dynamic Sampling Context data from an upstream SDK. + // Propagate this Dynamic Sampling Context. + $result['dynamicSamplingContext'] = $samplingContext; + } + + // Store the propagated traces sample rate + if ($samplingContext->has('sample_rate')) { + $result['parentSamplingRate'] = (float) $samplingContext->get('sample_rate'); + } + + // Store the propagated trace sample rand or generate a new one + if ($samplingContext->has('sample_rand')) { + $result['sampleRand'] = (float) $samplingContext->get('sample_rand'); + } else { + if ($samplingContext->has('sample_rate') && $result['parentSampled'] !== null) { + if ($result['parentSampled'] === true) { + // [0, rate) + $result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6); + } else { + // [rate, 1) + $result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6); + } + } elseif ($result['parentSampled'] !== null) { + // [0, 1) + $result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6); + } + } + + return $result; + } +} diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index 95ba78979..a4c55c04a 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -4,9 +4,11 @@ namespace Sentry\Tracing; +use Sentry\Tracing\Traits\TraceHeaderParserTrait; + final class TransactionContext extends SpanContext { - private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; + use TraceHeaderParserTrait; public const DEFAULT_NAME = ''; @@ -147,61 +149,30 @@ public static function fromHeaders(string $sentryTraceHeader, string $baggageHea private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self { $context = new self(); - $hasSentryTrace = false; - - if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) { - if (!empty($matches['trace_id'])) { - $context->traceId = new TraceId($matches['trace_id']); - $hasSentryTrace = true; - } - - if (!empty($matches['span_id'])) { - $context->parentSpanId = new SpanId($matches['span_id']); - $hasSentryTrace = true; - } - - if (isset($matches['sampled'])) { - $context->parentSampled = $matches['sampled'] === '1'; - $hasSentryTrace = true; - } + $parsedData = self::parseTraceAndBaggageHeaders($sentryTrace, $baggage); + + if ($parsedData['traceId'] !== null) { + $context->traceId = $parsedData['traceId']; } - $samplingContext = DynamicSamplingContext::fromHeader($baggage); + if ($parsedData['parentSpanId'] !== null) { + $context->parentSpanId = $parsedData['parentSpanId']; + } - if ($hasSentryTrace && !$samplingContext->hasEntries()) { - // The request comes from an old SDK which does not support Dynamic Sampling. - // Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries. - $samplingContext->freeze(); - $context->getMetadata()->setDynamicSamplingContext($samplingContext); + if ($parsedData['parentSampled'] !== null) { + $context->parentSampled = $parsedData['parentSampled']; } - if ($hasSentryTrace && $samplingContext->hasEntries()) { - // The baggage header contains Dynamic Sampling Context data from an upstream SDK. - // Propagate this Dynamic Sampling Context. - $context->getMetadata()->setDynamicSamplingContext($samplingContext); + if ($parsedData['dynamicSamplingContext'] !== null) { + $context->getMetadata()->setDynamicSamplingContext($parsedData['dynamicSamplingContext']); } - // Store the propagated traces sample rate - if ($samplingContext->has('sample_rate')) { - $context->getMetadata()->setParentSamplingRate((float) $samplingContext->get('sample_rate')); + if ($parsedData['parentSamplingRate'] !== null) { + $context->getMetadata()->setParentSamplingRate($parsedData['parentSamplingRate']); } - // Store the propagated trace sample rand or generate a new one - if ($samplingContext->has('sample_rand')) { - $context->getMetadata()->setSampleRand((float) $samplingContext->get('sample_rand')); - } else { - if ($samplingContext->has('sample_rate') && $context->parentSampled !== null) { - if ($context->parentSampled === true) { - // [0, rate) - $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6)); - } else { - // [rate, 1) - $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6)); - } - } elseif ($context->parentSampled !== null) { - // [0, 1) - $context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6)); - } + if ($parsedData['sampleRand'] !== null) { + $context->getMetadata()->setSampleRand($parsedData['sampleRand']); } return $context; From 0bc8a0592a57c1ab47ce70909860fb9eceeeee35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:04:51 +0200 Subject: [PATCH 1104/1161] chore(deps): bump actions/checkout from 4 to 5 (#1878) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/publish-release.yaml | 2 +- .github/workflows/static-analysis.yaml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f95de17b..8bc0b3293 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 2 diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 5488ac5b0..9dfdd7484 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -29,7 +29,7 @@ jobs: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: token: ${{ steps.token.outputs.token }} fetch-depth: 0 diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 69de38c90..8be436451 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -52,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 2 # needed by codecov sometimes From 458c0cfe93c4e316fa8702046998310db0c548e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:04:58 +0200 Subject: [PATCH 1105/1161] chore(deps): bump actions/create-github-app-token from 2.0.6 to 2.1.1 (#1879) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 9dfdd7484..207c12f94 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From e2e6cea3b52690bd91ebaa431f4b806b5a749d0d Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 20 Aug 2025 08:18:18 -0600 Subject: [PATCH 1106/1161] Fix non string indexed attributes passed as log attributes (#1882) --- .php-cs-fixer.dist.php | 1 + src/Logs/LogsAggregator.php | 16 +++++++- tests/Logs/LogsAggregatorTest.php | 62 +++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 6a94c4c5c..9cf23f912 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -34,6 +34,7 @@ 'after_heredoc' => false, 'elements' => ['arrays'], ], + 'no_whitespace_before_comma_in_array' => false, // Should be dropped when we drop support for PHP 7.x ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index e4587b124..ba1cc68b3 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -108,15 +108,27 @@ public function add( foreach ($attributes as $key => $value) { $attribute = Attribute::tryFromValue($value); + if (!\is_string($key)) { + if ($sdkLogger !== null) { + $sdkLogger->info( + \sprintf("Dropping log attribute with non-string key '%s' and value of type '%s'.", $key, \gettype($value)) + ); + } + + continue; + } + if ($attribute === null) { if ($sdkLogger !== null) { $sdkLogger->info( \sprintf("Dropping log attribute {$key} with value of type '%s' because it is not serializable or an unsupported type.", \gettype($value)) ); } - } else { - $log->setAttribute($key, $attribute); + + continue; } + + $log->setAttribute($key, $attribute); } $log = ($options->getBeforeSendLogCallback())($log); diff --git a/tests/Logs/LogsAggregatorTest.php b/tests/Logs/LogsAggregatorTest.php index 7e5e38b98..d60aaf553 100644 --- a/tests/Logs/LogsAggregatorTest.php +++ b/tests/Logs/LogsAggregatorTest.php @@ -20,6 +20,68 @@ final class LogsAggregatorTest extends TestCase { + /** + * This test is kept simple to ensure the `LogAggregator` is able to handle attributes passed in different formats. + * + * Extensive testing of attributes is done in the `Attributes/*` test classes. + * + * @dataProvider attributesDataProvider + */ + public function testAttributes(array $attributes, array $expected): void + { + $client = ClientBuilder::create([ + 'enable_logs' => true, + ])->getClient(); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + $aggregator = new LogsAggregator(); + + $aggregator->add(LogLevel::info(), 'Test message', [], $attributes); + + $logs = $aggregator->all(); + + $this->assertCount(1, $logs); + + $log = $logs[0]; + + $this->assertSame( + $expected, + array_filter( + $log->attributes()->toSimpleArray(), + static function (string $key) { + // We are not testing internal Sentry attributes here, only the ones the user supplied + return !str_starts_with($key, 'sentry.'); + }, + \ARRAY_FILTER_USE_KEY + ) + ); + } + + public static function attributesDataProvider(): \Generator + { + yield [ + [], + [], + ]; + + yield [ + ['foo', 'bar'], + [], + ]; + + yield [ + ['foo' => 'bar'], + ['foo' => 'bar'], + ]; + + yield [ + ['foo' => ['bar']], + [], + ]; + } + /** * @dataProvider messageFormattingDataProvider */ From 5669037f7768c72048cb99980207e46b7a5d27bf Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Wed, 20 Aug 2025 16:23:44 +0200 Subject: [PATCH 1107/1161] Prepare 4.15.0 (#1883) --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d604d9e5d..8c8efe226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # CHANGELOG +## 4.15.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.15.0. + +### Features + +- Add Monolog Sentry Logs handler [(#1867)](https://github.com/getsentry/sentry-php/pull/1867) + + This new handler allows you to capture Monolog logs as Sentry logs. To use it, configure your Monolog logger: + + ```php + use Monolog\Logger; + use Sentry\Monolog\LogsHandler; + use Sentry\Logs\LogLevel; + + // Initialize Sentry SDK first (make sure 'enable_logs' is set to true) + \Sentry\init([ + 'dsn' => '__YOUR_DSN__', + 'enable_logs' => true, + ]); + + // Create a Monolog logger + $logger = new Logger('my-app'); + + // Add the Sentry logs handler + // Optional: specify minimum log level (defaults to LogLevel::debug()) + $handler = new LogsHandler(LogLevel::info()); + $logger->pushHandler($handler); + + // Now your logs will be sent to Sentry + $logger->info('User logged in', ['user_id' => 123]); + $logger->error('Payment failed', ['order_id' => 456]); + ``` + + Note: The handler will not collect logs for exceptions (they should be handled separately via `captureException`). + +### Bug Fixes + +- Fix non string indexed attributes passed as log attributes [(#1882)](https://github.com/getsentry/sentry-php/pull/1882) +- Use correct `sample_rate` key when deriving sampleRand [(#1874)](https://github.com/getsentry/sentry-php/pull/1874) +- Do not call `Reflection*::setAccessible()` in PHP >= 8.1 [(#1872)](https://github.com/getsentry/sentry-php/pull/1872) + ## 4.14.2 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.14.2. From b2d84de69f3eda8ca22b0b00e9f923be3b837355 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 20 Aug 2025 14:26:37 +0000 Subject: [PATCH 1108/1161] release: 4.15.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 51d87bcb4..6a8c0e999 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.14.2'; + public const SDK_VERSION = '4.15.0'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 9113a1bb4ed4ae29880c00acb6b8be9fd83ba3ae Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 28 Aug 2025 09:37:50 -0600 Subject: [PATCH 1109/1161] Do not send template attribute with logs when there are no template values (#1885) --- src/Logs/LogsAggregator.php | 9 ++++++--- tests/Logs/LogsAggregatorTest.php | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index ba1cc68b3..70b371504 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -76,7 +76,6 @@ public function add( ->setAttribute('sentry.release', $options->getRelease()) ->setAttribute('sentry.environment', $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT) ->setAttribute('sentry.server.address', $options->getServerName()) - ->setAttribute('sentry.message.template', $message) ->setAttribute('sentry.trace.parent_span_id', $hub->getSpan() ? $hub->getSpan()->getSpanId() : null); if ($client instanceof Client) { @@ -99,8 +98,12 @@ public function add( } }); - foreach ($values as $key => $value) { - $log->setAttribute("sentry.message.parameter.{$key}", $value); + if (\count($values)) { + $log->setAttribute('sentry.message.template', $message); + + foreach ($values as $key => $value) { + $log->setAttribute("sentry.message.parameter.{$key}", $value); + } } $attributes = Arr::simpleDot($attributes); diff --git a/tests/Logs/LogsAggregatorTest.php b/tests/Logs/LogsAggregatorTest.php index d60aaf553..377b9deff 100644 --- a/tests/Logs/LogsAggregatorTest.php +++ b/tests/Logs/LogsAggregatorTest.php @@ -105,6 +105,12 @@ public function testMessageFormatting(string $message, array $values, string $ex $log = $logs[0]; $this->assertSame($expected, $log->getBody()); + + if (\count($values)) { + $this->assertNotNull($log->attributes()->get('sentry.message.template')); + } else { + $this->assertNull($log->attributes()->get('sentry.message.template')); + } } public static function messageFormattingDataProvider(): \Generator From 32b97203beb56cb29325e975dc9a8893e5b554fd Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 28 Aug 2025 17:44:32 +0200 Subject: [PATCH 1110/1161] Prepare 4.15.1 (#1886) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c8efe226..d9d88599c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 4.15.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.15.1. + +### Bug Fixes + +- Do not send `template` attribute with logs when there are no template values [(#1885)](https://github.com/getsentry/sentry-php/pull/1885) + ## 4.15.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.15.0. From 0d09baf3700869ec4b723c95eb466de56c3d74b6 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 28 Aug 2025 15:45:14 +0000 Subject: [PATCH 1111/1161] release: 4.15.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 6a8c0e999..fb08b44f0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.15.0'; + public const SDK_VERSION = '4.15.1'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 042560059870dea42d2dd87e94a24d545ea267cb Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Tue, 2 Sep 2025 12:06:46 +0200 Subject: [PATCH 1112/1161] fix(logs): check if record should be handled within handle method (#1888) --- src/Monolog/LogsHandler.php | 3 + tests/Monolog/LogsHandlerTest.php | 110 +++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 1d47fa90b..08fc55e74 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -54,6 +54,9 @@ public function isHandling($record): bool */ public function handle($record): bool { + if (!$this->isHandling($record)) { + return false; + } // Do not collect logs for exceptions, they should be handled seperately by the `Handler` or `captureException` if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { return false; diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 4abda1919..9500fee59 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -16,6 +16,11 @@ final class LogsHandlerTest extends TestCase { + protected function setUp(): void + { + Logs::getInstance()->flush(); + } + /** * @dataProvider handleDataProvider */ @@ -36,9 +41,6 @@ public function testHandle($record, Log $expectedLog): void $logs = Logs::getInstance()->aggregator()->all(); - // Clear the logs aggregator to avoid side effects in other tests - Logs::getInstance()->aggregator()->flush(); - $this->assertCount(1, $logs); $log = $logs[0]; @@ -58,6 +60,28 @@ static function (string $key) { ); } + /** + * @dataProvider logLevelDataProvider + */ + public function testLogLevels($record, int $countLogs): void + { + $client = ClientBuilder::create([ + 'enable_logs' => true, + 'before_send' => static function () { + return null; + }, + ])->getClient(); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + $handler = new LogsHandler(LogLevel::warn()); + $handler->handle($record); + + $logs = Logs::getInstance()->aggregator()->all(); + $this->assertCount($countLogs, $logs); + } + public static function handleDataProvider(): iterable { yield [ @@ -197,4 +221,84 @@ public static function handleDataProvider(): iterable ->setAttribute('bar', 'baz'), ]; } + + public static function logLevelDataProvider(): iterable + { + yield [ + RecordFactory::create( + 'foo bar', + Logger::DEBUG, + 'channel.foo', + [], + [] + ), + 0, + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::NOTICE, + 'channel.foo', + [], + [] + ), + 0, + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::INFO, + 'channel.foo', + [], + [] + ), + 0, + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::WARNING, + 'channel.foo', + [], + [] + ), + 1, + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::CRITICAL, + 'channel.foo', + [], + [] + ), + 1, + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::ALERT, + 'channel.foo', + [], + [] + ), + 1, + ]; + + yield [ + RecordFactory::create( + 'foo bar', + Logger::EMERGENCY, + 'channel.foo', + [], + [] + ), + 1, + ]; + } } From ea80724327632ae81420a60c372ad00c3b6dca34 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Wed, 3 Sep 2025 09:21:32 +0200 Subject: [PATCH 1113/1161] Prepare 4.15.2 (#1889) Co-authored-by: Michi Hoffmann --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9d88599c..62db5cf85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 4.15.2 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.15.2. + +### Bug Fixes + +- Ensure the Monolog handler only processes records permitted by their log level. [(#1888)](https://github.com/getsentry/sentry-php/pull/1888) + ## 4.15.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.15.1. From 61a2d918e8424b6de4a2e265c15133a00c17db51 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 3 Sep 2025 07:23:48 +0000 Subject: [PATCH 1114/1161] release: 4.15.2 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index fb08b44f0..3ca82713e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.15.1'; + public const SDK_VERSION = '4.15.2'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 03b51d123502e7c29df14c3eb03c08ca67f3ac81 Mon Sep 17 00:00:00 2001 From: Felix Bernhard Date: Fri, 12 Sep 2025 12:06:36 +0200 Subject: [PATCH 1115/1161] Remove `max_breadcrumbs` limit (#1890) --- src/Options.php | 4 ++-- tests/OptionsTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Options.php b/src/Options.php index ea5e04787..28093ed6e 100644 --- a/src/Options.php +++ b/src/Options.php @@ -1457,13 +1457,13 @@ private function validateDsnOption($dsn): bool } /** - * Validates if the value of the max_breadcrumbs option is in range. + * Validates if the value of the max_breadcrumbs option is valid. * * @param int $value The value to validate */ private function validateMaxBreadcrumbsOptions(int $value): bool { - return $value >= 0 && $value <= self::DEFAULT_MAX_BREADCRUMBS; + return $value >= 0; } /** diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 4192781a0..4bc7c7fa4 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -586,7 +586,7 @@ public static function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): a [true, 0], [true, 1], [true, Options::DEFAULT_MAX_BREADCRUMBS], - [false, Options::DEFAULT_MAX_BREADCRUMBS + 1], + [true, Options::DEFAULT_MAX_BREADCRUMBS + 1], [false, 'string'], [false, '1'], ]; From 3155eeb2ddd156c92d5139e05b0096787b2ddc69 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 15 Sep 2025 11:04:32 +0200 Subject: [PATCH 1116/1161] CI cleanup (#1901) --- .github/workflows/ci.yml | 2 +- .github/workflows/static-analysis.yaml | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8bc0b3293..6dee415f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,4 +94,4 @@ jobs: - name: Check benchmarks run: vendor/bin/phpbench run --revs=1 --iterations=1 - if: ${{ matrix.dependencies == 'highest' && matrix.php.version == '8.3' }} + if: ${{ matrix.dependencies == 'highest' && matrix.php.version == '8.4' }} diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 8be436451..d9e875a52 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -5,7 +5,7 @@ on: push: branches: - master - - develop + - release/** permissions: contents: read @@ -21,7 +21,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist @@ -39,7 +39,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist @@ -53,8 +53,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v5 - with: - fetch-depth: 2 # needed by codecov sometimes - name: Setup PHP uses: shivammathur/setup-php@v2 From 80692c6b0632b0e940b0fac810fdd9691a4173e1 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 15 Sep 2025 11:06:53 +0200 Subject: [PATCH 1117/1161] ref: remove @internal annotation from Result (#1904) Co-authored-by: Michi Hoffmann --- src/Transport/Result.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Transport/Result.php b/src/Transport/Result.php index a4a6b8238..7d5061bc4 100644 --- a/src/Transport/Result.php +++ b/src/Transport/Result.php @@ -9,8 +9,6 @@ /** * This class contains the details of the sending operation of an event, e.g. * if it was sent successfully or if it was skipped because of some reason. - * - * @internal */ class Result { From bb7320c32f7c27a12e3c22f10f32af08ebb681b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:28:38 +0200 Subject: [PATCH 1118/1161] chore(deps): bump actions/create-github-app-token from 2.1.1 to 2.1.4 (#1908) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 207c12f94..8fee24055 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From ab01c8858dc8cf43f47cafd017e131ca7aba48a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= <114897+dingsdax@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:46:12 +0200 Subject: [PATCH 1119/1161] chore: add pull request template (#1909) --- .github/pull_request_template.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..46b00071e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +### Description + + +#### Issues + + +#### Reminders +- Add GH Issue ID _&_ Linear ID +- PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `chore:`) +- For external contributors: [CONTRIBUTING.md](../CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr) \ No newline at end of file From c89592c9d9e9d6b00d4da217ea2db404fd23164d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= <114897+dingsdax@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:56:41 +0200 Subject: [PATCH 1120/1161] fix: Fix link to CONTRIBUTING.md in PR template (#1910) --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 46b00071e..4c1a73e6c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,5 +9,5 @@ #### Reminders - Add GH Issue ID _&_ Linear ID -- PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `chore:`) -- For external contributors: [CONTRIBUTING.md](../CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr) \ No newline at end of file +- PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `meta:`) +- For external contributors: [CONTRIBUTING.md](https://github.com/getsentry/sentry-php/blob/master/CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr) \ No newline at end of file From e26db2e5edb952a16e11ce1d59003e7eddfefde5 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 22 Sep 2025 15:13:12 +0200 Subject: [PATCH 1121/1161] ref(logs): implement __destruct to make sure logs are always flushed (#1916) --- src/Monolog/LogsHandler.php | 9 ++++++ tests/Monolog/LogsHandlerTest.php | 49 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 08fc55e74..134c67d19 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -114,4 +114,13 @@ public function getFormatter(): FormatterInterface // To adhere to the interface we need to return a formatter so we return a default one return new LineFormatter(); } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // Just in case so that the destructor can never fail. + } + } } diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 9500fee59..33d7091a9 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -7,12 +7,16 @@ use Monolog\Logger; use PHPUnit\Framework\TestCase; use Sentry\ClientBuilder; +use Sentry\Event; use Sentry\Logs\Log; use Sentry\Logs\LogLevel; use Sentry\Logs\Logs; use Sentry\Monolog\LogsHandler; use Sentry\SentrySdk; use Sentry\State\Hub; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; +use Sentry\Transport\TransportInterface; final class LogsHandlerTest extends TestCase { @@ -82,6 +86,51 @@ public function testLogLevels($record, int $countLogs): void $this->assertCount($countLogs, $logs); } + public function testLogsHandlerDestructor() + { + $transport = new class implements TransportInterface { + private $events = []; + + public function send(Event $event): Result + { + $this->events[] = $event; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); + } + + /** + * @return Event[] + */ + public function getEvents(): array + { + return $this->events; + } + }; + $client = ClientBuilder::create([ + 'enable_logs' => true, + ])->setTransport($transport) + ->getClient(); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); + + $this->handleLogAndDrop(); + + $this->assertCount(1, $transport->getEvents()); + $this->assertSame('I was dropped :(', $transport->getEvents()[0]->getLogs()[0]->getBody()); + } + + private function handleLogAndDrop(): void + { + $handler = new LogsHandler(); + $handler->handle(RecordFactory::create('I was dropped :(', Logger::INFO, 'chanel.foo', [], [])); + } + public static function handleDataProvider(): iterable { yield [ From ac9168772b3032829137fdf05fc01350a1c3ef8b Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 22 Sep 2025 15:13:45 +0200 Subject: [PATCH 1122/1161] fix(logs): use PSR log level when using PSR-3 logger (#1907) Co-authored-by: Michi Hoffmann --- src/Logs/Log.php | 5 +++++ src/Logs/LogLevel.php | 18 ++++++++++++++++++ src/Logs/LogsAggregator.php | 3 +-- tests/Logs/LogTest.php | 27 +++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Logs/Log.php b/src/Logs/Log.php index 7c5b5b23b..318f70974 100644 --- a/src/Logs/Log.php +++ b/src/Logs/Log.php @@ -82,6 +82,11 @@ public function setLevel(LogLevel $level): self return $this; } + public function getPsrLevel(): string + { + return $this->level->toPsrLevel(); + } + public function getBody(): string { return $this->body; diff --git a/src/Logs/LogLevel.php b/src/Logs/LogLevel.php index 4e096daa3..2ed049f71 100644 --- a/src/Logs/LogLevel.php +++ b/src/Logs/LogLevel.php @@ -70,6 +70,24 @@ public function getPriority(): int return $this->priority; } + public function toPsrLevel(): string + { + switch ($this->value) { + case 'trace': + case 'debug': + return \Psr\Log\LogLevel::DEBUG; + case 'warn': + return \Psr\Log\LogLevel::WARNING; + case 'error': + return \Psr\Log\LogLevel::ERROR; + case 'fatal': + return \Psr\Log\LogLevel::CRITICAL; + case 'info': + default: + return \Psr\Log\LogLevel::INFO; + } + } + private static function getInstance(string $value, int $priority): self { if (!isset(self::$instances[$value])) { diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index 70b371504..d17b04939 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -147,9 +147,8 @@ public function add( return; } - // We check if it's a `LogsLogger` to avoid a infinite loop where the logger is logging the logs it's writing if ($sdkLogger !== null) { - $sdkLogger->log((string) $log->getLevel(), "Logs item: {$log->getBody()}", $log->attributes()->toSimpleArray()); + $sdkLogger->log($log->getPsrLevel(), "Logs item: {$log->getBody()}", $log->attributes()->toSimpleArray()); } $this->logs[] = $log; diff --git a/tests/Logs/LogTest.php b/tests/Logs/LogTest.php index 1cfba9c83..33d013dab 100644 --- a/tests/Logs/LogTest.php +++ b/tests/Logs/LogTest.php @@ -32,4 +32,31 @@ public function testGettersAndSetters(): void $log->setBody('bar'); $this->assertSame('bar', $log->getBody()); } + + /** + * @dataProvider logLevelDataProvider + */ + public function testLogLevelToPsrMapping(LogLevel $logLevel, $expected): void + { + $this->assertSame($expected, $logLevel->toPsrLevel()); + } + + /** + * @dataProvider logLevelDataProvider + */ + public function testLogAndLogLevelConsistent(LogLevel $level, $expected): void + { + $log = new Log(1.0, '123', $level, 'foo'); + $this->assertSame($expected, $log->getPsrLevel()); + } + + public function logLevelDataProvider(): \Generator + { + yield 'Debug -> Debug' => [LogLevel::debug(), \Psr\Log\LogLevel::DEBUG]; + yield 'Trace -> Debug' => [LogLevel::trace(), \Psr\Log\LogLevel::DEBUG]; + yield 'Info -> Info' => [LogLevel::info(), \Psr\Log\LogLevel::INFO]; + yield 'Warn -> Warning' => [LogLevel::warn(), \Psr\Log\LogLevel::WARNING]; + yield 'Error -> Error' => [LogLevel::error(), \Psr\Log\LogLevel::ERROR]; + yield 'Fatal -> Critical' => [LogLevel::fatal(), \Psr\Log\LogLevel::CRITICAL]; + } } From 115d1fc5eda55c4399357baef41b8a11ac931b81 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 22 Sep 2025 15:19:43 +0200 Subject: [PATCH 1123/1161] ref(logs): add sentry.origin attribute to monolog handler (#1917) --- src/Monolog/LogsHandler.php | 2 +- tests/Monolog/LogsHandlerTest.php | 46 +++++++++++++++++++------------ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 134c67d19..6d6842edd 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -66,7 +66,7 @@ public function handle($record): bool self::getSentryLogLevelFromMonologLevel($record['level']), $record['message'], [], - array_merge($record['context'], $record['extra']) + array_merge($record['context'], $record['extra'], ['sentry.origin' => 'auto.logger.monolog']) ); return $this->bubble === false; diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 33d7091a9..20d69ec9a 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -23,13 +23,6 @@ final class LogsHandlerTest extends TestCase protected function setUp(): void { Logs::getInstance()->flush(); - } - - /** - * @dataProvider handleDataProvider - */ - public function testHandle($record, Log $expectedLog): void - { $client = ClientBuilder::create([ 'enable_logs' => true, 'before_send' => static function () { @@ -39,7 +32,13 @@ public function testHandle($record, Log $expectedLog): void $hub = new Hub($client); SentrySdk::setCurrentHub($hub); + } + /** + * @dataProvider handleDataProvider + */ + public function testHandle($record, Log $expectedLog): void + { $handler = new LogsHandler(); $handler->handle($record); @@ -69,16 +68,6 @@ static function (string $key) { */ public function testLogLevels($record, int $countLogs): void { - $client = ClientBuilder::create([ - 'enable_logs' => true, - 'before_send' => static function () { - return null; - }, - ])->getClient(); - - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); - $handler = new LogsHandler(LogLevel::warn()); $handler->handle($record); @@ -131,6 +120,29 @@ private function handleLogAndDrop(): void $handler->handle(RecordFactory::create('I was dropped :(', Logger::INFO, 'chanel.foo', [], [])); } + public function testOriginTagAppliedWithHandler(): void + { + $handler = new LogsHandler(LogLevel::warn()); + $handler->handle(RecordFactory::create('with origin', Logger::WARNING, 'channel.foo', [], [])); + + $logs = Logs::getInstance()->aggregator()->all(); + $this->assertCount(1, $logs); + $log = $logs[0]; + $this->assertArrayHasKey('sentry.origin', $log->attributes()->toSimpleArray()); + $this->assertSame('auto.logger.monolog', $log->attributes()->toSimpleArray()['sentry.origin']); + } + + public function testOriginTagNotAppliedWhenUsingDirectly() + { + \Sentry\logger()->info('No origin attribute'); + + $logs = Logs::getInstance()->aggregator()->all(); + $this->assertCount(1, $logs); + $log = $logs[0]; + $this->assertSame('No origin attribute', $log->getBody()); + $this->assertArrayNotHasKey('sentry.origin', $log->attributes()->toSimpleArray()); + } + public static function handleDataProvider(): iterable { yield [ From 1599595af68f64866eb82bc1f7fb579474664b60 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 22 Sep 2025 15:36:16 +0200 Subject: [PATCH 1124/1161] Prepare 4.16.0 (#1920) --- CHANGELOG.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62db5cf85..7710789d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # CHANGELOG +## 4.16.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.16.0. + +### Features + +- Remove `max_breadcrumbs` limit. [(#1890)](https://github.com/getsentry/sentry-php/pull/1890) +- Implement `__destruct` in `LogsHandler` to make sure logs are always flushed. [(#1916)](https://github.com/getsentry/sentry-php/pull/1916) + +### Bug Fixes + +- Use PSR log level when logging messages using the PSR-3 logger within the SDK. [(#1907)](https://github.com/getsentry/sentry-php/pull/1907) +- Remove `@internal` annotation from `Sentry\Transport\Result`. [(#1904)](https://github.com/getsentry/sentry-php/pull/1904) + +### Misc + +- Add `sentry.origin` attribute to `LogsHandler`. [(#1917)](https://github.com/getsentry/sentry-php/pull/1917) + ## 4.15.2 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.15.2. @@ -30,26 +48,26 @@ The Sentry SDK team is happy to announce the immediate availability of Sentry PH use Monolog\Logger; use Sentry\Monolog\LogsHandler; use Sentry\Logs\LogLevel; - + // Initialize Sentry SDK first (make sure 'enable_logs' is set to true) \Sentry\init([ 'dsn' => '__YOUR_DSN__', 'enable_logs' => true, ]); - + // Create a Monolog logger $logger = new Logger('my-app'); - + // Add the Sentry logs handler // Optional: specify minimum log level (defaults to LogLevel::debug()) $handler = new LogsHandler(LogLevel::info()); $logger->pushHandler($handler); - + // Now your logs will be sent to Sentry $logger->info('User logged in', ['user_id' => 123]); $logger->error('Payment failed', ['order_id' => 456]); ``` - + Note: The handler will not collect logs for exceptions (they should be handled separately via `captureException`). ### Bug Fixes From c5b086e4235762da175034bc463b0d31cbb38d2e Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 22 Sep 2025 13:38:03 +0000 Subject: [PATCH 1125/1161] release: 4.16.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 3ca82713e..1c51e6de2 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.15.2'; + public const SDK_VERSION = '4.16.0'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 1895ab6525292cc75fefbb37680f5b693e5af49a Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Mon, 29 Sep 2025 11:04:43 +0200 Subject: [PATCH 1126/1161] meta: Pin symfony/phpunit-bridge (#1928) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0be4a6531..d8e863cc8 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^8.5|^9.6", - "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", + "symfony/phpunit-bridge": "^5.2|6.4.25|7.3.3", "vimeo/psalm": "^4.17" }, "suggest": { From bc33d2b81d81de4de0e8016466a94e7e26919809 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Tue, 30 Sep 2025 00:30:34 +0200 Subject: [PATCH 1127/1161] ref: remove phpunit bridge (#1930) --- composer.json | 1 - phpstan.neon | 1 + phpunit.xml.dist | 4 - psalm.xml.dist | 1 + src/Util/ClockMock.php | 202 +++++++++++++++++++++ tests/BreadcrumbTest.php | 2 +- tests/ClientTest.php | 3 - tests/DsnTest.php | 3 - tests/Metrics/MetricsTest.php | 2 +- tests/OptionsTest.php | 3 - tests/Serializer/PayloadSerializerTest.php | 2 +- tests/Tracing/SpanTest.php | 2 +- tests/Tracing/TransactionContextTest.php | 3 - tests/Tracing/TransactionTest.php | 2 +- tests/Transport/HttpTransportTest.php | 2 +- tests/Transport/RateLimiterTest.php | 2 +- tests/bootstrap.php | 3 +- 17 files changed, 213 insertions(+), 25 deletions(-) create mode 100644 src/Util/ClockMock.php diff --git a/composer.json b/composer.json index d8e863cc8..40e58869b 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,6 @@ "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^8.5|^9.6", - "symfony/phpunit-bridge": "^5.2|6.4.25|7.3.3", "vimeo/psalm": "^4.17" }, "suggest": { diff --git a/phpstan.neon b/phpstan.neon index 91af9c48e..61e18b96c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,6 +10,7 @@ parameters: excludePaths: - tests/resources - tests/Fixtures + - src/Util/ClockMock.php dynamicConstantNames: - Monolog\Logger::API bootstrapFiles: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d064f8775..8d5384d66 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -34,10 +34,6 @@ - - - - diff --git a/psalm.xml.dist b/psalm.xml.dist index ea60216a6..502f940b3 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -10,6 +10,7 @@ + diff --git a/src/Util/ClockMock.php b/src/Util/ClockMock.php new file mode 100644 index 000000000..ea3f18f18 --- /dev/null +++ b/src/Util/ClockMock.php @@ -0,0 +1,202 @@ + + * @author Dominic Tubach + */ +class ClockMock +{ + private static $now; + + public static function withClockMock($enable = null): ?bool + { + if ($enable === null) { + return self::$now !== null; + } + + self::$now = is_numeric($enable) ? (float) $enable : ($enable ? microtime(true) : null); + + return null; + } + + public static function time(): int + { + if (self::$now === null) { + return time(); + } + + return (int) self::$now; + } + + public static function sleep($s): int + { + if (self::$now === null) { + return sleep($s); + } + + self::$now += (int) $s; + + return 0; + } + + public static function usleep($us): void + { + if (self::$now === null) { + usleep($us); + } else { + self::$now += $us / 1000000; + } + } + + /** + * @return string|float + */ + public static function microtime($asFloat = false) + { + if (self::$now === null) { + return microtime($asFloat); + } + + if ($asFloat) { + return self::$now; + } + + return \sprintf('%0.6f00 %d', self::$now - (int) self::$now, (int) self::$now); + } + + public static function date($format, $timestamp = null): string + { + if ($timestamp === null) { + $timestamp = self::time(); + } + + return date($format, $timestamp); + } + + public static function gmdate($format, $timestamp = null): string + { + if ($timestamp === null) { + $timestamp = self::time(); + } + + return gmdate($format, $timestamp); + } + + /** + * @return array|int|float + */ + public static function hrtime($asNumber = false) + { + $ns = (self::$now - (int) self::$now) * 1000000000; + + if ($asNumber) { + $number = \sprintf('%d%d', (int) self::$now, $ns); + + return \PHP_INT_SIZE === 8 ? (int) $number : (float) $number; + } + + return [(int) self::$now, (int) $ns]; + } + + /** + * @return false|int + */ + public static function strtotime(string $datetime, ?int $timestamp = null) + { + if ($timestamp === null) { + $timestamp = self::time(); + } + + return strtotime($datetime, $timestamp); + } + + public static function register($class): void + { + $self = static::class; + + $mockedNs = [substr($class, 0, strrpos($class, '\\'))]; + if (strpos($class, '\\Tests\\') > 0) { + $ns = str_replace('\\Tests\\', '\\', $class); + $mockedNs[] = substr($ns, 0, strrpos($ns, '\\')); + } elseif (str_starts_with($class, 'Tests\\')) { + $mockedNs[] = substr($class, 6, strrpos($class, '\\') - 6); + } + foreach ($mockedNs as $ns) { + if (\function_exists($ns . '\time')) { + continue; + } + eval(<< Date: Mon, 13 Oct 2025 09:47:56 +0200 Subject: [PATCH 1128/1161] ref: replace str_starts_with with strpos in ClockMock (#1936) --- src/Util/ClockMock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/ClockMock.php b/src/Util/ClockMock.php index ea3f18f18..004291d39 100644 --- a/src/Util/ClockMock.php +++ b/src/Util/ClockMock.php @@ -146,7 +146,7 @@ public static function register($class): void if (strpos($class, '\\Tests\\') > 0) { $ns = str_replace('\\Tests\\', '\\', $class); $mockedNs[] = substr($ns, 0, strrpos($ns, '\\')); - } elseif (str_starts_with($class, 'Tests\\')) { + } elseif (strpos($class, 'Tests\\') === 0) { $mockedNs[] = substr($class, 6, strrpos($class, '\\') - 6); } foreach ($mockedNs as $ns) { From 7ed64c110d0314a01aea92769bca19fc022f4aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Chav=C3=A9e?= Date: Mon, 20 Oct 2025 14:40:50 +0200 Subject: [PATCH 1129/1161] fix: displaying "encoding error" when serializing empty strings (#1940) --- src/Serializer/AbstractSerializer.php | 4 ++++ tests/Serializer/SerializerTest.php | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 42946bf8d..9e6312325 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -216,6 +216,10 @@ protected function serializeObject($object, int $_depth = 0, array $hashes = []) */ protected function serializeString(string $value): string { + if ($value === '') { + return ''; + } + // we always guarantee this is coerced, even if we can't detect encoding if ($currentEncoding = mb_detect_encoding($value, $this->mbDetectOrder)) { $encoded = mb_convert_encoding($value, 'UTF-8', $currentEncoding) ?: ''; diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index d402f210f..3f0662027 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -225,6 +225,15 @@ public function testClippingUTF8Characters(): void $this->assertSame(\JSON_ERROR_NONE, json_last_error()); } + public function testEmptyString(): void + { + $serializer = $this->createSerializer(); + + $result = $this->invokeSerialization($serializer, ''); + + $this->assertSame('', $result); + } + /** * @return Serializer */ From 41b15c9c58f7eec9dde42f0c5dd1f30c49e42a76 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 20 Oct 2025 14:44:14 +0200 Subject: [PATCH 1130/1161] fix(logs): `sentry.origin` value (#1938) --- src/Monolog/LogsHandler.php | 2 +- tests/Monolog/LogsHandlerTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 6d6842edd..f96a6a5e7 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -66,7 +66,7 @@ public function handle($record): bool self::getSentryLogLevelFromMonologLevel($record['level']), $record['message'], [], - array_merge($record['context'], $record['extra'], ['sentry.origin' => 'auto.logger.monolog']) + array_merge($record['context'], $record['extra'], ['sentry.origin' => 'auto.log.monolog']) ); return $this->bubble === false; diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 20d69ec9a..2a6af3298 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -129,7 +129,7 @@ public function testOriginTagAppliedWithHandler(): void $this->assertCount(1, $logs); $log = $logs[0]; $this->assertArrayHasKey('sentry.origin', $log->attributes()->toSimpleArray()); - $this->assertSame('auto.logger.monolog', $log->attributes()->toSimpleArray()['sentry.origin']); + $this->assertSame('auto.log.monolog', $log->attributes()->toSimpleArray()['sentry.origin']); } public function testOriginTagNotAppliedWhenUsingDirectly() From 945f863abd98f26e241ff10260d7c92518b3a725 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 20 Oct 2025 14:56:25 +0200 Subject: [PATCH 1131/1161] Prepare 4.17.0 (#1941) --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7710789d0..53119b8a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # CHANGELOG +## 4.17.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.17.0. + +### Bug Fixes + +- Empty strings will no longer display `` when serialized. [(#1940)](https://github.com/getsentry/sentry-php/pull/1940) + +### Misc + +- Replace polyfill methods with PHP 7.2 equivalents. [(#1922)](https://github.com/getsentry/sentry-php/pull/1922) +- Remove `symfony/phpunit-bridge` as a dev dependency. [(#1930)](https://github.com/getsentry/sentry-php/pull/1930) +- Update `sentry.origin` to be consistent with other SDKs. [(#1938)](https://github.com/getsentry/sentry-php/pull/1938) + ## 4.16.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.16.0. From 62927369a572efc27ddbd89e466e17788329224b Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 20 Oct 2025 12:57:02 +0000 Subject: [PATCH 1132/1161] release: 4.17.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 1c51e6de2..962a8bdb6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.16.0'; + public const SDK_VERSION = '4.17.0'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From c81a4db74f867f006a872a19648916c2be2cbab2 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 21 Oct 2025 11:09:56 +0200 Subject: [PATCH 1133/1161] Remove changelog entry for unreleased feature (#1943) --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53119b8a0..646fbedfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ The Sentry SDK team is happy to announce the immediate availability of Sentry PH ### Misc -- Replace polyfill methods with PHP 7.2 equivalents. [(#1922)](https://github.com/getsentry/sentry-php/pull/1922) - Remove `symfony/phpunit-bridge` as a dev dependency. [(#1930)](https://github.com/getsentry/sentry-php/pull/1930) - Update `sentry.origin` to be consistent with other SDKs. [(#1938)](https://github.com/getsentry/sentry-php/pull/1938) From 775c7accd7c0e62b5ac56aa9a8bbfec20cfc55db Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 23 Oct 2025 15:00:18 +0200 Subject: [PATCH 1134/1161] ref: change doc to use `never` instead of `none` (#1944) --- src/Options.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options.php b/src/Options.php index 28093ed6e..84ead7836 100644 --- a/src/Options.php +++ b/src/Options.php @@ -1130,7 +1130,7 @@ public function getMaxRequestBodySize(): string * captured. It can be set to one of the * following values: * - * - none: request bodies are never sent + * - never: request bodies are never sent * - small: only small request bodies will * be captured where the cutoff for small * depends on the SDK (typically 4KB) From 4885d13a47ab9e2cb9f89ce2e3d4f781246eda5a Mon Sep 17 00:00:00 2001 From: Jamie Burchell Date: Thu, 23 Oct 2025 15:37:16 +0100 Subject: [PATCH 1135/1161] fix: Only call curl_close() in PHP < 8 (#1947) --- src/HttpClient/HttpClient.php | 8 ++++++-- src/Spotlight/SpotlightClient.php | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index aa5873df3..2a373d930 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -106,7 +106,9 @@ public function sendRequest(Request $request, Options $options): Response if ($body === false) { $errorCode = curl_errno($curlHandle); $error = curl_error($curlHandle); - curl_close($curlHandle); + if (\PHP_MAJOR_VERSION < 8) { + curl_close($curlHandle); + } $message = 'cURL Error (' . $errorCode . ') ' . $error; @@ -115,7 +117,9 @@ public function sendRequest(Request $request, Options $options): Response $statusCode = curl_getinfo($curlHandle, \CURLINFO_HTTP_CODE); - curl_close($curlHandle); + if (\PHP_MAJOR_VERSION < 8) { + curl_close($curlHandle); + } $error = $statusCode >= 400 ? $body : ''; diff --git a/src/Spotlight/SpotlightClient.php b/src/Spotlight/SpotlightClient.php index f32af4cda..0b56517b3 100644 --- a/src/Spotlight/SpotlightClient.php +++ b/src/Spotlight/SpotlightClient.php @@ -42,7 +42,9 @@ public static function sendRequest(Request $request, string $url): Response if ($body === false) { $errorCode = curl_errno($curlHandle); $error = curl_error($curlHandle); - curl_close($curlHandle); + if (\PHP_MAJOR_VERSION < 8) { + curl_close($curlHandle); + } $message = 'cURL Error (' . $errorCode . ') ' . $error; @@ -51,7 +53,9 @@ public static function sendRequest(Request $request, string $url): Response $statusCode = curl_getinfo($curlHandle, \CURLINFO_HTTP_CODE); - curl_close($curlHandle); + if (\PHP_MAJOR_VERSION < 8) { + curl_close($curlHandle); + } return new Response($statusCode, [], ''); } From ebbfb6ad3ca6535b30f193fb408d52b418330673 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 23 Oct 2025 17:18:34 +0200 Subject: [PATCH 1136/1161] Prepare 4.17.1 (#1948) Co-authored-by: Michi Hoffmann --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 646fbedfa..503487953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 4.17.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.17.1. + +### Misc + +- Call `curl_close` only on PHP version 7.4 and below to prevent deprecation warnings. [(#1947)](https://github.com/getsentry/sentry-php/pull/1947) + ## 4.17.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.17.0. From 5c696b8de57e841a2bf3b6f6eecfd99acfdda80c Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 23 Oct 2025 15:19:24 +0000 Subject: [PATCH 1137/1161] release: 4.17.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 962a8bdb6..46f142e1c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.17.0'; + public const SDK_VERSION = '4.17.1'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 6df603de4453f9eb8253c9fcc34bf7795954e33b Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Tue, 28 Oct 2025 11:29:26 -0400 Subject: [PATCH 1138/1161] Add support for Symfony 8 (#1895) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 40e58869b..607891141 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "guzzlehttp/psr7": "^1.8.4|^2.1.1", "jean85/pretty-package-versions": "^1.5|^2.0.4", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0|^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.4", From 8e24c116c5017111c06b78780b33a7a2e011c0da Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 4 Nov 2025 10:00:41 +0100 Subject: [PATCH 1139/1161] feat: feature flags (#1951) --- src/State/Scope.php | 53 +++++++++++++++++++++ src/Tracing/Span.php | 34 +++++++++++++- tests/State/ScopeTest.php | 94 ++++++++++++++++++++++++++++++++++++++ tests/Tracing/SpanTest.php | 30 ++++++++++++ 4 files changed, 210 insertions(+), 1 deletion(-) diff --git a/src/State/Scope.php b/src/State/Scope.php index e4e054c3c..7d3736c42 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -21,6 +21,13 @@ */ class Scope { + /** + * Maximum number of flags allowed. We only track the first flags set. + * + * @internal + */ + public const MAX_FLAGS = 100; + /** * @var PropagationContext */ @@ -46,6 +53,11 @@ class Scope */ private $tags = []; + /** + * @var array> The list of flags associated to this scope + */ + private $flags = []; + /** * @var array A set of extra data associated to this scope */ @@ -130,6 +142,35 @@ public function removeTag(string $key): self return $this; } + /** + * Adds a feature flag to the scope. + * + * @return $this + */ + public function addFeatureFlag(string $key, bool $result): self + { + // If the flag was already set, remove it first + // This basically mimics an LRU cache so that the most recently added flags are kept + foreach ($this->flags as $flagIndex => $flag) { + if (isset($flag[$key])) { + unset($this->flags[$flagIndex]); + } + } + + // Keep only the most recent MAX_FLAGS flags + if (\count($this->flags) >= self::MAX_FLAGS) { + array_shift($this->flags); + } + + $this->flags[] = [$key => $result]; + + if ($this->span !== null) { + $this->span->setFlag($key, $result); + } + + return $this; + } + /** * Sets data to the context by a given name. * @@ -331,6 +372,7 @@ public function clear(): self $this->fingerprint = []; $this->breadcrumbs = []; $this->tags = []; + $this->flags = []; $this->extra = []; $this->contexts = []; @@ -359,6 +401,17 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op $event->setTags(array_merge($this->tags, $event->getTags())); } + if (!empty($this->flags)) { + $event->setContext('flags', [ + 'values' => array_map(static function (array $flag) { + return [ + 'flag' => key($flag), + 'result' => current($flag), + ]; + }, $this->flags), + ]); + } + if (!empty($this->extra)) { $event->setExtra(array_merge($this->extra, $event->getExtra())); } diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 51308b06e..e55c4c948 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -22,6 +22,13 @@ */ class Span { + /** + * Maximum number of flags allowed. We only track the first flags set. + * + * @internal + */ + public const MAX_FLAGS = 10; + /** * @var SpanId Span ID */ @@ -62,6 +69,11 @@ class Span */ protected $tags = []; + /** + * @var array A List of flags associated to this span + */ + protected $flags = []; + /** * @var array An arbitrary mapping of additional metadata */ @@ -328,6 +340,20 @@ public function setTags(array $tags) return $this; } + /** + * Sets a feature flag associated to this span. + * + * @return $this + */ + public function setFlag(string $key, bool $result) + { + if (\count($this->flags) < self::MAX_FLAGS) { + $this->flags[$key] = $result; + } + + return $this; + } + /** * Gets the ID of the span. */ @@ -369,7 +395,13 @@ public function setSampled(?bool $sampled) public function getData(?string $key = null, $default = null) { if ($key === null) { - return $this->data; + $data = $this->data; + + foreach ($this->flags as $flagKey => $flagValue) { + $data["flag.evaluation.{$flagKey}"] = $flagValue; + } + + return $data; } return $this->data[$key] ?? $default; diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index caf6b3fd7..281cbadb9 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -12,6 +12,7 @@ use Sentry\State\Scope; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\PropagationContext; +use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; @@ -77,6 +78,88 @@ public function testRemoveTag(): void $this->assertSame(['bar' => 'baz'], $event->getTags()); } + public function testSetFlag(): void + { + $scope = new Scope(); + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertNotNull($event); + $this->assertArrayNotHasKey('flags', $event->getContexts()); + + $scope->addFeatureFlag('foo', true); + $scope->addFeatureFlag('bar', false); + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertNotNull($event); + $this->assertArrayHasKey('flags', $event->getContexts()); + $this->assertEquals([ + 'values' => [ + [ + 'flag' => 'foo', + 'result' => true, + ], + [ + 'flag' => 'bar', + 'result' => false, + ], + ], + ], $event->getContexts()['flags']); + } + + public function testSetFlagLimit(): void + { + $scope = new Scope(); + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertNotNull($event); + $this->assertArrayNotHasKey('flags', $event->getContexts()); + + $expectedFlags = []; + + foreach (range(1, Scope::MAX_FLAGS) as $i) { + $scope->addFeatureFlag("feature{$i}", true); + + $expectedFlags[] = [ + 'flag' => "feature{$i}", + 'result' => true, + ]; + } + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertNotNull($event); + $this->assertArrayHasKey('flags', $event->getContexts()); + $this->assertEquals(['values' => $expectedFlags], $event->getContexts()['flags']); + + array_shift($expectedFlags); + + $scope->addFeatureFlag('should-not-be-discarded', true); + + $expectedFlags[] = [ + 'flag' => 'should-not-be-discarded', + 'result' => true, + ]; + + $event = $scope->applyToEvent(Event::createEvent()); + + $this->assertNotNull($event); + $this->assertArrayHasKey('flags', $event->getContexts()); + $this->assertEquals(['values' => $expectedFlags], $event->getContexts()['flags']); + } + + public function testSetFlagPropagatesToSpan(): void + { + $span = new Span(); + + $scope = new Scope(); + $scope->setSpan($span); + + $scope->addFeatureFlag('feature', true); + + $this->assertSame(['flag.evaluation.feature' => true], $span->getData()); + } + public function testSetAndRemoveContext(): void { $propgationContext = PropagationContext::fromDefaults(); @@ -364,6 +447,7 @@ public function testClear(): void $scope->setFingerprint(['foo']); $scope->setExtras(['foo' => 'bar']); $scope->setTags(['bar' => 'foo']); + $scope->addFeatureFlag('feature', true); $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); $scope->clear(); @@ -376,6 +460,7 @@ public function testClear(): void $this->assertEmpty($event->getExtra()); $this->assertEmpty($event->getTags()); $this->assertEmpty($event->getUser()); + $this->assertArrayNotHasKey('flags', $event->getContexts()); } public function testApplyToEvent(): void @@ -403,6 +488,7 @@ public function testApplyToEvent(): void $scope->setUser($user); $scope->setContext('foocontext', ['foo' => 'bar']); $scope->setContext('barcontext', ['bar' => 'foo']); + $scope->addFeatureFlag('feature', true); $scope->setSpan($span); $this->assertSame($event, $scope->applyToEvent($event)); @@ -417,6 +503,14 @@ public function testApplyToEvent(): void 'foo' => 'foo', 'bar' => 'bar', ], + 'flags' => [ + 'values' => [ + [ + 'flag' => 'feature', + 'result' => true, + ], + ], + ], 'trace' => [ 'span_id' => '566e3688a61d4bc8', 'trace_id' => '566e3688a61d4bc888951642d6f14a19', diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index 987feffb5..e0db7c6d5 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -187,4 +187,34 @@ public function testOriginIsCopiedFromContext(): void $this->assertSame($context->getOrigin(), $span->getOrigin()); $this->assertSame($context->getOrigin(), $span->getTraceContext()['origin']); } + + public function testFlagIsRecorded(): void + { + $span = new Span(); + + $span->setFlag('feature', true); + + $this->assertSame(['flag.evaluation.feature' => true], $span->getData()); + } + + public function testFlagLimitRecorded(): void + { + $span = new Span(); + + $expectedFlags = [ + 'flag.evaluation.should-not-be-discarded' => true, + ]; + + $span->setFlag('should-not-be-discarded', true); + + foreach (range(1, Span::MAX_FLAGS - 1) as $i) { + $span->setFlag("feature{$i}", true); + + $expectedFlags["flag.evaluation.feature{$i}"] = true; + } + + $span->setFlag('should-be-discarded', true); + + $this->assertSame($expectedFlags, $span->getData()); + } } From 6cad7b2f01a262486a5a4b6ba81fa589a75addf2 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Tue, 4 Nov 2025 10:01:24 +0100 Subject: [PATCH 1140/1161] feat: try to represent any logs attribute as string (#1950) --- src/Attributes/Attribute.php | 25 +++++++++++----- src/Logs/LogsAggregator.php | 4 +-- tests/Attributes/AttributeTest.php | 48 +++++++++++++++++++++++++++--- tests/Logs/LogsAggregatorTest.php | 2 +- tests/Logs/LogsTest.php | 7 ++++- 5 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/Attributes/Attribute.php b/src/Attributes/Attribute.php index ce792220e..3a79c84c5 100644 --- a/src/Attributes/Attribute.php +++ b/src/Attributes/Attribute.php @@ -4,6 +4,9 @@ namespace Sentry\Attributes; +use Sentry\Serializer\SerializableInterface; +use Sentry\Util\JSON; + /** * @phpstan-type AttributeType 'string'|'boolean'|'integer'|'double' * @phpstan-type AttributeValue string|bool|int|float @@ -68,7 +71,7 @@ public static function fromValue($value): self public static function tryFromValue($value): ?self { if ($value === null) { - return null; + return new self('null', 'string'); } if (\is_bool($value)) { @@ -83,14 +86,22 @@ public static function tryFromValue($value): ?self return new self($value, 'double'); } - if (\is_string($value) || (\is_object($value) && method_exists($value, '__toString'))) { - $stringValue = (string) $value; - - if (empty($stringValue)) { - return null; + if ($value instanceof SerializableInterface) { + try { + return new self(JSON::encode($value->toSentry()), 'string'); + } catch (\Throwable $e) { + // Ignore the exception and continue trying other methods } + } + + if (\is_string($value) || (\is_object($value) && method_exists($value, '__toString'))) { + return new self((string) $value, 'string'); + } - return new self($stringValue, 'string'); + try { + return new self(JSON::encode($value), 'string'); + } catch (\Throwable $e) { + // Ignore the exception } return null; diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index d17b04939..e2fb5ea78 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -109,8 +109,6 @@ public function add( $attributes = Arr::simpleDot($attributes); foreach ($attributes as $key => $value) { - $attribute = Attribute::tryFromValue($value); - if (!\is_string($key)) { if ($sdkLogger !== null) { $sdkLogger->info( @@ -121,6 +119,8 @@ public function add( continue; } + $attribute = Attribute::tryFromValue($value); + if ($attribute === null) { if ($sdkLogger !== null) { $sdkLogger->info( diff --git a/tests/Attributes/AttributeTest.php b/tests/Attributes/AttributeTest.php index 030f628bd..1cde9a4bf 100644 --- a/tests/Attributes/AttributeTest.php +++ b/tests/Attributes/AttributeTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Sentry\Attributes\Attribute; +use Sentry\Serializer\SerializableInterface; /** * @phpstan-import-type AttributeType from Attribute @@ -43,6 +44,14 @@ public static function fromValueDataProvider(): \Generator ], ]; + yield [ + '', + [ + 'type' => 'string', + 'value' => '', + ], + ]; + yield [ 123, [ @@ -67,6 +76,14 @@ public static function fromValueDataProvider(): \Generator ], ]; + yield [ + null, + [ + 'type' => 'string', + 'value' => 'null', + ], + ]; + yield [ new class { public function __toString(): string @@ -80,19 +97,41 @@ public function __toString(): string ], ]; + yield [ + new class implements SerializableInterface { + public function toSentry(): ?array + { + return ['foo' => 'bar']; + } + }, + [ + 'type' => 'string', + 'value' => '{"foo":"bar"}', + ], + ]; + yield [ new class {}, - null, + [ + 'type' => 'string', + 'value' => '{}', + ], ]; yield [ new \stdClass(), - null, + [ + 'type' => 'string', + 'value' => '{}', + ], ]; yield [ [], - null, + [ + 'type' => 'string', + 'value' => '[]', + ], ]; } @@ -112,6 +151,7 @@ public function testFromValueFactoryMethod(): void { $this->expectException(\InvalidArgumentException::class); - Attribute::fromValue([]); + // Since we support almost any type, we use a resource to trigger the exception + Attribute::fromValue(fopen(__FILE__, 'r')); } } diff --git a/tests/Logs/LogsAggregatorTest.php b/tests/Logs/LogsAggregatorTest.php index 377b9deff..83a91e1da 100644 --- a/tests/Logs/LogsAggregatorTest.php +++ b/tests/Logs/LogsAggregatorTest.php @@ -78,7 +78,7 @@ public static function attributesDataProvider(): \Generator yield [ ['foo' => ['bar']], - [], + ['foo' => '["bar"]'], ]; } diff --git a/tests/Logs/LogsTest.php b/tests/Logs/LogsTest.php index 1917bd48b..3aab97419 100644 --- a/tests/Logs/LogsTest.php +++ b/tests/Logs/LogsTest.php @@ -119,12 +119,17 @@ public function testLogWithNestedAttributes(): void $this->assertNotNull($attribute); $this->assertEquals('bar', $attribute->getValue()); + + $attribute = $logItem->attributes()->get('nested.baz'); + + $this->assertNotNull($attribute); + $this->assertEquals(json_encode([1, 2, 3]), $attribute->getValue()); }); logger()->info('Some message', [], [ 'nested' => [ 'foo' => 'bar', - 'should-be-missing' => [1, 2, 3], + 'baz' => [1, 2, 3], ], ]); From 01de10badeba858763be596fa592c54bac6caa63 Mon Sep 17 00:00:00 2001 From: Deeka Wong Date: Wed, 5 Nov 2025 21:38:23 +0800 Subject: [PATCH 1141/1161] ref(logs): extract attribute compilation to dedicated method in LogsHandler (#1931) Co-authored-by: Deeka Wong <8337659+huangdijia@users.noreply.github.com> --- src/Monolog/LogsHandler.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index f96a6a5e7..9ee342a4f 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -66,7 +66,7 @@ public function handle($record): bool self::getSentryLogLevelFromMonologLevel($record['level']), $record['message'], [], - array_merge($record['context'], $record['extra'], ['sentry.origin' => 'auto.log.monolog']) + $this->compileAttributes($record) ); return $this->bubble === false; @@ -123,4 +123,14 @@ public function __destruct() // Just in case so that the destructor can never fail. } } + + /** + * @param array|LogRecord $record + * + * @return array + */ + protected function compileAttributes($record): array + { + return array_merge($record['context'], $record['extra'], ['sentry.origin' => 'auto.log.monolog']); + } } From 0e9d3090b64b487296da61b94cd506f303726168 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Wed, 5 Nov 2025 15:09:25 +0100 Subject: [PATCH 1142/1161] Prepare 4.18.0 (#1958) --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 503487953..0bff8e9c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # CHANGELOG +## 4.18.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.18.0. + +### Features + +- Add support for feature flags. [(#1951)](https://github.com/getsentry/sentry-php/pull/1951) +```php +\Sentry\SentrySdk::getCurrentHub()->configureScope(function (\Sentry\State\Scope $scope) { + $scope->addFeatureFlag("my.feature.enabled", true); +}); +``` +- Add more representations for log attributes instead of dropping them. [(#1950)](https://github.com/getsentry/sentry-php/pull/1950) + +### Misc + +- Merge log attributes in a separate method. [(#1931)](https://github.com/getsentry/sentry-php/pull/1931) + ## 4.17.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.17.1. From 75f7efb7d435d24767c93d0081b8edf228be5772 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 5 Nov 2025 14:37:07 +0000 Subject: [PATCH 1143/1161] release: 4.18.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 46f142e1c..d7950fb76 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.17.1'; + public const SDK_VERSION = '4.18.0'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 60486ee1061aaf1901f76c18a9d5860cb146b2a0 Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 6 Nov 2025 15:28:20 +0100 Subject: [PATCH 1144/1161] meta: Run CI on PHP 8.5 (#1927) Co-authored-by: Martin Linzmayer --- .github/workflows/ci.yml | 1 + ...ry_fatal_error_increases_memory_limit.phpt | 5 ++ ...ry_fatal_error_increases_memory_limit.phpt | 84 +++++++++++++++++++ .../error_handler_captures_fatal_error.phpt | 5 ++ ...er_captures_out_of_memory_fatal_error.phpt | 5 ++ ...rror_integration_captures_fatal_error.phpt | 5 ++ ...tegration_respects_error_types_option.phpt | 5 ++ .../error_handler_captures_fatal_error.phpt | 80 ++++++++++++++++++ ...er_captures_out_of_memory_fatal_error.phpt | 46 ++++++++++ ...rror_integration_captures_fatal_error.phpt | 69 +++++++++++++++ ...tegration_respects_error_types_option.phpt | 69 +++++++++++++++ 11 files changed, 374 insertions(+) rename tests/phpt-oom/{ => php84}/out_of_memory_fatal_error_increases_memory_limit.phpt (95%) create mode 100644 tests/phpt-oom/php85/out_of_memory_fatal_error_increases_memory_limit.phpt rename tests/phpt/{ => php84}/error_handler_captures_fatal_error.phpt (94%) rename tests/phpt/{ => php84}/error_handler_captures_out_of_memory_fatal_error.phpt (90%) rename tests/phpt/{ => php84}/fatal_error_integration_captures_fatal_error.phpt (93%) rename tests/phpt/{ => php84}/fatal_error_integration_respects_error_types_option.phpt (93%) create mode 100644 tests/phpt/php85/error_handler_captures_fatal_error.phpt create mode 100644 tests/phpt/php85/error_handler_captures_out_of_memory_fatal_error.phpt create mode 100644 tests/phpt/php85/fatal_error_integration_captures_fatal_error.phpt create mode 100644 tests/phpt/php85/fatal_error_integration_respects_error_types_option.phpt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6dee415f4..e4921fc52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ jobs: - { version: '8.2', phpunit: '^9.6.21' } - { version: '8.3', phpunit: '^9.6.21' } - { version: '8.4', phpunit: '^9.6.21' } + - { version: '8.5', phpunit: '^9.6.25' } dependencies: - lowest - highest diff --git a/tests/phpt-oom/out_of_memory_fatal_error_increases_memory_limit.phpt b/tests/phpt-oom/php84/out_of_memory_fatal_error_increases_memory_limit.phpt similarity index 95% rename from tests/phpt-oom/out_of_memory_fatal_error_increases_memory_limit.phpt rename to tests/phpt-oom/php84/out_of_memory_fatal_error_increases_memory_limit.phpt index acef1edd9..321ed418a 100644 --- a/tests/phpt-oom/out_of_memory_fatal_error_increases_memory_limit.phpt +++ b/tests/phpt-oom/php84/out_of_memory_fatal_error_increases_memory_limit.phpt @@ -1,5 +1,10 @@ --TEST-- Test that when handling a out of memory error the memory limit is increased with 5 MiB and the event is serialized and ready to be sent +--SKIPIF-- += 80400) { + die('skip - only works for PHP 8.4 and below'); +} --INI-- memory_limit=67108864 --FILE-- diff --git a/tests/phpt-oom/php85/out_of_memory_fatal_error_increases_memory_limit.phpt b/tests/phpt-oom/php85/out_of_memory_fatal_error_increases_memory_limit.phpt new file mode 100644 index 000000000..92e030588 --- /dev/null +++ b/tests/phpt-oom/php85/out_of_memory_fatal_error_increases_memory_limit.phpt @@ -0,0 +1,84 @@ +--TEST-- +Test that when handling a out of memory error the memory limit is increased with 5 MiB and the event is serialized and ready to be sent +--SKIPIF-- + 'http://public@example.com/sentry/1', +]); + +$transport = new class(new PayloadSerializer($options)) implements TransportInterface { + private $payloadSerializer; + + public function __construct(PayloadSerializerInterface $payloadSerializer) + { + $this->payloadSerializer = $payloadSerializer; + } + + public function send(Event $event): Result + { + $serialized = $this->payloadSerializer->serialize($event); + + echo 'Transport called' . \PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); + } +}; + +$options->setTransport($transport); + +$client = (new ClientBuilder($options))->getClient(); + +SentrySdk::init()->bindClient($client); + +echo 'Before OOM memory limit: ' . \ini_get('memory_limit'); + +register_shutdown_function(function () { + echo 'After OOM memory limit: ' . \ini_get('memory_limit'); +}); + +$array = []; +for ($i = 0; $i < 100000000; ++$i) { + $array[] = 'sentry'; +} +--EXPECTF-- +Before OOM memory limit: 67108864 +Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d +Stack trace: +%A +Transport called +After OOM memory limit: 72351744 diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/php84/error_handler_captures_fatal_error.phpt similarity index 94% rename from tests/phpt/error_handler_captures_fatal_error.phpt rename to tests/phpt/php84/error_handler_captures_fatal_error.phpt index e7b457d2b..2c4b9143b 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/php84/error_handler_captures_fatal_error.phpt @@ -1,5 +1,10 @@ --TEST-- Test catching fatal errors +--SKIPIF-- += 80500) { + die('skip - only works for PHP 8.4 and below'); +} --FILE-- = 80500) { + die('skip - only works for PHP 8.4 and below'); +} --INI-- memory_limit=67108864 --FILE-- diff --git a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/php84/fatal_error_integration_captures_fatal_error.phpt similarity index 93% rename from tests/phpt/fatal_error_integration_captures_fatal_error.phpt rename to tests/phpt/php84/fatal_error_integration_captures_fatal_error.phpt index ba82e9bca..2c8e40c4d 100644 --- a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/php84/fatal_error_integration_captures_fatal_error.phpt @@ -1,5 +1,10 @@ --TEST-- Test that the FatalErrorListenerIntegration integration captures only the errors allowed by the error_types option +--SKIPIF-- += 80500) { + die('skip - only works for PHP 8.4 and below'); +} --FILE-- = 80500) { + die('skip - only works for PHP 8.4 and below'); +} --FILE-- 'http://public@example.com/sentry/1', +]; + +$client = ClientBuilder::create($options) + ->setTransport($transport) + ->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +$errorHandler = ErrorHandler::registerOnceErrorHandler(); +$errorHandler->addErrorHandlerListener(static function (): void { + echo 'Error listener called (it should not have been)' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); +$errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called' . PHP_EOL; +}); + +$errorHandler = ErrorHandler::registerOnceExceptionHandler(); +$errorHandler->addExceptionHandlerListener(static function (): void { + echo 'Exception listener called (it should not have been)' . PHP_EOL; +}); + +final class TestClass implements \JsonSerializable +{ +} +?> +--EXPECTF-- +Fatal error: Class Sentry\Tests\TestClass contains 1 abstract method and must therefore be declared abstract or implement the remaining method (JsonSerializable::jsonSerialize) in %s on line %d +Stack trace: +%A +Transport called +Fatal error listener called diff --git a/tests/phpt/php85/error_handler_captures_out_of_memory_fatal_error.phpt b/tests/phpt/php85/error_handler_captures_out_of_memory_fatal_error.phpt new file mode 100644 index 000000000..cd3986d9c --- /dev/null +++ b/tests/phpt/php85/error_handler_captures_out_of_memory_fatal_error.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test catching out of memory fatal error without increasing memory limit +--SKIPIF-- +addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called' . PHP_EOL; + + echo 'After OOM memory limit: ' . ini_get('memory_limit'); +}); + +$errorHandler->setMemoryLimitIncreaseOnOutOfMemoryErrorInBytes(null); + +echo 'Before OOM memory limit: ' . ini_get('memory_limit'); + +$foo = str_repeat('x', 1024 * 1024 * 1024); +?> +--EXPECTF-- +Before OOM memory limit: 67108864 +Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes) in %s on line %d +Stack trace: +%A +Fatal error listener called +After OOM memory limit: 67108864 diff --git a/tests/phpt/php85/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/php85/fatal_error_integration_captures_fatal_error.phpt new file mode 100644 index 000000000..88fdd8f5c --- /dev/null +++ b/tests/phpt/php85/fatal_error_integration_captures_fatal_error.phpt @@ -0,0 +1,69 @@ +--TEST-- +Test that the FatalErrorListenerIntegration integration captures only the errors allowed by the error_types option +--SKIPIF-- + false, + 'integrations' => [ + new FatalErrorListenerIntegration(), + ], +]); + +$client = (new ClientBuilder($options)) + ->setTransport($transport) + ->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +final class TestClass implements \JsonSerializable +{ +} +?> +--EXPECTF-- +Fatal error: Class Sentry\Tests\TestClass contains 1 abstract method and must therefore be declared abstract or implement the remaining method (JsonSerializable::jsonSerialize) in %s on line %d +Stack trace: +%A +Transport called diff --git a/tests/phpt/php85/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/php85/fatal_error_integration_respects_error_types_option.phpt new file mode 100644 index 000000000..7d74233ce --- /dev/null +++ b/tests/phpt/php85/fatal_error_integration_respects_error_types_option.phpt @@ -0,0 +1,69 @@ +--TEST-- +Test that the FatalErrorListenerIntegration integration captures only the errors allowed by the error_types option +--SKIPIF-- + E_ALL & ~E_ERROR, + 'default_integrations' => false, + 'integrations' => [ + new FatalErrorListenerIntegration(), + ], +]); + +$client = (new ClientBuilder($options)) + ->setTransport($transport) + ->getClient(); + +SentrySdk::getCurrentHub()->bindClient($client); + +final class TestClass implements \JsonSerializable +{ +} +?> +--EXPECTF-- +Fatal error: Class Sentry\Tests\TestClass contains 1 abstract method and must therefore be declared abstract or implement the remaining method (JsonSerializable::jsonSerialize) in %s on line %d +Stack trace: +%A From 7797f856cd6bdba357de6f8061b0c2271fca6bdc Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Fri, 7 Nov 2025 08:29:07 +0100 Subject: [PATCH 1145/1161] chore: Update X handle (#1961) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b8c54e21..b46be953d 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ If you need help setting up or configuring the PHP SDK (or anything else in the - [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/quickstart/) - [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) - [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](http://stackoverflow.com/questions/tagged/sentry) -- [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry) +- [![X Follow](https://img.shields.io/twitter/follow/sentry?label=sentry&style=social)](https://x.com/intent/follow?screen_name=sentry) ## License From 8973b70953891e229cbaa0edeb48ae98bc1e7ee9 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 10 Nov 2025 12:55:44 +0100 Subject: [PATCH 1146/1161] ref: add `addFeatureFlag` helper function (#1960) --- src/functions.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/functions.php b/src/functions.php index 23f6eb1e1..c01349821 100644 --- a/src/functions.php +++ b/src/functions.php @@ -379,3 +379,14 @@ function metrics(): Metrics { return Metrics::getInstance(); } + +/** + * Adds a feature flag evaluation to the current scope. + * When invoked repeatedly for the same name, the most recent value is used. + */ +function addFeatureFlag(string $name, bool $result): void +{ + SentrySdk::getCurrentHub()->configureScope(function (Scope $scope) use ($name, $result) { + $scope->addFeatureFlag($name, $result); + }); +} From afb6cb582c16516c51235d4ea91cced4e0df3c92 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Tue, 11 Nov 2025 10:34:15 +0100 Subject: [PATCH 1147/1161] Prepare 4.18.1 (#1965) --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bff8e9c9..be545ceea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # CHANGELOG +## 4.18.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.18.1. + +### Misc + +- Add `addFeatureFlag` helper function. [(#1960)](https://github.com/getsentry/sentry-php/pull/1960) +```php +\Sentry\addFeatureFlag("my.feature.enabled", true); +``` + ## 4.18.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.18.0. From 04dcf20b39742b731b676f8b8d4f02d1db488af8 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 11 Nov 2025 09:34:53 +0000 Subject: [PATCH 1148/1161] release: 4.18.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index d7950fb76..74c8cfa77 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.18.0'; + public const SDK_VERSION = '4.18.1'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From cdbfb21e489a168ea25a6737d7dbd9f481b868b9 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Mon, 24 Nov 2025 10:38:53 +0100 Subject: [PATCH 1149/1161] ref(rate-limit): handle profile and check_in rate limiting (#1970) --- src/Transport/HttpTransport.php | 14 ++++ src/Transport/RateLimiter.php | 16 +++- tests/Transport/HttpTransportTest.php | 104 +++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 5 deletions(-) diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index f47867fe8..71da8d5a3 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -100,6 +100,20 @@ public function send(Event $event): Result return new Result(ResultStatus::rateLimit()); } + // Since profiles are attached to transaction we have to check separately if they are rate limited. + // We can do this after transactions have been checked because if transactions are rate limited, + // so are profiles but not the other way around. + if ($event->getSdkMetadata('profile') !== null) { + if ($this->rateLimiter->isRateLimited(RateLimiter::DATA_CATEGORY_PROFILE)) { + // Just remove profiling data so the normal transaction can be sent. + $event->setSdkMetadata('profile', null); + $this->logger->warning( + 'Rate limit exceeded for sending requests of type "profile". The profile has been dropped.', + ['event' => $event] + ); + } + } + $request = new Request(); $request->setStringBody($this->payloadSerializer->serialize($event)); diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index dbb8deccf..ab219577f 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -11,6 +11,11 @@ final class RateLimiter { + /** + * @var string + */ + public const DATA_CATEGORY_PROFILE = 'profile'; + /** * @var string */ @@ -21,6 +26,11 @@ final class RateLimiter */ private const DATA_CATEGORY_LOG_ITEM = 'log_item'; + /** + * @var string + */ + private const DATA_CATEGORY_CHECK_IN = 'monitor'; + /** * The name of the header to look at to know the rate limits for the events * categories supported by the server. @@ -103,9 +113,7 @@ public function handleResponse(Response $response): bool */ public function isRateLimited($eventType): bool { - $disabledUntil = $this->getDisabledUntil($eventType); - - return $disabledUntil > time(); + return $this->getDisabledUntil($eventType) > time(); } /** @@ -119,6 +127,8 @@ public function getDisabledUntil($eventType): int $eventType = self::DATA_CATEGORY_ERROR; } elseif ($eventType === 'log') { $eventType = self::DATA_CATEGORY_LOG_ITEM; + } elseif ($eventType === 'check_in') { + $eventType = self::DATA_CATEGORY_CHECK_IN; } return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$eventType] ?? 0); diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 942fb9b49..66d564b85 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -12,6 +12,7 @@ use Sentry\HttpClient\HttpClientInterface; use Sentry\HttpClient\Response; use Sentry\Options; +use Sentry\Profiling\Profile; use Sentry\Serializer\PayloadSerializerInterface; use Sentry\Transport\HttpTransport; use Sentry\Transport\ResultStatus; @@ -25,7 +26,7 @@ final class HttpTransportTest extends TestCase private $logger; /** - * @var MockObject&HttpAsyncClientInterface + * @var MockObject&HttpClientInterface */ private $httpClient; @@ -180,7 +181,7 @@ public function testSendFailsDueToHttpClientException(): void $this->assertSame(ResultStatus::failed(), $result->getStatus()); } - public function testSendFailsDueToCulrError(): void + public function testSendFailsDueToCurlError(): void { $event = Event::createEvent(); @@ -263,6 +264,105 @@ public function testSendFailsDueToExceedingRateLimits(): void $this->assertSame(ResultStatus::rateLimit(), $result->getStatus()); } + /** + * @group time-sensitive + */ + public function testDropsProfileAndSendsTransactionWhenProfileRateLimited(): void + { + ClockMock::withClockMock(1644105600); + + $transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/1']), + $this->httpClient, + $this->payloadSerializer, + $this->logger + ); + + $event = Event::createTransaction(); + $event->setSdkMetadata('profile', new Profile()); + + $this->payloadSerializer->expects($this->exactly(2)) + ->method('serialize') + ->willReturn('{"foo":"bar"}'); + + $this->httpClient->expects($this->exactly(2)) + ->method('sendRequest') + ->willReturnOnConsecutiveCalls( + new Response(429, ['X-Sentry-Rate-Limits' => ['60:profile:key']], ''), + new Response(200, [], '') + ); + + // First request is rate limited because of profiles + $result = $transport->send($event); + + $this->assertEquals(ResultStatus::rateLimit(), $result->getStatus()); + + // profile information is still present + $this->assertNotNull($event->getSdkMetadata('profile')); + + $event = Event::createTransaction(); + $event->setSdkMetadata('profile', new Profile()); + + $this->logger->expects($this->once()) + ->method('warning') + ->with( + $this->stringContains('Rate limit exceeded for sending requests of type "profile".'), + ['event' => $event] + ); + + $result = $transport->send($event); + + // Sending transaction is successful because only profiles are rate limited + $this->assertEquals(ResultStatus::success(), $result->getStatus()); + + // profile information is removed because it was rate limited + $this->assertNull($event->getSdkMetadata('profile')); + } + + /** + * @group time-sensitive + */ + public function testCheckInsAreRateLimited(): void + { + ClockMock::withClockMock(1644105600); + + $transport = new HttpTransport( + new Options(['dsn' => 'http://public@example.com/1']), + $this->httpClient, + $this->payloadSerializer, + $this->logger + ); + + $event = Event::createCheckIn(); + + $this->payloadSerializer->expects($this->exactly(1)) + ->method('serialize') + ->willReturn('{"foo":"bar"}'); + + $this->httpClient->expects($this->exactly(1)) + ->method('sendRequest') + ->willReturn( + new Response(429, ['X-Sentry-Rate-Limits' => ['60:monitor:key']], '') + ); + + $result = $transport->send($event); + + $this->assertEquals(ResultStatus::rateLimit(), $result->getStatus()); + + $event = Event::createCheckIn(); + + $this->logger->expects($this->once()) + ->method('warning') + ->with( + $this->stringContains('Rate limit exceeded for sending requests of type "check_in".'), + ['event' => $event] + ); + + $result = $transport->send($event); + + $this->assertEquals(ResultStatus::rateLimit(), $result->getStatus()); + } + public function testClose(): void { $transport = new HttpTransport( From 856d46215ec0f9aac57d125eb5973a20b7eddb96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 10:33:19 +0100 Subject: [PATCH 1150/1161] chore(deps): bump actions/checkout from 5 to 6 (#1971) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/publish-release.yaml | 2 +- .github/workflows/static-analysis.yaml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4921fc52..83775534f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 2 diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 8fee24055..9ae5cc3c4 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -29,7 +29,7 @@ jobs: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: token: ${{ steps.token.outputs.token }} fetch-depth: 0 diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index d9e875a52..ba6ffe12c 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -52,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 From ccd7081fbe032e8c7e32cb4913ec182aff378a72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 10:34:43 +0100 Subject: [PATCH 1151/1161] chore(deps): bump actions/create-github-app-token from 2.1.4 to 2.2.0 (#1972) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 9ae5cc3c4..705c0958a 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} From c6f5826ebfc3ae4cccad3e2d7ef4abe8098084aa Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Thu, 27 Nov 2025 12:52:01 +0000 Subject: [PATCH 1152/1161] docs: Update list of integrations regarding Neos (& TYPO3) (#1975) --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b46be953d..eb9e7ff34 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,13 @@ The following integrations are available and maintained by members of the Sentry - [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) - Magento 2 by [JustBetter](https://github.com/justbetter/magento2-sentry) or by [Mygento](https://github.com/mygento/module-sentry) - [Joomla!](https://github.com/AlterBrains/sentry-joomla) +- Neos Flow (and CMS) using [flownative/sentry](https://github.com/flownative/flow-sentry) or [networkteam/sentryclient](https://github.com/networkteam/Networkteam.SentryClient) +- Neos CMS with specific Fusion handling using [networkteam/neos-sentryclient](https://github.com/networkteam/Netwokteam.Neos.SentryClient) +- [TYPO3](https://github.com/networkteam/sentry_client) - ... feel free to be famous, create a port to your favourite platform! ## 3rd party integrations using the old SDK 3.x -- [Neos Flow](https://github.com/flownative/flow-sentry) - [ZendFramework](https://github.com/facile-it/sentry-module) - [Yii2](https://github.com/notamedia/yii2-sentry) - [Silverstripe](https://github.com/phptek/silverstripe-sentry) @@ -77,14 +79,11 @@ The following integrations are available and maintained by members of the Sentry ## 3rd party integrations using the old SDK 2.x -- [Neos Flow](https://github.com/networkteam/Networkteam.SentryClient) - [OXID eShop](https://github.com/OXIDprojects/sentry) -- [TYPO3](https://github.com/networkteam/sentry_client) - [CakePHP](https://github.com/Connehito/cake-sentry/tree/3.x) ## 3rd party integrations using the old SDK 1.x -- [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient) - [OpenCart](https://github.com/BurdaPraha/oc_sentry) - [TYPO3](https://github.com/networkteam/sentry_client/tree/2.1.1) From dc31502a74366721f8a6263a3f17f06184336841 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 27 Nov 2025 15:23:54 +0100 Subject: [PATCH 1153/1161] feat(metrics): introduce metrics (#1968) Co-authored-by: Michael Hoffmann --- phpstan-baseline.neon | 10 - src/Event.php | 17 +- src/EventType.php | 16 +- src/Metrics/Metrics.php | 20 +- src/Metrics/MetricsAggregator.php | 140 +++++++++++++ src/Metrics/TraceMetrics.php | 100 +++++++++ src/Metrics/Types/CounterMetric.php | 64 ++++++ src/Metrics/Types/DistributionMetric.php | 64 ++++++ src/Metrics/Types/GaugeMetric.php | 64 ++++++ src/Metrics/Types/Metric.php | 111 ++++++++++ src/Options.php | 62 ++++++ src/Serializer/EnvelopItems/MetricsItem.php | 52 +++++ src/Serializer/PayloadSerializer.php | 9 +- src/Tracing/Span.php | 4 +- src/{Metrics/MetricsUnit.php => Unit.php} | 13 +- src/Util/RingBuffer.php | 215 ++++++++++++++++++++ src/functions.php | 8 +- tests/Metrics/MetricsTest.php | 12 +- tests/Metrics/TraceMetricsTest.php | 119 +++++++++++ tests/Serializer/PayloadSerializerTest.php | 70 +++++-- tests/StubTransport.php | 47 +++++ tests/Util/RingBufferTest.php | 146 +++++++++++++ 22 files changed, 1309 insertions(+), 54 deletions(-) create mode 100644 src/Metrics/MetricsAggregator.php create mode 100644 src/Metrics/TraceMetrics.php create mode 100644 src/Metrics/Types/CounterMetric.php create mode 100644 src/Metrics/Types/DistributionMetric.php create mode 100644 src/Metrics/Types/GaugeMetric.php create mode 100644 src/Metrics/Types/Metric.php create mode 100644 src/Serializer/EnvelopItems/MetricsItem.php rename src/{Metrics/MetricsUnit.php => Unit.php} (92%) create mode 100644 src/Util/RingBuffer.php create mode 100644 tests/Metrics/TraceMetricsTest.php create mode 100644 tests/StubTransport.php create mode 100644 tests/Util/RingBufferTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3ff509ab0..e4ce16ef5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -25,21 +25,11 @@ parameters: count: 1 path: src/Dsn.php - - - message: "#^Method Sentry\\\\Event\\:\\:getMetrics\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Event.php - - message: "#^Method Sentry\\\\Event\\:\\:getMetricsSummary\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 path: src/Event.php - - - message: "#^Method Sentry\\\\Event\\:\\:setMetrics\\(\\) has parameter \\$metrics with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Event.php - - message: "#^Method Sentry\\\\Event\\:\\:setMetricsSummary\\(\\) has parameter \\$metricsSummary with no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/Event.php b/src/Event.php index 5244f945c..e2d2a8c0f 100644 --- a/src/Event.php +++ b/src/Event.php @@ -7,6 +7,7 @@ use Sentry\Context\OsContext; use Sentry\Context\RuntimeContext; use Sentry\Logs\Log; +use Sentry\Metrics\Types\Metric; use Sentry\Profiling\Profile; use Sentry\Tracing\Span; @@ -71,6 +72,11 @@ final class Event */ private $logs = []; + /** + * @var Metric[] + */ + private $metrics = []; + /** * @var string|null The name of the server (e.g. the host name) */ @@ -241,9 +247,6 @@ public static function createLogs(?EventId $eventId = null): self return new self($eventId, EventType::logs()); } - /** - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. - */ public static function createMetrics(?EventId $eventId = null): self { return new self($eventId, EventType::metrics()); @@ -446,18 +449,20 @@ public function setLogs(array $logs): self } /** - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + * @return Metric[] */ public function getMetrics(): array { - return []; + return $this->metrics; } /** - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + * @param Metric[] $metrics */ public function setMetrics(array $metrics): self { + $this->metrics = $metrics; + return $this; } diff --git a/src/EventType.php b/src/EventType.php index 3c2d13fb3..679f96633 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -47,12 +47,9 @@ public static function logs(): self return self::getInstance('log'); } - /** - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. - */ public static function metrics(): self { - return self::getInstance('metrics'); + return self::getInstance('trace_metric'); } /** @@ -71,6 +68,17 @@ public static function cases(): array ]; } + public function requiresEventId(): bool + { + switch ($this) { + case self::metrics(): + case self::logs(): + return false; + default: + return true; + } + } + public function __toString(): string { return $this->value; diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php index 6f0fa6764..936538e19 100644 --- a/src/Metrics/Metrics.php +++ b/src/Metrics/Metrics.php @@ -6,9 +6,15 @@ use Sentry\EventId; use Sentry\Tracing\SpanContext; +use Sentry\Unit; use function Sentry\trace; +class_alias(Unit::class, '\Sentry\Metrics\MetricsUnit'); + +/** + * @deprecated use TraceMetrics instead + */ class Metrics { /** @@ -28,12 +34,12 @@ public static function getInstance(): self /** * @param array $tags * - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + * @deprecated Use TraceMetrics::count() instead. To be removed in 5.x. */ public function increment( string $key, float $value, - ?MetricsUnit $unit = null, + ?Unit $unit = null, array $tags = [], ?int $timestamp = null, int $stackLevel = 0 @@ -43,12 +49,12 @@ public function increment( /** * @param array $tags * - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + * @deprecated Use TraceMetrics::distribution() instead. Metrics API is a no-op and will be removed in 5.x. */ public function distribution( string $key, float $value, - ?MetricsUnit $unit = null, + ?Unit $unit = null, array $tags = [], ?int $timestamp = null, int $stackLevel = 0 @@ -58,12 +64,12 @@ public function distribution( /** * @param array $tags * - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + * @deprecated Use TraceMetrics::gauge() instead. To be removed in 5.x. */ public function gauge( string $key, float $value, - ?MetricsUnit $unit = null, + ?Unit $unit = null, array $tags = [], ?int $timestamp = null, int $stackLevel = 0 @@ -79,7 +85,7 @@ public function gauge( public function set( string $key, $value, - ?MetricsUnit $unit = null, + ?Unit $unit = null, array $tags = [], ?int $timestamp = null, int $stackLevel = 0 diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php new file mode 100644 index 000000000..fa0dfd5be --- /dev/null +++ b/src/Metrics/MetricsAggregator.php @@ -0,0 +1,140 @@ + + */ + private $metrics; + + public function __construct() + { + $this->metrics = new RingBuffer(self::METRICS_BUFFER_SIZE); + } + + private const METRIC_TYPES = [ + CounterMetric::TYPE => CounterMetric::class, + DistributionMetric::TYPE => DistributionMetric::class, + GaugeMetric::TYPE => GaugeMetric::class, + ]; + + /** + * @param int|float $value + * @param array $attributes + */ + public function add( + string $type, + string $name, + $value, + array $attributes, + ?Unit $unit + ): void { + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + if ($client instanceof Client) { + $options = $client->getOptions(); + + if ($options->getEnableMetrics() === false) { + return; + } + + $defaultAttributes = [ + 'sentry.sdk.name' => $client->getSdkIdentifier(), + 'sentry.sdk.version' => $client->getSdkVersion(), + 'sentry.environment' => $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, + 'server.address' => $options->getServerName(), + ]; + + if ($options->shouldSendDefaultPii()) { + $hub->configureScope(function (Scope $scope) use (&$defaultAttributes) { + $user = $scope->getUser(); + if ($user !== null) { + if ($user->getId() !== null) { + $defaultAttributes['user.id'] = $user->getId(); + } + if ($user->getEmail() !== null) { + $defaultAttributes['user.email'] = $user->getEmail(); + } + if ($user->getUsername() !== null) { + $defaultAttributes['user.name'] = $user->getUsername(); + } + } + }); + } + + $release = $options->getRelease(); + if ($release !== null) { + $defaultAttributes['sentry.release'] = $release; + } + + $attributes += $defaultAttributes; + } + + $spanId = null; + $traceId = null; + + $span = $hub->getSpan(); + if ($span !== null) { + $spanId = $span->getSpanId(); + $traceId = $span->getTraceId(); + } else { + $hub->configureScope(function (Scope $scope) use (&$traceId, &$spanId) { + $propagationContext = $scope->getPropagationContext(); + $traceId = $propagationContext->getTraceId(); + $spanId = $propagationContext->getSpanId(); + }); + } + + $metricTypeClass = self::METRIC_TYPES[$type]; + /** @var Metric $metric */ + /** @phpstan-ignore-next-line */ + $metric = new $metricTypeClass($name, $value, $traceId, $spanId, $attributes, microtime(true), $unit); + + if ($client !== null) { + $beforeSendMetric = $client->getOptions()->getBeforeSendMetricCallback(); + $metric = $beforeSendMetric($metric); + if ($metric === null) { + return; + } + } + + $this->metrics->push($metric); + } + + public function flush(): ?EventId + { + if ($this->metrics->isEmpty()) { + return null; + } + + $hub = SentrySdk::getCurrentHub(); + $event = Event::createMetrics()->setMetrics($this->metrics->drain()); + + return $hub->captureEvent($event); + } +} diff --git a/src/Metrics/TraceMetrics.php b/src/Metrics/TraceMetrics.php new file mode 100644 index 000000000..a3ef4a0a0 --- /dev/null +++ b/src/Metrics/TraceMetrics.php @@ -0,0 +1,100 @@ +aggregator = new MetricsAggregator(); + } + + public static function getInstance(): self + { + if (self::$instance === null) { + self::$instance = new TraceMetrics(); + } + + return self::$instance; + } + + /** + * @param int|float $value + * @param array $attributes + */ + public function count( + string $name, + $value, + array $attributes = [], + ?Unit $unit = null + ): void { + $this->aggregator->add( + CounterMetric::TYPE, + $name, + $value, + $attributes, + $unit + ); + } + + /** + * @param int|float $value + * @param array $attributes + */ + public function distribution( + string $name, + $value, + array $attributes = [], + ?Unit $unit = null + ): void { + $this->aggregator->add( + DistributionMetric::TYPE, + $name, + $value, + $attributes, + $unit + ); + } + + /** + * @param int|float $value + * @param array $attributes + */ + public function gauge( + string $name, + $value, + array $attributes = [], + ?Unit $unit = null + ): void { + $this->aggregator->add( + GaugeMetric::TYPE, + $name, + $value, + $attributes, + $unit + ); + } + + public function flush(): ?EventId + { + return $this->aggregator->flush(); + } +} diff --git a/src/Metrics/Types/CounterMetric.php b/src/Metrics/Types/CounterMetric.php new file mode 100644 index 000000000..a309e176d --- /dev/null +++ b/src/Metrics/Types/CounterMetric.php @@ -0,0 +1,64 @@ + $attributes + */ + public function __construct( + string $name, + $value, + TraceId $traceId, + SpanId $spanId, + array $attributes, + float $timestamp, + ?Unit $unit + ) { + parent::__construct($name, $traceId, $spanId, $timestamp, $attributes, $unit); + + $this->value = (float) $value; + } + + /** + * @param int|float $value + */ + public function setValue($value): void + { + $this->value = $value; + } + + /** + * @return int|float + */ + public function getValue() + { + return $this->value; + } + + public function getType(): string + { + return self::TYPE; + } +} diff --git a/src/Metrics/Types/DistributionMetric.php b/src/Metrics/Types/DistributionMetric.php new file mode 100644 index 000000000..bfbc85ce9 --- /dev/null +++ b/src/Metrics/Types/DistributionMetric.php @@ -0,0 +1,64 @@ + $attributes + */ + public function __construct( + string $name, + $value, + TraceId $traceId, + SpanId $spanId, + array $attributes, + float $timestamp, + ?Unit $unit + ) { + parent::__construct($name, $traceId, $spanId, $timestamp, $attributes, $unit); + + $this->value = (float) $value; + } + + /** + * @param int|float $value + */ + public function setValue($value): void + { + $this->value = $value; + } + + /** + * @return int|float + */ + public function getValue() + { + return $this->value; + } + + public function getType(): string + { + return self::TYPE; + } +} diff --git a/src/Metrics/Types/GaugeMetric.php b/src/Metrics/Types/GaugeMetric.php new file mode 100644 index 000000000..2b58745be --- /dev/null +++ b/src/Metrics/Types/GaugeMetric.php @@ -0,0 +1,64 @@ + $attributes + */ + public function __construct( + string $name, + $value, + TraceId $traceId, + SpanId $spanId, + array $attributes, + float $timestamp, + ?Unit $unit + ) { + parent::__construct($name, $traceId, $spanId, $timestamp, $attributes, $unit); + + $this->value = (float) $value; + } + + /** + * @param int|float $value + */ + public function setValue($value): void + { + $this->value = $value; + } + + /** + * @return int|float + */ + public function getValue() + { + return $this->value; + } + + public function getType(): string + { + return self::TYPE; + } +} diff --git a/src/Metrics/Types/Metric.php b/src/Metrics/Types/Metric.php new file mode 100644 index 000000000..999acbed9 --- /dev/null +++ b/src/Metrics/Types/Metric.php @@ -0,0 +1,111 @@ + $attributes + */ + public function __construct( + string $name, + TraceId $traceId, + SpanId $spanId, + float $timestamp, + array $attributes, + ?Unit $unit + ) { + $this->name = $name; + $this->unit = $unit; + $this->traceId = $traceId; + $this->spanId = $spanId; + $this->timestamp = $timestamp; + $this->attributes = new AttributeBag(); + + foreach ($attributes as $key => $value) { + $this->attributes->set($key, $value); + } + } + + /** + * @param int|float $value + */ + abstract public function setValue($value): void; + + abstract public function getType(): string; + + /** + * @return int|float + */ + abstract public function getValue(); + + public function getName(): string + { + return $this->name; + } + + public function getUnit(): ?Unit + { + return $this->unit; + } + + public function getTraceId(): TraceId + { + return $this->traceId; + } + + public function getSpanId(): SpanId + { + return $this->spanId; + } + + public function getAttributes(): AttributeBag + { + return $this->attributes; + } + + public function getTimestamp(): float + { + return $this->timestamp; + } +} diff --git a/src/Options.php b/src/Options.php index 84ead7836..8e1ee2279 100644 --- a/src/Options.php +++ b/src/Options.php @@ -10,6 +10,7 @@ use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Logs\Log; +use Sentry\Metrics\Types\Metric; use Sentry\Transport\TransportInterface; use Symfony\Component\OptionsResolver\Options as SymfonyOptions; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -177,6 +178,31 @@ public function getEnableLogs(): bool return $this->options['enable_logs'] ?? false; } + /** + * Sets if metrics should be enabled or not. + */ + public function setEnableMetrics(bool $enableTracing): self + { + $options = array_merge($this->options, ['enable_metrics' => $enableTracing]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + + /** + * Returns whether metrics are enabled or not. + */ + public function getEnableMetrics(): bool + { + /** + * @var bool $enableMetrics + */ + $enableMetrics = $this->options['enable_metrics'] ?? true; + + return $enableMetrics; + } + /** * Sets the sampling factor to apply to transactions. A value of 0 will deny * sending any transactions, and a value of 1 will send 100% of transactions. @@ -676,6 +702,35 @@ public function getBeforeSendMetricsCallback(): callable return $this->options['before_send_metrics']; } + /** + * Gets a callback that will be invoked before a metric is added. + * Returning `null` means that the metric will be discarded. + */ + public function getBeforeSendMetricCallback(): callable + { + /** + * @var callable $callback + */ + $callback = $this->options['before_send_metric']; + + return $callback; + } + + /** + * Sets a new callback that is invoked before metrics are sent. + * Returning `null` means that the metric will be discarded. + * + * @return $this + */ + public function setBeforeSendMetricCallback(callable $callback): self + { + $options = array_merge($this->options, ['before_send_metric' => $callback]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + /** * Sets a callable to be called to decide whether metrics should * be send or not. @@ -1220,6 +1275,7 @@ private function configureOptions(OptionsResolver $resolver): void 'sample_rate' => 1, 'enable_tracing' => null, 'enable_logs' => false, + 'enable_metrics' => true, 'traces_sample_rate' => null, 'traces_sampler' => null, 'profiles_sample_rate' => null, @@ -1256,10 +1312,14 @@ private function configureOptions(OptionsResolver $resolver): void }, /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + * Use `before_send_metric` instead. */ 'before_send_metrics' => static function (Event $metrics): ?Event { return null; }, + 'before_send_metric' => static function (Metric $metric): Metric { + return $metric; + }, 'trace_propagation_targets' => null, 'strict_trace_propagation' => false, 'tags' => [], @@ -1290,6 +1350,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('enable_tracing', ['null', 'bool']); $resolver->setAllowedTypes('enable_logs', 'bool'); + $resolver->setAllowedTypes('enable_metrics', 'bool'); $resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('traces_sampler', ['null', 'callable']); $resolver->setAllowedTypes('profiles_sample_rate', ['null', 'int', 'float']); @@ -1309,6 +1370,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('before_send_transaction', ['callable']); $resolver->setAllowedTypes('before_send_log', 'callable'); + $resolver->setAllowedTypes('before_send_metric', ['callable']); $resolver->setAllowedTypes('ignore_exceptions', 'string[]'); $resolver->setAllowedTypes('ignore_transactions', 'string[]'); $resolver->setAllowedTypes('trace_propagation_targets', ['null', 'string[]']); diff --git a/src/Serializer/EnvelopItems/MetricsItem.php b/src/Serializer/EnvelopItems/MetricsItem.php new file mode 100644 index 000000000..1a2b41092 --- /dev/null +++ b/src/Serializer/EnvelopItems/MetricsItem.php @@ -0,0 +1,52 @@ +getMetrics(); + + $header = [ + 'type' => (string) EventType::metrics(), + 'item_count' => \count($metrics), + 'content_type' => 'application/vnd.sentry.items.trace-metric+json', + ]; + + return \sprintf( + "%s\n%s", + JSON::encode($header), + JSON::encode([ + 'items' => array_map(static function (Metric $metric): array { + return [ + 'timestamp' => $metric->getTimestamp(), + 'trace_id' => (string) $metric->getTraceId(), + 'span_id' => (string) $metric->getSpanId(), + 'name' => $metric->getName(), + 'value' => $metric->getValue(), + 'unit' => $metric->getUnit() ? (string) $metric->getUnit() : null, + 'type' => $metric->getType(), + 'attributes' => array_map(static function (Attribute $attribute): array { + return [ + 'type' => $attribute->getType(), + 'value' => $attribute->getValue(), + ]; + }, $metric->getAttributes()->all()), + ]; + }, $metrics), + ]) + ); + } +} diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 4878cc767..45de0f29f 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -10,6 +10,7 @@ use Sentry\Serializer\EnvelopItems\CheckInItem; use Sentry\Serializer\EnvelopItems\EventItem; use Sentry\Serializer\EnvelopItems\LogsItem; +use Sentry\Serializer\EnvelopItems\MetricsItem; use Sentry\Serializer\EnvelopItems\ProfileItem; use Sentry\Serializer\EnvelopItems\TransactionItem; use Sentry\Tracing\DynamicSamplingContext; @@ -40,12 +41,15 @@ public function serialize(Event $event): string { // @see https://develop.sentry.dev/sdk/envelopes/#envelope-headers $envelopeHeader = [ - 'event_id' => (string) $event->getId(), 'sent_at' => gmdate('Y-m-d\TH:i:s\Z'), 'dsn' => (string) $this->options->getDsn(), 'sdk' => $event->getSdkPayload(), ]; + if ($event->getType()->requiresEventId()) { + $envelopeHeader['event_id'] = (string) $event->getId(); + } + $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); if ($dynamicSamplingContext instanceof DynamicSamplingContext) { $entries = $dynamicSamplingContext->getEntries(); @@ -73,6 +77,9 @@ public function serialize(Event $event): string case EventType::logs(): $items[] = LogsItem::toEnvelopeItem($event); break; + case EventType::metrics(): + $items[] = MetricsItem::toEnvelopeItem($event); + break; } return \sprintf("%s\n%s", JSON::encode($envelopeHeader), implode("\n", array_filter($items))); diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index e55c4c948..09fe62a20 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -5,9 +5,9 @@ namespace Sentry\Tracing; use Sentry\EventId; -use Sentry\Metrics\MetricsUnit; use Sentry\SentrySdk; use Sentry\State\Scope; +use Sentry\Unit; /** * This class stores all the information about a span. @@ -548,7 +548,7 @@ public function setMetricsSummary( string $type, string $key, $value, - MetricsUnit $unit, + Unit $unit, array $tags ): void { } diff --git a/src/Metrics/MetricsUnit.php b/src/Unit.php similarity index 92% rename from src/Metrics/MetricsUnit.php rename to src/Unit.php index e94951fe3..5a83ab720 100644 --- a/src/Metrics/MetricsUnit.php +++ b/src/Unit.php @@ -2,12 +2,9 @@ declare(strict_types=1); -namespace Sentry\Metrics; +namespace Sentry; -/** - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. - */ -final class MetricsUnit implements \Stringable +final class Unit implements \Stringable { /** * @var string The value of the enum instance @@ -144,11 +141,17 @@ public static function percent(): self return self::getInstance('percent'); } + /** + * @deprecated `none` is not supported and will be removed in 5.x + */ public static function none(): self { return self::getInstance('none'); } + /** + * @deprecated custom unit types are currently not supported. Will be removed in 5.x + */ public static function custom(string $unit): self { return new self($unit); diff --git a/src/Util/RingBuffer.php b/src/Util/RingBuffer.php new file mode 100644 index 000000000..7852f53db --- /dev/null +++ b/src/Util/RingBuffer.php @@ -0,0 +1,215 @@ + + */ + private $buffer; + + /** + * @var int + */ + private $capacity; + + /** + * Points at the first element in the buffer. + * + * @var int + */ + private $head = 0; + + /** + * Points at the index where the next insertion will happen. + * If the buffer is not full, this will point to an empty array index. + * When full, it will point to the position where the oldest element is. + * + * @var int + */ + private $tail = 0; + + /** + * @var int + */ + private $count = 0; + + /** + * Creates a new buffer with a fixed capacity. + */ + public function __construct(int $capacity) + { + if ($capacity <= 0) { + throw new \RuntimeException('RingBuffer capacity must be greater than 0'); + } + $this->capacity = $capacity; + $this->buffer = new \SplFixedArray($capacity); + } + + /** + * Returns how many elements can be stored in the buffer before it starts overwriting + * old elements. + */ + public function capacity(): int + { + return $this->capacity; + } + + /** + * The current number of stored elements. + */ + public function count(): int + { + return $this->count; + } + + /** + * Whether the buffer contains any element or not. + */ + public function isEmpty(): bool + { + return $this->count === 0; + } + + /** + * Whether the buffer is at capacity and will start to overwrite old elements on push. + */ + public function isFull(): bool + { + return $this->count === $this->capacity; + } + + /** + * Adds a new element to the back of the buffer. If the buffer is at capacity, it will + * overwrite the oldest element. + * + * Insertion order is still maintained. + * + * @param T $value + */ + public function push($value): void + { + $this->buffer[$this->tail] = $value; + + $this->tail = ($this->tail + 1) % $this->capacity; + + if ($this->isFull()) { + $this->head = ($this->head + 1) % $this->capacity; + } else { + ++$this->count; + } + } + + /** + * Returns and removes the first element in the buffer. + * If the buffer is empty, it will return null instead. + * + * @return T|null + */ + public function shift() + { + if ($this->isEmpty()) { + return null; + } + $value = $this->buffer[$this->head]; + + $this->buffer[$this->head] = null; + + $this->head = ($this->head + 1) % $this->capacity; + --$this->count; + + return $value; + } + + /** + * Returns the last element in the buffer without removing it. + * If the buffer is empty, it will return null instead. + * + * @return T|null + */ + public function peekBack() + { + if ($this->isEmpty()) { + return null; + } + $idx = ($this->tail - 1 + $this->capacity) % $this->capacity; + + return $this->buffer[$idx]; + } + + /** + * Returns the first element in the buffer without removing it. + * If the buffer is empty, it will return null instead. + * + * @return T|null + */ + public function peekFront() + { + if ($this->isEmpty()) { + return null; + } + + return $this->buffer[$this->head]; + } + + /** + * Resets the count and removes all elements from the buffer. + */ + public function clear(): void + { + for ($i = 0; $i < $this->count; ++$i) { + $this->buffer[($this->head + $i) % $this->capacity] = null; + } + $this->count = 0; + $this->head = 0; + $this->tail = 0; + } + + /** + * Returns the content of the buffer as array. The resulting array will have the size of `count` + * and not `capacity`. + * + * @return array + */ + public function toArray(): array + { + $result = []; + for ($i = 0; $i < $this->count; ++$i) { + $value = $this->buffer[($this->head + $i) % $this->capacity]; + /** @var T $value */ + $result[] = $value; + } + + return $result; + } + + /** + * Returns the content of the buffer and clears all elements that it contains in the process. + * + * @return array + */ + public function drain(): array + { + $result = $this->toArray(); + $this->clear(); + + return $result; + } +} diff --git a/src/functions.php b/src/functions.php index c01349821..af967c8f9 100644 --- a/src/functions.php +++ b/src/functions.php @@ -9,6 +9,7 @@ use Sentry\Integration\IntegrationInterface; use Sentry\Logs\Logs; use Sentry\Metrics\Metrics; +use Sentry\Metrics\TraceMetrics; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SpanContext; @@ -373,13 +374,18 @@ function logger(): Logs } /** - * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. + * @deprecated use `trace_metrics` instead */ function metrics(): Metrics { return Metrics::getInstance(); } +function trace_metrics(): TraceMetrics +{ + return TraceMetrics::getInstance(); +} + /** * Adds a feature flag evaluation to the current scope. * When invoked repeatedly for the same name, the most recent value is used. diff --git a/tests/Metrics/MetricsTest.php b/tests/Metrics/MetricsTest.php index 1e33a53d5..3de480657 100644 --- a/tests/Metrics/MetricsTest.php +++ b/tests/Metrics/MetricsTest.php @@ -104,12 +104,12 @@ public function testTiming(): void /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); $client->expects($this->any()) - ->method('getOptions') - ->willReturn(new Options([ - 'release' => '1.0.0', - 'environment' => 'development', - 'attach_metric_code_locations' => true, - ])); + ->method('getOptions') + ->willReturn(new Options([ + 'release' => '1.0.0', + 'environment' => 'development', + 'attach_metric_code_locations' => true, + ])); $self = $this; diff --git a/tests/Metrics/TraceMetricsTest.php b/tests/Metrics/TraceMetricsTest.php new file mode 100644 index 000000000..39ebf0705 --- /dev/null +++ b/tests/Metrics/TraceMetricsTest.php @@ -0,0 +1,119 @@ +bindClient(new Client(new Options(), StubTransport::getInstance())); + StubTransport::$events = []; + } + + public function testCounterMetrics(): void + { + trace_metrics()->count('test-count', 2, ['foo' => 'bar']); + trace_metrics()->count('test-count', 2, ['foo' => 'bar']); + trace_metrics()->flush(); + + $this->assertCount(1, StubTransport::$events); + $event = StubTransport::$events[0]; + $this->assertCount(2, $event->getMetrics()); + $metrics = $event->getMetrics(); + $metric = $metrics[0]; + $this->assertEquals('test-count', $metric->getName()); + $this->assertEquals(CounterMetric::TYPE, $metric->getType()); + $this->assertEquals(2, $metric->getValue()); + $this->assertArrayHasKey('foo', $metric->getAttributes()->toSimpleArray()); + } + + public function testGaugeMetrics(): void + { + trace_metrics()->gauge('test-gauge', 10, ['foo' => 'bar']); + trace_metrics()->flush(); + + $this->assertCount(1, StubTransport::$events); + $event = StubTransport::$events[0]; + $this->assertCount(1, $event->getMetrics()); + $metrics = $event->getMetrics(); + $metric = $metrics[0]; + $this->assertEquals('test-gauge', $metric->getName()); + $this->assertEquals(GaugeMetric::TYPE, $metric->getType()); + $this->assertEquals(10, $metric->getValue()); + $this->assertArrayHasKey('foo', $metric->getAttributes()->toSimpleArray()); + } + + public function testDistributionMetrics(): void + { + trace_metrics()->distribution('test-distribution', 10, ['foo' => 'bar']); + trace_metrics()->flush(); + $this->assertCount(1, StubTransport::$events); + $event = StubTransport::$events[0]; + $this->assertCount(1, $event->getMetrics()); + $metrics = $event->getMetrics(); + $metric = $metrics[0]; + $this->assertEquals('test-distribution', $metric->getName()); + $this->assertEquals(DistributionMetric::TYPE, $metric->getType()); + $this->assertEquals(10, $metric->getValue()); + $this->assertArrayHasKey('foo', $metric->getAttributes()->toSimpleArray()); + } + + public function testMetricsBufferFull(): void + { + for ($i = 0; $i < MetricsAggregator::METRICS_BUFFER_SIZE + 100; ++$i) { + trace_metrics()->count('test', 1, ['foo' => 'bar']); + } + trace_metrics()->flush(); + $this->assertCount(1, StubTransport::$events); + $event = StubTransport::$events[0]; + $metrics = $event->getMetrics(); + $this->assertCount(MetricsAggregator::METRICS_BUFFER_SIZE, $metrics); + } + + public function testEnableMetrics(): void + { + HubAdapter::getInstance()->bindClient(new Client(new Options([ + 'enable_metrics' => false, + ]), StubTransport::getInstance())); + + trace_metrics()->count('test-count', 2, ['foo' => 'bar']); + trace_metrics()->flush(); + + $this->assertEmpty(StubTransport::$events); + } + + public function testBeforeSendMetricAltersContent() + { + HubAdapter::getInstance()->bindClient(new Client(new Options([ + 'before_send_metric' => static function (Metric $metric) { + $metric->setValue(99999); + + return $metric; + }, + ]), StubTransport::getInstance())); + + trace_metrics()->count('test-count', 2, ['foo' => 'bar']); + trace_metrics()->flush(); + + $this->assertCount(1, StubTransport::$events); + $event = StubTransport::$events[0]; + + $this->assertCount(1, $event->getMetrics()); + $metric = $event->getMetrics()[0]; + $this->assertEquals(99999, $metric->getValue()); + } +} diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 1855bf487..3d3cc4c75 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -18,6 +18,9 @@ use Sentry\Frame; use Sentry\Logs\Log; use Sentry\Logs\LogLevel; +use Sentry\Metrics\Types\CounterMetric; +use Sentry\Metrics\Types\DistributionMetric; +use Sentry\Metrics\Types\GaugeMetric; use Sentry\MonitorConfig; use Sentry\MonitorSchedule; use Sentry\Options; @@ -31,6 +34,7 @@ use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TraceId; use Sentry\Tracing\TransactionMetadata; +use Sentry\Unit; use Sentry\UserDataBag; use Sentry\Util\ClockMock; use Sentry\Util\SentryUid; @@ -65,7 +69,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable yield [ Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","sapi":"cli","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}},{"type":"default","category":"log","level":"info","timestamp":1597790835,"data":{"0":"foo","1":"bar"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} TEXT @@ -183,7 +187,7 @@ public static function serializeAsEnvelopeDataProvider(): iterable yield [ $event, <<setMetrics([ + new CounterMetric('test-counter', 5, new TraceId('21160e9b836d479f81611368b2aa3d2c'), new SpanId('d051f34163cd45fb'), ['foo' => 'bar'], 1597790835.0, Unit::bit()), + ]); + + yield [ + $event, + <<setMetrics([ + new GaugeMetric('test-gauge', 5, new TraceId('21160e9b836d479f81611368b2aa3d2c'), new SpanId('d051f34163cd45fb'), ['foo' => 'bar'], ClockMock::microtime(true), Unit::second()), + ]); + + yield [ + $event, + <<setMetrics([ + new DistributionMetric('test-distribution', 5, new TraceId('21160e9b836d479f81611368b2aa3d2c'), new SpanId('d051f34163cd45fb'), ['foo' => 'bar'], ClockMock::microtime(true), Unit::day()), + ]); + + yield [ + $event, + <<push('foo'); + $buffer->push('bar'); + + $result = $buffer->toArray(); + $this->assertSame(2, $buffer->count()); + $this->assertEquals(['foo', 'bar'], $result); + } + + public function testPeekBack(): void + { + $buffer = new RingBuffer(5); + $buffer->push('foo'); + $buffer->push('bar'); + + $this->assertSame(2, $buffer->count()); + $this->assertSame('bar', $buffer->peekBack()); + } + + public function testPeekBackEmpty(): void + { + $buffer = new RingBuffer(5); + + $this->assertEmpty($buffer); + $this->assertNull($buffer->peekBack()); + } + + public function testPeekFront(): void + { + $buffer = new RingBuffer(5); + $buffer->push('foo'); + $buffer->push('bar'); + + $this->assertSame(2, $buffer->count()); + $this->assertSame('foo', $buffer->peekFront()); + } + + public function testPeekFrontEmpty(): void + { + $buffer = new RingBuffer(5); + + $this->assertEmpty($buffer); + $this->assertNull($buffer->peekFront()); + } + + public function testFixedCapacity(): void + { + $buffer = new RingBuffer(2); + $buffer->push('foo'); + $buffer->push('bar'); + $buffer->push('baz'); + + $this->assertSame(2, $buffer->count()); + $this->assertEquals(['bar', 'baz'], $buffer->toArray()); + } + + public function testClear(): void + { + $buffer = new RingBuffer(5); + $buffer->push('foo'); + $buffer->push('bar'); + + $this->assertSame(2, $buffer->count()); + $buffer->clear(); + $this->assertTrue($buffer->isEmpty()); + } + + public function testDrain(): void + { + $buffer = new RingBuffer(5); + $buffer->push('foo'); + $buffer->push('bar'); + + $this->assertSame(2, $buffer->count()); + $result = $buffer->drain(); + $this->assertTrue($buffer->isEmpty()); + $this->assertEquals(['foo', 'bar'], $result); + } + + public function testShift(): void + { + $buffer = new RingBuffer(5); + $buffer->push('foo'); + $buffer->push('bar'); + + $this->assertEquals('foo', $buffer->shift()); + $this->assertCount(1, $buffer); + } + + public function testShiftAndPush(): void + { + $buffer = new RingBuffer(5); + $buffer->push('foo'); + $buffer->push('bar'); + + $buffer->shift(); + + $buffer->push('baz'); + + $this->assertCount(2, $buffer); + $this->assertEquals(['bar', 'baz'], $buffer->toArray()); + } + + public function testCapacityOne(): void + { + $buffer = new RingBuffer(1); + $buffer->push('foo'); + $buffer->push('bar'); + + $this->assertCount(1, $buffer); + $this->assertSame('bar', $buffer->shift()); + } + + public function testInvalidCapacity(): void + { + $this->expectException(\RuntimeException::class); + $buffer = new RingBuffer(-1); + } + + public function testIsEmpty(): void + { + $buffer = new RingBuffer(5); + $this->assertTrue($buffer->isEmpty()); + } + + public function testIsFull(): void + { + $buffer = new RingBuffer(2); + $buffer->push('foo'); + $buffer->push('bar'); + $this->assertTrue($buffer->isFull()); + } +} From 43cc60dcaa19df1a288883e8e47c3baf4d242946 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 27 Nov 2025 15:31:49 +0100 Subject: [PATCH 1154/1161] fix: spotlight not emitting logs or registering error handlers (#1964) --- src/Integration/IntegrationRegistry.php | 2 +- src/Transport/HttpTransport.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index be2df22eb..e07926645 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -147,7 +147,7 @@ private function getDefaultIntegrations(Options $options): array new ModulesIntegration(), ]; - if ($options->getDsn() !== null) { + if ($options->getDsn() !== null || $options->isSpotlightEnabled()) { array_unshift($integrations, new ExceptionListenerIntegration(), new ErrorListenerIntegration(), new FatalErrorListenerIntegration()); } diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 71da8d5a3..12666ebd4 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -171,6 +171,15 @@ private function sendRequestToSpotlight(Event $event): void return; } + $eventDescription = \sprintf( + '%s%s [%s]', + $event->getLevel() !== null ? $event->getLevel() . ' ' : '', + (string) $event->getType(), + (string) $event->getId() + ); + + $this->logger->info(\sprintf('Sending %s to Spotlight.', $eventDescription), ['event' => $event]); + $request = new Request(); $request->setStringBody($this->payloadSerializer->serialize($event)); From 0068471e246e02b7a6c4131c0ae593bada1e3c99 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 27 Nov 2025 15:51:49 +0100 Subject: [PATCH 1155/1161] Prepare 4.19.0 (#1976) --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be545ceea..95ef15e41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # CHANGELOG +## 4.19.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.19.0. + +### Features + +- Add support for metrics. [(#1968)](https://github.com/getsentry/sentry-php/pull/1968) +```php +// Counter metric +\Sentry\trace_metrics()->count('test-counter', 10, ['my-attribute' => 'foo']); + +// Gauge metric +\Sentry\trace_metrics()->gauge('test-gauge', 50.0, ['my-attribute' => 'foo'], \Sentry\Unit::millisecond()); + +// Distribution metric +\Sentry\trace_metrics()->distribution('test-distribution', 20.0, ['my-attribute' => 'foo'], \Sentry\Unit::kilobyte()); + +// Flush metrics +\Sentry\trace_metrics()->flush(); +``` + +### Bug Fixes + +- Add rate limiting for profiles and cron check-ins. [(#1970)](https://github.com/getsentry/sentry-php/pull/1970) +- Fix Spotlight so it always registers the error integrations and emits transport logs even when no DSN is configured. [(#1964)](https://github.com/getsentry/sentry-php/pull/1964) + ## 4.18.1 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.18.1. From 1d29a07c8fb68ae9ad9bb8c3fecfaad3cbc23053 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 27 Nov 2025 14:53:55 +0000 Subject: [PATCH 1156/1161] release: 4.19.0 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 74c8cfa77..390ae5a3b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.18.1'; + public const SDK_VERSION = '4.19.0'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From f5d279069b47a3305dfe2a448522af7a763498db Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Tue, 2 Dec 2025 16:46:29 +0100 Subject: [PATCH 1157/1161] fix(metrics): don't cast all values to float, reject invalid types (#1981) --- .php-cs-fixer.dist.php | 3 ++ src/Metrics/MetricsAggregator.php | 8 +++++ src/Metrics/Types/CounterMetric.php | 2 +- src/Metrics/Types/DistributionMetric.php | 2 +- src/Metrics/Types/GaugeMetric.php | 2 +- src/Metrics/Types/Metric.php | 3 -- tests/Metrics/TraceMetricsTest.php | 39 ++++++++++++++++++++++++ 7 files changed, 53 insertions(+), 6 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 9cf23f912..8a5c51fdb 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,5 +1,7 @@ setRules([ '@PHP71Migration' => true, @@ -35,6 +37,7 @@ 'elements' => ['arrays'], ], 'no_whitespace_before_comma_in_array' => false, // Should be dropped when we drop support for PHP 7.x + 'stringable_for_to_string' => false, ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php index fa0dfd5be..e18ca7ddf 100644 --- a/src/Metrics/MetricsAggregator.php +++ b/src/Metrics/MetricsAggregator.php @@ -56,6 +56,14 @@ public function add( $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); + if (!\is_int($value) && !\is_float($value)) { + if ($client !== null) { + $client->getOptions()->getLoggerOrNullLogger()->debug('Metrics value is neither int nor float. Metric will be discarded'); + } + + return; + } + if ($client instanceof Client) { $options = $client->getOptions(); diff --git a/src/Metrics/Types/CounterMetric.php b/src/Metrics/Types/CounterMetric.php index a309e176d..ff7d636dc 100644 --- a/src/Metrics/Types/CounterMetric.php +++ b/src/Metrics/Types/CounterMetric.php @@ -38,7 +38,7 @@ public function __construct( ) { parent::__construct($name, $traceId, $spanId, $timestamp, $attributes, $unit); - $this->value = (float) $value; + $this->value = $value; } /** diff --git a/src/Metrics/Types/DistributionMetric.php b/src/Metrics/Types/DistributionMetric.php index bfbc85ce9..6d2ea7306 100644 --- a/src/Metrics/Types/DistributionMetric.php +++ b/src/Metrics/Types/DistributionMetric.php @@ -38,7 +38,7 @@ public function __construct( ) { parent::__construct($name, $traceId, $spanId, $timestamp, $attributes, $unit); - $this->value = (float) $value; + $this->value = $value; } /** diff --git a/src/Metrics/Types/GaugeMetric.php b/src/Metrics/Types/GaugeMetric.php index 2b58745be..22dc944c2 100644 --- a/src/Metrics/Types/GaugeMetric.php +++ b/src/Metrics/Types/GaugeMetric.php @@ -38,7 +38,7 @@ public function __construct( ) { parent::__construct($name, $traceId, $spanId, $timestamp, $attributes, $unit); - $this->value = (float) $value; + $this->value = $value; } /** diff --git a/src/Metrics/Types/Metric.php b/src/Metrics/Types/Metric.php index 999acbed9..a66cbd6ea 100644 --- a/src/Metrics/Types/Metric.php +++ b/src/Metrics/Types/Metric.php @@ -9,9 +9,6 @@ use Sentry\Tracing\TraceId; use Sentry\Unit; -/** - * @internal - */ abstract class Metric { /** diff --git a/tests/Metrics/TraceMetricsTest.php b/tests/Metrics/TraceMetricsTest.php index 39ebf0705..72f497002 100644 --- a/tests/Metrics/TraceMetricsTest.php +++ b/tests/Metrics/TraceMetricsTest.php @@ -116,4 +116,43 @@ public function testBeforeSendMetricAltersContent() $metric = $event->getMetrics()[0]; $this->assertEquals(99999, $metric->getValue()); } + + public function testIntType() + { + trace_metrics()->count('test-count', 2, ['foo' => 'bar']); + trace_metrics()->flush(); + + $this->assertCount(1, StubTransport::$events); + $event = StubTransport::$events[0]; + + $this->assertCount(1, $event->getMetrics()); + $metric = $event->getMetrics()[0]; + + $this->assertEquals('test-count', $metric->getName()); + $this->assertEquals(2, $metric->getValue()); + } + + public function testFloatType(): void + { + trace_metrics()->gauge('test-gauge', 10.50, ['foo' => 'bar']); + trace_metrics()->flush(); + + $this->assertCount(1, StubTransport::$events); + $event = StubTransport::$events[0]; + + $this->assertCount(1, $event->getMetrics()); + $metric = $event->getMetrics()[0]; + + $this->assertEquals('test-gauge', $metric->getName()); + $this->assertEquals(10.50, $metric->getValue()); + } + + public function testInvalidTypeIsDiscarded(): void + { + // @phpstan-ignore-next-line + trace_metrics()->count('test-count', 'test-value'); + trace_metrics()->flush(); + + $this->assertEmpty(StubTransport::$events); + } } From 68c2faa1e3323a5bc4f382fccd179b68e06ca209 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Tue, 2 Dec 2025 16:53:18 +0100 Subject: [PATCH 1158/1161] Prepare 4.19.1 (#1982) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ef15e41..249cfb093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## 4.19.1 + +The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.19.1. + +### Bug Fixes + +- Don't cast metrics value to `float` in constructor, drop invalid metrics instead. [(#1981)](https://github.com/getsentry/sentry-php/pull/1981) + ## 4.19.0 The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v4.19.0. From 1c21d60bebe67c0122335bd3fe977990435af0a3 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 2 Dec 2025 15:57:41 +0000 Subject: [PATCH 1159/1161] release: 4.19.1 --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 390ae5a3b..e54cb3d29 100644 --- a/src/Client.php +++ b/src/Client.php @@ -32,7 +32,7 @@ class Client implements ClientInterface /** * The version of the SDK. */ - public const SDK_VERSION = '4.19.0'; + public const SDK_VERSION = '4.19.1'; /** * Regex pattern to detect if a string is a regex pattern (starts and ends with / optionally followed by flags). From 1fbcf15a35deadd6710ea0dae19f23aea61286c8 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Wed, 3 Dec 2025 13:34:12 +0100 Subject: [PATCH 1160/1161] ref(tests): move `ClockMock` into `tests` (#1983) --- psalm.xml.dist | 1 - tests/BreadcrumbTest.php | 2 +- tests/Metrics/MetricsTest.php | 2 +- tests/Serializer/PayloadSerializerTest.php | 2 +- {src/Util => tests/TestUtil}/ClockMock.php | 2 +- tests/Tracing/SpanTest.php | 2 +- tests/Tracing/TransactionTest.php | 3 +-- tests/Transport/HttpTransportTest.php | 2 +- tests/Transport/RateLimiterTest.php | 2 +- tests/bootstrap.php | 2 +- 10 files changed, 9 insertions(+), 11 deletions(-) rename {src/Util => tests/TestUtil}/ClockMock.php (99%) diff --git a/psalm.xml.dist b/psalm.xml.dist index 502f940b3..ea60216a6 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -10,7 +10,6 @@ - diff --git a/tests/BreadcrumbTest.php b/tests/BreadcrumbTest.php index 77329f191..932b49a1c 100644 --- a/tests/BreadcrumbTest.php +++ b/tests/BreadcrumbTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; -use Sentry\Util\ClockMock; +use Sentry\Tests\TestUtil\ClockMock; final class BreadcrumbTest extends TestCase { diff --git a/tests/Metrics/MetricsTest.php b/tests/Metrics/MetricsTest.php index 3de480657..760bf90c0 100644 --- a/tests/Metrics/MetricsTest.php +++ b/tests/Metrics/MetricsTest.php @@ -11,9 +11,9 @@ use Sentry\Options; use Sentry\SentrySdk; use Sentry\State\Hub; +use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\SpanContext; use Sentry\Tracing\TransactionContext; -use Sentry\Util\ClockMock; use function Sentry\metrics; diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 3d3cc4c75..53f3235e8 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -28,6 +28,7 @@ use Sentry\Serializer\PayloadSerializer; use Sentry\Severity; use Sentry\Stacktrace; +use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\SpanId; @@ -36,7 +37,6 @@ use Sentry\Tracing\TransactionMetadata; use Sentry\Unit; use Sentry\UserDataBag; -use Sentry\Util\ClockMock; use Sentry\Util\SentryUid; /** diff --git a/src/Util/ClockMock.php b/tests/TestUtil/ClockMock.php similarity index 99% rename from src/Util/ClockMock.php rename to tests/TestUtil/ClockMock.php index 004291d39..73809f591 100644 --- a/src/Util/ClockMock.php +++ b/tests/TestUtil/ClockMock.php @@ -26,7 +26,7 @@ * THE SOFTWARE. */ -namespace Sentry\Util; +namespace Sentry\Tests\TestUtil; /** * @author Nicolas Grekas diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index e0db7c6d5..cf8956172 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -5,13 +5,13 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; +use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; -use Sentry\Util\ClockMock; /** * @group time-sensitive diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index 61c8d44f6..76a2c3aab 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -12,11 +12,10 @@ use Sentry\Options; use Sentry\State\Hub; use Sentry\State\HubInterface; -use Sentry\Tracing\Span; +use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; -use Sentry\Util\ClockMock; /** * @group time-sensitive diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 66d564b85..1d34137ae 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -14,9 +14,9 @@ use Sentry\Options; use Sentry\Profiling\Profile; use Sentry\Serializer\PayloadSerializerInterface; +use Sentry\Tests\TestUtil\ClockMock; use Sentry\Transport\HttpTransport; use Sentry\Transport\ResultStatus; -use Sentry\Util\ClockMock; final class HttpTransportTest extends TestCase { diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index c1112e9ab..6a34556c7 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -7,8 +7,8 @@ use PHPUnit\Framework\TestCase; use Sentry\EventType; use Sentry\HttpClient\Response; +use Sentry\Tests\TestUtil\ClockMock; use Sentry\Transport\RateLimiter; -use Sentry\Util\ClockMock; /** * @group time-sensitive diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 49e701ceb..a3a416679 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -5,9 +5,9 @@ use Sentry\Breadcrumb; use Sentry\Event; use Sentry\Metrics\Metrics; +use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\Span; use Sentry\Transport\RateLimiter; -use Sentry\Util\ClockMock; require_once __DIR__ . '/../vendor/autoload.php'; From a3ff5466b040d8f5c6da278d53cca4bbf4b253d7 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 4 Dec 2025 11:10:16 +0100 Subject: [PATCH 1161/1161] ref(spotlight): normalize spotlight URL to not end with `/stream` (#1984) --- src/Options.php | 15 ++++++++++++- tests/OptionsTest.php | 52 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/Options.php b/src/Options.php index 8e1ee2279..39c3d92e4 100644 --- a/src/Options.php +++ b/src/Options.php @@ -1408,6 +1408,7 @@ private function configureOptions(OptionsResolver $resolver): void return array_map([$this, 'normalizeAbsolutePath'], $value); }); + $resolver->setNormalizer('spotlight_url', \Closure::fromCallable([$this, 'normalizeSpotlightUrl'])); $resolver->setNormalizer('spotlight', \Closure::fromCallable([$this, 'normalizeBooleanOrUrl'])); $resolver->setNormalizer('in_app_exclude', function (SymfonyOptions $options, array $value) { @@ -1445,12 +1446,24 @@ private function normalizeBooleanOrUrl(SymfonyOptions $options, ?string $boolean } if (filter_var($booleanOrUrl, \FILTER_VALIDATE_URL)) { - return $booleanOrUrl; + return $this->normalizeSpotlightUrl($options, $booleanOrUrl); } return filter_var($booleanOrUrl, \FILTER_VALIDATE_BOOLEAN); } + /** + * Normalizes the spotlight URL by removing the `/stream` at the end if present. + */ + private function normalizeSpotlightUrl(SymfonyOptions $options, string $url): string + { + if (substr_compare($url, '/stream', -7, 7) === 0) { + return substr($url, 0, -7); + } + + return $url; + } + /** * Normalizes the DSN option by parsing the host, public and secret keys and * an optional path. diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index fed07e9bb..d229dad8b 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -751,4 +751,56 @@ public static function enableTracingDataProvider(): array [true, null, true], ]; } + + /** + * @dataProvider spotlightUrlNormalizationDataProvider + */ + public function testSpotlightUrlNormalization(array $data, string $expected): void + { + $options = new Options($data); + $this->assertSame($expected, $options->getSpotlightUrl()); + } + + public static function spotlightUrlNormalizationDataProvider(): \Generator + { + yield [['spotlight_url' => 'http://localhost:8969'], 'http://localhost:8969']; + yield [['spotlight_url' => 'http://localhost:8969/stream'], 'http://localhost:8969']; + yield [['spotlight_url' => 'http://localhost:8969/foo'], 'http://localhost:8969/foo']; + yield [['spotlight_url' => 'http://localhost:8969/foo/stream'], 'http://localhost:8969/foo']; + yield [['spotlight_url' => 'http://localhost:8969/stream/foo'], 'http://localhost:8969/stream/foo']; + yield [['spotlight' => 'http://localhost:8969'], 'http://localhost:8969']; + yield [['spotlight' => 'http://localhost:8969/stream'], 'http://localhost:8969']; + yield [['spotlight' => 'http://localhost:8969/foo'], 'http://localhost:8969/foo']; + yield [['spotlight' => 'http://localhost:8969/foo/stream'], 'http://localhost:8969/foo']; + yield [['spotlight' => 'http://localhost:8969/stream/foo'], 'http://localhost:8969/stream/foo']; + } + + /** + * @dataProvider setSpotlightUrlNormalizationDataProvider + */ + public function testSetSpotlightUrlNormalization(string $url, string $expected): void + { + $options = new Options(); + $options->setSpotlightUrl($url); + $this->assertSame($expected, $options->getSpotlightUrl()); + } + + /** + * @dataProvider setSpotlightUrlNormalizationDataProvider + */ + public function testEnableSpotlightNormalization(string $url, string $expected): void + { + $options = new Options(); + $options->enableSpotlight($url); + $this->assertSame($expected, $options->getSpotlightUrl()); + } + + public static function setSpotlightUrlNormalizationDataProvider(): \Generator + { + yield ['http://localhost:8969', 'http://localhost:8969']; + yield ['http://localhost:8969/stream', 'http://localhost:8969']; + yield ['http://localhost:8969/foo', 'http://localhost:8969/foo']; + yield ['http://localhost:8969/foo/stream', 'http://localhost:8969/foo']; + yield ['http://localhost:8969/stream/foo', 'http://localhost:8969/stream/foo']; + } }